Amazon EC2でCentOSにKubernetesを構築する（前編）

# はじめに

- Amazon EC2でCentOSにKubernetesを構築する（前編）　ここ
Kubernetesのインストール～Dashboard導入まで

- [Amazon EC2でCentOSにKubernetesを構築する（後編）](https://qiita.com/rawr/private/17352f29dacb4342f0b0)
Dockerイメージの作成～サービスの公開まで

Kubernetes（くーばねぃてす）はコンテナーとなっているアプリケーションをデプロイするプラットフォームです。Kubernetes自体にはコンテナーの機能は持っておらず、外部のコンテナーを使用するようになっていますが、大抵はDockerが使用されます。この記事でもDockerを使用します。

クラウドサービスでもKubernetesのサービス、KaaS(Kubernetes as a Service)を提供し始めており、Kubernetesの最初の開発であったGoogleが提供しているGoogle Cloud Platformはもちろん、Microsoft Azureでも提供されています。また、Amazonでも去年の11月にEKSが発表されて（まだメニューには無いようですが）、もはやKubernetesを１から手で構築する必要性は今後薄くなるのかもしれませんが、Kubernetesの機能をちょっと試してみたり、内部の動作を見たい場合には、手で構築する価値もあるでしょう。

Amazon EC2インスタンスで構築するのが少しやっかいである理由は、minikubeで構築できない（仮想ネットワークにVirtual Box が使えない）ことにあると思います。

この記事では、よくあるローカルPCにminikubeやUbuntuではなく、（RedHatを想定して）CentOS7で上、とにかくKubernetesで何か始められるところまで構築します。

# 環境

Workerノードは最低１つあれば良いのですが、clusterができる、ということを見るために２つ用意しました。またDocker private repositoryは、この記事では別マシンにしてしまいましたが、Masterノードと同一マシンで良いです。

- サーバー構成

|名前(兼hostname)|役割|IPアドレス|
|---|---|---|
|kube-master|Masterノード|172.26.22.85|
|kube-node1|Workerノード１|172.26.22.71|
|kube-node2|Workerノード２|172.26.22.99|
|docker-repo|Docker private repository|172.26.22.6|

- コンポーネントのバージョン

|名前|バージョン|補足|
|---|:-:|---|
|OS|CentOS 7.4||
|Kubernetes|1.10|2018年4月時点での最新|
|Docker|1.13.1||
|flannel|0.91|0.90ではバグあり|

# 事前準備 [全ノード]

## host名の変更とhostsファイルを編集する

Kubernetesはお互いのノードをhost名で解決するので、hostnameを変更しておきます。

- Masterノードの場合

```
# hostnamectl set-hostname kube-master
```

CentOS7では、hostnameの変更にOSの再起動が不要になりました。SSHのセッションを再作成すれば、host名の変更が反映されているのを確認できます。

hostsファイルに追加しておきます。

```
# vi /etc/hosts
172.26.22.85 kube-master
```

- Workerノード１の場合

Masterノードと同様ですが、自分自身のhost名もhostsファイルに追加しておきます。

```
# hostnamectl set-hostname kube-node1
# vi /etc/hosts
172.26.22.85 kube-master
172.26.22.71 kube-node1
```

- Workerノード２の場合

```
# hostnamectl set-hostname kube-node2
# vi /etc/hosts
172.26.22.85 kube-master
172.26.22.99 kube-node2
```

# Dockerのインストール [全ノード]

## Dockerのインストール

1.11以上が必要です。CentOS7.4でインストールできる最新は1.13.1なので、このバージョンにしました。

```
# yum install docker
# systemctl enable docker && systemctl start docker
```

<table><tr><td>
:information_source: HTTPプロキシーを設定するには？<br>
<br>
Service Unitファイルに環境変数を追加します。<code>NO_PROXY</code>にはprivate repositoryのIPアドレスを加えておきます。<br>
※注1　<code>NO_PROXY</code> にはCIDR記法で<b>書けない</b>。<br>
※注2　複数ある場合はカンマ区切り。<br>

<pre>
# mkdir -p /etc/systemd/system/docker.service.d/
# vi /etc/systemd/system/docker.service.d/http-proxy.conf
Environment="HTTP_PROXY=http://(ユーザー名):(パスワード)@(プロキシーのサーバー名)" "HTTPS_PROXY=http://(ユーザー名):(パスワード)@(プロキシーのサーバー名)"
Environment="NO_PROXY=127.0.0.1,172.26.22.6"
</pre>
</td></tr></table>

## 確認する

```
# docker info | grep -i version
  WARNING: You're not using the default seccomp profile
Server Version: 1.13.1
containerd version:  (expected: aa8187dbd3b7ad67d8e5e3a15115d3eef43a7ed1)
runc version: N/A (expected: 9df8b306d01f59d3a8029be411de015b7304dd8f)
init version: N/A (expected: 949e6facb77383876aeff8a6944dde66b3089574)
Kernel Version: 3.10.0-693.21.1.el7.x86_64

# docker info | grep -i driver
  WARNING: You're not using the default seccomp profile
Storage Driver: overlay2
Logging Driver: journald
Cgroup Driver: systemd
```

`docker info | grep -i driver`の結果は、後で使用するのでメモっておきます。

## Dockerのprivate repositoryへアクセスできるようにする

`docker pull`はデフォルトで`https`になるので、private repositoryに対しては`http`でアクセスするように設定しておきます。
※注1　`--insecure-registry`はCIDR記法で**書けない**。
※注2　`=`（イコール）いらない
※注3　` INSECURE_REGISTRY`というシェルの変数はなくなったようなので、`OPTIONS`に加えます。
※注4　jsonに入れる、という記述もありますが、それは docker-ce のようです。

```
# vi /etc/sysconfig/docker
OPTIONS='--selinux-enabled --log-driver=journald --signature-verification=false'
　↓
OPTIONS='--selinux-enabled --log-driver=journald --signature-verification=false --insecure-registry 172.26.22.6:5000'
```

Service Unitファイルを直接書き変えたので、リロードして再起動します。

```
# systemctl daemon-reload && systemctl restart docker
```

# Kubernetesのインストール [全ノード]

## iptablesの編集

```
# cat <<EOF >  /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
EOF
# sysctl --system
```

## インストール

yumレポジトリーからパッケージを取得してインストールするため、自動的に最新になります。2018年4月時点では1.10でした。また、`setenforce 0`でSELINUXを無効にしておきます。

```
# cat <<EOF > /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=https://packages.cloud.google.com/yum/repos/kubernetes-el7-\$basearch
enabled=1
gpgcheck=1
repo_gpgcheck=1
gpgkey=https://packages.cloud.google.com/yum/doc/yum-key.gpg https://packages.cloud.google.com/yum/doc/rpm-package-key.gpg
EOF

# setenforce 0
# yum install kubelet kubeadm kubectl
```

<table><tr><td>
:information_source: HTTPプロキシーを設定するには？<br>
<br>
/etc/yum.conf に追加します。<br>
<pre>
proxy=http://(プロキシーのサーバー名)
proxy_username=(ユーザー名)
proxy_password=(パスワード)
</pre>
</td></tr></table>

## swap無効＆cgroup設定

```
# vi /etc/systemd/system/kubelet.service.d/90-local-extras.conf
Environment="KUBELET_EXTRA_ARGS=--fail-swap-on=false"
Environment="KUBELET_CGROUP_ARGS=--cgroup-driver=systemd"
```

`--cgroup-driver`の値は、Dockerインストール時の`docker info | grep -i driver`で出力されて値にします。CentOSなら大抵`systemd`でしょう。

## 起動

```
# systemctl daemon-reload
# systemctl enable kubelet && systemctl start kubelet
```

## 確認

```
# kubectl version
Client Version: version.Info{Major:"1", Minor:"10", GitVersion:"v1.10.0", GitCommit:"fc32d2f3698e36b93322a3465f63a14e9f0eaead", GitTreeState:"clean", BuildDate:"2018-03-26T16:55:54Z", GoVersion:"go1.9.3", Compiler:"gc", Platform:"linux/amd64"}
The connection to the server localhost:8080 was refused - did you specify the right host or port?
```

この段階では、まだapiserverへの接続はエラーが出ます。

# kubeadm [Masterノードのみ]

## cgroupの設定

`--cgroup-driver`の値は、Dockerインストール時の `docker info | grep -i driver` で出力されて値にします。初期設定値は `systemd` になっているはずですが、そうなっていない場合は変更しておきます。

```
# vi /etc/systemd/system/kubelet.service.d/10-kubeadm.conf
Environment="KUBELET_CGROUP_ARGS=--cgroup-driver=systemd"
```

## インストール

```
# kubeadm init \
--pod-network-cidr=10.244.0.0/16 \
--apiserver-advertise-address=172.26.22.85
```

内部仮想ネットワークにflannelを使う場合は、マニュアルの書かれている通り`--pod-network-cidr`にアドレスを指定する必要があります。`--apiserver-advertise-address`にMasterノードのアドレスを指定します。

<table><tr><td>
:information_source: HTTPプロキシーを設定するには？<br>
<br>
<code>kubeadm init</code>では外に接続するため、環境変数<code>http_proxy, https_proxy, no_proxy</code>入れておきます。<br>
※注1　環境変数名は小文字。CIDR記法で書ける。<br>
※注2　private networkのIPアドレスも入れておくこと！<br>
<pre>
# export http_proxy=http://(ユーザー名):(パスワード)@(プロキシーのサーバー名)
# export no_proxy=172.26.22.0/24,localhost,127.0.0.1,10.96.0.0/12,10.244.0.0/16
</pre>
</td></tr></table>

標準出力で出る最後の行は、Workerノードを追加するコマンドであるため、メモっておきます。

```
Your Kubernetes master has initialized successfully!

To start using your cluster, you need to run the following as a regular user:

  mkdir -p $HOME/.kube
  sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
  sudo chown $(id -u):$(id -g) $HOME/.kube/config

You should now deploy a pod network to the cluster.
Run "kubectl apply -f [podnetwork].yaml" with one of the options listed at:
  https://kubernetes.io/docs/concepts/cluster-administration/addons/

You can now join any number of machines by running the following on each node
as root:

  kubeadm join 172.26.22.85:6443 --token ddpdzf.s89pe0whxm0osqoc --discovery-token-ca-cert-hash sha256:ca6dcf315312663170f72cf081604b6635e7ad98bb3440656d43d6ed5cd34d0e
```

<table><tr><td>
:information_source: トークンの有効期限と再発行は？<br>
<br>
トークンの有効期限は24時間です。有効期限が切れた場合は、再発行できます。<br>
<pre>
# kubeadm token list
# kubeadm token create --print-join-command
</pre>
</td></tr></table>

## kubeconfig

`kubeadm init`で出力された通り、`kubeadm`コマンドを使えるようにしておきます。

```
# mkdir -p $HOME/.kube
# cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
# chown $(id -u):$(id -g) $HOME/.kube/config
```

もしくは、

```
# vi ~/.bash_profile
export KUBECONFIG=/etc/kubernetes/admin.conf
```

## 確認

```
# kubectl version
Client Version: version.Info{Major:"1", Minor:"10", GitVersion:"v1.10.0", GitCommit:"fc32d2f3698e36b93322a3465f63a14e9f0eaead", GitTreeState:"clean", BuildDate:"2018-03-26T16:55:54Z", GoVersion:"go1.9.3", Compiler:"gc", Platform:"linux/amd64"}
Server Version: version.Info{Major:"1", Minor:"10", GitVersion:"v1.10.0", GitCommit:"fc32d2f3698e36b93322a3465f63a14e9f0eaead", GitTreeState:"clean", BuildDate:"2018-03-26T16:44:10Z", GoVersion:"go1.9.3", Compiler:"gc", Platform:"linux/amd64"}
```

Server Versionが出力されるようになります。

```
# kubectl get nodes
NAME            STATUS     ROLES     AGE       VERSION
kube-master     NotReady   master    11m       v1.10.0
```

まだflannelを入れていないため、`STATUS`が`NotReady`になります。

```
# kubectl get pods --all-namespaces -o wide
NAMESPACE     NAME                                     READY     STATUS     RESTARTS   AGE       IP             NODE
kube-system   etcd-kube-master                         0/1       Pending    0          4s        <none>         kube-master
kube-system   kube-apiserver-kube-master               0/1       Pending    0          4s        <none>         kube-master
kube-system   kube-controller-manager-kube-master      0/1       Pending    0          4s        <none>         kube-master
kube-system   kube-dns-86f4d74b45-ccq57                0/3       Pending    0          5m        <none>         <none>
kube-system   kube-proxy-tbrdc                         1/1       NodeLost   0          5m        172.26.22.85   kube-master
kube-system   kube-scheduler-kube-master               0/1       Pending    0          4s        <none>         kube-master
```

まだflannelを入れていないため、`STATUS`が`Pending`になります。

# flannel [Masterノードのみ]

## deployする

```
# kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/v0.9.1/Documentation/kube-flannel.yml
clusterrole.rbac.authorization.k8s.io "flannel" created
clusterrolebinding.rbac.authorization.k8s.io "flannel" created
serviceaccount "flannel" created
configmap "kube-flannel-cfg" created
daemonset.extensions "kube-flannel-ds" created
```

## 確認

```
# kubectl get nodes
NAME            STATUS    ROLES     AGE       VERSION
kube-master     Ready     master    15m       v1.10.0
```

`STATUS`が`NotReady`から`Ready`に変わります。

```
# kubectl get pods --all-namespaces -o wide
NAMESPACE     NAME                                     READY     STATUS    RESTARTS   AGE       IP             NODE
kube-system   etcd-kube-master                         1/1       Running   0          59s       172.26.22.85   kube-master
kube-system   kube-apiserver-kube-master               1/1       Running   0          59s       172.26.22.85   kube-master
kube-system   kube-controller-manager-kube-master      1/1       Running   0          59s       172.26.22.85   kube-master
kube-system   kube-dns-86f4d74b45-ccq57                3/3       Running   0          6m        10.244.0.2     kube-master
kube-system   kube-flannel-ds-79gcb                    1/1       Running   0          1m        172.26.22.85   kube-master
kube-system   kube-proxy-tbrdc                         1/1       Running   0          6m        172.26.22.85   kube-master
kube-system   kube-scheduler-kube-master               1/1       Running   0          58s       172.26.22.85   kube-master
```

`STATUS`が`NotReady`から`Running`に変わります。kube-dns-xxxxxが`Running`になるには、時間が掛かります。

# Workerノードを追加する [Workerノードのみ]

## ノードを追加する

`kubeadm init`で出力されたコマンドを、そのままコピペして実行します。

```
# kubeadm join 172.26.22.85:6443 --token ddpdzf.s89pe0whxm0osqoc --discovery-token-ca-cert-hash sha256:ca6dcf315312663170f72cf081604b6635e7ad98bb3440656d43d6ed5cd34d0e
```

## kubeconfig

```
# mkdir -p $HOME/.kube
# cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
# chown $(id -u):$(id -g) $HOME/.kube/config
```

もしくは、

```
# vi ~/.bash_profile
export KUBECONFIG=/etc/kubernetes/admin.conf
```

## 確認 [MasterとWorkerノード両方]

```
# kubectl get nodes
NAME            STATUS    ROLES     AGE       VERSION
kube-master     Ready     master    23m       v1.10.0
kube-node1      Ready     <none>    59s       v1.10.0
```

Workerノードも`STATUS`が`Ready`に変わります。（すぐには変わらない）

```
# kubectl get cs
NAME                 STATUS    MESSAGE              ERROR
scheduler            Healthy   ok
controller-manager   Healthy   ok
etcd-0               Healthy   {"health": "true"}
```

<table><tr><td>
:information_source: Workerノードを削除するには？<br>
<br>
<pre>
# kubectl drain (node name) --delete-local-data --force --ignore-daemonsets
# kubectl delete node (node name)
</pre>

Kubernetes自体のアンインストールは、次のコマンドで可能です。<br>
<pre>
# kubeadm reset
</pre>
</td></tr></table>

# Dashboard [Masterノードのみ]

## インストール

```
# kubectl apply -f https://raw.githubusercontent.com/kubernetes/dashboard/master/src/deploy/recommended/kubernetes-dashboard.yaml
secret "kubernetes-dashboard-certs" created
serviceaccount "kubernetes-dashboard" created
role.rbac.authorization.k8s.io "kubernetes-dashboard-minimal" created
rolebinding.rbac.authorization.k8s.io "kubernetes-dashboard-minimal" created
deployment.apps "kubernetes-dashboard" created
service "kubernetes-dashboard" created
```

## アクセスする

kube proxyを起動して、LISTENできるようにします。

```
# kubectl proxy
```

バインドアドレスが`localhost`のみなので、SSHポート転送をしてアクセスします。TeraTermの場合は、MasterノードにSSHでログインした後、メニューの「設定」＞「SSH転送...」から「追加」を押して次のように設定します。

![2018-05-01_115006.png](https://qiita-image-store.s3.amazonaws.com/0/208281/df871c6e-0a0f-3946-420b-39643e37cd3f.png)

これで、ローカルPCの8001ポートを、Masterノードを通してlocalhost(=Masterノード)の8001へ転送できるので、Masterノードから見ると、localhostから接続したように見えます。

ブラウザから次のアドレスでアクセスすると、Dashboardのトップページが見えるはずです。

```
http://localhost:8001/api/v1/namespaces/kube-system/services/https:kubernetes-dashboard:/proxy/
```

<table><tr><td>
:information_source: <code>http://localhost:8001/ui</code> は1.9で非推奨でしたが、1.10で廃止されました。
</td></tr></table>

![2018-05-01_115516.png](https://qiita-image-store.s3.amazonaws.com/0/208281/e2d7bd69-6cfd-438e-29c9-c08aaeb72cc6.png)

この段階では、まだDashboardにログインできません。

## ポート転送なしでアクセスできるようにする

毎回ポート転送するのも面倒なので、ポート転送なしでもアクセスできるように、kube proxyを起動します。

```
# kubectl proxy --address=0.0.0.0 --accept-hosts='^.*$' &
```

`--address`でバインドアドレスを指定しても、`--accept-hosts`を付けないと `<h3>Unauthorized</h3>` が出力されてしまいます。


## 権限を付与する

全機能を未認証で使用できるようにしておきます。適当なファイル名で、次の内容を保存します。

```
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
  name: kubernetes-dashboard
  labels:
    k8s-app: kubernetes-dashboard
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cluster-admin
subjects:
- kind: ServiceAccount
  name: kubernetes-dashboard
  namespace: kube-system
---
```

次のコマンドで適用します。

```
# kubectl apply -f (ファイル名)
```

これで「SKIP」を押すと、Dashboardの画面が見られるようになります。

# Docker private repository [docker-repoのみ]

Kuberentesでは、Podをdeployするとき`docker pull`でイメージを取得してからコンテナー化するため、private repositoryがあったほうが便利です。もちろんdocker.ioのprivate repositoryでも良いですが、この記事では自前のrepositoryを立ててみます。

## インストール

```
# docker pull registry:2
```

## 起動

- １回目

ストレージを永続化できるように起動します。

```
# docker run -d -p 5000:5000 -v /var/opt:/var/lib/registry registry:2
```
- ２回目以降

```
# docker start (コンテナー名)
```

コンテナー名は `docker ps -a`で確認します。

## 確認

```
# netstat -anp | grep "LISTEN " | grep :5000
```

`--insecure-registry`の設定も忘れずに。

# まとめ

これで、Kubernetesで何かをする準備ができました。後編では実際にDockerイメージからKubernetesでサービスを公開してみたいと思います。

# 参考文献

- [マニュアル(インストール)](https://kubernetes.io/docs/setup/independent/install-kubeadm/)
- [マニュアル(clusterの作成)](https://kubernetes.io/docs/setup/independent/create-cluster-kubeadm/)
マニュアルは英語ですが、一度は目を通しておいたほうが良いでしょう。

- [RedHat7上に構築](https://kazuhisya.netlify.com/2018/02/08/how-to-install-k8s-on-el7/)
メモ書きレベルですが、UbuntuではなくRedHat7上で、かつKubernetes最新版の記述になっています。

- [flannel 0.90ではバグあり](https://qiita.com/hichihara/items/79ef6613026f8c13eb99)
私は最初から0.91なのでハマりませんでしたが、大きなバグがあるようです。

- [Kubernetesの本](https://www.amazon.co.jp/%E5%85%A5%E9%96%80-Kubernetes-Kelsey-Hightower/dp/4873118409/ref=sr_1_1?s=books&ie=UTF8&qid=1525144350&sr=1-1&keywords=kubernetes)
O'ReillyからKubernetesの本の日本語訳がでました。内容は1.6ベースになっているようで、1.8や1.9の機能であるRoleやMulti Masterの記述はありませんでした。