kind (Kuberenetes in Docker) に deep dive してみる

kuKubernetes の Install Tools というページでは、kubernetes を local で動かすための tool として kind が紹介されています。今日はこの kind について内部構造及び使い方を見てみます。

kind とは

kind は「Docker container の中で Kubernetes を動かすことが出来るツール」です。kind という命名は「Kubernetes in Docker」から来ていて、K-in-D という頭文字をとったものになっています。

kind については KubeCon + CloudNativeCon で何度か紹介されているようです。例えば KubeCon + CloudNativeCon North America 2019 における以下の "Deep Dive: Kind" というトーク では、「kind とは何か?」について内部実装など含めて紹介されています。

www.youtube.com

上記のトークについて簡単に summary を書くと、kind は以下のようなものとして紹介されています。

  • Docker container として Node(をシミュレートする container)を動かし、その中で Kubernetes を動かすツール
    • Node image の中に、Kubernetes を動かすために必要な全てを詰める
      • kubelet
      • kubeadm
      • docker
      • systemd
      • core images (Kuberentes にとって重要な container image)
        • etcd
        • coredns
        • pause
        • kube-apiserver
        • etc.
  • multi-node 構成も可能
  • Kubernetes を source code から build して動かすことが可能
  • 30s 程度で Kuberenetes cluster を作ることが出来る
  • kind は「Kuberentes 自体の test」のために作られた
    • Kuberenetes の CI は Kubernetes で動いており、全ての test は Pod の中で実行される。そのため、Kuberenetes 自体を test するには、「Kuberentes を container の中で動かす」必要があった。

上記のトークの中で出てくる以下のスライドが、kind の仕組みを端的に示していて分かりやすいと思います。"Node" Container の中で、kubelet や containerd, そして containered を通じて起動された「Kuberentes の動作を支える各種 container」が動いている事が分かります。

f:id:south37:20201230223917p:plain

ここまでで、「kind の内部構造」を説明しました。次に、実際に kind を動かしてみましょう。

kind を動かしてみる

kind の利用方法は実はとても簡単で、README で以下のように言及されている通り「GO111MODULE="on" go get sigs.k8s.io/kind@v0.9.0 && kind create cluster を実行するだけ」となっています。

If you have go (1.11+) and docker installed GO111MODULE="on" go get sigs.k8s.io/kind@v0.9.0 && kind create cluster is all you need!

cf. https://github.com/kubernetes-sigs/kind#please-see-our-documentation-for-more-in-depth-installation-etc

実際に実行すると、以下のように Kubernetes cluster 作成が進みます。自分の環境では、Kuberentes cluster 作成にかかる時間は 1min 強でした。

(なお、Ensuring node image という step で少し時間がかかりますが、これは後述する「kind が利用する kindest/node という docker image の pull に時間がかかっている」だけです。一度 image pull が完了すると次からは 30s 程度で高速に Kubernetes cluster が作成出来る様になります)。

$ GO111MODULE="on" go get sigs.k8s.io/kind@v0.9.0 && kind create cluster
go: downloading sigs.k8s.io/kind v0.9.0
go: downloading github.com/spf13/cobra v1.0.0
go: downloading k8s.io/apimachinery v0.18.8
go: downloading gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776
go: downloading github.com/mattn/go-isatty v0.0.12
go: downloading github.com/alessio/shellescape v1.2.2
go: downloading sigs.k8s.io/yaml v1.2.0
go: downloading github.com/pelletier/go-toml v1.8.0
go: downloading github.com/evanphx/json-patch v0.0.0-20200808040245-162e5629780b
go: downloading golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed
go: downloading github.com/evanphx/json-patch/v5 v5.1.0
GO111MODULE="on" go get sigs.k8s.io/kind@v0.9.0  8.93s user 3.64s system 102% cpu 12.228 total

Creating cluster "kind" ...
 ✓ Ensuring node image (kindest/node:v1.19.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 create cluster  4.95s user 2.65s system 9% cpu 1:16.71 total

これで既に「Kubernetes cluster が Docker container の中で動いている状態」となっています。簡単ですね!

kind で動く Kubernetes cluster を利用してみる

次に、実際に kind で動く Kuberentes cluster を利用してみましょう。

$ kind create cluster を実行した際の message に Set kubectl context to "kind-kind" と出ていたように、既に kubectl の context は「kind で作成した Kubenetes cluster(= kind-kind という名前の cluster)」に切り替わっています。つまり、この状態で $ kubectl を利用すれば、kind-kind cluster の API server に対して通信が行われるようになっています。

実際、以下のように $ kubectl config current-context$ kubectl cluster-info の結果を見てみると「local で動く kind-kind cluster に context が切り替わっている」事が確認出来ます。

$ kubectl config current-context
kind-kind

$ kubectl cluster-info
Kubernetes control plane is running at https://127.0.0.1:55999
KubeDNS is running at https://127.0.0.1:55999/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy

To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.

$ kubectl で cluster 内の k8s object を見てみましょう。例えば namespace や pod, service を見てみると、以下のような内容になっています。

$ kubectl get namespaces
NAME                 STATUS   AGE
default              Active   11m
kube-node-lease      Active   11m
kube-public          Active   11m
kube-system          Active   11m
local-path-storage   Active   11m

$ kubectl get po --all-namespaces
NAMESPACE            NAME                                         READY   STATUS    RESTARTS   AGE
kube-system          coredns-f9fd979d6-9cgbl                      1/1     Running   0          11m
kube-system          coredns-f9fd979d6-wlnmw                      1/1     Running   0          11m
kube-system          etcd-kind-control-plane                      1/1     Running   0          11m
kube-system          kindnet-phgz9                                1/1     Running   0          11m
kube-system          kube-apiserver-kind-control-plane            1/1     Running   0          11m
kube-system          kube-controller-manager-kind-control-plane   1/1     Running   0          11m
kube-system          kube-proxy-dxx9q                             1/1     Running   0          11m
kube-system          kube-scheduler-kind-control-plane            1/1     Running   0          11m
local-path-storage   local-path-provisioner-78776bfc44-66wln      1/1     Running   0          11m

$ kubectl get svc --all-namespaces
NAMESPACE     NAME         TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)                  AGE
default       kubernetes   ClusterIP   10.96.0.1    <none>        443/TCP                  18m
kube-system   kube-dns     ClusterIP   10.96.0.10   <none>        53/UDP,53/TCP,9153/TCP   18m

さらに、適当に deployment と service の追加もしてみましょう。自分が昔作った https://github.com/south37/dumper という「request header を print するだけの Docker container」を動かしてみます。

まず、deployment と service の manifest file を用意します。

# dumper-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: dumper
  name: dumper
  namespace: default
spec:
  selector:
    matchLabels:
      app: dumper
  template:
    metadata:
      annotations:
      labels:
        app: dumper
      name: dumper
    spec:
      containers:
      - image: south37/dumper
        livenessProbe:
          httpGet:
            path: /ping
            port: 8080
        name: dumper
        ports:
        - containerPort: 8080
          name: http
        readinessProbe:
          httpGet:
            path: /ping
            port: 8080
# dumper-service.yaml
apiVersion: v1
kind: Service
metadata:
  labels:
    app: dumper
  name: dumper
  namespace: default
spec:
  ports:
  - name: http
    port: 80
    protocol: TCP
    targetPort: 8080
  selector:
    app: dumper
  type: ClusterIP

次に、これらの manfest file を apply します。

$ kubectl apply -f dumper-deployment.yaml
deployment.apps/dumper created

$ kubectl apply -f dumper-service.yaml
service/dumper created

apply したものがちゃんと作られている事を確認します。

$ kubectl get all -n default
NAME                          READY   STATUS    RESTARTS   AGE
pod/dumper-6465654fdc-qn729   1/1     Running   0          118s

NAME                 TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE
service/dumper       ClusterIP   10.110.60.42   <none>        80/TCP    114s
service/kubernetes   ClusterIP   10.96.0.1      <none>        443/TCP   21m

NAME                     READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/dumper   1/1     1            1           118s

NAME                                DESIRED   CURRENT   READY   AGE
replicaset.apps/dumper-6465654fdc   1         1         1       118s

まず、pod の log を見てみます。すると、healthcheck 用の GET /ping の reqeust が来ている事が確認できます。

$ kubectl logs dumper-6465654fdc-qn729
.
.
.
2020/12/30 12:09:55 GET /ping HTTP/1.1
2020/12/30 12:09:55 Host: 10.244.0.5:8080
2020/12/30 12:09:55 User-Agent: kube-probe/1.19
2020/12/30 12:09:55 Accept-Encoding: gzip
2020/12/30 12:09:55 Connection: close

次に、Kuberentes cluster の中から Service を通した request をしてみましょう。 curl が入っている docker image として radial/busyboxplus:curl を使うことにします(注: radial/busyboxplus:curlhttps://kubernetes.io/docs/concepts/services-networking/connect-applications-service/ の中でも利用されてるので、安全な image だと判断して利用してます)。

すると、以下のように Kubernetes cluster 内の pod から $ curl http://dumper.default で 「default namespace の dumper service へ HTTP request」をして、ちゃんと response が返る事を確認出来ました!

$ kubectl run --rm -it busybox --image=radial/busyboxplus:curl
If you don't see a command prompt, try pressing enter.
[ root@busybox:/ ]$ curl http://dumper.default
Hello, dumper!

pod の log を見てみると、ちゃんと上記 request が pod に到達していることも確認できます。

$ kubectl logs dumper-6465654fdc-qn729
.
.
.
2020/12/30 12:12:00 GET / HTTP/1.1
2020/12/30 12:12:00 Host: dumper.default
2020/12/30 12:12:00 User-Agent: curl/7.35.0
2020/12/30 12:12:00 Accept: */*

簡単にではありますが、Kubernetes cluster としての動作が確認できました。

Docker container として動く Node container の中を見てみる

次に、kind の動作をもう少し深掘りしてみます。具体的には、Deep Dive: Kind - KubeCon + CloudNativeCon North America 2019 の中で紹介されていた「Node container の動作」をもう少し見てみます。

まず、Kubernetes cluster を一度削除して作り直すことにします。これは、「作成されたばかりの状態の Kubernetes cluster」で実験するための操作です(特に気にならない人は不要です)。

$ kind delete cluster
Deleting cluster "kind" ...

$ kind create cluster
Creating cluster "kind" ...
 ✓ Ensuring node image (kindest/node:v1.19.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 create cluster  4.62s user 2.53s system 19% cpu 36.518 total

次に、$ kind create cluster を実行した後の状態で $ docker ps すると、kindest/node:v1.19.1 という image の container が起動している事が分かります。これが、kind が利用する「Node container」のようです。

$ docker ps
CONTAINER ID   IMAGE                  COMMAND                  CREATED              STATUS          PORTS                       NAMES
45a383679dd2   kindest/node:v1.19.1   "/usr/local/bin/entr…"   About a minute ago   Up 59 seconds   127.0.0.1:56856->6443/tcp   kind-control-plane

$ docker exec して Node container の中をみてみましょう。$ ps fax で process 一覧を見てみると、Deep Dive: Kind - KubeCon + CloudNativeCon North America 2019 の中で説明されていた通り、Kubernetes の動作を支える様々な process が起動している事が分かります。containerdkubelet など一部を除くと、そのほかの process は /usr/local/bin/containerd-shim-runc-v2 経由で起動している(= container として起動している)ことも分かります。

$ docker exec -it 45a383679dd2 bash
root@kind-control-plane:/# ps fax
    PID TTY      STAT   TIME COMMAND
   2060 pts/1    Ss     0:00 bash
   2189 pts/1    R+     0:00  \_ ps fax
      1 ?        Ss     0:00 /sbin/init
    124 ?        S<s    0:00 /lib/systemd/systemd-journald
    135 ?        Ssl    0:11 /usr/local/bin/containerd
    310 ?        Sl     0:00 /usr/local/bin/containerd-shim-runc-v2 -namespace k8s.io -id 1c426469eb3ae09b744f1c116e6798c65886e218271dfa105fba747b4bfde0d3 -address /run/containerd/containerd.soc
    405 ?        Ss     0:00  \_ /pause
    502 ?        Ssl    0:06  \_ kube-scheduler --authentication-kubeconfig=/etc/kubernetes/scheduler.conf --authorization-kubeconfig=/etc/kubernetes/scheduler.conf --bind-address=127.0.0.1 --ku
    311 ?        Sl     0:00 /usr/local/bin/containerd-shim-runc-v2 -namespace k8s.io -id 23d54713b4401fb309725273c78961ea5d7f3a5be157d2a139813b9b9611e220 -address /run/containerd/containerd.soc
    418 ?        Ss     0:00  \_ /pause
    620 ?        Ssl    0:20  \_ etcd --advertise-client-urls=https://172.19.0.2:2379 --cert-file=/etc/kubernetes/pki/etcd/server.crt --client-cert-auth=true --data-dir=/var/lib/etcd --initial-a
    318 ?        Sl     0:00 /usr/local/bin/containerd-shim-runc-v2 -namespace k8s.io -id a24a84c14111721de85b657f2bb0b89db44d87e97452ef86690060a0f0fcd3bc -address /run/containerd/containerd.soc
    425 ?        Ss     0:00  \_ /pause
    563 ?        Ssl    1:08  \_ kube-apiserver --advertise-address=172.19.0.2 --allow-privileged=true --authorization-mode=Node,RBAC --client-ca-file=/etc/kubernetes/pki/ca.crt --enable-admissi
    373 ?        Sl     0:00 /usr/local/bin/containerd-shim-runc-v2 -namespace k8s.io -id 8a6a56e83a590f4958dbd3262e4b0adbe36acad229ebcb54ef13c657f39c2c0a -address /run/containerd/containerd.soc
    433 ?        Ss     0:00  \_ /pause
    548 ?        Ssl    0:24  \_ kube-controller-manager --allocate-node-cidrs=true --authentication-kubeconfig=/etc/kubernetes/controller-manager.conf --authorization-kubeconfig=/etc/kubernetes
    667 ?        Ssl    0:25 /usr/bin/kubelet --bootstrap-kubeconfig=/etc/kubernetes/bootstrap-kubelet.conf --kubeconfig=/etc/kubernetes/kubelet.conf --config=/var/lib/kubelet/config.yaml --cont
    797 ?        Sl     0:00 /usr/local/bin/containerd-shim-runc-v2 -namespace k8s.io -id 841020b0c947c1a8c2047a542268bf3dd91f8fb6b15cfc0a993dc61c0769e660 -address /run/containerd/containerd.soc
    819 ?        Ss     0:00  \_ /pause
    898 ?        Ssl    0:00  \_ /usr/local/bin/kube-proxy --config=/var/lib/kube-proxy/config.conf --hostname-override=kind-control-plane
    833 ?        Sl     0:00 /usr/local/bin/containerd-shim-runc-v2 -namespace k8s.io -id 6171f628b9c0658a1983b198160fe76b53eeb4118aa8a0c71491ff5516453268 -address /run/containerd/containerd.soc
    864 ?        Ss     0:00  \_ /pause
    948 ?        Ssl    0:00  \_ /bin/kindnetd
   1110 ?        Sl     0:00 /usr/local/bin/containerd-shim-runc-v2 -namespace k8s.io -id 1fd32fb832a5e3782d1f39b26b99a30de8872a0a69600a77db3631f274eeb819 -address /run/containerd/containerd.soc
   1153 ?        Ss     0:00  \_ /pause
   1226 ?        Ssl    0:03  \_ /coredns -conf /etc/coredns/Corefile
   1112 ?        Sl     0:00 /usr/local/bin/containerd-shim-runc-v2 -namespace k8s.io -id 725b634f7fc5d93ebfb1e63afb8aabcd936e6297ed7ab552e314e1cc90efeec5 -address /run/containerd/containerd.soc
   1160 ?        Ss     0:00  \_ /pause
   1229 ?        Ssl    0:01  \_ local-path-provisioner --debug start --helper-image k8s.gcr.io/build-image/debian-base:v2.1.0 --config /etc/config/config.json
   1350 ?        Sl     0:00 /usr/local/bin/containerd-shim-runc-v2 -namespace k8s.io -id 9d07db434b356d9fddb2ae31715adc1209406a604b7068560f379832f5d79ba2 -address /run/containerd/containerd.soc
   1373 ?        Ss     0:00  \_ /pause
   1404 ?        Ssl    0:03  \_ /coredns -conf /etc/coredns/Corefile

containerd で起動している container は、containerd の CLI tool である ctr で管理する事が出来るはずです。少し見てみましょう。

まず、namespace 一覧を見てみると k8s.io という名前の namespace が見つかります(注: この namespace は kuberentes の namespace とは無関係で、「containerd が container を管理する際に利用する namespace 機能」のはずです)。

root@kind-control-plane:/# ctr namespaces ls
NAME   LABELS
k8s.io

次に、この k8s.io namespace 内の container 一覧を $ ctr --namespace k8s.io containers ls で見てみると、予想通り「Kuberentes cluster の動作に利用される container 一覧」をみる事が出来ました。

root@kind-control-plane:/# ctr --namespace k8s.io containers ls
CONTAINER                                                           IMAGE                                                                      RUNTIME
0e7fc11f71638b86f8fd41f046101ebcb16b48976f06826add2d35df4e2ccc10    k8s.gcr.io/kube-controller-manager:v1.19.1                                 io.containerd.runc.v2
114d8f7f34ebc00c00236d7a111961193a6fa300dc90a4114385134f9eeda412    k8s.gcr.io/kube-proxy:v1.19.1                                              io.containerd.runc.v2
1c426469eb3ae09b744f1c116e6798c65886e218271dfa105fba747b4bfde0d3    k8s.gcr.io/pause:3.3                                                       io.containerd.runc.v2
1fd32fb832a5e3782d1f39b26b99a30de8872a0a69600a77db3631f274eeb819    k8s.gcr.io/pause:3.3                                                       io.containerd.runc.v2
2299e2a7b2b7afbb0789b30a4d7f4e57220a650f7368c04439e91c72e5049356    k8s.gcr.io/kube-apiserver:v1.19.1                                          io.containerd.runc.v2
23d54713b4401fb309725273c78961ea5d7f3a5be157d2a139813b9b9611e220    k8s.gcr.io/pause:3.3                                                       io.containerd.runc.v2
35ab22ae753e043553188810bddfec43aa048d8c93f1ca38cf868ee31dbe06fc    k8s.gcr.io/coredns:1.7.0                                                   io.containerd.runc.v2
6171f628b9c0658a1983b198160fe76b53eeb4118aa8a0c71491ff5516453268    k8s.gcr.io/pause:3.3                                                       io.containerd.runc.v2
6212a3ea51397a8491a8defb188faa1c9afb4b678a8fa102ab15ba8c78f98aa2    sha256:0369cf4303ffdb467dc219990960a9baa8512a54b0ad9283eaf55bd6c0adb934    io.containerd.runc.v2
624a9cf197014dcdbf0be5ddd68995566d14ceab00c8ca18fd51eb35cfe999cb    k8s.gcr.io/kube-scheduler:v1.19.1                                          io.containerd.runc.v2
62d494fe1a94a396494ecd30cfa8538db2e1d2055fedac216d19fd21332d3841    sha256:b77790820d01598b2c56f823fa489e3f56be2cb5d6f7dd9eecd68a1995b89c13    io.containerd.runc.v2
725b634f7fc5d93ebfb1e63afb8aabcd936e6297ed7ab552e314e1cc90efeec5    k8s.gcr.io/pause:3.3                                                       io.containerd.runc.v2
841020b0c947c1a8c2047a542268bf3dd91f8fb6b15cfc0a993dc61c0769e660    k8s.gcr.io/pause:3.3                                                       io.containerd.runc.v2
8a6a56e83a590f4958dbd3262e4b0adbe36acad229ebcb54ef13c657f39c2c0a    k8s.gcr.io/pause:3.3                                                       io.containerd.runc.v2
9d07db434b356d9fddb2ae31715adc1209406a604b7068560f379832f5d79ba2    k8s.gcr.io/pause:3.3                                                       io.containerd.runc.v2
a24a84c14111721de85b657f2bb0b89db44d87e97452ef86690060a0f0fcd3bc    k8s.gcr.io/pause:3.3                                                       io.containerd.runc.v2
b7775d2582ea9fdd481cf308d9c8bafa28fffbdaa2c8c0bad3377a9254876a59    k8s.gcr.io/coredns:1.7.0                                                   io.containerd.runc.v2
ecbad3d6f6f5321e46b0d3ac395cb25227b42cfc04b1cec5a2b659fe45fab6cc    sha256:e422121c9c5f97623245b7e600eeb5e223ee623f21fa04da985ae71057d8d70b    io.containerd.runc.v2

このように、「Node container の中で containerd を動かし、その containerd 経由で Kuberentes cluster に必要な container を動かす」という形で kind は動作するようです。Deep Dive: Kind - KubeCon + CloudNativeCon North America 2019 の中で説明されていた事ではありますが、改めてその動作を確認できました。

kindminikube の使い分けについて

さて、ここまでは kind というツールの機能について説明してきました。一方で、「local で Kubernetes を動かすツール」としては他に minikube も存在します。これらの使い分けはどうするのが良いでしょうか?

kind vs minikube で検索すると、この2つの使い分けについて言及している記事がいくつか見つかります。例えば https://brennerm.github.io/posts/minikube-vs-kind-vs-k3s.html という記事では、以下のようにそれぞれの特徴が述べられています。

  • minikube
    1. VMKubernetes を起動する
    2. $ minikube dashboard 機能や minikube の addon system が有用
  • kind
    1. Docker container で Kubernetes を起動する
    2. $ kind load docker-image <my-custom-image>:<tag> を実行する事で local で build した image を container registry へ push する事なく Kubernetes cluster から利用する事が可能

kind について言えば、$ kind load docker-image の機能はかなり強力で、例えば custom controller のような「Kubernetes の中で動かしながら開発を進めたいもの」においては極めて有用です。実際、こういった側面があるためか、kubebuilder の Tutorial の Quick Start では「local で Kuberentes cluster を動かす選択肢」として kind が紹介されていたりします。

You’ll need a Kubernetes cluster to run against. You can use KIND to get a local cluster for testing, or run against a remote cluster.

cf. https://book.kubebuilder.io/quick-start.html#test-it-out

また、「VM よりも Docker container の方が扱いに慣れてる」などのケースでも kind の利用にはメリットがありそうだと個人的には感じました。

まとめ

kind の内部構造や使い方についてざっと眺めてみました。

「Kuberentes を Docker container の中で動かす」という言葉だけを聞くと突拍子も無いアイディアに聞こえますが、kubeletcontainerd, その他の Kubernetes cluster に必要な component を "Node" image の中にまとめて配布してると思えば確かに自然ですし、実際にちゃんと動作することも確認できました。

Kuberentes の CI で使われている限り、これからも継続してメンテナンスされていきそうなのも良い点です。「30s で Kubernetes cluster を起動して気軽に動かせるツール」として、とても有用なものだと言えそうです。

なお、今回は「kind の Node container で動く container 一覧を眺めた」だけで、1つ1つの container の動きについては特に言及しませんでした。そのほとんどは「Kubernetes cluster に共通で必要な component」のはずですが、kindnetd は kind におけるデフォルトの CNI plugin である kindnetdaemon だそうです。

Deep Dive: Kind - KubeCon + CloudNativeCon North America 2019 ではこの kindnet を含めて、このブログで言及してない様々な事を説明してるので、さらに詳細が気になる方はぜひ動画の方もみてみてください。また、自分も一部しか読んでませんが、kind の document (https://kind.sigs.k8s.io/) も理解を深める上でとても有用だと思います。

参考情報

おまけ: 既に node image を pull 済の場合に Kuberentes cluster 作成にかかる時間について

最初に kind を動かしてみた際に、「kindest/node の docker pull 部分で時間がかかる」と書きました。そこで、「既に node image を pull 済みの場合」についても、試しに計測してみましょう。

まず、先ほど作成した cluster を削除します。

$ kind delete cluster
Deleting cluster "kind" ...

この状態でも、kindest/node docker image は残っている事が確認できます。

$ docker images | grep kindest
kindest/node                      <none>               37ddbc9063d2   3 months ago    1.33GB

次に、この状態で再度 $ kind create cluster を実行してみます。

$ kind create cluster
Creating cluster "kind" ...
 ✓ Ensuring node image (kindest/node:v1.19.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

Have a nice day! 👋
kind create cluster  4.53s user 2.37s system 21% cpu 32.535 total

今回は、上記のように 30s 程度で Kubernetes cluster が起動することを確認できました!🎉 Node image の pull が無ければ、Kuberentes cluster をとても素早く作成出来る事が分かりますね!

おまけ 2: deployment を apply した状態で Node container の中を見てみる

deployment を apply した状態で、Node container の中を見てみましょう。

$ kubectl apply -f dumper-deployment.yaml
deployment.apps/dumper created

この状態だと、(当然ではありますが)apply した内容の pod に相当する container が起動している様子を確認できます。containerd の動作を感じる事が出来て、とても良いですね!

$ docker exec -it 7ff1d05ef709 bash

root@kind-control-plane:/# ps fax
    PID TTY      STAT   TIME COMMAND
.
.
.
   1729 ?        Sl     0:00 /usr/local/bin/containerd-shim-runc-v2 -namespace k8s.io -id 34794c98df201080b5b22bcef08805e03748023c35f0deec77f962ff301a6835 -address /run/containerd/containerd.sock
   1752 ?        Ss     0:00  \_ /pause
   1879 ?        Ssl    0:00  \_ /app/dumper

root@kind-control-plane:/# ctr --namespace k8s.io images ls | grep dumper
docker.io/south37/dumper:latest                                                                  application/vnd.docker.distribution.manifest.v2+json sha256:5efcf15fbd3439b2c2fff2415957933b45b9531401526c026c41219aed15701c 290.0 MiB linux/amd64 io.cri-containerd.image=managed
docker.io/south37/dumper@sha256:5efcf15fbd3439b2c2fff2415957933b45b9531401526c026c41219aed15701c application/vnd.docker.distribution.manifest.v2+json sha256:5efcf15fbd3439b2c2fff2415957933b45b9531401526c026c41219aed15701c 290.0 MiB linux/amd64 io.cri-containerd.image=managed

root@kind-control-plane:/# ctr --namespace k8s.io containers ls | grep dumper
ab80cafc653433c2b74713b85679797b4ffad5ae54eed733bb40d41af7bb9f43    docker.io/south37/dumper:latest                                            io.containerd.runc.v2

おまけ 3: $ kind load docker-image の実装について

kind の強力な機能である $ kind load docker-image 機能がどう実現されているのか気になったので、少しコードを読んでみました(対象は kind の v0.9.0 tag のコードです)。

まず、$ kind load コマンド自体は https://github.com/kubernetes-sigs/kind/blob/v0.9.0/pkg/cmd/kind/load/load.go で実装されていて、その中の $ kind load docker-image サブコマンドは https://github.com/kubernetes-sigs/kind/blob/v0.9.0/pkg/cmd/kind/load/docker-image/docker-image.go で実装されてるようです。さらにコードを読み進めると、nodeutils package の LoadImageArchive という関数にたどり着きます。

import (
    ...
    dockerimage "sigs.k8s.io/kind/pkg/cmd/kind/load/docker-image"
    ...
)

// NewCommand returns a new cobra.Command for get
func NewCommand(logger log.Logger, streams cmd.IOStreams) *cobra.Command {
    cmd := &cobra.Command{
        Args:  cobra.NoArgs,
        Use:   "load",
        Short: "Loads images into nodes",
        Long:  "Loads images into node from an archive or image on host",
    }
    // add subcommands
    cmd.AddCommand(dockerimage.NewCommand(logger, streams))
    cmd.AddCommand(imagearchive.NewCommand(logger, streams))
    return cmd
}

cf. https://github.com/kubernetes-sigs/kind/blob/v0.9.0/pkg/cmd/kind/load/load.go#L38

   // Load the image on the selected nodes
    fns := []func() error{}
    for _, selectedNode := range selectedNodes {
        selectedNode := selectedNode // capture loop variable
        fns = append(fns, func() error {
            return loadImage(imageTarPath, selectedNode)
        })
    }

cf. https://github.com/kubernetes-sigs/kind/blob/v0.9.0/pkg/cmd/kind/load/docker-image/docker-image.go#L149-L156

// loads an image tarball onto a node
func loadImage(imageTarName string, node nodes.Node) error {
    f, err := os.Open(imageTarName)
    if err != nil {
        return errors.Wrap(err, "failed to open image")
    }
    defer f.Close()
    return nodeutils.LoadImageArchive(node, f)
}

cf. https://github.com/kubernetes-sigs/kind/blob/v0.9.0/pkg/cmd/kind/load/docker-image/docker-image.go#L162-L170

nodeutils.LoadImageArchive は以下のような実装になっていて、「Node container の中で $ ctr コマンドを呼び出して、container image の load を行う」ようです。

// LoadImageArchive loads image onto the node, where image is a Reader over an image archive
func LoadImageArchive(n nodes.Node, image io.Reader) error {
    cmd := n.Command("ctr", "--namespace=k8s.io", "images", "import", "-").SetStdin(image)
    if err := cmd.Run(); err != nil {
        return errors.Wrap(err, "failed to load image")
    }
    return nil
}

cf. https://github.com/kubernetes-sigs/kind/blob/v0.9.0/pkg/cluster/nodeutils/util.go#L77-L84

nodeutils.LoadImageArchive は「1 つ 1 つの Node container に対して loop を回して実行してる」ようです。つまり、魔法のように見えた $ kind load docker-image の機能は、「それぞれの Node container の中で $ ctr images import を実行して、container image を import する」事で実現しているようです。面白いですね!