注: Container runtime 统称为容器运行时
在 Docker 时代,关于容器运行时术语的定义是非常明确的,其为运行和管理容器的软件。但随着 Docker 涵盖的内容日益增多,以及多种容器编排工具的引入,该定义变得日益模糊了。
当你运行一个 Docker 容器时,一般的步骤是:
下载镜像
将镜像解压成一个 bundle,即将各层文件平铺到一个单一的文件系统中。
运行容器
最初的规范规定,只有运行容器的部分定义为容器运行时,但一般用户,将上述三个步骤都默认为容器运行时所必须的能力,从而让容器运行时的定义成为一个令人困惑的话题。
当人们想到容器运行时,可能会想到一连串的相关概念;runc、runv、lxc、lmctfy、Docker(containerd)、rkt、cri-o。每一个都是基于不同的场景而实现的,均实现了不同的功能。如containerd和cri-o,实际均可使用runc来运行容器,但其实现了如镜像管理、容器API等功能,可以将这些看作是比runc具备的更高级的功能。
可以发现,容器运行时是相当复杂的。每个运行时都涵盖了从低级到高级的不同部分,如下图所示。
只认识 rootfs 和 config.json,没有其他镜像能力
不提供网络实现
不提供持久实现
无法跨平台等
$ CID=$(docker create busybox)
$ ROOTFS=$(mktemp -d)
$ docker export $CID | tar -xf - -C $ROOTFS
$ UUID=$(uuidgen)
$ cgcreate -g cpu,memory:$UUID
$ cgset -r memory.limit_in_bytes=100000000 $UUID
$ cgset -r cpu.shares=512 $UUID
$ cgset -r cpu.cfs_period_us=1000000 $UUID
$ cgset -r cpu.cfs_quota_us=2000000 $UUID
接下来在容器中执行命令。
$ cgexec -g cpu,memory:$UUID \
> unshare -uinpUrf --mount-proc \
> sh -c "/bin/hostname $UUID && chroot $ROOTFS /bin/sh"
/ # echo "Hello from in a container"
Hello from in a container
/ # exit
最后,删除前面创建的 cgroup 和临时目录。
$ cgdelete -r -g cpu,memory:$UUID
$ rm -r $ROOTFS
为了更好地理解低级容器运行时,以下列举了几个低级运行时代表,各自实现了不同的功能。
runC
runC是目前使用最广泛的容器运行时。它最初是集成在Docker的内部,后来作为一个单独的工具,并以公共库的方式提取出来。
首先创建根文件系统。这里我们将再次使用 busybox。
$ mkdir rootfs
$ docker export $(docker create busybox) | tar -xf - -C rootfs
接下来创建一个 config.json 文件
$ runc spec
这个命令为容器创建一个模板config.json。
$ cat config.json
{
"ociVersion": "1.0.2",
"process": {
"terminal": true,
"user": {
"uid": 0,
"gid": 0
},
"args": [
"sh"
],
"env": [
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
"TERM=xterm"
],
"cwd": "/",
"capabilities": {
...
默认情况下,它在根文件系统位于 ./rootfs
的目录下运行命令。
$ sudo runc run mycontainerid
/ # echo "Hello from in a container"
Hello from in a container
rkt(已废弃)
runV
youki
Docker
Containerd
$ sudo ctr images pull docker.io/library/redis:latest
列出所有的镜像:
$ sudo ctr images list
运行容器:
$ sudo ctr container create docker.io/library/redis:latest redis
列出运行容器:
$ sudo ctr container list
停止容器:
$ sudo ctr container delete redis
这些命令类似于用户与 Docker 的互动方式。
rkt(已废弃)
CRI规范
CRI定义了gRPC API,该规范定义在 Kubernetes 仓库中 cri-api 目录中。CRI定义了几个远程程序调用(RPC)和消息类型。这些RPC用于管理工作负载等内容,如 “拉取镜像”(ImageService.PullImage)、”创建pod”(RuntimeService.RunPodSandbox)、”创建容器”(RuntimeService.CreateContainer)、”启动容器”(RuntimeService.StartContainer)、”停止容器”(RuntimeService.StopContainer)等操作。
例如,通过CRI启动一个新的Pod(篇幅有限,进行了一些简化工作)。RunPodSandbox和CreateContainer RPCs在其响应中返回ID,在后续请求中使用。
ImageService.PullImage({image: "image1"})
ImageService.PullImage({image: "image2"})
podID = RuntimeService.RunPodSandbox({name: "mypod"})
id1 = RuntimeService.CreateContainer({
pod: podID,
name: "container1",
image: "image1",
})
id2 = RuntimeService.CreateContainer({
pod: podID,
name: "container2",
image: "image2",
})
RuntimeService.StartContainer({id: id1})
RuntimeService.StartContainer({id: id2})
可以直接使用 crictl 工具与 CRI 运行时交互,可以用它来调试和测试CRI的相关实现。
cat <
runtime-endpoint: unix:///run/containerd/containerd.sock
EOF
或者通过命令行指定:
crictl --runtime-endpoint unix:///run/containerd/containerd.sock …
关于 crictl 的使用参见官网。
Containerd
Docker
docker-shim 是 K8s 社区第一个被开发的,作为kubelet 和 Docker 之间的 shim。随着Docker 将其许多功能分解到 containerd 中,现在通过 containerd 支持 CRI。
当现代版本的 Docker 被安装时,containerd 也一起被安装,CRI 直接与 containerd 对话,随着docker-shim正式废弃,是时候考虑相关迁移的工作了,K8s在这方面做了大量的工作,具体可参看官方文档。
CRI-O
cri-o 是一个轻量级的 CRI 运行时,它支持 OCI,并提供镜像的管理、容器进程管理、监控日志及资源隔离等工作。
cri-o 的通信地址默认是在 /var/run/crio/crio.sock。
下图为CRI插件的演变史。
由于笔者时间、视野、认知有限,本文难免出现错误、疏漏等问题,期待各位读者朋友、业界专家指正交流。
参考文献
1.https://blog.mobyproject.org/where-are-containerds-graph-drivers-145fc9b7255
2.https://insujang.github.io/2019-10-31/container-runtime/
3.https://github.com/cri-o/cri-o
来源:本文转自公众号 DCOS,点击查看原文。
通信行业数字化转型,AIOps、MLOps、云原生、SRE等相关精彩内容,2022年最值得参加的运维大会,就在这里啦!