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 也能和自己愉快地玩耍,温水煮青蛙,养肥了再杀。。。
作为容器云平台的事实标准,如今已被广泛使用,俨然已成为大厂标配。Kubernetes原生支持Docker,让Docker的市场占有率一直居高不下,如图是2019年容器运行时的市场占有率。
就这样,Kubernetes 一边假装和 Docker 愉快玩耍,一边背地里不断优化 Containerd 的健壮性以及和 CRI 对接的丝滑性,现在 Containerd 的翅膀已经完全硬了,是时候卸下我的伪装,和 Docker say bye bye 了,后面的事情大家也都知道了~~
但在2020年,Kubernetes突然宣布在1.20版本以后,也就是2021年以后,不再支持Docker作为默认的容器运行时,将在代码主干中去除dockershim
。
如图所示,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,所以上面的图,真正应该长这样。
Docker公司是一个典型的小公司因一个爆款项目火起来的案例,不管是技术层面、公司经营层面以及如何跟大厂缠斗,不管是好的方面还是坏的方面,都值得我们去学习和了解其背后的故事,Docker 这门技术成功了,Docker 这个公司却失败了。
Containerd 架构
时至今日,Containerd 已经变成一个工业级的容器运行时了,连口号都有了:超简单!超健壮!可移植性超强!
当然,为了让 Docker 以为自己不会抢饭碗,Containerd 声称自己的设计目的主要是为了嵌入到一个更大的系统中(暗指 Kubernetes),而不是直接由开发人员或终端用户使用。
事实上呢,Containerd 现在基本上啥都能干了,开发人员或者终端用户可以在宿主机中管理完整的容器生命周期,包括容器镜像的传输和存储、容器的执行和管理、存储和网络等,大家可以考虑学起来了。
先来看看 Containerd 的架构
可以看到 Containerd 仍然采用标准的 C/S 架构,服务端通过 GRPC
协议提供稳定的 API,客户端通过调用服务端的 API 进行高级的操作。
Containerd 组成
为了解耦,Containerd 将不同的职责划分给不同的组件,每个组件就相当于一个子系统(subsystem),连接不同子系统的组件被称为模块,总体上 Containerd 被划分为两个子系统:
- Bundle : 在 Containerd 中,
Bundle
包含了配置、元数据和根文件系统数据,你可以理解为容器的文件系统。而 Bundle 子系统允许用户从镜像中提取和打包 Bundles。 - Runtime : Runtime 子系统用来执行 Bundles,比如创建容器。
其中,每一个子系统的行为都由一个或多个模块协作完成(架构图中的 Core
部分),每一种类型的模块都以插件的形式集成到 Containerd 中,而且插件之间是相互依赖的。
例如,上图中的每一个长虚线的方框都表示一种类型的插件,包括 Service Plugin
、Metadata Plugin
、GC Plugin
、Runtime Plugin
等,其中 Service Plugin
又会依赖 Metadata Plugin、GC Plugin 和 Runtime Plugin。
每一个小方框都表示一个细分的插件,例如 Metadata Plugin
依赖 Containers Plugin、Content Plugin 等。 总之,万物皆插件,插件就是模块,模块就是插件。
常见插件介绍
这里介绍几个常用的插件:
- Content Plugin : 提供对镜像中可寻址内容的访问,所有不可变的内容都被存储在这里。
- Snapshot Plugin : 用来管理容器镜像的文件系统快照。镜像中的每一个 layer 都会被解压成文件系统快照,类似于 Docker 中的
graphdriver
。 - Metrics : 暴露各个组件的监控指标。
模块划分
从总体来看,Containerd 被分为三个大块:
Storage
、Metadata
和Runtime
,可以将上面的架构图提炼一下:
性能对比
这是使用 bucketbench对
Docker
、crio
和Containerd
的性能测试结果,包括启动、停止和删除容器,以比较它们所耗的时间:
可以看到 Containerd 在各个方面都表现良好,总体性能还是优越于 Docker
和 crio
的。
和Docker的对比
下面来讲讲 docker 与 Containerd 使用有那些方面不同。
调用链比较
选择 containerd作为运行时的组件,它调用链更短,组件更少,更稳定,占用节点资源更少
命令比较
命令 | 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 版本
因为下载速度慢,可以从镜像地址下载
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 | echo "export PATH=$PATH:/usr/local/bin:/usr/local/sbin" >> ~/.bashrc |
配置Containerd
生成默认配置
containerd 的默认配置文件为 /etc/containerd/config.toml,我们可以通过如下所示的命令生成一个默认的配置
1 | mkdir -p /etc/containerd |
启动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 | [plugins."io.containerd.grpc.v1.cri".registry] |
参数解释
- registry.mirrors.”xxx”: 表示需要配置 mirror 的镜像仓库,例如 registry.mirrors.”docker.io” 表示配置 docker.io 的 mirror
- endpoint:表示提供 mirror 的镜像加速服务,比如我们可以注册一个阿里云的镜像服务来作为 docker.io 的 mirror
重启服务
1 | service containerd restart |