Containerd上手

摘要

Containerd 架构

An industry-standard container runtime with an emphasis on simplicity, robustness and portability

行业标准的容器运行时,强调简单,健壮和可移植。

Containerd 的架构:

image-20201221113500479

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

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

总体上 Containerd 被划分为两个子系统:

  • Bundle : 在 Containerd 中,Bundle 包含了配置、元数据和根文件系统数据,你可以理解为容器的文件系统。而 Bundle 子系统允许用户从镜像中提取和打包 Bundles。
  • Runtime : Runtime 子系统用来执行 Bundles,比如创建容器。

其中,每一个子系统的行为都由一个或多个模块协作完成(架构图中的 Core 部分)。每一种类型的模块都以插件的形式集成到 Containerd 中,而且插件之间是相互依赖的。例如,上图中的每一个长虚线的方框都表示一种类型的插件,包括 Service PluginMetadata PluginGC PluginRuntime Plugin 等,其中 Service Plugin 又会依赖 Metadata Plugin、GC Plugin 和 Runtime Plugin。每一个小方框都表示一个细分的插件,例如 Metadata Plugin 依赖 Containers Plugin、Content Plugin 等。总之,万物皆插件,插件就是模块,模块就是插件。

图片

这里介绍几个常用的插件:

  • Content Plugin : 提供对镜像中可寻址内容的访问,所有不可变的内容都被存储在这里。
  • Snapshot Plugin : 用来管理容器镜像的文件系统快照。镜像中的每一个 layer 都会被解压成文件系统快照,类似于 Docker 中的 graphdriver
  • Metrics : 暴露各个组件的监控指标。

从总体来看,Containerd 被分为三个大块:StorageMetadataRuntime,可以将上面的架构图提炼一下:

图片

这是使用bucketbench对 DockercrioContainerd 的性能测试结果,包括启动、停止和删除容器,以比较它们所耗的时间:

图片

Containerd 安装

可以看到 Containerd 在各个方面都表现良好,总体性能还是优越于 Dockercrio 的。

了解了 Containerd 的概念后,就可以动手安装体验一把了。本文的演示环境为 CentOS 7.6

YUM安装Containerd

1
2
3
yum install -y yum-utils device-mapper-persistent-data lvm2
yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
yum install -y containerd.io

接下面生成配置文件

下载并解压 Containerd 程序

Containerd 提供了两个压缩包,一个叫 containerd-${VERSION}.${OS}-${ARCH}.tar.gz,另一个叫 cri-containerd-${VERSION}.${OS}-${ARCH}.tar.gz。其中 cri-containerd-${VERSION}.${OS}-${ARCH}.tar.gz 包含了所有 Kubernetes 需要的二进制文件。如果你只是本地测试,可以选择前一个压缩包;如果是作为 Kubernetes 的容器运行时,需要选择后一个压缩包。

Containerd 是需要调用 runc 的,而第一个压缩包是不包含 runc 二进制文件的,如果你选择第一个压缩包,还需要提前安装 runc。所以我建议直接使用 cri-containerd 压缩包。

首先从 release 页面[2]下载最新版本的压缩包,当前最新版本为 1.4.3:

1
2
3
wget https://github.com/containerd/containerd/releases/download/v1.4.3/cri-containerd-cni-1.4.3-linux-amd64.tar.gz
# 也可以替换成下面的 URL 加速下载
wget https://download.fastgit.org/containerd/containerd/releases/download/v1.4.3/cri-containerd-cni-1.4.3-linux-amd64.tar.gz

直接将压缩包解压到系统的各个目录中:

1
tar -C / -xzf cri-containerd-cni-1.4.3-linux-amd64.tar.gz

生成配置文件

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

1
2
mkdir /etc/containerd
containerd config default > /etc/containerd/config.toml

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

存储配置

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

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

修改为

root = "/data/containerd"
state = "/run/containerd"

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

镜像加速

镜像加速的配置就在 cri 插件配置块下面的 registry 配置块,所以需要修改的部分如下:

endpoint = ["https://registry-1.docker.io"]

改为

endpoint = ["https://ojtwovh1.mirror.aliyuncs.com"]

  • registry.mirrors.”xxx” : 表示需要配置 mirror 的镜像仓库。例如,registry.mirrors."docker.io" 表示配置 docker.io 的 mirror。
  • endpoint : 表示提供 mirror 的镜像加速服务。例如,这里推荐使用西北农林科技大学提供的镜像加速服务作为 docker.io 的 mirror。

私有仓库

1
2
3
4
5
6
7
8
  [plugins."io.containerd.grpc.v1.cri".registry.mirrors."192.168.0.2"]
endpoint = ["https://192.168.0.2"]
[plugins."io.containerd.grpc.v1.cri".registry.configs]
[plugins."io.containerd.grpc.v1.cri".registry.configs."192.168.0.2".tls]
insecure_skip_verify = true
[plugins."io.containerd.grpc.v1.cri".registry.configs."192.168.0.2".auth]
username = "admin"
password = "Harbor12345"

OOM

还有一项配置需要留意:

oom_score = 0

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

The value of /proc/<pid>/oom_score_adj is added to the badness score before it is used to determine which task to kill. Acceptable values range from -1000 (OOM_SCORE_ADJ_MIN) to +1000 (OOM_SCORE_ADJ_MAX). This allows userspace to polarize the preference for oom killing either by always preferring a certain task or completely disabling it. The lowest possible value, -1000, is equivalent to disabling oom killing entirely for that task since it will always report a badness score of 0.

在计算最终的 badness score 时,会在计算结果是中加上 oom_score_adj ,这样用户就可以通过该在值来保护某个进程不被杀死或者每次都杀某个进程。其取值范围为 -10001000

如果将该值设置为 -1000,则进程永远不会被杀死,因为此时 badness score 永远返回0。

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

Systemd 配置

建议通过 systemd 配置 Containerd 作为守护进程运行,配置文件在上文已经被解压出来了:

1
cat /etc/systemd/system/containerd.service

这里有两个重要的参数:

  • Delegate : 这个选项允许 Containerd 以及运行时自己管理自己创建的容器的 cgroups。如果不设置这个选项,systemd 就会将进程移到自己的 cgroups 中,从而导致 Containerd 无法正确获取容器的资源使用情况。

  • KillMode : 这个选项用来处理 Containerd 进程被杀死的方式。默认情况下,systemd 会在进程的 cgroup 中查找并杀死 Containerd 的所有子进程,这肯定不是我们想要的。KillMode字段可以设置的值如下。

    我们需要将 KillMode 的值设置为 process,这样可以确保升级或重启 Containerd 时不杀死现有的容器。

    • control-group(默认值):当前控制组里面的所有子进程,都会被杀掉
    • process:只杀主进程
    • mixed:主进程将收到 SIGTERM 信号,子进程收到 SIGKILL 信号
    • none:没有进程会被杀掉,只是执行服务的 stop 命令。

现在到了最关键的一步:启动 Containerd。执行一条命令就完事:

1
2
3
systemctl enable containerd.service
systemctl start containerd.service
systemctl status containerd.service

接下来进入本文最后一部分:Containerd 的基本使用方式。

ctr 使用

ctr 目前很多功能做的还没有 docker 那么完善,但基本功能已经具备了。下面将围绕镜像容器这两个方面来介绍其使用方法。

镜像

镜像下载:

1
ctr images pull docker.io/library/nginx:alpine

1
ctr i pull docker.io/library/nginx:alpine

我尝试在http协议私有仓库拉取镜像是失败的,通过以下操作可以拉取成功

1
ctr i pull --skip-verify --plain-http --user admin:admin 192.168.0.2/public/nginx:alpine

本地镜像列表查询:

1
ctr i ls

这里需要注意PLATFORMS,它是镜像的能够运行的平台标识。

其他操作可以自己查看帮助:

1
ctr i --help

容器

创建容器:

1
2
ctr c create docker.io/library/nginx:alpine nginx
ctr c ls

其他操作可以自己查看帮助:

1
ctr c --help

任务

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

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

所以还需要通过 task 启动容器:

1
2
ctr task start -d nginx
ctr task ls

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

1
ctr run -d docker.io/library/nginx:alpine nginx

进入容器:

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

1
ctr task exec --exec-id 0 -t nginx sh

暂停容器:

和 docker pause 类似

1
ctr task pause nginx

容器状态变成了 PAUSED:

1
ctr task ls

恢复容器:

1
ctr task resume nginx

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

杀死容器:

1
ctr task kill nginx

命名空间

除了 k8s 有命名空间以外,Containerd 也支持命名空间。

1
ctr ns ls

如果不指定,ctr 默认是 default 空间。

目前 Containerd 的定位还是解决运行时,所以目前他还不能完全替代 dockerd,例如使用 Dockerfile 来构建镜像。

参考资料

https://fuckcloudnative.io/posts/getting-started-with-containerd/