K8s 宣布與 Dockershim 分手,Docker 用戶該怎麼辦?
這邊要討論的事件,起源於一年多前(2020年底),K8s 官方宣佈 v1.20 版之後,將不再支援 Docker,這在當時引起許多論壇以及使用者間的騷動(彷彿一群吃瓜群眾在看 Johnny Depp 與 Amber Heard 之間發生了什麼事),也有一些文章出來解釋,請大家不用慌張。而接著就這樣過了一年半,在 K8s 即將迎來最新的 v1.24 釋出中,其中一項重大改變就是 K8s 正式將 Dockershim 移除了(我們祝福它們分手快樂)。
這邊先列出兩位事件主角過去的幾個重要的時間點:
- 2014年10月,Docker 釋出第一版。
- 2015年7月,K8s 釋出第一版,以 Docker 為容器運行時(container runtime)。
- 2016年12月,K8s 在 v1.5 導入 CRI 架構,並將操作 Docker 相關邏輯獨立出來,產生 Dockershim。
- 2019年11月,Mirantis 買下 Docker。
- 2020年12月,K8s 宣布 v1.20 後不再支援 Docker,Dockershim 被標示為棄用。
- 2021年4月,Mirantis 釋出 cri-dockerd,宣稱可取代 Dockershim。
- 2022年5月,K8s 釋出的 v1.24 正式移除 Dockershim。
這篇文章的主要目的是要解釋以下兩個問題:
- K8s 為何要移除 Dockershim?
- Dockershim 被移除後,對於 Docker 的使用者有何影響?
一、Container Runtime Interface (CRI) 與 Dockershim 的背景介紹
在回答上面兩個問題之前,必須先解釋何謂 CRI。CRI 是由雲原生計算基金會(CNCF)在2016年首次引入,其主要目的是希望 K8s 能夠透過一個標準介面,與各種容器運行時介接,這個介接的介面標準就是所謂的 CRI(容器運行時介面)。
以下是一個遵循 CRI 的示意架構圖:
CRI shim 就是一個基於 CRI 所實作出來,扮演 Kubelet 與容器運行時之間的溝通橋樑。CRI shim 會透過 gRPC 接收 Kubelet 傳送過來的 Protobuf 格式資料(與 K8s 相同,gRPC 和 Protobuf 都是 Google 的產物),其中包含各式各樣針對 Pod 或 Container 的操作,如 Run, Stop, List, Remove, Start … 等。
而 Dockershim 就是一種 CRI shim 的實現,讓 Docker 的使用者能夠透過 K8s 對容器進行管理。這五年來,K8s 社群一直在維護著 Dockershim,但隨著有越來越多人使用不同的容器運行時,且 Docker 也被 Mirantis 買走後,K8s 社群就覺得這部分的責任不需要在他們身上。即使 K8s 在 v1.20 就宣布捨棄,但社群還是持續維護 Dockershim 到 v1.23。一直到現在 v1.24 才終於斷捨離,結束這段維持 5年多的關係…
二、Dockershim 被移除後,K8s 要如何使用 Docker
由於 Mirantis 在 2021年宣稱透過他們的 cri-dockerd,可以取代原本的 Dockershim,接下來我們就來驗證這件事。
此外,這篇文章撰寫的時間點是在 2022年5月,此時的 Kubespray 還無法支援 K8s v1.24 的部署。因此,以下會順便先介紹如何將舊版的 K8s (v1.22.2)升級至 v1.24。接著再介紹如何部署 Mirantis 的 cri-dockerd 以及設定 K8s CRI 使用 Docker。最後再測試 Docker 的容器運行時是否能正常使用。
前置環境
- Host OS: CentOS 7
- Host Kernel: 3.10
- K8s: v1.22.2
部署步驟
A. 先準備一個 K8s 環境(可參考 Kubespray 部署),並切換至 root 身份。
[vagrant@fanchy3-1 ~]$ sudo su
[root@fanchy3-1 vagrant]# cd
[root@fanchy3-1 ~]#
B. Build Kubernetes v1.24.0。
[root@fanchy3-1 ~]# yum install -y git
[root@fanchy3-1 ~]# git clone https://github.com/kubernetes/kubernetes
[root@fanchy3-1 ~]# cd kubernetes/
[root@fanchy3-1 kubernetes]# git checkout v1.24.0-alpha.3
[root@fanchy3-1 kubernetes]# make quick-release
C. 解開壓縮檔,載入新的 images。
[root@fanchy3-1 kubernetes]# cd _output/release-tars/
[root@fanchy3-1 kubernetes]# tar zxvf kubernetes-server-linux-amd64.tar.gz
[root@fanchy3-1 kubernetes]# tar zxvf kubernetes-client-linux-amd64.tar.gz
[root@fanchy3-1 kubernetes]# cd kubernetes/server/bin/
[root@fanchy3-1 bin]# docker load -i kube-apiserver.tar
[root@fanchy3-1 bin]# docker load -i kube-controller-manager.tar
[root@fanchy3-1 bin]# docker load -i kube-proxy.tar
[root@fanchy3-1 bin]# docker load -i kube-scheduler.tar
D. 升級 K8s Control Plane & Client。
修改 manifests YAML 會自動重啟 Control Plane 服務。如需備份原始 YAML 的話,建議放到其它資料夾,否則會額外建立新的 Control Plane。
修改 kube-apiserver 的 image,包含 image 名稱和 tag。以及拿掉 --insecure-port
那行。
[root@fanchy3-1 bin]# diff /etc/kubernetes/manifests/kube-apiserver.yaml /backup/kube-apiserver.yaml
33a34
> - --insecure-port=0
55c56
< image: k8s.gcr.io/kube-apiserver-amd64:v1.24.0-alpha.3
---
> image: k8s.gcr.io/kube-apiserver:v1.22.2
修改 kube-controller-manager 的 image,包含 image 名稱和 tag。以及拿掉 --port
那行。
[root@fanchy3-1 bin]# diff /etc/kubernetes/manifests/kube-controller-manager.yaml /backup/kube-controller-manager.yaml
31a32
> - --port=0
39c40
< image: k8s.gcr.io/kube-controller-manager-amd64:v1.24.0-alpha.3
---
> image: k8s.gcr.io/kube-controller-manager:v1.22.2
修改 kube-scheduler 的 image,包含 image 名稱和 tag。以及拿掉 --port
那行。
[root@fanchy3-1 bin]# diff /etc/kubernetes/manifests/kube-scheduler.yaml /backup/kube-scheduler.yaml
20c20,21
< image: k8s.gcr.io/kube-scheduler-amd64:v1.24.0-alpha.3
---
> - --port=0
> image: k8s.gcr.io/kube-scheduler:v1.22.2
修改 kubescheduler-config 的 apiVersion:v1beta1
-> v1beta3
。
[root@fanchy3-1 bin]# diff /etc/kubernetes/kubescheduler-config.yaml /backup/kubescheduler-config.yaml
1c1
< apiVersion: kubescheduler.config.k8s.io/v1beta3
---
> apiVersion: kubescheduler.config.k8s.io/v1beta1
預期應該會看到 kube-apiserver, kube-controller-manager, kube-scheduler, autoscaler_dns, coredns 被自動重新開啟。
[root@fanchy3-1 bin]# docker ps | grep -v pause
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
3772990b76a7 8522d622299c "/opt/bin/flanneld -…" 3 minutes ago Up 3 minutes k8s_kube-flannel_kube-flannel-c4pf9_kube-system_ff7d21ee-427b-4735-8b52-20bd5775fa8f_1
ce2dd4f70ce8 21fc69048bd5 "/node-cache -locali…" 3 minutes ago Up 3 minutes k8s_node-cache_nodelocaldns-f2q5n_kube-system_f35fcc58-9f11-4b90-b2d3-e4491c3eeebd_1
f267c6e7581b 873127efbc8a "/usr/local/bin/kube…" 3 minutes ago Up 3 minutes k8s_kube-proxy_kube-proxy-9w9kc_kube-system_3785a792-2b2c-4244-a18c-c5e893d1159c_1
52cd89654dd8 quay.io/coreos/etcd:v3.4.13 "/usr/local/bin/etcd" 3 minutes ago Up 3 minutes etcd1
5cb89fd4d9bd 32214fab87ea "kube-controller-man…" 3 minutes ago Up 3 minutes k8s_kube-controller-manager_kube-controller-manager-fanchy3-1_kube-system_188b5e64a88454a974ec526fbc996ba6_1
e16b720dbef7 80bd8f5a50f0 "kube-apiserver --ad…" 3 minutes ago Up 3 minutes k8s_kube-apiserver_kube-apiserver-fanchy3-1_kube-system_0b273727edb9bada6fb21854aa6aad26_1
de4fa7bf56de a30e10ff1ba4 "kube-scheduler --au…" 3 minutes ago Up 3 minutes k8s_kube-scheduler_kube-scheduler-fanchy3-1_kube-system_92cf4cb430cbbeedc5bcdeef7a7accfb_3
將新的 kubectl 放到正確位置後,預期會看到 Client/Server 版本都被升級到 1.24+。
[root@fanchy3-1 bin]# mv /usr/local/bin/kubectl /usr/local/bin/kubectl.v1.22.2
[root@fanchy3-1 bin]# cp ../../client/bin/kubectl /usr/local/bin/kubectl
[root@fanchy3-1 bin]# kubectl version
Client Version: version.Info{Major:"1", Minor:"24+", GitVersion:"v1.24.0-alpha.3", GitCommit:"30a21e9abdbbeb78d2b7ce59a79e46299ced2742", GitTreeState:"clean", BuildDate:"2022-03-16T06:16:00Z", GoVersion:"go1.17.7", Compiler:"gc", Platform:"linux/amd64"}
Server Version: version.Info{Major:"1", Minor:"24+", GitVersion:"v1.24.0-alpha.3", GitCommit:"30a21e9abdbbeb78d2b7ce59a79e46299ced2742", GitTreeState:"clean", BuildDate:"2022-03-16T05:58:22Z", GoVersion:"go1.17.7", Compiler:"gc", Platform:"linux/amd64"}
E. Build & 安裝 cri-dockerd。
[root@fanchy3-1 bin]# cd
[root@fanchy3-1 ~]# wget https://go.dev/dl/go1.17.8.linux-amd64.tar.gz
[root@fanchy3-1 ~]# rm -rf /usr/local/go && tar -C /usr/local -xzf go1.17.8.linux-amd64.tar.gz
[root@fanchy3-1 ~]# export PATH=$PATH:/usr/local/go/bin
[root@fanchy3-1 ~]# echo "export PATH=$PATH:/usr/local/go/bin" >> /etc/profile
[root@fanchy3-1 ~]# go version
[root@fanchy3-1 ~]# git clone https://github.com/Mirantis/cri-dockerd.git
[root@fanchy3-1 ~]# cd cri-dockerd/
[root@fanchy3-1 cri-dockerd]# mkdir bin
[root@fanchy3-1 cri-dockerd]# cd src && go get && go build -o ../bin/cri-dockerd
[root@fanchy3-1 src]# cd ..
[root@fanchy3-1 cri-dockerd]# install -o root -g root -m 0755 bin/cri-dockerd /usr/local/bin/cri-dockerd
[root@fanchy3-1 cri-dockerd]# cp -a packaging/systemd/* /etc/systemd/system
[root@fanchy3-1 cri-dockerd]# sed -i -e 's,/usr/bin/cri-dockerd,/usr/local/bin/cri-dockerd,' /etc/systemd/system/cri-docker.service
修改 cri-docker.service,--pod-cidr
帶入的數值是要給 Pod 使用的 IPs。
[root@fanchy3-1 cri-dockerd]# sed -i -e 's,--network-plugin=,--cni-bin-dir=/opt/cni/bin --cni-cache-dir=/var/lib/cni --cni-conf-dir=/etc/cni/net.d --network-plugin=cni --pod-cidr=10.233.64.0/18,' /etc/systemd/system/cri-docker.service
啟動 cri-docker。
[root@fanchy3-1 cri-dockerd]# systemctl daemon-reload
[root@fanchy3-1 cri-dockerd]# systemctl enable cri-docker.service
[root@fanchy3-1 cri-dockerd]# systemctl enable --now cri-docker.socket
[root@fanchy3-1 cri-dockerd]# systemctl start cri-docker
[root@fanchy3-1 cri-dockerd]# systemctl status cri-docker
F. 升級 Kubelet。
[root@fanchy3-1 cri-dockerd]# cd /root/kubernetes/_output/release-tars/kubernetes/server/bin/
[root@fanchy3-1 bin]# systemctl stop kubelet
[root@fanchy3-1 bin]# mv /usr/local/bin/kubelet kubelet.v1.22.2
[root@fanchy3-1 bin]# cp kubelet /usr/local/bin/
接下來,修改 kubelet.service。因為 K8s v1.24 的 KUBELET_NETWORK_PLUGIN
相關 flag 都被捨棄,所以必須移除此參數。並加入 --container-runtime=remote --container-runtime-endpoint=/var/run/cri-dockerd.sock
。
[root@fanchy3-1 bin]# vi /etc/systemd/system/kubelet.service
重新啟動 kubelet 後,預期會看到 node 已被升級至 v1.24.0-alpha.3。
[root@fanchy3-1 bin]# systemctl start kubelet
[root@fanchy3-1 bin]# kubectl get node
驗證測試
A. 功能是否正常。
a1. 建立一個 nginx deployment。
[root@fanchy3-1 ~]# kubectl get po
No resources found in default namespace.
[root@fanchy3-1 ~]# kubectl apply -f test.yaml
deployment.apps/nginx-deployment created
[root@fanchy3-1 ~]# kubectl get po
NAME READY STATUS RESTARTS AGE
nginx-deployment-9456bbbf9-vqhz6 0/1 ContainerCreating 0 4s
nginx-deployment-9456bbbf9-w2pd9 0/1 ContainerCreating 0 4s
a2. 可以看到 pod 正常開啟,透過 docker ps 也可以看到。
[root@fanchy3-1 ~]# kubectl get po
NAME READY STATUS RESTARTS AGE
nginx-deployment-9456bbbf9-vqhz6 1/1 Running 0 80s
nginx-deployment-9456bbbf9-w2pd9 1/1 Running 0 80s
[root@fanchy3-1 ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
806b3d103c9c nginx "nginx -g 'daemon of…" About a minute ago Up About a minute k8s_nginx_nginx-deployment-9456bbbf9-w2pd9_default_a1918aec-57f4-46b2-87bc-1e16256109f8_0
4fdffc974ea7 nginx "nginx -g 'daemon of…" About a minute ago Up About a minute k8s_nginx_nginx-deployment-9456bbbf9-vqhz6_default_f8f4d1c7-d491-4cd5-b013-c19eaae76e0e_0
8451bc26461f k8s.gcr.io/pause:3.1 "/pause" About a minute ago Up About a minute k8s_POD_nginx-deployment-9456bbbf9-w2pd9_default_a1918aec-57f4-46b2-87bc-1e16256109f8_0
cce14b929f50 k8s.gcr.io/pause:3.1 "/pause" About a minute ago Up About a minute k8s_POD_nginx-deployment-9456bbbf9-vqhz6_default_f8f4d1c7-d491-4cd5-b013-c19eaae76e0e_0
...
a3. kubectl exec 可以正常使用。
[root@fanchy3-1 ~]# kubectl exec -it nginx-deployment-9456bbbf9-vqhz6 -- bash
root@nginx-deployment-9456bbbf9-vqhz6:/#
B. 不使用 cri-dockerd。
在部署步驟 f. (升級 Kubelet),如果不使用 --container-runtime=remote --container-runtime-endpoint=/var/run/cri-dockerd.sock
flag,Kubelet 會無法正常運行,並出現以下錯誤訊息。
[root@fanchy3-1 ~]# vi /etc/systemd/system/kubelet.service
[root@fanchy3-1 ~]# systemctl daemon-reload
[root@fanchy3-1 ~]# systemctl restart kubelet
[root@fanchy3-1 ~]# systemctl status kubelet
● kubelet.service - Kubernetes Kubelet Server
Loaded: loaded (/etc/systemd/system/kubelet.service; enabled; vendor preset: disabled)
Active: activating (auto-restart) (Result: exit-code) since 一 2022-03-21 00:31:10 UTC; 5s ago
Docs: https://github.com/GoogleCloudPlatform/kubernetes
Process: 24978 ExecStart=/usr/local/bin/kubelet $KUBE_LOGTOSTDERR $KUBE_LOG_LEVEL $KUBELET_API_SERVER $KUBELET_ADDRESS $KUBELET_PORT $KUBELET_HOSTNAME $KUBELET_ARGS $DOCKER_SOCKET $KUBELET_VOLUME_PLUGIN $KUBELET_CLOUDPROVIDER (code=exited, status=1/FAILURE)
Main PID: 24978 (code=exited, status=1/FAILURE)
...
[root@fanchy3-1 ~]# journalctl -xeu kubelet -f
...
3月 21 00:31:20 fanchy3-1 kubelet[25005]: I0321 00:31:20.852907 25005 util_unix.go:104] "Using this format as endpoint is deprecated, please consider using full url format." deprecatedFormat="" fullURLFormat="unix://"
3月 21 00:31:20 fanchy3-1 kubelet[25005]: W0321 00:31:20.853255 25005 clientconn.go:1331] [core] grpc: addrConn.createTransport failed to connect to { <nil> 0 <nil>}. Err: connection error: desc = "transport: Error while dialing dial unix: missing address". Reconnecting...
3月 21 00:31:20 fanchy3-1 kubelet[25005]: Error: failed to run Kubelet: unable to determine runtime API version: rpc error: code = Unavailable desc = connection error: desc = "transport: Error while dialing dial unix: missing address"
3月 21 00:31:20 fanchy3-1 kubelet[25005]: Usage:
3月 21 00:31:20 fanchy3-1 kubelet[25005]: kubelet [flags]
3月 21 00:31:20 fanchy3-1 kubelet[25005]: Flags:
...
透過上述步驟推論,K8s 1.24+ 之後,必須(可以)使用 cri-dockerd,才能繼續使用 Docker。
三、後續影響與個人觀點
撇開一些商業策略的原因,一般軟體架構的改變,過程中雖然會來回不斷的做調整,但大方向不外乎是朝著更高的效能、彈性(Flexibility)、可擴充性(Scalability)、高可用性(High Availability)、廠商獨立性(Vendor Independence)… 等方向去做改善。雖然軟體的開發過程中,常會面臨需要作權衡(trade-off)的階段,但理應不會去做弊大於利的調整。
K8s 社群決定使用 CRI 並移除 Dockershim 的主要目的是為了 K8s 本身的彈性,以支援不同的容器運行時,並減少支援單一容器運行時(Docker)的維護成本,可以更專注在 K8s 本身的功能開發與維護。(也或許是因為 Mirantis 這位第三者的介入,加速了 K8s 與 Docker 之間關係的變化…)
目前 Mirantis 已經買下了 Docker,而他們也會希望更多 K8s 的使用者會去選擇使用 Docker,所以他們也勢必會維護好 cri-dockerd 的功能。因此,原本在 K8s 下使用 Docker 用戶,也無需太過擔心。