今回は Kubernetes の開発で使われている公式ツールの kind を使ってみる。 このツールを使うと Docker のコンテナを使って Kubernetes のクラスタが素早く簡単に構築できる。
使った環境は次のとおり。
$ sw_vers ProductName: macOS ProductVersion: 11.6 BuildVersion: 20G165 $ uname -m arm64 $ kind version kind v0.11.1 go1.16.4 darwin/arm64 $ kubectl version Client Version: version.Info{Major:"1", Minor:"21", GitVersion:"v1.21.5", GitCommit:"aea7bbadd2fc0cd689de94a54e5b7b758869d691", GitTreeState:"clean", BuildDate:"2021-09-15T21:10:45Z", GoVersion:"go1.16.8", Compiler:"gc", Platform:"darwin/arm64"} Server Version: version.Info{Major:"1", Minor:"21", GitVersion:"v1.21.1", GitCommit:"5e58841cce77d4bc13713ad2b91fa0d961e69192", GitTreeState:"clean", BuildDate:"2021-05-21T23:06:30Z", GoVersion:"go1.16.4", Compiler:"gc", Platform:"linux/arm64"} $ docker version Client: Cloud integration: 1.0.17 Version: 20.10.8 API version: 1.41 Go version: go1.16.6 Git commit: 3967b7d Built: Fri Jul 30 19:55:20 2021 OS/Arch: darwin/arm64 Context: default Experimental: true Server: Docker Engine - Community Engine: Version: 20.10.8 API version: 1.41 (minimum version 1.12) Go version: go1.16.6 Git commit: 75249d8 Built: Fri Jul 30 19:53:48 2021 OS/Arch: linux/arm64 Experimental: false containerd: Version: 1.4.9 GitCommit: e25210fe30a0a703442421b0f60afac609f950a3 runc: Version: 1.0.1 GitCommit: v1.0.1-0-g4144b63 docker-init: Version: 0.19.0 GitCommit: de40ad0
もくじ
- もくじ
- 下準備
- Kubernetes クラスタを作る
- 複数のクラスタを作る・壊す
- 自前のコンテナイメージを使って Pod を立ち上げてみる
- マルチノードクラスタを作ってみる
- 自前のイメージで Deployment を立ち上げてみる
下準備
はじめに Docker Desktop をインストールしておく。
$ brew install --cask docker
Docker を起動する。
$ open /Applications/Docker.app
docker version
コマンドを実行する。
クライアントとサーバのバージョンが正しく表示されることを確認する。
$ docker version Client: Cloud integration: 1.0.17 Version: 20.10.8 API version: 1.41 Go version: go1.16.6 Git commit: 3967b7d Built: Fri Jul 30 19:55:20 2021 OS/Arch: darwin/arm64 Context: default Experimental: true Server: Docker Engine - Community Engine: Version: 20.10.8 API version: 1.41 (minimum version 1.12) Go version: go1.16.6 Git commit: 75249d8 Built: Fri Jul 30 19:53:48 2021 OS/Arch: linux/arm64 Experimental: false containerd: Version: 1.4.9 GitCommit: e25210fe30a0a703442421b0f60afac609f950a3 runc: Version: 1.0.1 GitCommit: v1.0.1-0-g4144b63 docker-init: Version: 0.19.0 GitCommit: de40ad0
続いて kind をインストールする。 macOS なら Homebrew で入る。
$ brew install kind
これで kind
コマンドが使えるようになる。
$ kind version kind v0.11.1 go1.16.4 darwin/arm64
Kubernetes クラスタを作る
kind を使って Kubernetes のクラスタを作るには kind create cluster
コマンドを使う。
最初に実行したときは Kubernetes のノード用のイメージファイルをダウンロードするので時間がかかる。
$ kind create cluster Creating cluster "kind" ... ✓ Ensuring node image (kindest/node:v1.21.1) 🖼 ✓ Preparing nodes 📦 ✓ Writing configuration 📜 ✓ Starting control-plane 🕹️ ✓ Installing CNI 🔌 ✓ Installing StorageClass 💾 Set kubectl context to "kind-kind" You can now use your cluster with: kubectl cluster-info --context kind-kind Not sure what to do next? 😅 Check out https://kind.sigs.k8s.io/docs/user/quick-start/
コマンドの実行が完了すると、デフォルトの名前 (kind
) でクラスタができる。
$ kind get clusters kind
kubectl
コマンドの設定ファイルも自動で作られるので、すぐ使える状態になってる。
$ kubectl config get-contexts CURRENT NAME CLUSTER AUTHINFO NAMESPACE * kind-kind kind-kind kind-kind $ kubectl config view --minify apiVersion: v1 clusters: - cluster: certificate-authority-data: DATA+OMITTED server: https://127.0.0.1:49921 name: kind-kind contexts: - context: cluster: kind-kind user: kind-kind name: kind-kind current-context: kind-kind kind: Config preferences: {} users: - name: kind-kind user: client-certificate-data: REDACTED client-key-data: REDACTED
デフォルトではシングルノード構成でクラスタができる。
$ kubectl get nodes NAME STATUS ROLES AGE VERSION kind-control-plane Ready control-plane,master 103s v1.21.1
docker container list
コマンドを使って稼働しているコンテナを見ると Kubernetes のコントロールプレーンのコンテナが見える。
$ docker container list CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES eec5aa7fa35d kindest/node:v1.21.1 "/usr/local/bin/entr…" 2 minutes ago Up 2 minutes 127.0.0.1:49921->6443/tcp kind-control-plane
コンテナの中身を覗くと、必要なサービスが色々と立ち上がっているようだ。
$ docker container exec -it eec5aa7fa35d ss -tlnp State Recv-Q Send-Q Local Address:Port Peer Address:Port Process LISTEN 0 4096 172.18.0.2:2379 0.0.0.0:* users:(("etcd",pid=675,fd=6)) LISTEN 0 4096 127.0.0.1:2379 0.0.0.0:* users:(("etcd",pid=675,fd=5)) LISTEN 0 4096 172.18.0.2:2380 0.0.0.0:* users:(("etcd",pid=675,fd=3)) LISTEN 0 4096 127.0.0.1:2381 0.0.0.0:* users:(("etcd",pid=675,fd=10)) LISTEN 0 4096 127.0.0.1:10257 0.0.0.0:* users:(("kube-controller",pid=607,fd=7)) LISTEN 0 4096 127.0.0.1:10259 0.0.0.0:* users:(("kube-scheduler",pid=546,fd=7)) LISTEN 0 4096 127.0.0.1:42015 0.0.0.0:* users:(("containerd",pid=183,fd=13)) LISTEN 0 4096 127.0.0.1:10248 0.0.0.0:* users:(("kubelet",pid=722,fd=23)) LISTEN 0 4096 127.0.0.1:10249 0.0.0.0:* users:(("kube-proxy",pid=957,fd=16)) LISTEN 0 1024 127.0.0.11:33609 0.0.0.0:* LISTEN 0 4096 *:6443 *:* users:(("kube-apiserver",pid=593,fd=7)) LISTEN 0 4096 *:10256 *:* users:(("kube-proxy",pid=957,fd=18)) LISTEN 0 4096 *:10250 *:* users:(("kubelet",pid=722,fd=27))
ちなみに、推奨される環境としては Docker のランタイムに 6 ~ 8GB 以上のメモリを割り当てることが望ましいらしい。
以下のコマンドで、今のランタイムがどれくらい使えるか確認できる。 デフォルトでは 2GB になっている可能性があるので必要に応じて割り当てを増やそう。
$ docker stats --no-stream CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDS eec5aa7fa35d kind-control-plane 132.22% 358.5MiB / 5.8GiB 6.04% 2.98kB / 1.34kB 18.1MB / 65.5kB 125
複数のクラスタを作る・壊す
特定の名前でクラスタを作りたいときは --name
オプションを指定する。
$ kind create cluster --name kind-2
これで、2 つのクラスタができた。
$ kind get clusters kind kind-2
kubectl
にも複数のクラスタが登録されている。
$ kubectl config get-contexts CURRENT NAME CLUSTER AUTHINFO NAMESPACE kind-kind kind-kind kind-kind * kind-kind-2 kind-kind-2 kind-kind-2
クラスタを消すときは kind delete cluster
コマンドを使う。
$ kind delete cluster --name kind-2
消したら、コンテキストを元のクラスタに切り替えておく。
$ kubectl config use-context kind-kind
Switched to context "kind-kind".
$ kubectl config current-context
kind-kind
自前のコンテナイメージを使って Pod を立ち上げてみる
次は、試しに自前のコンテナイメージを使って Pod を立ち上げてみよう。
とりあえず、適当に WSGI でホスト名を返すアプリケーションを用意する。
$ cat << 'EOF' > server.py #!/usr/bin/env python3 # -*- coding: utf-8 -*- import os from wsgiref.simple_server import make_server def application(environ, start_response): start_response('200 OK', [('Content-Type', 'text/plain')]) uname = os.uname() nodename = uname.nodename msg = f'Hello, World! by {nodename=}\n' return [msg.encode('ascii')] def main(): with make_server('', 8080, application) as httpd: print("Serving on port 8080...") httpd.serve_forever() if __name__ == '__main__': main() EOF
上記のアプリケーションを組み込んだ Dockerfile を用意する。
$ cat << 'EOF' > Dockerfile FROM python:3.9 EXPOSE 8080 COPY server.py . CMD python3 server.py EOF
Docker イメージをビルドする。
$ docker build -t example/helloworld:0.1 .
手元のイメージは kind load docker-image
コマンドを使ってクラスタに登録できる。
$ kind load docker-image example/helloworld:0.1
次のように自前のイメージを使って Pod を立ち上げるマニフェストファイルを用意する。
ポイントは imagePullPolicy: Never
で、これで必ずローカルのイメージが使われるようになる。
$ cat << 'EOF' > helloworld.yaml apiVersion: v1 kind: Pod metadata: name: helloworld-pod spec: containers: - name: helloworld-server image: example/helloworld:0.1 imagePullPolicy: Never EOF
マニフェストファイルを適用する。
$ kubectl apply -f helloworld.yaml pod/helloworld-pod created
すると、次のように Pod が立ち上がる。
$ kubectl get pods NAME READY STATUS RESTARTS AGE helloworld-pod 1/1 Running 0 12s
ポートフォワーディングで Pod の TCP:8080 ポートを引き出してみよう。
$ kubectl port-forward helloworld-pod 8080:8080 Forwarding from 127.0.0.1:8080 -> 8080 Forwarding from [::1]:8080 -> 8080
別のターミナルからリクエストを送ると、ちゃんと HTTP のレスポンスが得られる。
$ curl http://localhost:8080 Hello, World! by nodename='helloworld-pod'
kubectl exec
コマンドで Pod に入っても、ちゃんとプロセスが稼働していることが確認できる。
$ kubectl exec -it helloworld-pod -- /bin/bash root@helloworld-pod:/# uname -a Linux helloworld-pod 5.10.47-linuxkit #1 SMP PREEMPT Sat Jul 3 21:50:16 UTC 2021 aarch64 GNU/Linux root@helloworld-pod:/# apt-get update && apt-get -y install iproute2 root@helloworld-pod:/# ss -tlnp State Recv-Q Send-Q Local Address:Port Peer Address:Port Process LISTEN 0 5 0.0.0.0:8080 0.0.0.0:* users:(("python3",pid=8,fd=3)) root@helloworld-pod:/# exit
マルチノードクラスタを作ってみる
kind の良いところは手軽にマルチノードクラスタも作れるところ。
一旦、シングルノードのクラスタは削除しておく。
$ kind delete cluster
そして、以下のような kind 用の設定ファイルを用意する。 これでコントロールプレーンとワーカーノード x2 のクラスタが作れる。
$ cat << 'EOF' > kind-multi-node-cluster.yaml kind: Cluster apiVersion: kind.x-k8s.io/v1alpha4 nodes: - role: control-plane - role: worker - role: worker EOF
設定ファイルを使ってクラスタを作る。
$ kind create cluster \ --name kind-multi \ --config kind-multi-node-cluster.yaml Creating cluster "kind-multi" ... ✓ Ensuring node image (kindest/node:v1.21.1) 🖼 ✓ Preparing nodes 📦 📦 📦 ✓ Writing configuration 📜 ✓ Starting control-plane 🕹️ ✓ Installing CNI 🔌 ✓ Installing StorageClass 💾 ✓ Joining worker nodes 🚜 Set kubectl context to "kind-kind-multi" You can now use your cluster with: kubectl cluster-info --context kind-kind-multi Have a nice day! 👋
すると、次のように 3 つのノードから成るクラスタができる。
$ kubectl get nodes NAME STATUS ROLES AGE VERSION kind-multi-control-plane Ready control-plane,master 53s v1.21.1 kind-multi-worker Ready <none> 24s v1.21.1 kind-multi-worker2 Ready <none> 24s v1.21.1
確認すると、Docker コンテナも 3 つ稼働している。
$ docker container list CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES bcb2f337b802 kindest/node:v1.21.1 "/usr/local/bin/entr…" About a minute ago Up About a minute kind-multi-worker eb32f4375353 kindest/node:v1.21.1 "/usr/local/bin/entr…" About a minute ago Up About a minute kind-multi-worker2 22da177bed79 kindest/node:v1.21.1 "/usr/local/bin/entr…" About a minute ago Up About a minute 127.0.0.1:50566->6443/tcp kind-multi-control-plane
自前のイメージで Deployment を立ち上げてみる
マルチノードのクラスタを使って遊んでいこう。 試しに、複数の Pod を持った Deployment を作ってみる。
まずは先ほどビルドした Docker イメージをマルチノードのクラスタに登録する。
$ kind load docker-image example/helloworld:0.1 \
--name kind-multi
そして、次のように Deployment 用のマニフェストファイルを用意する。 これで、Deployment x1 / ReplicaSet x1 / Pod x2 のリソースができる。
$ cat << 'EOF' > deployment.yaml apiVersion: apps/v1 kind: Deployment metadata: name: helloworld-deployment labels: app: helloworld spec: replicas: 2 selector: matchLabels: app: helloworld template: metadata: labels: app: helloworld spec: containers: - name: helloworld-server image: example/helloworld:0.1 imagePullPolicy: Never ports: - containerPort: 8080 EOF
マニフェストファイルを適用する。
$ kubectl apply -f deployment.yaml deployment.apps/helloworld-deployment created
すると、次のようにそれぞれのオブジェクトができた。 Pod を見ると、ちゃんとそれぞれのワーカーで動作している様子が確認できる。
$ kubectl get deployments NAME READY UP-TO-DATE AVAILABLE AGE helloworld-deployment 2/2 2 2 12s $ kubectl get replicaset NAME DESIRED CURRENT READY AGE helloworld-deployment-d857c4cb6 2 2 2 25s $ kubectl get pods NAME READY STATUS RESTARTS AGE helloworld-deployment-d857c4cb6-8tvzk 1/1 Running 0 44s helloworld-deployment-d857c4cb6-zcfl7 1/1 Running 0 44s
Pod に HTTP でアクセスする
続いては上記の Pod に HTTP でアクセスしてみる。
kubectl expose deployment
コマンドで Service オブジェクトを作る。
$ kubectl expose deployment helloworld-deployment service/helloworld-deployment exposed
これでアクセスするための IP アドレス (CLUSTER-IP) が割り当てられた。
$ kubectl get services -l app=helloworld NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE helloworld-deployment ClusterIP 10.96.190.89 <none> 8080/TCP 102s
正し、この IP アドレスはクラスタ内部向けなので、Kind を実行しているホストからだと直接はアクセスできない。
$ ping -c 3 10.96.190.89 PING 10.96.190.89 (10.96.190.89): 56 data bytes Request timeout for icmp_seq 0 Request timeout for icmp_seq 1 --- 10.96.190.89 ping statistics --- 3 packets transmitted, 0 packets received, 100.0% packet loss
そこで、代わりにクラスタのコンテナ経由でアクセスしてみよう。
$ docker container exec -it kind-multi-control-plane curl http://10.96.190.89:8080 Hello, World! by nodename='helloworld-deployment-d857c4cb6-zcfl7' $ docker container exec -it kind-multi-control-plane curl http://10.96.190.89:8080 Hello, World! by nodename='helloworld-deployment-d857c4cb6-8tvzk'
ちゃんと、それぞれの Pod がリクエストを捌いていることが確認できた。
ちなみに、デフォルトでは --type=LoadBalancer
な Service は作れないようだ。
$ kubectl delete service helloworld-deployment $ kubectl expose deployment helloworld-deployment --type=LoadBalancer service/helloworld-deployment exposed $ kubectl get services -l app=helloworld NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE helloworld-deployment LoadBalancer 10.96.93.210 <pending> 8080:30640/TCP 20s
上記のように、状態が <pending>
となっており、IP アドレスが割り振られない。
確認が済んだら Service を削除しておこう。
$ kubectl delete service helloworld-deployment
オートヒーリングを確認する
続いてはオートヒーリングの様子を観察するために Pod を壊してみよう。
まずは今の Pod の状態をかくにんしておく。
$ kubectl get pods -l app=helloworld NAME READY STATUS RESTARTS AGE helloworld-deployment-d857c4cb6-8tvzk 1/1 Running 0 8m9s helloworld-deployment-d857c4cb6-zcfl7 1/1 Running 0 8m9s
ここで、Pod を片方削除してみよう。
$ kubectl delete pods helloworld-deployment-d857c4cb6-8tvzk
pod "helloworld-deployment-d857c4cb6-8tvzk" deleted
別のターミナルから Pod の状態を確認すると、指定した Pod が削除されて新たに別の Pod が作られる様子が見える。
$ kubectl get pods -l app=helloworld NAME READY STATUS RESTARTS AGE helloworld-deployment-d857c4cb6-8tvzk 0/1 Terminating 0 9m29s helloworld-deployment-d857c4cb6-ndprj 1/1 Running 0 34s helloworld-deployment-d857c4cb6-zcfl7 1/1 Running 0 9m29s
最終的には以下のように Pod が 2 つの状態で安定する。
$ kubectl get pods -l app=helloworld NAME READY STATUS RESTARTS AGE helloworld-deployment-d857c4cb6-ndprj 1/1 Running 0 45s helloworld-deployment-d857c4cb6-zcfl7 1/1 Running 0 9m40s
また、ログを確認すると Pod が作り直されていることがわかる。
$ kubectl describe replicaset helloworld-deployment | tail Environment: <none> Mounts: <none> Volumes: <none> Events: Type Reason Age From Message ---- ------ ---- ---- ------- Normal SuccessfulCreate 11m replicaset-controller Created pod: helloworld-deployment-d857c4cb6-8tvzk Normal SuccessfulCreate 11m replicaset-controller Created pod: helloworld-deployment-d857c4cb6-zcfl7 Normal SuccessfulCreate 2m49s replicaset-controller Created pod: helloworld-deployment-d857c4cb6-ndprj
そんな感じで。