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 http://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 = ["http://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、http://kubernetes.io/blog/2018/05/24/kubernetes-containerd-integration-goes-ga
2、http://kruyt.org/migrate-docker-containerd-kubernetes/
3、http://cloud.tencent.com/developer/article/1450788
4、http://github.com/containerd/cri/blob/v1.0.0/docs/installation.md
本文链接:http://www.yunweipai.com/40440.html
- 什么是社会工程学?看完才知道多可怕!
- 护网行动面试题目汇总
- 开发者们偏爱 Linux 操作系统的 9 个理由!
- 2022年10大顶级网络安全工具
- kali菜单中各工具的功能
- Go 内存管理
- 一份简单够用的 Nginx Location 配置讲解
- Linux 磁盘空间被吃掉了?这样排查不背锅!
- Linux中卸载提示设备正忙怎么办?
- 介绍一款地理数据可视化神器——keplergl
- Podman常用命令总结
- Kubernetes限制节点启动的Pod数量
- Python实现进阶版人脸识别
- 常见内网穿透工具使用总结
- linux命令最佳【alias别名】工具
- 分享18个 实用 Linux 运维命令及知识
- Go 实现 Nginx 加权轮询算法
- 超详细的秒杀架构设计,运维,了解一下
- iptables 详解
- Minikube 入门初体验