携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第2天。
Containerd
Containerd 是一个高度模块化的高级运行时,所有模块均可插拔,模块均以 RPC service 形式注册并调用(gRPC 或者 TTRPC)。不同插件通过声明互相依赖,由 Containerd 核心实现统一加载,使用方可以自行实现插件以实现定制化的功能。当然这种设计虽然使得 Containerd 拥有强大的跨平台、可插拔的能力,同时也带来一些缺点,模块之间功能互调必须通过 RPC 调用。
注: TTRPC 是一种基于 gRPC 的裁剪版通信协议。
containerd 是一个工业级标准的容器运行时,它强调简单性、健壮性和可移植性,containerd 可以负责干下面这些事情:
- 管理容器的生命周期(从创建容器到销毁容器)
- 拉取/推送容器镜像
- 存储管理(管理镜像及容器数据的存储)
- 调用 runc 运行容器(与 runc 等容器运行时交互)
- 管理容器网络接口及网络
Containerd 的设计是一个大的插件系统,图中每一个虚线框其实就对应一个插件。
从下往上看,底层支持 arm 和 x86 架构,支持 Linux 和 windows 系统。
中间 containerd 包含三层:Backend、core、API。其中 Backend 中 Runtime plugin 提供了容器运行时的具体操作,为了支持不同的容器运行时 containerd 也提供了一系列的 containerd-shim。Core 是核心部分,提供了各种功能的服务,其中比较常用的是 Content service ,提供对镜像中可寻址内容的访问,所有不可变的内容都被存储在这里;Images Service 提供镜像相关的操作;Snapshot Plugin : 用来管理容器镜像的文件系统快照。镜像中的每一个 layer 都会被解压成文件系统快照,类似于 Docker 中的 graphdriver。再往上则是 API 层,通过 GRPC 与客户端连接,这其中提供了给 Prometheus 使用 API 来进行监控,这里给个好评!
最上层则是各种客户端,包括 K8s 的 kubelet,containerd 自己的命令行 ctr 等。
简化一下上图:
architecture.png
简化后,Containerd 分为三大块:Storage 管理镜像文件的存储;Metadata 管理镜像和容器的元数据;另外由 Task 触发运行时。对外提供 GRPC 方式的 API 以及 Metrics 接口。
Containerd-shim
Runtime v2 为运行时实现者引入了一套shim API,以便与 Containerd 集成。shim API 只针对容器的执行生命周期管理。其是用于剥离 Containerd 守护进程与容器进程。目前已有 shim v1 和 shim v2 两个版本;它是Containerd 中的一个插件,其通过 shim 调用低级运行时命令来启动容器。
注: 简单来说引入shim是允许低级运行时(如runc、youki等)在创建和运行容器之后退出, 并将shim作为容器的父进程, 而不是containerd作为父进程,是否还记得Containerd 抽象uds漏洞?
每一个 Containerd 容器都有一个相应的shim守护进程,这个守护进程会提供一个 API,Containerd 使用该 API 来管理容器基本的生命周期(启/停等),在容器中执行新的进程、调整 TTY 的大小以及与特定平台相关的其他操作。shim 还有一个作用是向 Containerd 报告容器的退出状态,在容器退出状态被 Containerd 收集之前,shim 会一直存在。这一点和僵尸进程很像,僵尸进程在被父进程回收之前会一直存在,只不过僵尸进程不会占用资源,而 shim 会占用资源。
k8s + CRI
由于Docker与CRI不兼容,所以在kubelet内部实现了dockershim服务,以便它能与Docker通信。dockershim在Kubernetes 1.20中声明废弃,并已经在Kubernetes 1.24版本着手删除工作,下图为满足CRI的运行时。
Containerd 是一个高级的容器运行时,它可以与下面列出的低级运行时进行交互。
runc是Open Container Initiative(OCI)指定的容器运行时的实现参照。其是诸多Kubernetes发行版默认安装和使用的容器运行时,当然,安装和使用其他低级容器运行时也非常方便。为了提高工作负载的安全性,可能需要其他的安全容器。
docker 默认使用containerd容器运行时。
Containerd 使用
Docker CLI 工具提供了需要增强用户体验的功能,containerd 同样也提供一个对应的 CLI 工具:ctr,不过 ctr 的功能没有 docker 完善,但是关于镜像和容器的基本功能都是有的。接下来我们就先简单介绍下 ctr 的使用。
帮助
直接输入 ctr 命令即可获得所有相关的操作命令使用方式:
➜ ~ ctr
NAME:
ctr -
__
_____/ /______
/ ___/ __/ ___/
/ /__/ /_/ /
\___/\__/_/
containerd CLI
USAGE:
ctr [global options] command [command options] [arguments...]
VERSION:
v1.5.5
DESCRIPTION:
ctr is an unsupported debug and administrative client for interacting
with the containerd daemon. Because it is unsupported, the commands,
options, and operations are not guaranteed to be backward compatible or
stable from release to release of the containerd project.
COMMANDS:
plugins, plugin provides information about containerd plugins
version print the client and server versions
containers, c, container manage containers
content manage content
events, event display containerd events
images, image, i manage images
leases manage leases
namespaces, namespace, ns manage namespaces
pprof provide golang pprof outputs for containerd
run run a container
snapshots, snapshot manage snapshots
tasks, t, task manage tasks
install install a new package
oci OCI tools
shim interact with a shim directly
help, h Shows a list of commands or help for one command
GLOBAL OPTIONS:
--debug enable debug output in logs
--address value, -a value address for containerd's GRPC server (default: "/run/containerd/containerd.sock") [$CONTAINERD_ADDRESS]
--timeout value total timeout for ctr commands (default: 0s)
--connect-timeout value timeout for connecting to containerd (default: 0s)
--namespace value, -n value namespace to use with commands (default: "default") [$CONTAINERD_NAMESPACE]
--help, -h show help
--version, -v print the version
镜像操作
拉取镜像
拉取镜像可以使用 ctr image pull 来完成,比如拉取 Docker Hub 官方镜像 nginx:alpine,需要注意的是镜像地址需要加上 docker.io Host 地址:
➜ ~ ctr image pull docker.io/library/nginx:alpine
docker.io/library/nginx:alpine: resolved |++++++++++++++++++++++++++++++++++++++|
index-sha256:bead42240255ae1485653a956ef41c9e458eb077fcb6dc664cbc3aa9701a05ce: exists |++++++++++++++++++++++++++++++++++++++|
manifest-sha256:ce6ca11a3fa7e0e6b44813901e3289212fc2f327ee8b1366176666e8fb470f24: done |++++++++++++++++++++++++++++++++++++++|
layer-sha256:9a6ac07b84eb50935293bb185d0a8696d03247f74fd7d43ea6161dc0f293f81f: done |++++++++++++++++++++++++++++++++++++++|
layer-sha256:e82f830de071ebcda58148003698f32205b7970b01c58a197ac60d6bb79241b0: done |++++++++++++++++++++++++++++++++++++++|
layer-sha256:d7c9fa7589ae28cd3306b204d5dd9a539612593e35df70f7a1d69ff7548e74cf: done |++++++++++++++++++++++++++++++++++++++|
layer-sha256:bf2b3ee132db5b4c65432e53aca69da4e609c6cb154e0d0e14b2b02259e9c1e3: done |++++++++++++++++++++++++++++++++++++++|
config-sha256:7ce0143dee376bfd2937b499a46fb110bda3c629c195b84b1cf6e19be1a9e23b: done |++++++++++++++++++++++++++++++++++++++|
layer-sha256:3c1eaf69ff492177c34bdbf1735b6f2e5400e417f8f11b98b0da878f4ecad5fb: done |++++++++++++++++++++++++++++++++++++++|
layer-sha256:29291e31a76a7e560b9b7ad3cada56e8c18d50a96cca8a2573e4f4689d7aca77: done |++++++++++++++++++++++++++++++++++++++|
elapsed: 11.9s total: 8.7 Mi (748.1 KiB/s)
unpacking linux/amd64 sha256:bead42240255ae1485653a956ef41c9e458eb077fcb6dc664cbc3aa9701a05ce...
done: 410.86624ms
也可以使用 --platform 选项指定对应平台的镜像。当然对应的也有推送镜像的命令 ctr image push,如果是私有镜像则在推送的时候可以通过 --user 来自定义仓库的用户名和密码。
列出本地镜像
➜ ~ ctr image ls
REF TYPE DIGEST SIZE PLATFORMS LABELS
docker.io/library/nginx:alpine application/vnd.docker.distribution.manifest.list.v2+json sha256:bead42240255ae1485653a956ef41c9e458eb077fcb6dc664cbc3aa9701a05ce 9.5 MiB linux/386,linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64/v8,linux/ppc64le,linux/s390x -
➜ ~ ctr image ls -q
docker.io/library/nginx:alpine
使用 -q(--quiet) 选项可以只打印镜像名称。
检测本地镜像
➜ ~ ctr image check
REF TYPE DIGEST STATUS SIZE UNPACKED
docker.io/library/nginx:alpine application/vnd.docker.distribution.manifest.list.v2+json sha256:bead42240255ae1485653a956ef41c9e458eb077fcb6dc664cbc3aa9701a05ce complete (7/7) 9.5 MiB/9.5 MiB true
主要查看其中的 STATUS,complete 表示镜像是完整可用的状态。
重新打标签
同样的我们也可以重新给指定的镜像打一个 Tag:
➜ ~ ctr image tag docker.io/library/nginx:alpine harbor.k8s.local/course/nginx:alpine
harbor.k8s.local/course/nginx:alpine
➜ ~ ctr image ls -q
docker.io/library/nginx:alpine
harbor.k8s.local/course/nginx:alpine
删除镜像
不需要使用的镜像也可以使用 ctr image rm 进行删除:
➜ ~ ctr image rm harbor.k8s.local/course/nginx:alpine
harbor.k8s.local/course/nginx:alpine
➜ ~ ctr image ls -q
docker.io/library/nginx:alpine
加上 --sync 选项可以同步删除镜像和所有相关的资源。
将镜像挂载到主机目录
➜ ~ ctr image mount docker.io/library/nginx:alpine /mnt
sha256:c3554b2d61e3c1cffcaba4b4fa7651c644a3354efaafa2f22cb53542f6c600dc
/mnt
➜ ~ tree -L 1 /mnt
/mnt
├── bin
├── dev
├── docker-entrypoint.d
├── docker-entrypoint.sh
├── etc
├── home
├── lib
├── media
├── mnt
├── opt
├── proc
├── root
├── run
├── sbin
├── srv
├── sys
├── tmp
├── usr
└── var
18 directories, 1 file
将镜像从主机目录上卸载
➜ ~ ctr image unmount /mnt/mnt
将镜像导出为压缩包
➜ ~ ctr image export nginx.tar.gz docker.io/library/nginx:alpine
从压缩包导入镜像
➜ ~ ctr image import nginx.tar.gz
容器操作
容器相关操作可以通过 ctr container 获取。
创建容器
➜ ~ ctr container create docker.io/library/nginx:alpine nginx
列出容器
➜ ~ ctr container lsCONTAINER IMAGE RUNTIMEnginx docker.io/library/nginx:alpine io.containerd.runc.v2
同样可以加上 -q 选项精简列表内容:
➜ ~ ctr container ls -qnginx
查看容器详细配置
类似于 docker inspect 功能。
➜ ~ ctr container info nginx
{
"ID": "nginx",
"Labels": {
"io.containerd.image.config.stop-signal": "SIGQUIT"
},
"Image": "docker.io/library/nginx:alpine",
"Runtime": {
"Name": "io.containerd.runc.v2",
"Options": {
"type_url": "containerd.runc.v1.Options"
}
},
"SnapshotKey": "nginx",
"Snapshotter": "overlayfs",
"CreatedAt": "2021-08-12T08:23:13.792871558Z",
"UpdatedAt": "2021-08-12T08:23:13.792871558Z",
"Extensions": null,
"Spec": {
......
删除容器
➜ ~ ctr container rm nginx➜ ~ ctr container lsCONTAINER IMAGE RUNTIME
除了使用 rm 子命令之外也可以使用 delete 或者 del 删除容器。
任务
上面我们通过 container create 命令创建的容器,并没有处于运行状态,只是一个静态的容器。一个 container 对象只是包含了运行一个容器所需的资源及相关配置数据,表示 namespaces、rootfs 和容器的配置都已经初始化成功了,只是用户进程还没有启动。
一个容器真正运行起来是由 Task 任务实现的,Task 可以为容器设置网卡,还可以配置工具来对容器进行监控等。
Task 相关操作可以通过 ctr task 获取,如下我们通过 Task 来启动容器:
➜ ~ ctr task start -d nginx/docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration/docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d/
启动容器后可以通过 task ls 查看正在运行的容器:
➜ ~ ctr task lsTASK PID STATUSnginx 3630 RUNNING
同样也可以使用 exec 命令进入容器进行操作:
➜ ~ ctr task exec --exec-id 0 -t nginx sh/ #
不过这里需要注意必须要指定 --exec-id 参数,这个 id 可以随便写,只要唯一就行。
暂停容器,和 docker pause 类似的功能:
➜ ~ ctr task pause nginx
暂停后容器状态变成了 PAUSED:
➜ ~ ctr task lsTASK PID STATUSnginx 3630 PAUSED
同样也可以使用 resume 命令来恢复容器:
➜ ~ ctr task resume nginx➜ ~ ctr task lsTASK PID STATUSnginx 3630 RUNNING
不过需要注意 ctr 没有 stop 容器的功能,只能暂停或者杀死容器。杀死容器可以使用 task kill 命令:
➜ ~ ctr task kill nginx➜ ~ ctr task lsTASK PID STATUSnginx 3630 STOPPED
杀掉容器后可以看到容器的状态变成了 STOPPED。同样也可以通过 task rm 命令删除 Task:
➜ ~ ctr task rm nginx➜ ~ ctr task lsTASK PID STATUS
除此之外我们还可以获取容器的 cgroup 相关信息,可以使用 task metrics 命令用来获取容器的内存、CPU 和 PID 的限额与使用量。
# 重新启动容器➜ ~ ctr task metrics nginxID TIMESTAMPnginx 2021-08-12 08:50:46.952769941 +0000 UTCMETRIC VALUEmemory.usage_in_bytes 8855552memory.limit_in_bytes 9223372036854771712memory.stat.cache 0cpuacct.usage 22467106cpuacct.usage_percpu [2962708 860891 1163413 1915748 1058868 2888139 6159277 5458062]pids.current 9pids.limit 0
还可以使用 task ps 命令查看容器中所有进程在宿主机中的 PID:
➜ ~ ctr task ps nginxPID INFO3984 -4029 -4030 -4031 -4032 -4033 -4034 -4035 -4036 -➜ ~ ctr task lsTASK PID STATUSnginx 3984 RUNNING
其中第一个 PID 3984 就是我们容器中的1号进程。
命名空间
另外 Containerd 中也支持命名空间的概念,比如查看命名空间:
➜ ~ ctr ns lsNAME LABELSdefault
如果不指定,ctr 默认使用的是 default 空间。同样也可以使用 ns create 命令创建一个命名空间:
➜ ~ ctr ns create test➜ ~ ctr ns lsNAME LABELSdefaulttest
使用 remove 或者 rm 可以删除 namespace:
➜ ~ ctr ns rm testtest➜ ~ ctr ns lsNAME LABELSdefault
有了命名空间后就可以在操作资源的时候指定 namespace,比如查看 test 命名空间的镜像,可以在操作命令后面加上 -n test 选项:
➜ ~ ctr -n test image lsREF TYPE DIGEST SIZE PLATFORMS LABELS
我们知道 Docker 其实也是默认调用的 containerd,事实上 Docker 使用的 containerd 下面的命名空间默认是 moby,而不是 default,所以假如我们有用 docker 启动容器,那么我们也可以通过 ctr -n moby 来定位下面的容器:
➜ ~ ctr -n moby container ls
同样 Kubernetes 下使用的 containerd 默认命名空间是 k8s.io,所以我们可以使用 ctr -n k8s.io 来查看 Kubernetes 下面创建的容器。后续我们再介绍如何将 Kubernetes 集群的容器运行时切换到 containerd。