Kubernetes runtime从docker迁移到containerd探索
01 前言
Kubernetes(以下简称k8s)宣布在1.20版本之后将弃用docker作为容器运行时,在2021年末发布的1.23版本中将彻底移除dockershim组件。Dockershim是kubelet内置的一个组件,功能是使k8s能够通过CRI(Container Runtime Interface)操作docker。一旦docker 有任何的功能特性变更,dockershim 代码必须加以改动来保证能够继续和docker通信。另外,docker的底层运行时是containerd,而containerd自身是可以支持CRI的,也就是说k8s可以绕过docker通过CRI直接与containerd通信,这也是k8s社区希望弃用dockershim的原因。
Containerd在1.0版本中虽然考虑了CRI,但是它将CRI-Containerd作为一个独立组件存在的,即:k8s需要先通过CRI接口调用CRI-Containerd,再由这个组件去调用containerd。在Containerd1.1版本之后对该特性做了重新的设计,它将CRI-plugin内嵌在containerd中,以此来达到与containerd通信的目的,调用链路更短了。Containerd1.1支持k8s1.10及以上版本作为容器运行时,并且支持k8s的全部特性。
下图说明了docker和containerd作为容器运行时的工作原理。由此可以看出,如果之前使用docker作为容器运行时,那么迁移到containerd是一个相对容易的选择,而且containerd具有更好的性能和更低的成本。
接下来,主要介绍如何将k8s的运行时从docker迁移到containerd,并且迁移之后使用上的一些变化。
02 K8s运行时从docker迁移到containerd
(1)环境准备
操作系统:SUSE 12 SP5
Kernel版本:4.12.14-120
K8s版本:v1.14.0
Docker版本:docker-ee-18.09.9
Containerd版本:1.4.4
(2)查看当前节点运行时信息
kubectl get node -o wide
可以看到,当前所有节点使用的运行时都是docker,通过systemctl status containerd可以看到containerd服务默认也是启动的。使用如下命令列出containerd的命名空间。
ctr namespaces list
可以看到有一个moby命名空间,这也是docker服务默认使用的命名空间。
ctr -namespace moby container list
使用如上命令列出moby命名空间下运行的所有容器,结果如下图,可以看到跟docker ps输出的容器个数相同。
(3)驱逐节点并停止节点上的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
(4)安装配置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运行正常。
(5)配置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
(6)配置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
(7)验证
kubectl get node -o wide
可以看到spk8mgr03节点的容器运行时已经变成了containerd,这时节点还是不可调度状态,执行如下命令将其改为可调度状态。
kubectl uncordon spk8mgr03
此时再查看containerd的命名空间,会发现多了一个k8s.io的命名空间,而且所有的容器都会运行在该命名空间中,而moby命名空间中没有任何容器运行了。
至此,我们成功完成了容器运行时从docker到containerd的迁移,集群中的其他节点可以重复上述步骤完成全部迁移。
03 Containerd和docker使用对比
当使用docker作为容器运行时,系统管理员有时会登录k8s节点执行docker命令来收集系统或者应用信息,这些命令都是通过docker CLI实现的。而迁移到containerd之后,可以通过containerd CLI工具ctr来实现与containerd的交互,但是从使用便捷性和功能性上考虑,更推荐使用crictl作为troubleshooting的工具。Crictl是类似于docker CLI的客户端调试工具,并且适用于所有与CRI兼容的容器运行时,包括docker。下面将围绕镜像、容器、pod方面比较一下docker、ctr、crictl常用命令的使用区别。
(1)镜像相关功能
(2)容器相关功能
这里要特别说明一下,通过ctr containers create创建容器后,只是一个静态的容器,容器中的用户进程并没有启动,所以还需要通过ctr task start来启动容器进程。当然,也可以用ctr run的命令直接创建并运行容器。在进入容器操作时,与docker不同的是,必须在ctr task exec命令后指定–exec-id参数,这个id可以随便写,只要唯一就行。另外,ctr没有stop容器的功能,只能暂停(ctr task pause)或者杀死(ctr task kill)容器。
(3)Pod相关功能
这里要说明的是:crictl pods列出的是pod的信息,包括pod所在的命名空间以及状态。crictl ps列出的是应用容器的信息,而docker ps列出的是初始化容器(pause容器)和应用容器的信息,初始化容器在每个pod启动时都会创建,通常不会关注,从这一点上来说,crictl使用起来更简洁明了一些。
docker和containerd除了上述常用命令有些区别外,在容器日志及相关参数配置方面也存在一些差异,详见下表。
04 总结
k8s弃用docker这一决定可能对从事相关工作的人员来说有些措手不及,但其实无需特别担心。对于k8s的终端用户来说,这仅仅是一个后端容器运行时的更改,从使用方面来说几乎感觉不到任何区别;对于应用开发/运维人员来说,依旧可以继续使用docker来构建镜像,以相同的方式将镜像推送到registry,并将这些镜像部署到k8s环境中;对于k8s集群管理员来说,只需要将docker切换成其它的容器运行时(比如containerd),并将节点troubleshooting工具从docker CLI切换到crictl即可。
参考资料:
1、https://kubernetes.io/blog/2018/05/24/kubernetes-containerd-integration-goes-ga
2、https://kruyt.org/migrate-docker-containerd-kubernetes/
3、https://cloud.tencent.com/developer/article/1450788
4、https://github.com/containerd/cri/blob/v1.0.0/docs/installation.md
声明:本文转载网络 发布,仅代表作者观点,不代表蜗牛120博客网的观点或立场,蜗牛120博客网仅提供信息发布平台,合作供稿、侵权删除、反馈建议请联系Telegram: @nice_seo
评论