Kubernetes runtime从Docker迁移到containerd探索


Kubernetes宣布在1.20版本之后将弃用Docker作为容器运行时,在2021年末发布的1.23版本中将彻底移除dockershim组件。Dockershim是kubelet内置的一个组件,功能是使Kubernetes能够通过CRI(Container Runtime Interface)操作Docker。一旦Docker有任何的功能特性变更,dockershim 代码必须加以改动来保证能够继续和Docker通信。另外,Docker的底层运行时是containerd,而containerd自身是可以支持CRI的,也就是说Kubernetes可以绕过Docker通过CRI直接与containerd通信,这也是Kubernetes社区希望弃用dockershim的原因。

Containerd在1.0版本中虽然考虑了CRI,但是它将CRI-Containerd作为一个独立组件存在的,即:Kubernetes需要先通过CRI接口调用CRI-Containerd,再由这个组件去调用containerd。在Containerd 1.1版本之后对该特性做了重新的设计,它将CRI-plugin内嵌在containerd中,以此来达到与containerd通信的目的,调用链路更短了。Containerd 1.1支持Kubernetes 1.10及以上版本作为容器运行时,并且支持Kubernetes的全部特性。

下图说明了Docker和containerd作为容器运行时的工作原理。由此可以看出,如果之前使用Docker作为容器运行时,那么迁移到containerd是一个相对容易的选择,而且containerd具有更好的性能和更低的成本。
1.png

接下来,主要介绍如何将Kubernetes的运行时从Docker迁移到containerd,并且迁移之后使用上的一些变化。

Kubernetes运行时从Docker迁移到containerd

环境准备

操作系统:SUSE 12 SP5

Kernel版本:4.12.14-120

Kubernetes版本:v1.14.0

Docker版本:docker-ee-18.09.9

Containerd版本:1.4.4

查看当前节点运行时信息

kubectl get node -o wide

2.png

可以看到,当前所有节点使用的运行时都是Docker,通过systemctl status containerd可以看到containerd服务默认也是启动的。使用如下命令列出containerd的命名空间。
ctr namespaces list

可以看到有一个moby命名空间,这也是Docker服务默认使用的命名空间。
ctr -namespace moby container list

使用如上命令列出Moby命名空间下运行的所有容器,结果如下图,可以看到跟docker ps输出的容器个数相同。
3.png

驱逐节点并停止节点上的Docker和kubelet服务

下面以节点spk8mgr03为例说明Docker到containerd的迁移流程。
kubectl drain spk8mgr03 --ignore-daemonsets --delete-local-data --force

systemctl stop kubelet

systemctl stop docker

卸载Docker(该步骤是可选的,为了排除测试过程中Docker的干扰,这里选择卸载):
zypper rm -y docker-ee docker-ee-cli containerd.io

安装配置containerd

下载containerd并解压安装:
wget https://github.com/containerd/containerd/releases/download/v1.4.4/cri-containerd-cni-1.4.4-linux-amd64.tar.gz

tar -C / -xzvf cri-containerd-cni-1.4.4-linux-amd64.tar.gz

解压后的文件包括如下内容:
/

/etc/

/etc/systemd/

/etc/systemd/system/

/etc/systemd/system/containerd.service

/etc/crictl.yaml

/etc/cni/

/etc/cni/net.d/

/etc/cni/net.d/10-containerd-net.conflist

/usr/

/usr/local/

/usr/local/bin/

/usr/local/bin/containerd

/usr/local/bin/containerd-shim

/usr/local/bin/crictl

/usr/local/bin/containerd-shim-runc-v2

/usr/local/bin/critest

/usr/local/bin/containerd-shim-runc-v1

/usr/local/bin/ctr

/usr/local/sbin/

/usr/local/sbin/runc

/opt/

/opt/containerd/

/opt/containerd/cluster/

/opt/containerd/cluster/gce/

/opt/containerd/cluster/gce/env

/opt/containerd/cluster/gce/cni.template

/opt/containerd/cluster/gce/configure.sh

/opt/containerd/cluster/gce/cloud-init/

/opt/containerd/cluster/gce/cloud-init/node.yaml

/opt/containerd/cluster/gce/cloud-init/master.yaml

/opt/containerd/cluster/version

/opt/cni/

/opt/cni/bin/

/opt/cni/bin/bandwidth

/opt/cni/bin/host-device

/opt/cni/bin/flannel

/opt/cni/bin/static

/opt/cni/bin/loopback

/opt/cni/bin/dhcp

/opt/cni/bin/ptp

/opt/cni/bin/ipvlan

/opt/cni/bin/vlan

/opt/cni/bin/host-local

/opt/cni/bin/firewall

/opt/cni/bin/tuning

/opt/cni/bin/sbr

/opt/cni/bin/bridge

/opt/cni/bin/portmap

/opt/cni/bin/macvlan

启动并配置containerd:
systemctl start containerd

systemctl enable containerd

mkdir -p /etc/containerd

containerd config default > /etc/containerd/config.toml

config.toml文件内容如下,注意修改sandbox_image参数:
version = 2

root = "/var/lib/containerd"

state = "/run/containerd"

plugin_dir = ""

disabled_plugins = []

required_plugins = []

oom_score = 0



[grpc]

address = "/run/containerd/containerd.sock"

tcp_address = ""

tcp_tls_cert = ""

tcp_tls_key = ""

uid = 0

gid = 0

max_recv_message_size = 16777216

max_send_message_size = 16777216



[ttrpc]

address = ""

uid = 0

gid = 0



[debug]

address = ""

uid = 0

gid = 0

level = ""



[metrics]

address = ""

grpc_histogram = false



[cgroup]

path = ""



[timeouts]

"io.containerd.timeout.shim.cleanup" = "5s"

"io.containerd.timeout.shim.load" = "5s"

"io.containerd.timeout.shim.shutdown" = "3s"

"io.containerd.timeout.task.state" = "2s"



[plugins]

[plugins."io.containerd.gc.v1.scheduler"]

pause_threshold = 0.02

deletion_threshold = 0

mutation_threshold = 100

schedule_delay = "0s"

startup_delay = "100ms"

[plugins."io.containerd.grpc.v1.cri"]

disable_tcp_service = true

stream_server_address = "127.0.0.1"

stream_server_port = "0"

stream_idle_timeout = "4h0m0s"

enable_selinux = false

selinux_category_range = 1024

sandbox_image = "k8s.gc.io/pause:3.1"

stats_collect_period = 10

systemd_cgroup = false

enable_tls_streaming = false

max_container_log_line_size = 16384

disable_cgroup = false

disable_apparmor = false

restrict_oom_score_adj = false

max_concurrent_downloads = 3

disable_proc_mount = false

unset_seccomp_profile = ""

tolerate_missing_hugetlb_controller = true

disable_hugetlb_controller = true

ignore_image_defined_volumes = false

[plugins."io.containerd.grpc.v1.cri".containerd]

snapshotter = "overlayfs"

default_runtime_name = "runc"

no_pivot = false

disable_snapshot_annotations = true

discard_unpacked_layers = false

[plugins."io.containerd.grpc.v1.cri".containerd.default_runtime]

runtime_type = ""

runtime_engine = ""

runtime_root = ""

privileged_without_host_devices = false

base_runtime_spec = ""

[plugins."io.containerd.grpc.v1.cri".containerd.untrusted_workload_runtime]

runtime_type = ""

runtime_engine = ""

runtime_root = ""

privileged_without_host_devices = false

base_runtime_spec = ""

[plugins."io.containerd.grpc.v1.cri".containerd.runtimes]

[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc]

runtime_type = "io.containerd.runc.v2"

runtime_engine = ""

runtime_root = ""

privileged_without_host_devices = false

base_runtime_spec = ""

[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options]

[plugins."io.containerd.grpc.v1.cri".cni]

bin_dir = "/opt/cni/bin"

conf_dir = "/etc/cni/net.d"

max_conf_num = 1

conf_template = ""

[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://registry-1.docker.io"]

[plugins."io.containerd.grpc.v1.cri".image_decryption]

key_model = ""

[plugins."io.containerd.grpc.v1.cri".x509_key_pair_streaming]

tls_cert_file = ""

tls_key_file = ""

[plugins."io.containerd.internal.v1.opt"]

path = "/opt/containerd"

[plugins."io.containerd.internal.v1.restart"]

interval = "10s"

[plugins."io.containerd.metadata.v1.bolt"]

content_sharing_policy = "shared"

[plugins."io.containerd.monitor.v1.cgroups"]

no_prometheus = false

[plugins."io.containerd.runtime.v1.linux"]

shim = "containerd-shim"

runtime = "runc"

runtime_root = ""

no_shim = false

shim_debug = false

[plugins."io.containerd.runtime.v2.task"]

platforms = ["linux/amd64"]

[plugins."io.containerd.service.v1.diff-service"]

default = ["walking"]

[plugins."io.containerd.snapshotter.v1.devmapper"]

root_path = ""

pool_name = ""

base_image_size = ""

async_remove = false

修改完配置后,重启containerd服务:
systemctl restart containerd

测试containerd:
ctr images pull docker.io/library/nginx:alpine

看到输出done,说明containerd运行正常。

配置crictl

crictl默认与Docker进行通信,如果希望crictl直接与containerd通信,需要修改crictl的配置文件,在/etc/crictl.yaml加入如下内容:
runtime-endpoint: unix:///run/containerd/containerd.sock

注:安装containerd时解压好的文件默认已经添加了该配置。

测试一下cri插件是否可用:
crictl pull docker.io/library/nginx:alpine

crictl images

配置kubelet

kubelet默认使用docker作为容器运行时,如果希望使用containerd,需要修改kubelet的配置文件。编辑/etc/systemd/system/kubelet.service.d/10-kubeadm.conf文件,添加如下内容:
[Service]

Environment="KUBELET_EXTRA_ARGS=--container-runtime=remote --runtime-request-timeout=15m --container-runtime-endpoint=unix:///run/containerd/containerd.sock"

重启kubelet服务:
systemctl daemon-reload

systemctl restart kubelet

验证

kubectl get node -o wide

4.png

可以看到spk8mgr03节点的容器运行时已经变成了containerd,这时节点还是不可调度状态,执行如下命令将其改为可调度状态。
kubectl uncordon spk8mgr03

此时再查看containerd的命名空间,会发现多了一个k8s.io的命名空间,而且所有的容器都会运行在该命名空间中,而Moby命名空间中没有任何容器运行了。
5.png

至此,我们成功完成了容器运行时从Docker到containerd的迁移,集群中的其他节点可以重复上述步骤完成全部迁移。

Containerd和Docker使用对比

当使用Docker作为容器运行时,系统管理员有时会登录Kubernetes节点执行Docker命令来收集系统或者应用信息,这些命令都是通过docker CLI实现的。而迁移到containerd之后,可以通过containerd CLI工具ctr来实现与containerd的交互,但是从使用便捷性和功能性上考虑,更推荐使用crictl作为troubleshooting的工具。Crictl是类似于docker CLI的客户端调试工具,并且适用于所有与CRI兼容的容器运行时,包括Docker。下面将围绕镜像、容器、Pod方面比较一下Docker、ctr、crictl常用命令的使用区别。

镜像相关功能

6.png

容器相关功能

7.png

这里要特别说明一下,通过ctr containers create创建容器后,只是一个静态的容器,容器中的用户进程并没有启动,所以还需要通过ctr task start来启动容器进程。当然,也可以用ctr run的命令直接创建并运行容器。在进入容器操作时,与docker不同的是,必须在ctr task exec命令后指定--exec-id参数,这个id可以随便写,只要唯一就行。另外,ctr没有stop容器的功能,只能暂停(ctr task pause)或者杀死(ctr task kill)容器。

Pod相关功能

8.png

这里要说明的是:crictl pods列出的是pod的信息,包括pod所在的命名空间以及状态。crictl ps列出的是应用容器的信息,而docker ps列出的是初始化容器(pause容器)和应用容器的信息,初始化容器在每个pod启动时都会创建,通常不会关注,从这一点上来说,crictl使用起来更简洁明了一些。
9.png

Docker和containerd除了上述常用命令有些区别外,在容器日志及相关参数配置方面也存在一些差异,详见下表。
10.png

总结

Kubernetes弃用Docker这一决定可能对从事相关工作的人员来说有些措手不及,但其实无需特别担心。对于Kubernetes的终端用户来说,这仅仅是一个后端容器运行时的更改,从使用方面来说几乎感觉不到任何区别;对于应用开发/运维人员来说,依旧可以继续使用Docker来构建镜像,以相同的方式将镜像推送到registry,并将这些镜像部署到Kubernetes环境中;对于Kubernetes集群管理员来说,只需要将Docker切换成其它的容器运行时(比如containerd),并将节点troubleshooting工具从docker CLI切换到crictl即可。

原文链接:https://mp.weixin.qq.com/s/0F-Gj0pAGqXinr-sg0vKYg

0 个评论

要回复文章请先登录注册