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的原因。

%title插图%num

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具有更好的性能和更低的成本。

%title插图%num

接下来,主要介绍如何将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

%title插图%num

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

ctr namespaces list

可以看到有一个moby命名空间,这也是docker服务默认使用的命名空间。

ctr -namespace moby container list

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

%title插图%num

(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

%title插图%num

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

kubectl uncordon spk8mgr03

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

%title插图%num

至此,我们成功完成了容器运行时从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使用起来更简洁明了一些。

%title插图%num

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

%title插图%num

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

本文链接:http://www.yunweipai.com/40440.html

Kubernetes runtime从docker迁移到containerd探索
%title插图%num
滚动到顶部