抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

Containerd简介

查看源图像

Containerd概述

Containerd 的前世今生

​ 很久以前,Docker 强势崛起,以“镜像”这个大招席卷全球,对其他容器技术进行致命的降维打击,使其毫无招架之力,就连 Google 也不例外,Google 为了不被拍死在沙滩上,被迫拉下脸面(当然,跪舔是不可能的),希望 Docker 公司和自己联合推进一个开源的容器运行时作为 Docker 的核心依赖,不然就走着瞧。Docker 公司觉得自己的智商被侮辱了,走着瞧就走着瞧,谁怕谁啊!

​ 很明显,Docker 公司的这个决策断送了自己的大好前程,造成了今天的悲剧。

​ 紧接着,Google 联合 Red Hat、IBM 等几位巨佬连哄带骗忽悠 Docker 公司将 libcontainer 捐给中立的社区(OCI,Open Container Intiative),并改名为 runc,不留一点 Docker 公司的痕迹~~

​ 这还不够,为了彻底扭转 Docker 一家独大的局面,几位大佬又合伙成立了一个基金会叫 CNCF(Cloud Native Computing Fundation),这个名字想必大家都很熟了,我就不详细介绍了,CNCF 的目标很明确,既然在当前的维度上干不过 Docker,干脆往上爬,升级到大规模容器编排的维度,以此来击败 Docker。

​ Docker 公司当然不甘示弱,搬出了 Swarm 和 Kubernetes 进行 PK,最后的结局大家都知道了,Swarm 战败,然后 Docker 公司耍了个小聪明,将自己的核心依赖 Containerd 捐给了 CNCF,以此来标榜 Docker 是一个 PaaS 平台。

​ 很明显,这个小聪明又大大加速了自己的灭亡。

​ 巨佬们心想,想当初想和你合作搞个中立的核心运行时,你死要面子活受罪,就是不同意,好家伙,现在自己搞了一个,还捐出来了,这是什么操作?也罢,这倒省事了,我就直接拿 Containerd 来做文章吧。

​ 首先呢,为了表示 Kubernetes 的中立性,当然要搞个标准化的容器运行时接口,只要适配了这个接口的容器运行时,都可以和我一起玩耍哦,第一个支持这个接口的当然就是 Containerd 啦,至于这个接口的名字,大家应该都知道了,它叫 CRI(Container Runntime Interface)。

​ 这样还不行,为了蛊惑 Docker 公司,Kubernetes 暂时先委屈自己,专门在自己的组件中集成了一个 shim(你可以理解为垫片),用来将 CRI 的调用翻译成 Docker 的 API,让 Docker 也能和自己愉快地玩耍,温水煮青蛙,养肥了再杀。。。

img

​ 作为容器云平台的事实标准,如今已被广泛使用,俨然已成为大厂标配。Kubernetes原生支持Docker,让Docker的市场占有率一直居高不下,如图是2019年容器运行时的市场占有率。

img

​ 就这样,Kubernetes 一边假装和 Docker 愉快玩耍,一边背地里不断优化 Containerd 的健壮性以及和 CRI 对接的丝滑性,现在 Containerd 的翅膀已经完全硬了,是时候卸下我的伪装,和 Docker say bye bye 了,后面的事情大家也都知道了~~

​ 但在2020年,Kubernetes突然宣布在1.20版本以后,也就是2021年以后,不再支持Docker作为默认的容器运行时,将在代码主干中去除dockershim

img

​ 如图所示,K8s自身定义了标准的容器运行时接口CRI(Container Runtime Interface),目的是能对接任何实现了CRI接口的容器运行时,在初期,Docker是容器运行时不容置疑的王者,K8s便内置了对Docker的支持,通过dockershim来实现标准CRI接口到Docker接口的适配,以此获得更多的用户。

​ 随着开源的容器运行时Containerd(实现了CRI接口,同样由Docker捐给CNCF)的成熟,K8s不再维护dockershim,仅负责维护标准的CRI,解除与某特定容器运行时的绑定,当然,也不是K8s不支持Docker了,只是dockershim谁维护的问题, 随着K8s态度的变化,预计将会有越来越多的开发者选择直接与开源的Containerd对接,Docker公司和Docker开源项目(现已改名为moby)未来将会发生什么样的变化,谁也说不好。

​ 讲到这里,不知道大家有没有注意到,Docker公司其实是捐献了Containerd和runC,这俩到底是啥东西,简单的说,runC是OCI标准的实现,也叫OCI运行时,是真正负责操作容器的,Containerd对外提供接口,管理、控制着runC,所以上面的图,真正应该长这样。

img

​ Docker公司是一个典型的小公司因一个爆款项目火起来的案例,不管是技术层面、公司经营层面以及如何跟大厂缠斗,不管是好的方面还是坏的方面,都值得我们去学习和了解其背后的故事,Docker 这门技术成功了,Docker 这个公司却失败了。

Containerd 架构

时至今日,Containerd 已经变成一个工业级的容器运行时了,连口号都有了:超简单!超健壮!可移植性超强!

​ 当然,为了让 Docker 以为自己不会抢饭碗,Containerd 声称自己的设计目的主要是为了嵌入到一个更大的系统中(暗指 Kubernetes),而不是直接由开发人员或终端用户使用。

​ 事实上呢,Containerd 现在基本上啥都能干了,开发人员或者终端用户可以在宿主机中管理完整的容器生命周期,包括容器镜像的传输和存储、容器的执行和管理、存储和网络等,大家可以考虑学起来了。

先来看看 Containerd 的架构

img

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

Containerd 组成

为了解耦,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 等。 总之,万物皆插件,插件就是模块,模块就是插件。

img

常见插件介绍

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

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

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

img

性能对比

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

img

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

和Docker的对比

下面来讲讲 docker 与 Containerd 使用有那些方面不同。

调用链比较

选择 containerd作为运行时的组件,它调用链更短,组件更少,更稳定,占用节点资源更少

img

命令比较

命令 Docker Containerd Containerd
docker crictl(推荐) crictl(推荐) ctr
查看容器列表 docker ps crictl ps ctr -n k8s.io c ls
查看容器详情 docker inspect crictl inspect ctr -n k8s.io c info
查看容器日志 docker logs crictl logs
容器内执行命令 docker exec crictl exec
挂载容器 docker attach crictl attach
显示容器资源使用情况 docker stats crictl stats
创建容器 docker create crictl create ctr -n k8s.io c create
启动容器 docker start crictl start ctr -n k8s.io run
停止容器 docker stop crictl stop
删除容器 docker rm crictl rm ctr -n k8s.io c del
查看镜像列表 docker images crictl images ctr -n k8s.io i ls
查看镜像详情 docker inspect crictl inspecti
拉取镜像 docker pull crictl pull ctr -n k8s.io i pull
推送镜像 docker push ctr -n k8s.io i push
删除镜像 docker rmi crictl rmi ctr -n k8s.io i rm
查看Pod列表 crictl pods
查看Pod详情 crictl inspectp
启动Pod crictl runp
停止Pod crictl stopp

日志相关配置

功能 Docker Containerd
存储路径 如果 Docker 作为 K8S 容器运行时,容器日志的落盘将由 docker 来完成,保存在类似/var/lib/docker/containers/$CONTAINERID 目录下。Kubelet 会在 /var/log/pods 和 /var/log/containers 下面建立软链接,指向 /var/lib/docker/containers/$CONTAINERID 该目录下的容器日志文件。 如果 Containerd 作为 K8S 容器运行时, 容器日志的落盘由 Kubelet 来完成,保存至 /var/log/pods/$CONTAINER_NAME 目录下,同时在 /var/log/containers 目录下创建软链接,指向日志文件。
配置参数 在 docker 配置文件中指定:”log-driver”: “json-file”, “log-opts”: {“max-size”: “100m”,”max-file”: “5”} 方法一:在 kubelet 参数中指定:–container-log-max-files=5 –container-log-max-size=”100Mi” ;方法二:在 KubeletConfiguration 中指定:”containerLogMaxSize”: “100Mi”, “containerLogMaxFiles”: 5
容器日志保存到数据盘 把数据盘挂载到 “data-root”(缺省是 /var/lib/docker)即可。 创建一个软链接 /var/log/pods 指向数据盘挂载点下的某个目录 或者 通过挂载目录,把 /var/log/pods 目录挂载到数据盘上。

网络相关配置

功能 Docker Containerd
谁负责调用 CNI Kubelet 内部的 docker-shim Containerd 内置的 cri-plugin(containerd 1.1 以后)
如何配置 CNI Kubelet 参数 –cni-bin-dir 和 –cni-conf-dir Containerd 配置文件(toml):[plugins.cri.cni] bin_dir = “/opt/cni/bin” conf_dir = “/etc/cni/net.d”

Containerd 安装部署

了解了 Containerd 的概念后,就可以动手安装体验一把了

下载安装

安装libseccomp依赖包

这里我使用的系统是 CentOS 7,首先需要安装 libseccomp 依赖,注意需要使用root用户安装

1
yum install -y libseccomp
下载containerd软件包并解压

由于 containerd 需要调用 runc,所以我们也需要先安装 runc

下文压缩包

由于 containerd 提供了一个包含相关依赖的压缩包 cri-containerd-cni-${VERSION}.${OS}-${ARCH}.tar.gz,可以直接使用这个包来进行安装,首先从 release 页面下载最新版本的压缩包,当前为 1.6.5 版本

image-20220708183355969

因为下载速度慢,可以从镜像地址下载

1
wget https://download.fastgit.org/containerd/containerd/releases/download/v1.6.6/cri-containerd-cni-1.6.6-linux-amd64.tar.gz
解压文件

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

1
tar -C / -xzf cri-containerd-cni-1.6.6-linux-amd64.tar.gz
配置环境变量

需要记得将 /usr/local/bin/usr/local/sbin 追加到 ~/.bashrc 文件的 PATH 环境变量中

1
2
echo "export PATH=$PATH:/usr/local/bin:/usr/local/sbin" >> ~/.bashrc
source ~/.bashrc
配置Containerd
生成默认配置

containerd 的默认配置文件为 /etc/containerd/config.toml,我们可以通过如下所示的命令生成一个默认的配置

1
2
mkdir -p /etc/containerd
containerd config default > /etc/containerd/config.toml
启动containerd服务

现在我们就可以启动 containerd 了,直接执行下面的命令即可:

1
systemctl enable containerd --now
验证

启动完成后就可以使用 containerd 的本地 CLI 工具 ctr 了,比如查看版本

1
ctr version

配置镜像加速器地址

我们可以通过以下命令来查看下上面默认生成的配置文件 /etc/containerd/config.toml

1
cat /etc/containerd/config.toml

​ 这个配置文件比较复杂,我们可以将重点放在其中的 plugins 配置上面,仔细观察我们可以发现每一个顶级配置块的命名都是 plugins.”io.containerd.xxx.vx.xxx” 这种形式,每一个顶级配置块都表示一个插件,其中 io.containerd.xxx.vx 表示插件的类型,vx 后面的 xxx 表示插件的 ID。

查看插件列表

我们可以通过 ctr 查看插件列表

1
ctr plugin ls

​ 顶级配置块下面的子配置块表示该插件的各种配置,比如 cri 插件下面就分为 containerd、cni 和 registry 的配置,而 containerd 下面又可以配置各种 runtime,还可以配置默认的 runtime。

配置镜像加速

比如现在我们要为镜像配置一个加速器,那么就需要在 cri 配置块下面的 registry 配置块下面进行配置 registry.mirrors,可以通过搜索registry.mirrors快速定位

1
vi /etc/containerd/config.toml

在这个操作需要在plugins."io.containerd.grpc.v1.cri".registry.mirrors下面新增仓库镜像

1
2
3
4
5
6
7
[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://kvuwuws2.mirror.aliyuncs.com"]
[plugins."io.containerd.grpc.v1.cri".registry.mirrors."k8s.gcr.io"]
endpoint = ["https://registry.aliyuncs.com/k8sxio"]
参数解释
  • registry.mirrors.”xxx”: 表示需要配置 mirror 的镜像仓库,例如 registry.mirrors.”docker.io” 表示配置 docker.io 的 mirror
  • endpoint:表示提供 mirror 的镜像加速服务,比如我们可以注册一个阿里云的镜像服务来作为 docker.io 的 mirror
重启服务
1
service containerd restart

评论