如何絲滑般將 Kubernetes 容器運行時從 Docker 切換成 Containerd
前面我們了解了 containerd 的發展歷史和基本使用方式,本節我們就來嘗試下使用 containerd 來作為 Kubernetes 集群的容器運行時。
前面我們安裝的集群默認使用的是 Docker 作為容器運行時,那么應該如何將容器運行時從 Docker 切換到 containerd 呢?
維護節點
首先標記需要切換的節點為維護模式,強制驅逐節點上正在運行的 Pod,這樣可以最大程度降低切換過程中影響應用的正常運行,比如我們先將 node1 節點切換到 containerd。
首先使用 kubectl cordon 命令將 node1 節點標記為 unschedulable 不可調度狀態:
- # 將 node1 標記為 unschedulable
- ➜ ~ kubectl cordon node1
- node/node1 cordoned
- ➜ ~ kubectl get nodes
- NAME STATUS ROLES AGE VERSION
- master Ready master 85d v1.19.11
- node1 Ready,SchedulingDisabled <none> 85d v1.19.11
- node2 Ready <none> 85d v1.19.11
執行完上面的命令后,node1 節點變成了一個 SchedulingDisabled 狀態,表示不可調度,這樣新創建的 Pod 就不會調度到當前節點上來了。
接下來維護 node1 節點,使用 kubectl drain 命令來維護節點并驅逐節點上的 Pod:
- # 維護 node1 節點,驅逐 Pod
- ➜ ~ kubectl drain node1 --ignore-daemonsets
- node/node1 already cordoned
- WARNING: ignoring DaemonSet-managed Pods: kube-system/kube-flannel-ds-mzdgl, kube-system/kube-proxy-vddh9, lens-metrics/node-exporter-2g4hr
- evicting pod "kiali-85c8cdd5b5-27cwv"
- evicting pod "jenkins-587b78f5cd-9gvn8"
- evicting pod "argocd-application-controller-0"
- pod/argocd-application-controller-0 evicted
- pod/kiali-85c8cdd5b5-27cwv evicted
- pod/jenkins-587b78f5cd-9gvn8 evicted
- node/node1 evicted
上面的命令會強制將 node1 節點上的 Pod 進行驅逐,我們加了一個 --ignore-daemonsets 的參數可以用來忽略 DaemonSet 控制器管理的 Pods,因為這些 Pods 不用驅逐到其他節點去,當節點驅逐完成后接下來我們就可以來對節點進行維護操作了,除了切換容器運行時可以這樣操作,比如我們需要變更節點配置、升級內核等操作的時候都可以先將節點進行驅逐,然后再進行維護。
切換 containerd
接下來停掉 docker、containerd 和 kubelet:
- ➜ ~ systemctl stop kubelet
- ➜ ~ systemctl stop docker
- ➜ ~ systemctl stop containerd
因為我們安裝的 Docker 默認安裝使用了 containerd 作為后端的容器運行時,所以不需要單獨安裝 containerd 了,當然你也可以將 Docker 和 containerd 完全卸載掉,然后重新安裝,這里我們選擇直接使用之前安裝的 containerd。
因為 containerd 中默認已經實現了 CRI,但是是以 plugin 的形式配置的,以前 Docker 中自帶的 containerd 默認是將 CRI 這個插件禁用掉了的(使用配置 disabled_plugins = ["cri"]),所以這里我們重新生成默認的配置文件來覆蓋掉:
- ➜ ~ containerd config default > /etc/containerd/config.toml
前面我們已經介紹過上面的配置文件了,首先我們修改默認的 pause 鏡像為國內的地址,替換 [plugins."io.containerd.grpc.v1.cri"] 下面的 sandbox_image:
- [plugins."io.containerd.grpc.v1.cri"]
- sandbox_image = "registry.aliyuncs.com/k8sxio/pause:3.2"
- ......
同樣再配置下鏡像倉庫的加速器地址:
- [plugins."io.containerd.grpc.v1.cri".registry]
- [plugins."io.containerd.grpc.v1.cri".registry.mirrors]
- [plugins."io.containerd.grpc.v1.cri".registry.mirrors."docker.io"]
- endpoint = ["https://bqr1dr1n.mirror.aliyuncs.com"]
- [plugins."io.containerd.grpc.v1.cri".registry.mirrors."k8s.gcr.io"]
- endpoint = ["https://registry.aliyuncs.com/k8sxio"]
接下來修改 kubelet 配置,將容器運行時配置為 containerd,打開 /etc/sysconfig/kubelet 文件,在該文件中可以添加一些額外的 kubelet 啟動參數,配置如下所示:
- KUBELET_EXTRA_ARGS="--container-runtime=remote --container-runtime-endpoint=unix:///run/containerd/containerd.sock"
上面的配置中我們增加了兩個參數,--container-runtime 參數是用來指定使用的容器運行時的,可選值為 docker 或者 remote,默認是 docker,由于我們這里使用的是 containerd 這種容器運行時,所以配置為 remote 值(也就是除 docker 之外的容器運行時都應該指定為 remote),然后第二個參數 --container-runtime-endpoint 是用來指定遠程的運行時服務的 endpiont 地址的,在 Linux 系統中一般都是使用 unix 套接字的形式,比如這里我們就是指定連接 containerd 的套接字地址 unix:///run/containerd/containerd.sock。
- 其實還應該配置一個 --image-service-endpoint 參數用來指定遠程 CRI 的鏡像服務地址,如果沒有指定則默認使用 --container-runtime-endpoint 的值了,因為 CRI 都會實現容器和鏡像服務的。
配置完成后重啟 containerd 和 kubelet 即可:
- ➜ ~ systemctl daemon-reload
- ➜ ~ systemctl restart containerd
- ➜ ~ systemctl restart kubelet
重啟完成后查看節點狀態是否正常:
- ➜ ~ kubectl get nodes -o wide
- NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME
- master Ready master 85d v1.19.11 192.168.31.30 <none> CentOS Linux 7 (Core) 3.10.0-1160.25.1.el7.x86_64 docker://19.3.9
- node1 Ready,SchedulingDisabled <none> 85d v1.19.11 192.168.31.95 <none> CentOS Linux 7 (Core) 3.10.0-1160.25.1.el7.x86_64 containerd://1.4.4
- node2 Ready <none> 85d v1.19.11 192.168.31.215 <none> CentOS Linux 7 (Core) 3.10.0-1160.25.1.el7.x86_64 docker://19.3.9
獲取節點的時候加上 -o wide 可以查看節點的更多信息,從上面對比可以看到 node1 節點的容器運行時已經切換到 containerd://1.4.4 了。
最后把 node1 節點重新加回到集群中來允許調度 Pod 資源:
- ➜ ~ kubectl uncordon node1
- node/node1 uncordoned
- ➜ ~ kubectl get nodes
- NAME STATUS ROLES AGE VERSION
- master Ready master 85d v1.19.11
- node1 Ready <none> 85d v1.19.11
- node2 Ready <none> 85d v1.19.11
用同樣的方法再去處理其他節點即可將整個集群切換成容器運行時 containerd 了。
crictl
現在我們可以 node1 節點上使用 ctr 命令來管理 containerd,查看多了一個名為 k8s.io 的命名空間:
- ➜ ~ ctr ns ls
- NAME LABELS
- k8s.io
- moby
上文我們已經介紹 kubernetes 集群對接的 containerd 所有資源都在 k8s.io 的命名空間下面,而 docker 的則默認在 moby 下面,當然現在 moby 下面沒有任何的數據了,但是在 k8s.io 命名空間下面就有很多鏡像和容器資源了:
- ➜ ~ ctr -n moby c ls
- CONTAINER IMAGE RUNTIME
- ➜ ~ ctr -n moby i ls
- REF TYPE DIGEST SIZE PLATFORMS LABELS
- ➜ ~ ctr -n moby t ls
- TASK PID STATUS
- ctr -n k8s.io i ls -q
- docker.io/library/busybox:latest
- docker.io/library/busybox@sha256:0f354ec1728d9ff32edcd7d1b8bbdfc798277ad36120dc3dc683be44524c8b60
- quay.io/coreos/flannel:v0.14.0
- quay.io/coreos/flannel@sha256:4a330b2f2e74046e493b2edc30d61fdebbdddaaedcb32d62736f25be8d3c64d5
- registry.aliyuncs.com/k8sxio/pause:3.2
- ......
我們當然可以直接使用 ctr 命令來直接管理鏡像或容器資源,但是我們在使用過程中明顯可以感覺到該工具沒有 docker CLI 方便,從使用便捷性和功能性上考慮,我們更推薦使用 crictl 作為管理工具,crictl 為 CRI 兼容的容器運行時提供 CLI,這允許 CRI 運行時開發人員在無需設置 Kubernetes 組件的情況下調試他們的運行時。
接下來我們就先簡單介紹下如何使用 crictl 工具來提升管理容器運行時的效率。
安裝
首先我們需要先安裝 crictl 工具,直接從 cri-tools 的 release 頁面下載對應的二進制包,解壓放入 PATH 路徑下即可:
- ➜ ~ VERSION="v1.22.0"
- ➜ ~ wget https://github.com/kubernetes-sigs/cri-tools/releases/download/$VERSION/crictl-$VERSION-linux-amd64.tar.gz
- # 如果有限制,也可以替換成下面的 URL 加速下載
- # wget https://download.fastgit.org/kubernetes-sigs/cri-tools/releases/download/$VERSION/crictl-$VERSION-linux-amd64.tar.gz
- ➜ ~ tar zxvf crictl-$VERSION-linux-amd64.tar.gz -C /usr/local/bin
- ➜ ~ rm -f crictl-$VERSION-linux-amd64.tar.gz
- ➜ ~ crictl -v
- crictl version v1.22.0
到這里證明 crictl 工具安裝成功了。
用法
crictl 安裝完成后,接下來我們來了解下該工具的一些常見使用方法。
首先需要修改下默認的配置文件,默認為 /etc/crictl.yaml,在文件中指定容器運行時和鏡像的 endpoint 地址,內容如下所示:
- runtime-endpoint: unix:///var/run/containerd/containerd.sock
- image-endpoint: unix:///var/run/containerd/containerd.sock
- debug: false
- pull-image-on-create: false
- disable-pull-on-run: false
配置完成后就可以使用 crictl 命令了。
獲取 Pod 列表
通過 crictl pods 命令可以獲取當前節點上運行的 Pods 列表,如下所示:
- ➜ ~ crictl pods
- POD ID CREATED STATE NAME NAMESPACE ATTEMPT RUNTIME
- cb18081b33933 39 minutes ago Ready kube-flannel-ds-mzdgl kube-system 1 (default)
- 95d6004c55902 40 minutes ago Ready node-exporter-2g4hr lens-metrics 1 (default)
- cfae80b3209db 40 minutes ago Ready kube-proxy-vddh9 kube-system 1 (default)
- 99ac2583da87f 40 minutes ago Ready jenkins-587b78f5cd-dfzns kube-ops 0 (default)
- 07ebdc51f1def 45 minutes ago NotReady node-exporter-2g4hr lens-metrics 0 (default)
- bec027b98f194 45 minutes ago NotReady kube-proxy-vddh9 kube-system 0 (default)
- b44b5ec385053 45 minutes ago NotReady kube-flannel-ds-mzdgl kube-system 0 (default)
還可以使用 --name 參數獲取指定的 Pod:
- ➜ ~ crictl pods --name kube-flannel-ds-mzdgl
- POD ID CREATED STATE NAME NAMESPACE ATTEMPT RUNTIME
- cb18081b33933 About an hour ago Ready kube-flannel-ds-mzdgl kube-system 1 (default)
同樣也可以根據標簽來篩選 Pod 列表:
- ➜ ~ crictl pods --label app=flannel
- POD ID CREATED STATE NAME NAMESPACE ATTEMPT RUNTIME
- cb18081b33933 About an hour ago Ready kube-flannel-ds-mzdgl kube-system 1 (default)
獲取鏡像列表
使用 crictl images 命令可以獲取所有的鏡像:
- ➜ ~ crictl images
- IMAGE TAG IMAGE ID SIZE
- docker.io/jenkins/jenkins lts 3b4ec91827f28 303MB
- docker.io/library/busybox latest 69593048aa3ac 771kB
- quay.io/coreos/flannel v0.14.0 8522d622299ca 21.1MB
- quay.io/prometheus/node-exporter v1.0.1 0e0218889c33b 13MB
- registry.aliyuncs.com/k8sxio/kube-proxy v1.19.11 732e0635ac9e0 49.3MB
- registry.aliyuncs.com/k8sxio/pause 3.2 80d28bedfe5de 300kB
同樣在命令后面可以加上 -v 參數來顯示鏡像的詳細信息:
- ➜ ~ crictl images -v
- ID: sha256:3b4ec91827f28ed482b08f6e379c56ea2308967d10aa4f458442c922e0771f87
- RepoTags: docker.io/jenkins/jenkins:lts
- RepoDigests: docker.io/jenkins/jenkins@sha256:abcd55c9f19c85808124a4d82e3412719cd5c511c03ebd7d4210e9fa9e8f1029
- Size: 302984002
- Username: jenkins
- ID: sha256:69593048aa3acfee0f75f20b77acb549de2472063053f6730c4091b53f2dfb02
- RepoTags: docker.io/library/busybox:latest
- RepoDigests: docker.io/library/busybox@sha256:0f354ec1728d9ff32edcd7d1b8bbdfc798277ad36120dc3dc683be44524c8b60
- Size: 770886
- ......
獲取容器列表
使用 crictl ps 命令可以獲取正在運行的容器列表:
- ➜ ~ crictl ps
- CONTAINER IMAGE CREATED STATE NAME ATTEMPT POD ID
- c8474738e4587 3b4ec91827f28 About an hour ago Running jenkins 0 99ac2583da87f
- 0f9c826f87ef8 8522d622299ca About an hour ago Running kube-flannel 1 cb18081b33933
- da444f718d37b 0e0218889c33b About an hour ago Running node-exporter 1 95d6004c55902
- a484a8a69ea59 732e0635ac9e0 About an hour ago Running kube-proxy 1 cfae80b3209db
還有更多其他可選參數,可以通過 crictl ps -h 獲取,比如顯示最近創建的兩個容器:
- ➜ ~ crictl ps -n 2
- CONTAINER IMAGE CREATED STATE NAME ATTEMPT POD ID
- c8474738e4587 3b4ec91827f28 About an hour ago Running jenkins 0 99ac2583da87f
- 0f9c826f87ef8 8522d622299ca About an hour ago Running kube-flannel 1 cb18081b33933
使用 -s 選項按照狀態進行過濾:
- ➜ ~ crictl ps -s Running
- CONTAINER IMAGE CREATED STATE NAME ATTEMPT POD ID
- c8474738e4587 3b4ec91827f28 About an hour ago Running jenkins 0 99ac2583da87f
- 0f9c826f87ef8 8522d622299ca About an hour ago Running kube-flannel 1 cb18081b33933
- da444f718d37b 0e0218889c33b About an hour ago Running node-exporter 1 95d6004c55902
- a484a8a69ea59 732e0635ac9e0 About an hour ago Running kube-proxy 1 cfae80b3209db
在容器中執行命令
crictl 也有類似 exec 的命令支持,比如在容器 ID 為 c8474738e4587 的容器中執行一個 date 命令:
- ➜ ~ crictl exec -it c8474738e4587 date
- Tue 17 Aug 2021 08:23:02 AM UTC
輸出容器日志
還可以獲取容器日志信息:
- ➜ ~ crictl logs c8474738e4587
- ......
- 2021-08-17 07:19:51.846+0000 [id=155] INFO hudson.model.AsyncPeriodicWork#lambda$doRun$0: Started Periodic background build discarder
- 2021-08-17 07:19:51.854+0000 [id=155] INFO hudson.model.AsyncPeriodicWork#lambda$doRun$0: Finished Periodic background build discarder. 6 ms
- 2021-08-17 08:19:51.846+0000 [id=404] INFO hudson.model.AsyncPeriodicWork#lambda$doRun$0: Started Periodic background build discarder
- 2021-08-17 08:19:51.848+0000 [id=404] INFO hudson.model.AsyncPeriodicWork#lambda$doRun$0: Finished Periodic background build discarder. 1 ms
和 kubectl logs 類似于,還可以使用 -f 選項來 Follow 日志輸出,--tail N 也可以指定輸出最近的 N 行日志。
資源統計
使用 crictl stats 命令可以列舉容器資源的使用情況:
- ➜ ~ crictl stats
- CONTAINER CPU % MEM DISK INODES
- 0f9c826f87ef8 0.00 21.2MB 0B 17
- a484a8a69ea59 0.00 23.55MB 12.29kB 25
- c8474738e4587 0.08 413.2MB 3.338MB 12
- da444f718d37b 0.00 14.46MB 0B 16
此外鏡像和容器相關的一些操作也都支持,比如:
- 拉取鏡像:crictl pull
- 運行 Pod:crictl runp
- 運行容器:crictl run
- 啟動容器:crictl start
- 刪除容器:crictl rm
- 刪除鏡像:crictl rmi
- 刪除 Pod:crictl rmp
- 停止容器:crictl stop
- 停止 Pod:crictl stopp
- ......
更多信息請參考 https://github.com/kubernetes-sigs/cri-tools。
CLI 對比
前面我們了解了圍繞鏡像、容器和 Pod 可以使用 docker、ctr、crictl 這些命令行工具進行管理,接下來我們就來比較下這幾個常用命令的使用區別。
需要注意的是通過 ctr containers create 命令創建的容器只是一個靜態的容器,所以還需要通過 ctr task start 來啟動容器進程。當然,也可以直接使用 ctr run 命令來創建并運行容器。在進入容器操作時,與 docker 不同的是,必須在 ctr task exec 命令后指定 --exec-id 參數,這個 id 可以隨便寫,只要唯一就行。另外,ctr 沒有 stop 容器的功能,只能暫停(ctr task pause)或者殺死(ctr task kill)容器。
另外要說明的是 crictl pods 列出的是 Pod 的信息,包括 Pod 所在的命名空間以及狀態。crictl ps 列出的是應用容器的信息,而 docker ps 列出的是初始化容器(pause 容器)和應用容器的信息,初始化容器在每個 Pod 啟動時都會創建,通常不會關注,所以 crictl 使用起來更簡潔明了一些。
日志配置
docker 和 containerd 除了在常用命令上有些區別外,在容器日志及相關參數配置方面也存在一些差異。
當使用 Docker 作為 Kubernetes 容器運行時的時候,容器日志的落盤是由 Docker 來完成的,日志被保存在類似 /var/lib/docker/containers/
- {
- "log-driver": "json-file",
- "log-opts": {
- "max-size": "100m",
- "max-file: "10"
- }
- }
而當使用 containerd 作為 Kubernetes 容器運行時的時候,容器日志的落盤則由 kubelet 來完成了,被直接保存在 /var/log/pods/
- --container-log-max-files=10 --container-log-max-size="100Mi"
所以如果我們有進行日志收集理論上來說兩種方案都是兼容的,基本上不用改動。
當然除了這些差異之外,可能對于我們來說鏡像構建這個環節是我們最需要關注的了。切換到 containerd 之后,需要注意 docker.sock 不再可用,也就意味著不能再在容器里面執行 docker 命令來構建鏡像了。所以接下來需要和大家介紹幾種不需要使用 docker.sock 也可以構建鏡像的方法。