kubeadm で kubernetes v1.8 + Flannel をインストール

# kubeadm について
kubeadm は kubernetes をデプロイするためのツール。[公式ドキュメント](https://kubernetes.io/docs/setup/)でも紹介されているツールで、まだベーダ版だが kubernetes クラスタを構築する際には今後はこのツールを使っていくことになる。他には Minikube というツールもあるが、こちらはローカルで kubernetes を構築するためのもので、主に開発者に使われることを想定している。

# 環境
自分の試した環境は以下のとおり。

- ホストOS: MacOS Sierra
- ゲストOS: Ubuntu 16.04.2 LTS
- VirtualBox 5.1.14
- Vagrant 1.9.1

用意した VM は3つ。マスターノード用に 2vCPU, 4GB の VM が1つ、ワーカーノード用に 1vCPU, 2GB の VM が2つ、それぞれのホスト名は manager, host1, host2 とする。各 VM のデフォルトの NIC はホストへの NAT になっていて、インターネットへのアクセスに使用する。これとは別に private_network を用意し、それぞれの IP アドレスを下記の通り 192.168.33.10, 192.168.33.11, 192.168.33.12 とする。Kubernetes のすべての通信はこの NIC を使用する。

```
       +---------------------------------------------+
       |                     NAT                     |
       +--+-------------------+-------------------+--+
          |                   |                   |
+---------+-------+  +--------+--------+  +-------+---------+
| enp0s3 10.0.2.15|  | enp0s3 10.0.2.15|  | enp0s3 10.0.2.15|
|                 |  |                 |  |                 |
|                 |  |                 |  |                 |
|                 |  |                 |  |                 |
|     manager     |  |     host1       |  |     host2       |
|                 |  |                 |  |                 |
|                 |  |                 |  |                 |
| enp0s8          |  | enp0s8          |  | enp0s8          |
| 192.168.33.10   |  | 192.168.33.11   |  | 192.168.33.12   |
+---------+-------+  +--------+--------+  +-------+---------+
          |                   |                   |
       +--+-------------------+-------------------+--+
       |               192.168.33.0/24               |
       +---------------------------------------------+


```

# kubeadm インストール
この章で実施している内容をマスター、ワーカーの各ノードで実行する。基本的には[公式ドキュメント](https://kubernetes.io/docs/setup/independent/install-kubeadm/)の最新情報を確認して欲しいが、いくつか補完する。

## `/etc/hosts` ファイルの編集
各ノードの `/etc/hosts` に下記の通り、ホスト名に対応する IP アドレスを追記する。このときループバックアドレス以外の別の IP アドレス (e.g. 10.0.2.15) とホスト名を紐付ける記述はしてはいけない。
#### manager ノード
```shell-session:/etc/hosts
192.168.33.10 manager
```
#### host1 ノード
```shell-session:/etc/hosts
192.168.33.11 host1
```
#### host2 ノード
```shell-session:/etc/hosts
192.168.33.12 host2
```

## Docker インストール
Ubuntu 公式リポジトリの docker を使うのが手間がなくて良いが、出来るだけ新しいものを使いたいので自分は docker リポジトリのものを使った。公式ドキュメントでは17.09を指定しているが、kubeadm が公式にサポートしているのは17.03で、kubeadm 実行時に warning も出るので自分は17.03を使った。
### Ubuntu リポジトリの Docker を使う場合
```
$ apt-get update
$ apt-get install -y docker.io
```
### Docker リポジトリの Docker を使う場合
```
$ apt-get update
$ apt-get install -y \
    apt-transport-https \
    ca-certificates \
    curl \
    software-properties-common
$ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | apt-key add -
$ add-apt-repository \
   "deb [arch=amd64] https://download.docker.com/linux/$(. /etc/os-release; echo "$ID") \
   $(lsb_release -cs) \
   stable"
$ apt-get update && apt-get install -y docker-ce=17.03.0~ce-0~ubuntu-xenial
```

## kubeadm, kubectl, kubelet インストール
```
$ apt-get update && apt-get install -y apt-transport-https
$ curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add -
$ cat <<EOF >/etc/apt/sources.list.d/kubernetes.list
deb http://apt.kubernetes.io/ kubernetes-xenial main
EOF
$ apt-get update
$ apt-get install -y kubelet kubeadm kubectl
```

# kubeadm 実行
今回マスターノードが使用する IP アドレスは 192.168.33.10 で、このアドレスを指定するには `--apiserver-advertise-address` を使用する。またマニュアルには flannel 使用時には `--pod-network-cidr` を指定するようにと記載があるため、そのように指定する。

```
$ sudo kubeadm init --apiserver-advertise-address=192.168.33.10 --pod-network-cidr=10.244.0.0/16
```

デプロイ後に表示される `kubeadm join` コマンドはメモしておくこと。後で、このコマンドと引数を使ってワーカーノードをクラスタに追加します。

```
kubeadm join --token 07953d.ad9fc560f05ae297 192.168.33.10:6443 --discovery-token-ca-cert-hash sha256:23b6e03c9d4c3cd752c29f3fb0bb0f04923dd69e39020d995b976f93607316e1
```

kube config ファイルをコピーする。

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

これで基本的なプロセスのデプロイは完了。 `kubectl` コマンドでノードの状態や pod の情報を取得してみる。

```
$ kubectl get nodes
NAME      STATUS     ROLES     AGE       VERSION
manager   NotReady   master    18m       v1.8.4

$ kubectl get pods --all-namespaces -o wide
NAMESPACE     NAME                              READY     STATUS    RESTARTS   AGE       IP              NODE
kube-system   etcd-manager                      1/1       Running   0          18m       192.168.33.10   manager
kube-system   kube-apiserver-manager            1/1       Running   0          18m       192.168.33.10   manager
kube-system   kube-controller-manager-manager   1/1       Running   0          18m       192.168.33.10   manager
kube-system   kube-dns-545bc4bfd4-pc5qh         0/3       Pending   0          19m       <none>          <none>
kube-system   kube-proxy-8fzzd                  1/1       Running   0          19m       192.168.33.10   manager
kube-system   kube-scheduler-manager            1/1       Running   0          18m       192.168.33.10   manager
```

マスターノードのステータスはまだ NotReady です。これは kube-dns がまだ動いていない、NW が未設定であるためです。

# flannel のデプロイ
通常は下記のコマンドを実行すれば終わりですが、問題2 でも取り上げている内容の通り kube-flannel.yml の修正が必要になります。

```
$ kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/v0.9.1/Documentation/kube-flannel.yml
```

kube-flannel.yml をダウンロードし、flanneld の起動オプションに NIC 指定を追記します。

```shell-session:kube-flannel.yaml
command: [ "/opt/bin/flanneld", "--ip-masq", "--kube-subnet-mgr", "--iface=enp0s8"]
```

編集し終わったら `kubectl apply` で NW 設定を適用します。

```
$ kubectl apply -f kube-flannel.yml
```

これでノードのステータスは Ready になります。

```
$ kubectl get nodes
NAME      STATUS    ROLES     AGE       VERSION
manager   Ready     master    35m       v1.8.4

$ kubectl get pods --all-namespaces -o wide
NAMESPACE     NAME                              READY     STATUS    RESTARTS   AGE       IP              NODE
kube-system   etcd-manager                      1/1       Running   0          34m       192.168.33.10   manager
kube-system   kube-apiserver-manager            1/1       Running   0          34m       192.168.33.10   manager
kube-system   kube-controller-manager-manager   1/1       Running   0          33m       192.168.33.10   manager
kube-system   kube-dns-545bc4bfd4-pc5qh         3/3       Running   0          34m       10.244.0.8      manager
kube-system   kube-flannel-ds-tggqx             1/1       Running   0          1m        192.168.33.10   manager
kube-system   kube-proxy-8fzzd                  1/1       Running   0          34m       192.168.33.10   manager
kube-system   kube-scheduler-manager            1/1       Running   0          34m       192.168.33.10   manager
```

# ワーカーノードをクラスタに追加
マスターノードの準備が終わったので、あとは各ノードでメモしておいた join コマンドをそのまま実行していくだけです。

```
$ sudo kubeadm join --token 07953d.ad9fc560f05ae297 192.168.33.10:6443 --discovery-token-ca-cert-hash sha256:23b6e03c9d4c3cd752c29f3fb0bb0f04923dd69e39020d995b976f93607316e1
```

完了。

```
$ kubectl get nodes
NAME      STATUS    ROLES     AGE       VERSION
host1     Ready     <none>    13m       v1.8.4
host2     Ready     <none>    13s       v1.8.4
manager   Ready     master    52m       v1.8.4
```

# 問題
今回の構築でいくつか問題にハマったので共有します。
## 問題1
各 pod の IP アドレスが各ノードのデフォルトインターフェースのアドレスになっている。下記を見てわかる通り manager も host1 もホストに対して NAT をしている NIC がデフォルトのため同じ IP アドレス (10.0.2.15) になってしまっている。この状態だとマスターノードから各ノード上の pod のログが取れないなど色々と支障が出る。

```
$ kubectl get pods --all-namespaces -o wide
NAMESPACE     NAME                              READY     STATUS    RESTARTS   AGE       IP           NODE
kube-system   etcd-manager                      1/1       Running   0          5m        10.0.2.15    manager
kube-system   kube-apiserver-manager            1/1       Running   0          5m        10.0.2.15    manager
kube-system   kube-controller-manager-manager   1/1       Running   0          5m        10.0.2.15    manager
kube-system   kube-dns-545bc4bfd4-m2cm4         3/3       Running   0          6m        10.244.0.6   manager
kube-system   kube-flannel-ds-cljpz             1/1       Running   0          2m        10.0.2.15    manager
kube-system   kube-flannel-ds-p9lfd             1/1       Running   0          10s       10.0.2.15    host1
kube-system   kube-proxy-cphtp                  1/1       Running   0          6m        10.0.2.15    manager
kube-system   kube-proxy-hs6b2                  1/1       Running   0          10s       10.0.2.15    host1
kube-system   kube-scheduler-manager            1/1       Running   0          5m        10.0.2.15    manager
```

この解決策の1つとして kubelet を起動する際に `--node-ip` というオプションで任意の IP アドレスを渡すことができるが、kubeadm では対応してない。指定されていない場合は `/etc/hosts` に設定されているホスト名の IP アドレスが使用されるため、あらかじめ各ノードで kubernetes 用に使用する IP アドレスを `/etc/hosts` に登録する必要がある。

```shell-session:/etc/hosts
kubernetesで使用するIPアドレス ホスト名
```

## 問題2
Flannel が VXLAN トンネルを張る際にデフォルトの NIC を使う。マシン同士が疎通可能ならそれでも問題はないが、今回のような環境 (NAT) だと VXLAN が張れないので flanneld 起動時に使用する NIC を指定する必要がある。今回は下記のように flannel を適用する際に使用した kube-flannel.yaml を直接編集した。

```shell-session:kube-flannel.yaml
command: [ "/opt/bin/flanneld", "--ip-masq", "--kube-subnet-mgr", "--iface=xxx" ]
```

## 問題3
任意の pod が起動してるノードから、その pod への通信は通るが、別のノードからその pod への通信が通らない。これは Docker v1.13 以降と Flannel の組み合わせのバグで、自分がハマっていたときには未修正だったが、[修正](https://github.com/coreos/flannel/commit/5df82dcbcb10205c419f2a9829c9a50e1e566352)はすでにされている。[kube-flannel.yml](https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml)もこの修正が入ったイメージ (v0.9.1-amd64) が指定されているので、今からやる人は特にハマらないはず。しかし、自分がハマった時は、tcpdump祭りで見つけられず、泣きながら iptables のルールを読んで、flannel のソースコード読んで原因を突き止めたので、v0.9.0 でハマって諦めた人用にメモとして残しておく。