kindで軽量テスト用Kubernetesクラスタを作る＆運用する時のTIPS

この記事は、[Kubernetes3 Advent Calendar](https://qiita.com/advent-calendar/2019/kubernetes3)の第1日目です。

Kubernetes最軽量構築ツール`kind`のよさ、初回起動方法、仕組み、TIPS（ホストディレクトリのマウント）、メンテナンス方法を紹介します。仕掛けがわかるととても楽しいですよ。

<img width="400" alt="image.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/96277/2031bd9e-cb62-8612-8741-7ab123590b67.png"/>

[更新]2021/4/27 kubeconfigの書き出し方法を追記
[更新]2019/12/28 [最新ドキュメント](https://kind.sigs.k8s.io/docs/user/configuration/)に合わせ `apiVersion: kind.x-k8s.io/v1alpha4`に変更。Kubernetesバージョン指定方法を追加。

# `kind`のよさ

以下のような良さがある**Kubernetes最軽量構築ツール**です。

- **軽量** = 前提としてDockerがインストールされたホストが一つあれば済む
- **クリーン** = ホスト環境を何も汚さない
- **作業ほぼゼロ** = コマンド一発で作成、削除可能

# まず`kind`を動かしてみるまで

### 参考になるドキュメント

[本家Quick start](https://kind.sigs.k8s.io/docs/user/quick-start)

### `kind`を動かす環境の前提

- dockerがインストールされている
- kubectlがインストールされている

今回は検証のため、Windows上のVirtualBox上のVMでDocker本体を動かして確認しましたが、私自身Ubuntu上でもMacでも同じ構成を作った実績があります。

### `kind`コマンドのインストール

以下はすべてDockerインストール済みのVM上で実行します。

```
curl -Lo ./kind https://github.com/kubernetes-sigs/kind/releases/download/v0.6.0/kind-$(uname)-amd64
chmod +x ./kind
sudo mv ./kind /usr/bin/kind
```

別のMacの環境では以下のコマンドだけでインストールできました。

```sh
brew install kind
```

### クラスタ構築

```
kind create cluster
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/96277/9fff8cb5-4bc5-d99c-4d82-a96629c5fb49.png)


docker上に一つだけコンテナが起動します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/96277/79c00560-f71b-1411-c2ae-2e646f766bb2.png)

これは、control-planeというKubernetesのマスターノードに相当するコンテナが起動した状態です。

Kubernetesでは、デフォルト設定でマスターノードにコンテナを配置しないように制御されますので、それ以外のワーカーノードも必要になります。
一度今のクラスタを削除して、再度ワーカーノード込みで作り直してみましょう。

### クラスタ削除

```
kind delete cluster
```

はい、全部消えました。とてもクリーンです。

### ワーカーノードを含めたインストール

`kind`用の設定ファイルを新規作成し、ワーカーノードを追加することを記載します。

```kind.yaml
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
- role: worker
```

この設定を使ってクラスタを作成します。

```
kind create cluster --config kind.yaml
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/96277/c1dd861d-975a-2e7a-2d18-b276950a4261.png)


処理の中に`Joining worker nodes`処理が増えているのがわかります。
コンテナが2つ作成されています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/96277/ca97053a-b8b3-bceb-6e31-b716d06a0c2e.png)

### `kubectl`で操作する

クラスタ作成後に表示される`kubectl cluster-info`をたたくと、ちゃんとコンテキストが追加されていることがわかります。

```
~$ kubectl cluster-info --context kind-kind

Kubernetes master is running at https://127.0.0.1:38921
KubeDNS is running at https://127.0.0.1:38921/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy
```

ではこのコンテキストを使って`kubectl`で操作しましょう。

```
$ kubectl config use-context kind-kind
Switched to context "kind-kind".
```

実際にコンテナを起動してみます。

```
$ kubectl run --generator=run-pod/v1 test --image=nginx
pod/test created
```

動いたかどうかを確認してみます。

```
~$ kubectl get po
NAME   READY   STATUS    RESTARTS   AGE
test   1/1     Running   0          2m39s
```

コンテナの中にも入ってみます。

```
$ kubectl exec -ti test /bin/bash
root@test:/# ls
bin  boot  dev  etc  home  lib  lib64  media  mnt  opt  proc  root  run  sbin  srv  sys  tmp  usr  var
```

コンテナを消してみます。

```
$ kubectl delete po test
pod "test" deleted
```

# `kind`の仕組み

### 2重のコンテナエンジン

Dockerの中にKubernetesのマスターノード(`kind-control-plane`)やワーカーノード(`kind-worker`)が起動し、それぞれの中にまたcontainerdというコンテナエンジンが起動する形になっています。

ユーザがKubernetes上に作るPodなどのコンテナは、この内部のcontainerdの中に作られます。だから、クラスタを削除したいときはこのcontainerdをまるごと消せばよく、ホストのDockerを汚さないので非常にクリーンに運用できるわけです。

<img width="600" alt="image.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/96277/1b1387d6-1575-92a2-585f-2dda300499a5.png"/>


### 安定した構築方法を採用している

Kubernetesの構築の仕方の中では老舗に部類される、安定した構築ツール`kubeadm`を内部で使っています。
したがって、`kubeadm`でできることはこの`kind`でもできるという嬉しさもあります。

# `kind`のTIPS

### ホストのディスクをkind上のpodに見せる

`kind`の仕組みで説明した通り、`kind`は2重のコンテナエンジン(Docker/containerd)で起動されています。コンテナエンジン自体はホストのディレクトリをコンテナに見せる機能を持っていますが、2重のコンテナエンジンの設定を一括でやってくれるわけではありません。
したがって、ホストのファイルをkind上のコンテナに見せるには、以下のような2重の設定が必要になります。

- `kind`自体が使っているdockerにホストのディレクトリAをBという名前でマウントする設定をする
- `kind`内部のcontainerdに、BというディレクトリをCという名前でマウントする

では、ホスト上の`/mnt/test/`というディレクトリをコンテナに見せてみます。
`/mnt/test/`はこうなっています。

```
$ ls /mnt/test/
myfile.txt
```

この`myfile.txt`をPodに見せるのがゴールです。

ではまず`kind`の設定です。`extraMounts`というフィールドで設定します。

```kind2.yaml
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
- role: worker
  extraMounts:
  - containerPath: /test
    hostPath: /mnt/test
```

次にコンテナの設定です。

```container.yaml
apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  containers:
  - image: nginx
    name: nginx
    volumeMounts:
    - mountPath: /test
      name: test-volume
  volumes:
  - name: test-volume
    hostPath:
      path: /test
      type: Directory
```

それぞれは以下の①②に対応しています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/96277/1a8110fa-6692-6aec-b0b7-10aeb7dc6564.png)



クラスタの作り直し

```
kind create cluster --config kind2.yaml
```

確認

```
$ docker exec -it kind-worker ls /test
myfile.txt
```

```
$ kubectl apply -f container.yaml
pod/nginx created
$ kubectl exec pod/nginx -it ls /test
myfile.txt
```

ちゃんとPodからホスト上の`myfile.txt`が見えました。
アプリのログなどのファイルを永続化したい場合に、このテクニックでホストのディレクトリに書き込むことで、`kind`やその上のPodが消えてもファイルを残すことができます。

### ホストのディスクをkind上のapi-server等に見せる

api-serverなどKubernetes自身が持つサービスにもホストのディスクをマウントすることができます。api-serverなどはコンテナとして起動されるという仕組みになっているので、今回もやはり前節と同じで、２重コンテナ構造のため２階層にディレクトリをマウントするという設定がそれぞれ必要となります。

ただし、今回は`kind`起動時の設定ファイル`kind3.yaml`のみで設定可能です。理由は、`kubeadm`を利用している`kind`では、そのapi-serverなどへの設定も同じファイルで記述できるためです。

```kind3.yaml
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
  extraMounts:
  - containerPath: /test
    hostPath: /mnt/test
- role: worker
kubeadmConfigPatches:
- |
  apiVersion: kubeadm.k8s.io/v1beta2
  kind: ClusterConfiguration
  metadata:
    name: config
  apiServer:
    extraVolumes:
    - name: "test"
      hostPath: /test
      mountPath: /test
```


ポイントは２つ。

- まず、`api-server`は`control-plane`というノードに配置されますので、今回は`extraMounts`を`control-plane`に記載します。。
- 次にこの`KubeadmConfigPatch`の部分です。ここに**`kubeadm`への設定の差分が書ける**というのが`kind`の嬉しさの一つでもあります。ディレクトリをマウントする設定のフィールドは、`extraVolumes`です。このお作法は、`kubeadm`のドキュメントを参考にできます。

さて再掲ですが、ホストのディレクトリはこうなっています。

```
$ ls /mnt/test/
myfile.txt
```

では、実際にクラスタを構築して確認してみます。

```
$ kind create cluster --config kind3.yaml
```

確認

```
$ kubectl exec -n kube-system kube-apiserver-kind-control-plane
myfile.txt
```

ちゃんとマウントされましたね。api-serverからaudit logなどをホストに書き出したいときにぴったりです。
もうこんなに設定ファイル一個だけでかんたんにKubernetesクラスタがいじれると、**楽しくてしょうがなくなりますね**。

### もしVM上のDockerがこんなエラーをはいたら

```error
docker: Error response from daemon: cgroups: cannot find cgroup mount destination: unknown.
```

VMのcgroupの使われ方が少し変なようなので、以下をたたくと回避できます。

```
sudo mkdir /sys/fs/cgroup/systemd
sudo mount -t cgroup -o none,name=systemd cgroup /sys/fs/cgroup/systemd
```

### [2019/12/28追加] Kubernetesバージョンを指定する

通常構築したクラスタのKubernetesは、`Kind`が対応した最新版になります。
最新版では現時点で `v0.16.3`です。
ただ、昔のKubernetesでないと動かないツールもあります。

そういう場合には、`kind create cluster`コマンドのオプション`--image`が使えます。

`--image`では`kind`のノードイメージを指定するのですが、デフォルトは`kindest/node:v<最新版>`となっています。
このノードイメージは[dockerhub](https://hub.docker.com/r/kindest/node/tags)で管理されており、どんなバージョンが用意されているかを確認することができます。

今回は`v0.15.6`を指定して起動してみます。

```
kind create cluster --image kindest/node:v0.15.6
```

正常に終了すると、以下のように各ノードのバージョンを確認できます。

```sh
$ kubectl get nodes
NAME                 STATUS     ROLES    AGE   VERSION
kind-control-plane   Ready      master   43s   v1.15.6
kind-worker          Ready      <none>   10s   v1.15.6
```

# `kind`のメンテナンス

## コンテナイメージを追加する

ホスト上でアプリを開発して、アプリを同梱したコンテナをPodとしてkind上で実行したいときがあります。しかし、**kind内のcontainerdはホストのdockerのイメージを直接読めません**ので、そのままではPodが起動しません。
ホストのイメージをkind内のcontainerdにアップロードする便利コマンドがあるので、それを使います。

```
kind load docker-image <image:tag>
```

注意が必要なのは、これをすることによってcontrol-planeやworkerすべての内部にイメージがアップロードされるため、**ノード数分ディスクを消費すること**です。

もし特定のノード、たとえばワーカーだけにイメージをアップロードしたい場合は以下のようにします。


```
kind load docker-image <image:tag> --nodes=<nodename>
```

複数のノードにアップロードする場合には、ノード名をコンマで連結して入れればよいです。

```
kind load docker-image <image:tag> --nodes=<nodename1>,<nodename2>
```

## コンテナイメージを消す

ディスクを圧迫してきたら、使わないコンテナイメージを削除することをお勧めします。

### まず確認する

```
$ docker exec kind-control-plane crictl images
IMAGE                                TAG                 IMAGE ID            SIZE
k8s.gcr.io/coredns                   1.6.2               bf261d1579144       44.2MB
k8s.gcr.io/etcd                      3.3.15-0            b2756210eeabf       248MB
k8s.gcr.io/kube-apiserver            v1.16.3             6750aac993260       185MB
k8s.gcr.io/kube-controller-manager   v1.16.3             74308f46cf2d3       128MB
k8s.gcr.io/kube-proxy                v1.16.3             2388adcec48cf       103MB
k8s.gcr.io/kube-scheduler            v1.16.3             0f7390a0dfb32       105MB
docker.io/kindest/kindnetd           <none>              aa67fec7d7ef7       32.4MB
k8s.gcr.io/pause                     3.1                 da86e6ba6ca19       746kB
test                                 0.1                 xxxxxxxxxxxxx       1.0GB
```

### 見つけたイメージを消す

```
docker exec kind-control-plane crictl rmi test:0.1
```

### 同じことをworkerに対しても実行する

`kind-control-plane`のところを`kind-worker-1`などに変えると同じ事が実行できます。


## ログを一括して吐き出す

Kubernetes周りのサービス（たとえばapi-server）のログを出力したいとき、便利コマンドが用意されています。

```
kind export logs
```

## kubeconfig(`kubectl`接続設定)を再度書き出す

通常は`kind create cluster`時に自動的に`$HOME/.kube/config`に書き出されるのですが、何かの拍子に消えてしまった場合には、以下コマンドで復元することができます。

```sh
kind export kubeconfig
```

また、`kind create cluster`時に書き出すファイルを変えるには

```sh
kind create cluster --kubeconfig <filepath>
```
で別のファイルに書き出すことができます。このときには、`kubectl`の前に

```sh
export KUBECONFIG=<filepath>
```
を実行しておいてから`kubectl`を実行すると、kindで作成したクラスタに接続することができます。

# まとめ

`kind`は軽量、クリーンなKubernetesテスト環境で、とても便利です。さまざまなKubernetes関係の開発者が利用していることも知られています。
TIPSで紹介したように、うまくすればホストのディレクトリもマウントできます。
開発者は「本番系には使わないでね」、と言っていますので、使い方は気を付けたほうがよさそうです。

### 参考情報

https://qiita.com/it__chago/items/c7d057d968da934199da
https://speakerdeck.com/ytaka23/cloud-native-developer-jp-13th
https://qiita.com/sourjp/items/281d2189516823950291