Blog
[Kubnernetes] kubelet の runtime を docker から cri-o に置き換える
みなさんこんにちは。
アドテク本部の makocchi です。
kubernetes で使われている container の runtime にはみなさん何をお使いでしょうか?
ほぼ全ての人が docker と答えると思います。実際 Google Kubernetes Engine(GKE) も runtime には docker が使われています。(2018年01月現在)
しかし kubernetes はこの container の runtime を自由に選択することができます。
kubernetes と container runtime の間は CRI(Container Runtime Interface) を通して行われます。
つまり CRI に対応している runtime であれば何でも良いということになります。
CRI に対応している代表的な runtime には docker、cri-o、rkt、cri-containerd があります。今後は様々な runtime が増えてくるかもしれないですね。
cri-o について
cri-o は Kubernetes Incubator Project として開発されています。
CRI に対応しているのはもちろんですが、もう一つの標準規格である OCI(Open Container Initiative) にも準拠しています。
docker の image は既に OCI に準拠していますので、docker build した image を cri-o も使用することができます。
※ http://cri-o.io/ より抜粋
それではさっそく kubernetes の runtime として cri-o を使用してみましょう。
kubernetes 1.8 から cri-o が stable になりましたので、1.8 以上の cluster を用意します。
今回は 1.9 の kubernetes Cluster(Ubuntu) を用意しました。(kubernetes の環境の構築の手順はここでは省略します)
1 2 3 |
$ kubectl version --short Client Version: v1.9.1 Server Version: v1.9.1 |
現状の runtime を確認しておきます。
1 2 |
$ kubectl get node cri-test-node -o jsonpath='{.status.nodeInfo.containerRuntimeVersion}{"\n"}' docker://1.12.6 |
kubelet と dockerd は落としておきます。
1 2 3 |
$ sudo systemctl stop kubelet $ sudo systemctl stop docker ※systemd 配下で動かしている場合 |
runc, crio の install
runc の install
現時点(2018年1月)での最新である v1.0.0-rc4 を使います。
1 2 3 4 5 |
$ sudo curl -o /usr/bin/runc -s -L https://github.com/opencontainers/runc/releases/download/v1.0.0-rc4/runc.amd64 $ sudo chmod +x /usr/bin/runc $ /usr/bin/runc --version runc version 1.0.0-rc4 spec: 1.0.0 |
crio の install
cri-o は binary の filename が crio となるようです(ややこしい)
crio の binary は用意されていないので自分で作る必要があります。
tutorial によると go 1.8.5 が指定されていますので、なるべく 1.8.5 で作るのがいいでしょう。
go の環境が整ったらまずは crictl を build します。
1 2 3 |
$ go get github.com/kubernetes-incubator/cri-tools/cmd/crictl $ ${GOPATH}/bin/crictl --version crictl version 1.0.0-alpha.0 |
その次に crio を build します。
1 2 3 4 5 6 7 8 9 10 11 12 |
$ sudo apt-get update && sudo apt-get install -y libglib2.0-dev \ libseccomp-dev \ libapparmor-dev \ libgpgme11-dev \ libdevmapper-dev \ make \ gcc \ git $ go get -d github.com/kubernetes-incubator/cri-o $ cd $GOPATH/src/github.com/kubernetes-incubator/cri-o $ make install.tools $ make |
続いて install します。
1 2 3 4 5 6 7 8 |
$ sudo make install install -D -m 755 bin/crio /usr/local/bin/crio install -D -m 755 bin/conmon /usr/local/libexec/crio/conmon install -D -m 755 bin/pause /usr/local/libexec/crio/pause install -d -m 755 /usr/local/share/man/man5 install -d -m 755 /usr/local/share/man/man8 install -m 644 docs/crio.conf.5 -t /usr/local/share/man/man5 install -m 644 docs/crio.8 -t /usr/local/share/man/man8 |
続いて config を配置します。
1 2 3 4 5 |
$ sudo make install.config install -D -m 644 crio.conf /etc/crio/crio.conf install -D -m 644 seccomp.json /etc/crio/seccomp.json install -D -m 644 crio-umount.conf /usr/local/share/oci-umount/oci-umount.d/crio-umount.conf install -D -m 644 crictl.yaml /etc |
crio の config は /etc/crio/crio.conf のようですね。
default ではこのような内容です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
$ grep -v "#" /etc/crio/crio.conf | grep -v "^$" [crio] root = "/var/lib/containers/storage" runroot = "/var/run/containers/storage" storage_driver = "" storage_option = [ ] [crio.api] listen = "/var/run/crio/crio.sock" stream_address = "" stream_port = "10010" file_locking = true [crio.runtime] runtime = "/usr/bin/runc" runtime_untrusted_workload = "" default_workload_trust = "trusted" no_pivot = false conmon = "/usr/local/libexec/crio/conmon" conmon_env = [ "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", ] selinux = false seccomp_profile = "/etc/crio/seccomp.json" apparmor_profile = "crio-default" cgroup_manager = "cgroupfs" hooks_dir_path = "/usr/share/containers/oci/hooks.d" default_mounts = [ ] pids_limit = 1024 enable_shared_pid_namespace = false log_size_max = -1 [crio.image] default_transport = "docker://" pause_image = "kubernetes/pause" pause_command = "/pause" signature_policy = "" image_volumes = "mkdir" insecure_registries = [ ] registries = [ ] [crio.network] network_dir = "/etc/cni/net.d/" plugin_dir = "/opt/cni/bin/" |
[crio.image] の registries のところは default では空になっています。
1 2 3 4 |
# registries is used to specify a comma separated list of registries to be used # when pulling an unqualified image (e.g. fedora:rawhide). registries = [ ] |
多くの場合は docker hub から image を取ってくると思うので、registries に docker.io を書いておくのがいいでしょう。
1 2 3 |
registries = [ "docker.io" ] |
もしくは crio の起動時に –registry で渡してあげてもいいと思います(書かなくても image は取ってこれます)
crio の起動 (systemd)
今回は crio を systemd 経由で起動させます。
crio の引数は自由に設定してください。ここでは loglevel を debug にしています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
$ sudo sh -c 'echo "[Unit] Description=OCI-based implementation of Kubernetes Container Runtime Interface Documentation=https://github.com/kubernetes-incubator/cri-o [Service] ExecStart=/usr/local/bin/crio --log-level debug Restart=on-failure RestartSec=5 [Install] WantedBy=multi-user.target" > /etc/systemd/system/crio.service' $ sudo systemctl daemon-reload $ sudo systemctl enable crio $ sudo systemctl start crio |
crio の動作確認
動作を確認してみましょう
version の確認
1 2 3 4 5 |
$ sudo crictl version Version: 0.1.0 RuntimeName: cri-o RuntimeVersion: 1.9.0-dev RuntimeApiVersion: v1alpha1 |
下記のようにエラーが出る場合は crio が正常に起動していないと思われます。
1 |
FATA[0000] getting the runtime version failed: rpc error: code = Unavailable desc = grpc: the connection is unavailable |
journalctl -u crio 等で log を確認しましょう。
image の操作
crictl で image を pull してみます。
その前に /etc/containers/policy.json という file が無いと怒られるので sample を github から持ってきます。
1 2 |
$ sudo mkdir -p /etc/containers $ sudo curl -L -s -o /etc/containers/policy.json https://raw.githubusercontent.com/kubernetes-incubator/cri-o/master/test/policy.json |
crictl pull で image を local に持ってくることが可能です。
crictl images で現在 local にある image 一覧を表示できます。
1 2 3 4 5 6 |
$ sudo crictl pull docker.io/nginx:alpine Image is update to date for docker.io/library/nginx@sha256:a4ac36c250ec97bb6108e04e3cdd2848e338ee197737cebd39c960d7e3237775 $ sudo crictl images IMAGE TAG IMAGE ID SIZE docker.io/library/nginx alpine bb00c21b4edf4 18MB |
先程落としてきた policy.json ですが、policy の中に
1 2 3 4 5 6 7 8 |
... "docker": { "docker.io/library/hello-world": [ { "type": "reject" } ], ... |
という部分があります。
これは hello-world の image pull は拒否するということで、実際に pull しようとするとちゃんと reject されます。
1 2 |
$ sudo crictl pull hello-world FATA[0006] pulling image failed: rpc error: code = Unknown desc = Source image rejected: Running image docker://hello-world:latest is rejected by policy. |
細かく policy を設定することも出来るようですね。
cri-o の storage_driver 変更(devicemapper から overlay2 へ)
image は crio.conf 内に定義されている root のディレクトリ配下に置かれます。
1 2 3 |
# root is a path to the "root directory". CRIO stores all of its data, # including container images, in this directory. root = "/var/lib/containers/storage" |
/var/lib/containers/storage のディレクトリ構成を見てみます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
$ sudo tree /var/lib/containers/storage /var/lib/containers/storage |-- devicemapper | |-- devicemapper | | |-- data | | `-- metadata | |-- metadata | | |-- 078e9ac4658c6d69ba8a0ab0a95fb4bf8f9a8efa59f0611cf26a33d02b7dc16e | | |-- 2c735e3fd96adf058d54e1fb5acf63b0466ff146c7e395494dc8d35d5a8a7a35 | | |-- 2e00a080cabc2f6d4b4a03ae381ae95044940cfb12d8f74f5be976737e6b5329 | | |-- base | | |-- d39d92664027be502c35cf1bf464c726d15b8ead0e3084be6e252a161730bc82 | | |-- deviceset-metadata | | `-- transaction-metadata | `-- mnt | |-- 078e9ac4658c6d69ba8a0ab0a95fb4bf8f9a8efa59f0611cf26a33d02b7dc16e | |-- 2c735e3fd96adf058d54e1fb5acf63b0466ff146c7e395494dc8d35d5a8a7a35 | |-- 2e00a080cabc2f6d4b4a03ae381ae95044940cfb12d8f74f5be976737e6b5329 | `-- d39d92664027be502c35cf1bf464c726d15b8ead0e3084be6e252a161730bc82 |-- devicemapper-containers | `-- containers.lock |-- devicemapper-images | |-- bb00c21b4edf411c9ef782c070d3cc6c46791298743d3d47c0de2a31e598b6cd | | |-- =c2hhMjU2OmJiMDBjMjFiNGVkZjQxMWM5ZWY3ODJjMDcwZDNjYzZjNDY3OTEyOTg3NDNkM2Q0N2MwZGUyYTMxZTU5OGI2Y2Q= | | `-- manifest | |-- images.json | `-- images.lock |-- devicemapper-layers | |-- 078e9ac4658c6d69ba8a0ab0a95fb4bf8f9a8efa59f0611cf26a33d02b7dc16e.tar-split.gz | |-- 2c735e3fd96adf058d54e1fb5acf63b0466ff146c7e395494dc8d35d5a8a7a35.tar-split.gz | |-- 2e00a080cabc2f6d4b4a03ae381ae95044940cfb12d8f74f5be976737e6b5329.tar-split.gz | |-- d39d92664027be502c35cf1bf464c726d15b8ead0e3084be6e252a161730bc82.tar-split.gz | |-- layers.json | `-- layers.lock |-- mounts |-- storage.lock `-- tmp |
先程 pull してきた nginx:alpine(bb00c21b4edf4) の image 定義は ${root}/devicemapper-images 配下に置かれています。
どうやら storage_driver には devicemapper 使われているようですね。
それではさっそく overlay2 に変えてみましょう。
crio.conf を編集して storage_driver に overlay2 を設定し、crio を再起動させます。
1 2 3 4 5 6 7 8 |
$ sudo vi /etc/crio/crio.conf ... # storage_driver select which storage driver is used to manage storage # of images and containers. storage_driver = "overlay2" ... $ sudo systemctl restart crio |
今度は redis の image を pull してみます。
1 2 |
$ sudo crictl pull redis:alpine Image is update to date for docker.io/library/redis@sha256:28a852279e97b1d8683759a3f70b17b057d94b683447d3fe32be947d1cd04a1c |
今度は ${root}/overlay2 が作成されていると思います。
cri-o で pod を起動させ、container を動かす
cri-o ではまず pod を起動させ、その中で container を動かすという手順になるようです。
pod を起動させる為に pod の設定が書かれた json or yaml を渡してあげる必要があります。
まずは sandbox の pod を起動させてみましょう。
cri-o の github repo 内の test/testdata 内に sample が置かれているので、そこから pod を起動させます。
pod の起動には crictl runp を使用します。crictl pods で表示させることができます。
1 2 3 4 5 6 7 |
$ cd $GOPATH/src/github.com/kubernetes-incubator/cri-o $ sudo crictl runp test/testdata/sandbox_config.json cd9069056ab8e48713bcee6f7e096af178b5a5c5c274fcea01d59d6bb0848d7d $ sudo crictl pods PODSANDBOX ID CREATED STATE NAME NAMESPACE ATTEMPT cd9069056ab8e 2 minutes ago SANDBOX_READY podsandbox1 redhat.test.crio 1 |
podsandbox1 の pod が起動しました。
inspectp をすることでより詳細な情報を見ることができます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
$ sudo crictl inspectp cd9069056ab8e { "status": { "id": "cd9069056ab8e48713bcee6f7e096af178b5a5c5c274fcea01d59d6bb0848d7d", "metadata": { "attempt": 1, "name": "podsandbox1", "namespace": "redhat.test.crio", "uid": "redhat-test-crio" }, "state": "SANDBOX_READY", "createdAt": "2018-02-01T11:01:01.967620802+09:00", "network": { "ip": "10.112.76.6" }, "linux": null, "labels": { "group": "test", "io.kubernetes.container.name": "POD" }, "annotations": { "owner": "hmeng", "security.alpha.kubernetes.io/seccomp/pod": "unconfined" } } } |
この pod には 10.112.76.6 の ip が振られました。
次に podsandbox1 の pod 内で redis の container を起動させてみます。
container を起動する時にも定義ファイルを渡してあげる必要があります。
今回は用意されている testdata 内の container_redis.json を使います。
1 2 3 4 5 6 |
$ sudo crictl create cd9069056ab8e test/testdata/container_redis.json test/testdata/sandbox_config.json b661c78f388c6af22df757a4f1ca7da5816233c579a4a1dc5cd2faecdc6d3794 $ sudo crictl ps -a CONTAINER ID IMAGE CREATED STATE NAME ATTEMPT b661c78f388c6 redis:alpine 28 seconds ago CONTAINER_CREATED podsandbox1-redis 0 |
crictl create が無事に終わったら crictl start させます。
1 2 3 4 5 6 |
$ sudo crictl start b661c78f388c6 b661c78f388c6 $ sudo crictl ps CONTAINER ID IMAGE CREATED STATE NAME ATTEMPT b661c78f388c6 redis:alpine 2 minutes ago CONTAINER_RUNNING podsandbox1-redis 0 |
STATE が CONTAINER_CREATED から CONTAINER_RUNNING になりました。
それではこの redis に接続してみましょう。
先程 inspectp した際に出てきた podsandbox1 の ip(10.112.76.6) に対して接続してみます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
$ telnet 10.112.76.6 6379 Trying 10.112.76.6... Connected to 10.112.76.6. Escape character is '^]'. info $2635 # Server redis_version:4.0.7 redis_git_sha1:00000000 redis_git_dirty:0 redis_build_id:f295d9beb14f1ade redis_mode:standalone ... ... |
無事に動作の確認ができました!
動作確認が終わったので、pod と container は消しておきます。
container を消す際には rm を、pod を消す際には rmp を使用します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
$ sudo crictl rm b661c78f388c6 b661c78f388c6 $ sudo crictl ps -a CONTAINER ID IMAGE CREATED STATE NAME ATTEMPT $ sudo crictl stopp cd9069056ab8e Stopped sandbox cd9069056ab8e $ sudo crictl rmp cd9069056ab8e Removed sandbox cd9069056ab8e $ sudo crictl pods PODSANDBOX ID CREATED STATE NAME NAMESPACE ATTEMPT |
kubelet の設定を変更して runtime に cri-o を指定する
kubelet の設定
それではいよいよ kubelet の設定を docker から cri-o に変更してみます。
kubelet に下記の option を設定して起動させます。
1 2 3 |
--container-runtime=remote --container-runtime-endpoint=/var/run/crio/crio.sock --runtime-request-timeout=10m (optional) |
–runtime-request-timeout は必要に応じて設定してください。
そして先程と同じように get node で表示させてみます。
1 2 |
$ kubectl get node cri-test-node -o jsonpath='{.status.nodeInfo.containerRuntimeVersion}{"\n"}' cri-o://1.9.0-dev |
無事に変更されました!
実際に pod を kubernetes 経由で起動させる
次に実際に pod を kubectl で作成してみます。
cri-o で動いている node に label を付け、nodeSelector で指定して作成します。
1 |
$ kubectl label node cri-test-node runtime=crio |
manifest はこんな感じです。
1 2 3 4 5 6 7 8 9 10 11 |
apiVersion: v1 kind: Pod metadata: name: nginx spec: containers: - name: nginx image: nginx imagePullPolicy: IfNotPresent nodeSelector: runtime: crio |
1 2 3 4 5 6 7 8 |
$ kubectl create -f nginx-crio.yaml $ kubectl get pods -o wide NAME READY STATUS RESTARTS AGE IP NODE nginx 1/1 Running 0 35s 10.112.76.11 cri-test-node $ kubectl get pod nginx -o jsonpath='{.status.containerStatuses[].containerID}{"\n"}' cri-o://f48039701e4c6d6239726d5e84cb23f9743e5194ffd47d16a5a3e4001acc0770 |
kubernetes 上から問題なく pod が作成されました。
cri-o 上では f48039701e4c6 で動いていることがわかりましたので、node 側で確認してみます。
1 2 3 |
$ sudo crictl ps CONTAINER ID IMAGE CREATED STATE NAME ATTEMPT f48039701e4c6 docker.io/library/nginx@sha256:926b086e1234b6ae9a11589c4cece66b267890d24d1da388c96dd8795b2ffcfb 6 minutes ago CONTAINER_RUNNING nginx 0 |
無事に起動していますね。
まとめ
このように docker から cri-o への runtime 変更することができるのですが、現状の docker で特に苦労していなければ無理に置き換える必要は無いと思っています。
それでも敢えて docker を置き換える理由として考えられるのは個人的には以下のようなものがあるのではと思っています。
- docker よりも軽量でスリムな crio を使用することで node 側の負担を少しでも軽減させる (手元の環境では dockerd よりも crio のほうが cpu/memory 使用量が小さかった)
- dockerd よりも安定しそう これは根拠が無いので憶測に過ぎないのですが、いろいろな機能が実装されている太った dockerd よりもスリムな crio の方が安定性があるのではないかと思っています
今はまだ導入するのに若干手順が多いですが、その内 rpm や deb の package が作られれば導入は楽になるでしょう。
crictl の option は docker の option に非常によく似ている為、docker に慣れていれば問題なく使うことができそうです。
それではよい Kubernetes life を!
Author