前言
通过《CRI shim:kubelet怎么与容器运行时交互(一)》这一篇文章,我们知道了:
- CRI 是服务于 Kubernetes 的,而且它呈现向上汇报的状态。它是帮助 Kubernetes 的,它不帮助OCI的。所以说当你去做这个集成时候,你会发现尤其对于 VM gVisor\KataContainer 来说,它与 CRI 的很多假设或者是 API 的写法上是不对应的。所以你的集成工作会比较费劲,这是一个不 match 的状态。
- 另一个问题就是我们维护起来非常困难,因为由于有了 CRI 之后,比如 RedHat 拥有自己的 CRI 实现叫 cri-o,他们和 containerd 在本质上没有任何区别,跑到最后都是靠 runC 起容器,为什么还需要cri-o这种东西?我们不知道,如果我想使用Kata container与containerd多运行时的话,我需要给他们两个分别写两部分的一体化把 Kata 集成进去。这就很麻烦,就意味着我有 100 种这样的 CRI ,我就要写 100 个shim去集成,而且他们的功能全部都是重复的。
- 多容器运行时用于不同目的,比如使用虚拟化容器引擎式运行不可信应用和多租户应用,而使用 Docker 运行系统组件或者无法虚拟化的容器(比如需要 HostNetwork 的容器。
所以这就产生了Containerd ShimV2的这样的shim来解决这个问题。
Containerd ShimV2
2018 年,由 containerd 社区主导的 shimv2 API 的出现,在 CRI 的基础上,为用户集成自己的容器运行时带来了更加成熟和方便的实践方法。
今天给大家 propose 的这个东西叫做 Containerd ShimV2。前面我们说过 CRI,CRI 决定的是 Runtime 和 Kubernetes 之间的关系,那么我们现在能不能再有一层更细致的 API 来决定我的 CRI Shim 跟下面的 Runtime 之间真正的接口是什么样的?
这就是 ShimV2 出现的原因,它是一层 CRI shim 到 Containerd runtime 之间的标准接口,所以前面我直接从 CRI 到 Containerd 到 runC,现在不是。我们是从 CRI 到 Containerd 到 ShimV2,然后 ShimV2 再到 RunC 或者 KataContainer。这么做有什么好处?
我们来看一下,最大的区别在于:在这种方式下,你可以为每一个 Pod 指定一个 Shim。因为在最开始的时候,Containerd 是直接启动了一个 Containerd Shim 来去做响应,但我们新的 API 是这样写的,是 Containerd Shim start 或者 stop。所以这个 start 和 stop 操作怎么去实现是你要做的事情。
现在,我作为一位 Kata Containers项目的维护者我就可以这么实现。我在 created Sandbox 的时候 call 这个 start 的时候,我启动一个 Containerd Shim。
但是当我下一步是调用 API 的时候,就是在前面那个 CRI 里面,访问 Container API 时候,我就不用再启动一个连接,我是复用。我重用创建好的这个 Sandbox,这就为你的实现提供了很大的自由度。所以这时候你会发现整个实现的方式变了,这时候 Containerd 用过来之后,它不再去关心每个容器起 Containerd Shim,而是由你自己去实现。我的实现方式是我只在 Sandbox 时候,去创建 containerd-shim-v2,而接下来整个后面的 container 层的操作,会全部走到这个 containerd-shim-v2 里面,去重用这个 Sandbox,所以这个跟前面的时间就出现很大的不同。
所以你现在去总结一下这个图的话,你发现我们实现方式是变成这个样子:
首先,你还是用原来的 CRI Containerd,只不过现在装的是 runC,你现在再装一个 kata container 放在那机器上面。接下来我们 Kata 那边会给你写一个实现叫 kata-Containerd-Shimv2。
现在,我们只聚焦在怎么去把 Containerd 对接在 kata container 上面,就是所谓的实现 Shimv2 API,这是我们要做的工作。而具体到我们这要做的事情上,其实它就是这样一系列与run一个容器相关的 API。比如说我可以去 create、start,这些操作全部映射在我 Shimv2 上面去实现,而不是说我现在考虑怎么去映射,去实现 CRI,这个自由度由于之前太大,造成了我们现在的一个局面,就有一堆 CRI Shim 可以用。这其实是一个不好的事情。有很多政治原因,有很多非技术原因,这都不是我们作为技术人员应该关心的事情,你现在只需要想我怎么去跟 Shimv2 对接就好了。
给 Kubernetes 提供 kata-runtime
通过直接创建 Container 可以使用 kata-runtime 。但在集群中,我们该如何告诉 Kubernetes 哪些负载需要使用 kata-runtime 呢?根据不同的版本,Kata 提供了不同的方式。1.以 CentOS 操作系统为例,安装kata及命令工具:
$ source /etc/os-release $ yum -y install yum-utils $ ARCH=$(arch) $ BRANCH="${BRANCH:-master}" $ yum-config-manager --add-repo "http://download.opensuse.org/repositories/home:/katacontainers:/releases:/${ARCH}:/${BRANCH}/CentOS_${VERSION_ID}/home:katacontainers:releases:${ARCH}:${BRANCH}.repo" $ yum -y install kata-runtime kata-proxy kata-shim
2.首先检查Kata 对硬件的要求是否满足以下任意条件:
- Intel VT-x technology.
- ARM Hyp mode (virtualization extension).
- IBM Power Systems.
- IBM Z mainframes.
安装完 kata-runtime 之后,执行检测命令:
$ kata-runtime kata-check $ System is capable of running Kata Containers $ System can currently create Kata Containers
3.安装 Kubernetes 集群 使用 Kubeadm 安装集群非常方便,可以参考之前的文档 使用 Kubeadm 安装 Kubernetes 集群 。4.生成 containerd 配置文件
containerd config default > /etc/containerd/config.toml
- RuntimeClass 的方式
这种方式对相关组件版本有要求:
Kata Containers v1.5.0 or above (including 1.5.0-rc) Containerd v1.2.0 or above Kubernetes v1.12.0 or above
在 config.toml 配置文件中,增加如下内容:
[plugins.cri.containerd] no_pivot = false [plugins.cri.containerd.runtimes] [plugins.cri.containerd.runtimes.runc] runtime_type = "io.containerd.runc.v1" [plugins.cri.containerd.runtimes.runc.options] NoPivotRoot = false NoNewKeyring = false ShimCgroup = "" IoUid = 0 IoGid = 0 BinaryName = "runc" Root = "" CriuPath = "" SystemdCgroup = false [plugins.cri.containerd.runtimes.kata] runtime_type = "io.containerd.kata.v2" [plugins.cri.containerd.runtimes.katacli] runtime_type = "io.containerd.runc.v1" [plugins.cri.containerd.runtimes.katacli.options] NoPivotRoot = false NoNewKeyring = false ShimCgroup = "" IoUid = 0 IoGid = 0 BinaryName = "/usr/bin/kata-runtime" Root = "" CriuPath = "" SystemdCgroup = false
这里 [plugins.cri.containerd.runtimes.kata] 中的 kata 将被作为 RuntimeClass handler 关键字。
- 使用 untrusted_workload_runtime 的方式
对于不符合上述版本要求的环境,可以使用之前的方式。在配置文件中新增如下内容:
[plugins.cri.containerd.untrusted_workload_runtime] runtime_type = "io.containerd.runtime.v1.linux" runtime_engine = "/usr/bin/kata-runtime"
最后,都需要重启 containerd。
$ containerd systemctl daemon-reload $ systemctl restart containerd
5.使用 kata-runtime 方式一:RuntimeClass 方式
- 创建 RuntimeClass
kind: RuntimeClass apiVersion: node.k8s.io/v1beta1 metadata: name: kata-containers handler: kata
也可以为 runc 创建 RuntimeClass
$ kubectl get runtimeclass NAME CREATED AT kata-containers 2020-08-30
- 创建负载 kata-pod.yaml
apiVersion: v1 kind: Pod metadata: name: kata-nginx spec: runtimeClassName: kata-containers containers: - name: nginx image: nginx ports: - containerPort: 80
执行创建:
$ kubectl apply -f kata-pod.yaml
查看负载:
$ kata-runtime list
方式二:untrusted_workload_runtime 的方式 untrusted_workload_runtime 使用 annotations 告诉 Kubernetes 集群哪些负载需要使用 kata-runtime。
annotations: io.kubernetes.cri.untrusted-workload: "true"
下面是一个示例 kata-pod-untrusted.yaml
apiVersion: v1 kind: Pod metadata: name: kata-nginx-untrusted annotations: io.kubernetes.cri.untrusted-workload: "true" spec: containers: - name: nginx image: nginx ports: - containerPort: 80
执行创建:
$ kubectl apply -f kata-pod-untrusted.yaml
查看负载:
$ kata-runtime list
总结
Kubernetes 现在的核心设计思想,就是通过接口化和插件化,将原本复杂的、对主干代码有侵入性的特性,逐一从核心库中剥离和解耦。而在这个过程中,CRI 就是 Kubernetes 项目中最早完成了插件化的一个调用接口。这里主要为你介绍了在CRI基础上的另一种集成容器运行时的思路,即:CRI + containerd shimv2 的方式。
- 通过这种方式,你就不需要再为自己的容器运行时专门编写一个 CRI 实现(CRI shim),而是可以直接重用 containerd对 CRI 的支持能力,然后通过 containerd shimv2的方式来对接具体的容器运行时(比如 runc)。
- 这种集成方式已经成为了社区对接下层容器运行时的主流思路,像很多类似于 KataContainers,gVisor,Firecracker 等基于独立内核或者虚拟化的容器项目,也都开始通过 shimv2 ,进而借助 containerd项目无缝接入到 Kubernetes 当中。