目录
一、理论
1.CRI
(1)概念
Kubernetes Node (kubelet) 的主要功能就是启动和停止容器的组件,我们 称之为容器运行时( Container Runtime) ,其中最知名的就是 Docker 。为了 更具扩展性, Kubernetes 1.5 版本开始就加入了容器运行时插件 API, Container Runtime Interface, 简称 CRI。
每个容器运行时都有特点,因此不少用户希望 Kubernetes 能够支持更多的容器运行时。Kubernetes从 1.5 版本开始引入了 CRI 接口规范,通过插件接口模式,Kubernetes 无须重新编译就可以使用更多的容器运行时。
(2)主要组件
CRI 包含的组件有 Protocol Buffers、gRPC API、运行库支持及开发中的标准规范和工具。
Docker 的 CRI 实现在 Kubernetes1.6 中被更新为 Beta 版本,并在 kubelet启动时默认启动。
CRI组件(cubelet)主要实现的功能 :
1)镜像管理:镜像拉取、查看和移除
2)pod和容器的生命周期管理,以及与容器的交互(exec/attach/port-forward)
kubelet 使用 gRPC 框架通过 UNIX Socket 与容器运行时 (或 RI 代理 )进行通信。在 这个过程中 kubelet 是客户端, CRI 代理 (shim) 是服务端,如图 所示:
Protocol Buffers API包含两个gRPC服务:ImageService和 RuntimeServiceoImageService提供了从仓库中拉取镜像、查看和移除镜像的功能。
1.镜像管理:镜像拉取、查看和移除
2.pod和容器的生命周期管理,以及与容器的交互(exec/attach/port-forward)
ImageService提供了从仓库中拉取镜像、查看和移除镜像的功能。
RuntimeService负责Pod和容器的生命周期管理,以及与容器的交互( exec/attach/port-forward )。rkt 和 Docker这样的容器运行时可以使用一个Socket同时提供两个服务,在kubelet中可以用--container-runtime-endpoint和--image-service-endpoint参数设置这个Socket。
(3)Pod 和容器的生命周期管理
Pod 由一组应用容器组成,其中包含共有的环境和资源约束,在 CRI 里,这个环境被称为 PodSandbox。
Kubernetes有意为容器运行时留下一些发挥空间,它们可以根据自己的内部实现来解释 PodSandbox。
对于 Hypervisor 类的运行时,PodSandbox 会具体化为一个虚拟机。
其他例如 Docker,会是一个 Linux 命名空间。在 vialpha1 API 中,kubelet 会创建 Pod 级别的 cgroup 传递给容器运行时,并以此运行所有进程来满足 PodSandbox对 Pod 的资源保障。
在启动 Pod 之前,kubelet调用 RuntimeService RunPodSandbox来创建环境。这一过程包括为 Pod 设置网络资源(分配IP等操作)。PodSandbox 被激活之后,就可以独立地创建、启动、停止和删除不同的容器了。kubelet 会在停止和删除 PodSandbox 之前首先停止和删除其中的容器。
kubelet 的职责在于通过 RPC 管理容器的生命周期,实现容器生命周期的钩子、存活和健康监测,以及执行 Pod 的重启策略等。
(4)面向容器级别的设计思路
CRI 机制能够发挥作用的核心,在于每一个容器项目现在都可以自己实现一个 CRI shim ,自行对 CRI 请求进行处理.这样, Kubernetes 就有了一个统一的容器抽象层,使得下层容器在运行的时候,可以自由地对接,从而进入 Kubernetes 当中去。
从kubernetes 架构图来分析CRI如何工作:
作为一个 CRI shim , container 对 CRI 的具体实现, CRI 接口的定义如下:
CRI 待实现接口,主要有两类::
RuntimeService .它提供的接口,主要就是和容器有关的操作.比如,创建和启动容器,删除容器,执行 exec 命令等.
ImageService .它提供的接口,主要是和容器镜像相关的操作,比如 拉取镜像,删除镜像等等.
CRI 设计的一个重要原则,就是确保这个接口本身,只关注容器,不关注 Pod(与pod相关的是SandBox(沙箱)相关API,沙箱即容器本身,是容器而非pod的API,之所以叫PodSandbox是因为创建的沙箱(容器)用于部署pod ).之所以这样涉及,原因有两个:
第一, Pod 是 Kubernetes 的编排概念,而不是容器运行时的概念,所以就不能假设所有的下层容器项目,都能够暴露出可以直接映射为 Pod 的 API .
第二,如果 CRI 里面引入了 Pod 的概念,那么只要 Pod API 对象的字段发生变化,那么 CRI 就很有可能需要变更。
基于以上原因, CRI 设计中,并没有一个直接创建 Pod 或者启动 Pod 的接口.
(5)Streaming API实现exec、log等功能
CRI shim 还有一个重要的工作,就是如何实现 exec , logs 等接口.这些接口不同在于, gRPC 接口调用期间, kubelet 需要和容器项目维护一个长连接来传输数据.这种 API ,就称之为 Streaming API 。
CRI shim 中对 Streaming API 的实现,依赖于一套独立的 Streaming Server 机制,如下:
当对一个容器执行 kubectl exec 命令时,这个请求首先会交给 API Server , 然后 API Server 就会调用 kubelet 的 Exec API .此时, kubelet 会调用 CRI 的 Exec 接口,而负责响应这个接口的,就是 CRI shim .但是在这一步, CRI shim 并不会直接去调用后端的容器项目(比如 Docker )来进行处理,而只会返回一个 URL 给 kubelet .这个 URL ,就是该 CRI shim 对应的 Streaming Server 的地址和端口。 kubelet 在拿到这个 URL 之后,就会把它以 Redirect 的方式返回给 API Server .所以这个时候, API Server 就会通过重定向来向 Streaming Server 发起真正的 /exec 请求,和它建立长连接.
Stream Server 这一部分具体怎么实现,完全可以由 CRI shim 的维护者自行决定。
所以, CRI 这个接口的设计,实际上还是比较宽松的.这就意味着,作为容器项目的维护者,在实现 CRI 的具体接口时,往往拥有着很高的自由度,这个自由度不仅包括了容器的生命周期管理,也包括了如何将 Pod 映射成自己的实现,还包括了如何调用 CNI 插件来为 Pod 设置网络的过程。
基于此,当维护者对容器这一层有特殊的需求时,优先建议考虑实现一个自己的 CRI shim 。
2.容器运行时层级
Container Runtime 分为高低两个层级。
(1) 高层级运行时
Dockershim、containerd 和 CRI-O 都是遵循 CRI 的容器运行时,属于高层级运行时,主要是面向外部提供 gRPC 调用。
注意这里是 Dockershim,并不是 Docker,Docker 至今也没有遵循 CRI。
OCI
OCI(OPen Container Initiative)定义了创建容器的格式和运行时的开源行业标准,包括镜像规范和运行时规范。
高层级运行时会下载一个 OCI 镜像,并把它解压成 OCI 运行时文件系统包(filesystem bundle)。
(2)低层级运行时
低层级运行时定义如何为新容器设置 Linux namespaces 和 cgroups,以及 rootfs 等操作, runC 就是具体的参考实现。除了 runC 外,还有很多其他的运行时遵循 OCI 标准,例如 kata 以及 gVisor。
3.容器运行时比较
Docker 的多层封装和调用,导致其在可维护性上略逊一筹。containerd 和 CRI-O 的方案比 Docker 简洁很多。
dockershim 遵循 CRI,并把请求转为 dockerd 可处理的请求,其代码集成在 kubelet 中,这也是 k8s 急于摆脱 Docker 的原因之一。
真正的启动容器是通过 containerd-shim 去调用 runC 来启动容器的,runC 启动完成后会直接退出,containerd-shim 会成为容器进程的父进程,负责收集容器进程的状态,上报给 containerd,并在容器中 pid 为 1 的进程退出后接管容器中的子进程,确保不会出现僵尸进程。同时也避免了宿主机上 containerd 进程挂掉的话,所有容器进程都退出。
(1) containerd 和 Docker 细节差异
Docker 作为容器运行时,k8s 其实根本没有使用 docker 本身的存储、网络等功能,只是用了 Docker 的 Image 功能,来满足 CRI 中的镜像服务。
(2)containerd 和 CRI-O
CRI-O是由红帽发起并开源的一款容器运行时,本身比较新,没有太多的生产实践。而且在社区的测试结果中,在操作容器方面的性能以及延时都没有 containerd 优秀。
二、总结
CRI组件(cubelet)主要实现的功能 :
1)镜像管理:镜像拉取、查看和移除
2)pod和容器的生命周期管理,以及与容器的交互(exec/attach/port-forward)
runtime容器运行时:
1)高层级运行时(Dockershim、containerd 和 CRI-O ),主要是面向外部提供 gRPC 调用。
2)低层级运行时(runC、kata和gVisor ),定义如何为新容器设置 Linux namespaces 和 cgroups,以及 rootfs 等操作。
CRI 设计的一个重要原则:
确保这个接口本身,只关注容器,不关注 Pod(与pod相关的是SandBox(沙箱)相关API,沙箱即容器本身,是容器而非pod的API,之所以叫PodSandbox是因为创建的沙箱(容器)用于部署pod )。