S_lion's Studio

CRI之containerd

字数统计: 3.2k阅读时长: 15 min
2021/12/15 Share

这篇文章可以了解云原生领域CRI与OCI的由来,为了制约docker在容器运行时的垄断地位,Google与IBM、Redhat等公司成立了CNCF,形成以kubernetes服务编排为基础,建设其周边云原生产品的生态,并陆续提出了CRI(容器运行时接口)、CSI(容器存储接口)、CNI(容器网络接口)的规范。

上篇文章开篇的图上可知,当前docker并没有实现CRI的规范,需要通过kubelet中集成了dokcershim(垫片)来进行“翻译”,并且对比后面直接使用containerd或者cri-o的方案来说,docker的这种实现太冗余了,性能会有较大的开销。但对于当前来说,docker依旧是主要的企业级容器运行时。

kubernetes在1.20版本之后废弃docker作为容器运行时,这也符合其产品理念,后续去除掉dockershim支持,还能实现kubernetes与docker的解耦,减少kubernetes社区的负担,也能够使得kubernetes的运行时支持上可以有更好的演进和发展。

类似的事情就如之前kubernetes支持的持久化存储,厂商会将存储插件代码到kubernetes核心代码中,后续改为统一的基于CSI的实现,而kubernetes就专注于CSI就行了,不用再跟很多厂商的代码耦合了。

containerd

Containerd是一个工业标准的容器运行时,重点是它简洁,健壮,便携,在Linux和window上可以作为一个守护进程运行,它可以管理主机系统上容器的完整的生命周期:镜像传输和存储,容器的执行和监控,低级别的存储和网络。

摘自《containerd官方文档》

Containerd 的架构

可以看到 Containerd 仍然采用标准的 C/S 架构,服务端通过 GRPC 协议提供稳定的 API,客户端通过调用服务端的 API 进行高级的操作。

为了解耦,Containerd 将不同的职责划分给不同的组件,每个组件就相当于一个子系统(subsystem)。连接不同子系统的组件被称为模块。

下图是官方文档中的架构图:

具体的实现细节还需要经过一段时间的研究才能了解。

CRI性能对比图

在安装使用前,看一张别人使用bucketbench对 DockercrioContainerd 的性能测试结果,包括启动、停止和删除容器,以比较它们所耗的时间:

可以看到,containerd在其中属于佼佼者。

containerd安装

环境描述

主机名 ip地址 系统环境 系统版本 内核版本
slions_pc2 192.168.100.11 cpu:x2 mem:4GB filesystem:20GB CentOS Linux release 7.6.1810 3.10.0-957.el7.x86_64

下载与解压资源包

主要有两种方式安装,可以使用yum安装,也可以下载离线包进行安装,另外如果是之前安装过docker的话,containerd也会自动安装的。

首先从官网找到对应的压缩包,我这里是使用的1.5.8版本来进行后续实验。

Containerd 提供了两个压缩包,一个叫 containerd-${VERSION}.${OS}-${ARCH}.tar.gz,另一个叫 cri-containerd-${VERSION}.${OS}-${ARCH}.tar.gz。其中 cri-containerd-${VERSION}.${OS}-${ARCH}.tar.gz 包含了所有 Kubernetes 需要的二进制文件。Containerd 是需要调用 runc 的,而第一个压缩包是不包含 runc 二进制文件的,如果选择第一个压缩包,还需要提前安装 runc。这里我本地环境是完全干净的,就直接使用 cri-containerd 压缩包。

下载到服务器解压

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
[root@slions_pc2 ~]# ls
anaconda-ks.cfg cri-containerd-cni-1.5.8-linux-amd64.tar.gz
[root@slions_pc2 ~]# tar -C / -zxf cri-containerd-cni-1.5.8-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/sbin/
usr/local/sbin/runc
usr/local/bin/
usr/local/bin/containerd
usr/local/bin/crictl
usr/local/bin/ctd-decoder
usr/local/bin/critest
usr/local/bin/containerd-shim-runc-v1
usr/local/bin/containerd-shim-runc-v2
usr/local/bin/containerd-stress
usr/local/bin/ctr
usr/local/bin/containerd-shim
opt/
opt/containerd/
opt/containerd/cluster/
opt/containerd/cluster/version
opt/containerd/cluster/gce/
opt/containerd/cluster/gce/configure.sh
opt/containerd/cluster/gce/cni.template
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/gce/env
opt/cni/
opt/cni/bin/
opt/cni/bin/dhcp
opt/cni/bin/firewall
opt/cni/bin/host-local
opt/cni/bin/ipvlan
opt/cni/bin/sbr
opt/cni/bin/vlan
opt/cni/bin/vrf
opt/cni/bin/tuning
opt/cni/bin/bridge
opt/cni/bin/macvlan
opt/cni/bin/bandwidth
opt/cni/bin/portmap
opt/cni/bin/host-device
opt/cni/bin/ptp
opt/cni/bin/flannel
opt/cni/bin/static
opt/cni/bin/loopback

生成配置文件

Containerd 的默认配置文件为 /etc/containerd/config.toml,需要通过命令来生成一个默认的配置:

1
2
[root@slions_pc2 ~]# mkdir /etc/containerd
[root@slions_pc2 ~]# containerd config default > /etc/containerd/config.toml

配置参数含义参照https://github.com/containerd/containerd/blob/master/docs/man/containerd-config.toml.5.md

存储配置

Containerd 有两个不同的存储路径,一个用来保存持久化数据,一个用来保存运行时状态。

1
2
root = "/var/lib/containerd"
state = "/run/containerd"

root用来保存持久化数据,包括 Snapshots, Content, Metadata 以及各种插件的数据。每一个插件都有自己单独的目录,Containerd 本身不存储任何数据,它的所有功能都来自于已加载的插件。

私有仓库

私有仓库字段位置:

1
2
3
4
5
6
7
8
9
[plugins."io.containerd.grpc.v1.cri".registry.mirrors]
[plugins."io.containerd.grpc.v1.cri".registry.mirrors."192.168.100.10"]
endpoint = ["https://192.168.100.10"] #镜像仓库加速地址
[plugins."io.containerd.grpc.v1.cri".registry.configs]
[plugins."io.containerd.grpc.v1.cri".registry.configs."192.168.100.10".tls]
insecure_skip_verify = true
[plugins."io.containerd.grpc.v1.cri".registry.configs."192.168.100.10".auth]
username = "admin"
password = "Harbor12345"

这里我就不修改了,默认是docker.io

OOM

有一项配置需要留意:

1
oom_score = 0

Containerd 是容器的守护者,一旦发生内存不足的情况,理想的情况应该是先杀死容器,而不是杀死 Containerd。所以需要调整 Containerd 的 OOM 权重,减少其被 OOM Kill 的几率。最好是将 oom_score 的值调整为比其他守护进程略低的值。这里的 oom_socre 其实对应的是 /proc/<pid>/oom_socre_adj,在早期的 Linux 内核版本里使用 oom_adj 来调整权重, 后来改用 oom_socre_adj 了。

建议 Containerd 将该值设置为 -9990 之间。如果作为 Kubernetes 的 Worker 节点,可以考虑设置为 -999

Systemd 配置

之前解压资源包时已经将containerd的systemd service配置文件放置在了etc/systemd/system/containerd.service,可以通过 systemd 配置 Containerd 作为守护进程运行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
[root@slions_pc2 ~]# systemctl enable containerd && systemctl start containerd
Created symlink from /etc/systemd/system/multi-user.target.wants/containerd.service to /etc/systemd/system/containerd.service.
[root@slions_pc2 ~]# systemctl status containerd
● containerd.service - containerd container runtime
Loaded: loaded (/etc/systemd/system/containerd.service; enabled; vendor preset: disabled)
Active: active (running) since 三 2021-12-15 19:32:15 CST; 11s ago
Docs: https://containerd.io
Process: 10475 ExecStartPre=/sbin/modprobe overlay (code=exited, status=0/SUCCESS)
Main PID: 10483 (containerd)
Tasks: 9
Memory: 19.2M
CGroup: /system.slice/containerd.service
└─10483 /usr/local/bin/containerd

12月 15 19:32:15 slions_pc2 containerd[10483]: time="2021-12-15T19:32:15.628370353+08:00" level=info msg="loading plugin \"io.containerd.grpc.v1.introspection\"..." type=io.containerd.grpc.v1
12月 15 19:32:15 slions_pc2 containerd[10483]: time="2021-12-15T19:32:15.629850993+08:00" level=info msg="Start subscribing containerd event"
12月 15 19:32:15 slions_pc2 containerd[10483]: time="2021-12-15T19:32:15.629895514+08:00" level=info msg="Start recovering state"
12月 15 19:32:15 slions_pc2 containerd[10483]: time="2021-12-15T19:32:15.630058843+08:00" level=info msg="Start event monitor"
12月 15 19:32:15 slions_pc2 containerd[10483]: time="2021-12-15T19:32:15.630072189+08:00" level=info msg="Start snapshots syncer"
12月 15 19:32:15 slions_pc2 containerd[10483]: time="2021-12-15T19:32:15.630079486+08:00" level=info msg="Start cni network conf syncer"
12月 15 19:32:15 slions_pc2 containerd[10483]: time="2021-12-15T19:32:15.630084202+08:00" level=info msg="Start streaming server"
12月 15 19:32:15 slions_pc2 containerd[10483]: time="2021-12-15T19:32:15.632856124+08:00" level=info msg=serving... address=/run/containerd/containerd.sock.ttrpc
12月 15 19:32:15 slions_pc2 containerd[10483]: time="2021-12-15T19:32:15.633175755+08:00" level=info msg=serving... address=/run/containerd/containerd.sock
12月 15 19:32:15 slions_pc2 containerd[10483]: time="2021-12-15T19:32:15.633729234+08:00" level=info msg="containerd successfully booted in 0.055757s"

ctr 使用

ctr命令是containerd的客户端工具,下面是一些常见的用法:

命名空间

containerd有namespaces的概念,不同命名空间下的container是互相隔离的,如果不指定,ctr 默认是 default 空间。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
[root@slions_pc2 ~]# ctr ns -h
NAME:
ctr namespaces - manage namespaces

USAGE:
ctr namespaces command [command options] [arguments...]

COMMANDS:
create, c create a new namespace
list, ls list namespaces
remove, rm remove one or more namespaces
label set and clear labels for a namespace

OPTIONS:
--help, -h show help

[root@slions_pc2 ~]# ctr ns create mytest1
[root@slions_pc2 ~]# ctr ns create mytest2
[root@slions_pc2 ~]# ctr ns ls
NAME LABELS
mytest1
mytest2

镜像

镜像下载

测试时发现需要写全镜像的名称,无法想docker一样省略默认的仓库名与项目名

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[root@slions_pc2 ~]# ctr -n mytest1 images pull docker.io/library/nginx:latest
ctr: failed to resolve reference "nginx": object required
docker.io/library/nginx:latest: resolved |++++++++++++++++++++++++++++++++++++++|
index-sha256:9522864dd661dcadfd9958f9e0de192a1fdda2c162a35668ab6ac42b465f0603: done |++++++++++++++++++++++++++++++++++++++|
manifest-sha256:4424e31f2c366108433ecca7890ad527b243361577180dfd9a5bb36e828abf47: done |++++++++++++++++++++++++++++++++++++++|
layer-sha256:44be98c0fab60b6cef9887dbad59e69139cab789304964a197c27e2574d37304: done |++++++++++++++++++++++++++++++++++++++|
config-sha256:f652ca386ed135a4cbe356333e08ef0816f81b2ac8d0619af01e2b256837ed3e: done |++++++++++++++++++++++++++++++++++++++|
layer-sha256:e5ae68f740265288a4888db98d2999a638fdcb6d725f427678814538d253aa4d: done |++++++++++++++++++++++++++++++++++++++|
layer-sha256:21e0df283cd68384e5e8dff7e6be1774c86ea3110c1b1e9326014d34c29c0657: done |++++++++++++++++++++++++++++++++++++++|
layer-sha256:ed835de16acd8f5821cf3f3ef77a66922510ee6349730d89a8dd4fd6da8437ef: done |++++++++++++++++++++++++++++++++++++++|
layer-sha256:881ff011f1c9c14982afc6e95ae70c25e38809843bb7d42abbb24eb540c93ce7: done |++++++++++++++++++++++++++++++++++++++|
layer-sha256:77700c52c9695053293be96f9cbcf42c91c5e097daa3829339333f1fe6f9d353: done |++++++++++++++++++++++++++++++++++++++|
elapsed: 99.8s total: 53.2 M (545.7 KiB/s)
unpacking linux/amd64 sha256:9522864dd661dcadfd9958f9e0de192a1fdda2c162a35668ab6ac42b465f0603...
done: 7.663355797s

查看该镜像。

1
2
3
[root@slions_pc2 ~]# ctr -n mytest1 image ls
REF TYPE DIGEST SIZE PLATFORMS LABELS
docker.io/library/nginx:latest application/vnd.docker.distribution.manifest.list.v2+json sha256:9522864dd661dcadfd9958f9e0de192a1fdda2c162a35668ab6ac42b465f0603 54.1 MiB linux/386,linux/amd64,linux/arm/v5,linux/arm/v7,linux/arm64/v8,linux/mips64le,linux/ppc64le,linux/s390x -

此时mytest2中是没有这个镜像的。

1
2
[root@slions_pc2 ~]# ctr -n mytest2 image ls
REF TYPE DIGEST SIZE PLATFORMS LABELS

容器

创建容器

1
2
3
4
5
6
[root@slions_pc2 ~]# ctr -n mytest1 container create docker.io/library/nginx:latest mynginx
[root@slions_pc2 ~]# ctr -n mytest1 c ls
CONTAINER IMAGE RUNTIME
mynginx docker.io/library/nginx:latest io.containerd.runc.v2
[root@slions_pc2 ~]# ps -elf|grep nginx
0 S root 10680 9878 0 80 0 - 28181 pipe_w 20:10 pts/0 00:00:00 grep --color=auto nginx

任务

上面 create 的命令创建了容器后,并没有处于运行状态,只是一个静态的容器。一个 container 对象只是包含了运行一个容器所需的资源及配置的数据结构,这意味着 namespaces、rootfs 和容器的配置都已经初始化成功了,只是用户进程(这里是 nginx)还没有启动。

然而一个容器真正的运行起来是由 task 对象实现的,task 代表任务的意思,可以为容器设置网卡,还可以配置工具来对容器进行监控等。

1
2
3
4
5
6
7
8
9
10
[root@slions_pc2 ~]# ctr -n mytest1 task start -d mynginx
[root@slions_pc2 ~]# ctr -n mytest1 task ls
TASK PID STATUS
mynginx 11330 RUNNING
[root@slions_pc2 ~]# ps -elf|grep nginx
0 S root 11305 1 0 80 0 - 177741 futex_ 21:03 ? 00:00:00 /usr/local/bin/containerd-shim-runc-v2 -namespace mytest1 -id mynginx -address /run/containerd/containerd.sock
4 S root 11330 11305 0 80 0 - 2169 sigsus 21:03 ? 00:00:00 nginx: master process nginx -g daemon off;
5 S 101 11367 11330 0 80 0 - 2270 ep_pol 21:03 ? 00:00:00 nginx: worker process
5 S 101 11368 11330 0 80 0 - 2270 ep_pol 21:03 ? 00:00:00 nginx: worker process
0 S root 11376 9878 0 80 0 - 28182 pipe_w 21:04 pts/0 00:00:00 grep --color=auto nginx

当然,也可以一步到位直接创建并运行容器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
[root@slions_pc2 ~]# ctr -n mytest2 images pull  docker.io/library/nginx:latest
docker.io/library/nginx:latest: resolved |++++++++++++++++++++++++++++++++++++++|
index-sha256:9522864dd661dcadfd9958f9e0de192a1fdda2c162a35668ab6ac42b465f0603: done |++++++++++++++++++++++++++++++++++++++|
manifest-sha256:4424e31f2c366108433ecca7890ad527b243361577180dfd9a5bb36e828abf47: done |++++++++++++++++++++++++++++++++++++++|
layer-sha256:44be98c0fab60b6cef9887dbad59e69139cab789304964a197c27e2574d37304: done |++++++++++++++++++++++++++++++++++++++|
config-sha256:f652ca386ed135a4cbe356333e08ef0816f81b2ac8d0619af01e2b256837ed3e: done |++++++++++++++++++++++++++++++++++++++|
layer-sha256:e5ae68f740265288a4888db98d2999a638fdcb6d725f427678814538d253aa4d: done |++++++++++++++++++++++++++++++++++++++|
layer-sha256:21e0df283cd68384e5e8dff7e6be1774c86ea3110c1b1e9326014d34c29c0657: done |++++++++++++++++++++++++++++++++++++++|
layer-sha256:ed835de16acd8f5821cf3f3ef77a66922510ee6349730d89a8dd4fd6da8437ef: done |++++++++++++++++++++++++++++++++++++++|
layer-sha256:881ff011f1c9c14982afc6e95ae70c25e38809843bb7d42abbb24eb540c93ce7: done |++++++++++++++++++++++++++++++++++++++|
layer-sha256:77700c52c9695053293be96f9cbcf42c91c5e097daa3829339333f1fe6f9d353: done |++++++++++++++++++++++++++++++++++++++|
elapsed: 5.8 s total: 0.0 B (0.0 B/s)
unpacking linux/amd64 sha256:9522864dd661dcadfd9958f9e0de192a1fdda2c162a35668ab6ac42b465f0603...
done: 2.327616113s
[root@slions_pc2 ~]# ctr -n mytest2 run -d docker.io/library/nginx:latest mynginx
[root@slions_pc2 ~]# ctr -n mytest2 t ls
TASK PID STATUS
mynginx 11472 RUNNING
[root@slions_pc2 ~]# ps -elf|grep nginx
0 S root 11305 1 0 80 0 - 177901 futex_ 21:03 ? 00:00:00 /usr/local/bin/containerd-shim-runc-v2 -namespace mytest1 -id mynginx -address /run/containerd/containerd.sock
4 S root 11330 11305 0 80 0 - 2169 sigsus 21:03 ? 00:00:00 nginx: master process nginx -g daemon off;
5 S 101 11367 11330 0 80 0 - 2270 ep_pol 21:03 ? 00:00:00 nginx: worker process
5 S 101 11368 11330 0 80 0 - 2270 ep_pol 21:03 ? 00:00:00 nginx: worker process
0 S root 11450 1 0 80 0 - 178173 futex_ 21:09 ? 00:00:00 /usr/local/bin/containerd-shim-runc-v2 -namespace mytest2 -id mynginx -address /run/containerd/containerd.sock
4 S root 11472 11450 0 80 0 - 2169 sigsus 21:09 ? 00:00:00 nginx: master process nginx -g daemon off;
5 S 101 11514 11472 0 80 0 - 2270 ep_pol 21:09 ? 00:00:00 nginx: worker process
5 S 101 11515 11472 0 80 0 - 2270 ep_pol 21:09 ? 00:00:00 nginx: worker process
0 S root 11533 9878 0 80 0 - 28182 pipe_w 21:10 pts/0 00:00:00 grep --color=auto nginx

进入容器

和 docker 的操作类似,但必须要指定 –exec-id ,这个 id 可以随便写,只要唯一就行

1
[root@slions_pc2 ~]# ctr -n mytest1 task exec --exec-id 0 -t mynginx bash

杀死容器

ctr 没有 stop 容器的功能,只能暂停或者杀死容器。

1
[root@slions_pc2 ~]# ctr -n mytest1 task kill mynginx

nerdctl使用

可以看到原生的ctr工具对用户不是很友好,如果是用惯docker命令再用这个体会的更加明显,好在containerd也发现了这个问题,后面出来了nerdctl工具。

1
2
3
4
5
6
7
8
9
10
11
[root@slions_pc2 ~]# mkdir nerdctl && tar xvf nerdctl-0.15.0-linux-amd64.tar.gz -C nerdctl
nerdctl
containerd-rootless-setuptool.sh
containerd-rootless.sh
[root@slions_pc2 ~]# cp -rp nerdctl/* /usr/local/bin/
[root@slions_pc2 ~]# nerdctl ps --namespace mytest1
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
mynginx docker.io/library/nginx:latest "/docker-entrypoint.…" About an hour ago Up
[root@slions_pc2 ~]# nerdctl images --namespace mytest1
REPOSITORY TAG IMAGE ID CREATED PLATFORM SIZE
nginx latest 9522864dd661 2 hours ago linux/amd64 146.2 MiB
CATALOG
  1. 1. containerd
    1. 1.1. Containerd 的架构
    2. 1.2. CRI性能对比图
  2. 2. containerd安装
    1. 2.1. 环境描述
    2. 2.2. 下载与解压资源包
    3. 2.3. 生成配置文件
      1. 2.3.1. 存储配置
      2. 2.3.2. 私有仓库
      3. 2.3.3. OOM
    4. 2.4. Systemd 配置
  3. 3. ctr 使用
    1. 3.1. 命名空间
    2. 3.2. 镜像
      1. 3.2.1. 镜像下载
    3. 3.3. 容器
      1. 3.3.1. 创建容器
    4. 3.4. 任务
    5. 3.5. 进入容器
    6. 3.6. 杀死容器
  4. 4. nerdctl使用