「Kubernetes」- 容器运行 | Container Runtime | 概念 | 术语

kubelet, container runtime, runc

K8s 网络之深入理解 CNI

K8s 已经在很早前的 1.20 版发布之前就宣布要移除内嵌在 kubelet 中的 docker shim 的代码,大概原因就是谷歌一直在 k8s 中使用一套叫做 CRI(container runtime interface)的规范,该规范旨在定义 k8s 如何更好地操作容器技术,该规范大概分为三部分:CRI Client,CRI Server,OCI Runtime;

简单来讲,就是在 kubelet 中放一个 grpc 的客户端,这个客户端要和一个 grpc 的服务端进行通信,这个 grpc 的服务端里头进行容器管理(“拉起”,“销毁” 等动作)的调用,而真正执行 “拉起”,“销毁” 等动作的代码由 OCI Runtime 实现;

或者再简单点,对应到实现来说:CRI Client 端有个实现就是 kubelet 程序;CRI Server 端有个实现叫 Containerd 程序;OCI Runtime 有好多实现其中有个叫 runc 程序;

然后,把他们串起来就是:
1)kubelet 在做完了一些准入校验操作之后(例如 CSI 的存储卷挂载等等),要去拉起一个 pod 了,在拉起 pod 的时候,就先启动一个 grpc 的客户端,然后与 Containerd 中的 grpc 的服务端通信,告诉它说要拉起一个 pod 了;
2)然后 containerd 收到后会按照一定的流程去“拉镜像”,“创建 sandbox”,“创建 netns”,“启动容器”,“创建容器网络”,“把容器加入到 sandbox” 中等;
3)containerd 基本上只负责调用(高级运行时),真正实现这些功能的地方在 OCI 的 runc(或其他低级运行时)中,有点像是通常服务端的 controller 和 service;

Container Runtime

Container Runtime(容器运行时)是运⾏于 Kubernetes Cluster 每个节点中的服务,负责容器的整个⽣命周期;

随着容器云的发展,涌现了很多容器运⾏时。Docker 就⽬前来说是应⽤最为⼴泛的;

CRI | Container Runtime Interface

Google 为了将 kubelet 与特定的 Container Runtime 解耦(比如底层不仅只能使用 Docker 服务),于是推出 CRI(容器运⾏时接⼝,Container Runtime Interface),其是 Kubernetes 定义的⼀组 gRPC 服务;

kubelet 作为 Client,基于 gRPC 框架,通过 Socket 来与 Container Runtime 通信:

kubelet <=== ( CRI gRPC ) ===> Container Runtime

CRI 包括两类服务:
1)Image Service(镜像服务):提供下载、检查、删除镜像的远程程序调⽤;
2)Runtime Service(运⾏时服务):负责管理容器的⽣命周期、和容器交互的调⽤(exec/attach/port-forward);

OCI | Open Container Initiative

OCI(OPen Container Initiative)定义了创建容器的格式和运⾏时的开源⾏业标准,包括镜像规范和运⾏时规范;

容器运行时层级

Container Runtime 分为两个层级:

1)⾼层级运⾏时:主要是⾯向外部提供 gRPC 调⽤;
Dockershim、containerd、CRI-O 都是遵循 CRI 的容器运⾏时,属于⾼层级运⾏时;
⾼层级运⾏时会下载⼀个 OCI 镜像,并把它解压成 OCI 运⾏时⽂件系统包(filesystem bundle);
注意 Dockershim 并不是 Docker(Docker ⾄今也没有遵循 CRI 规范);

2)低层级运⾏时:低层级运⾏时定义如何为新容器设置 Linux namespaces、cgroups、rootfs 等操作
runC、kata、gVisor 是具体的参考实现,还有等等,这些运⾏时都遵循 OCI 标准;

容器运行时比较

Docker vs. containerd vs. CRI-O

Docker 的多层封装和调⽤,导致其在可维护性上略逊⼀筹,而 containerd 和 CRI-O 的⽅案⽐ Docker 简洁很多;

dockershim 遵循 CRI,并把请求转为 dockerd 可处理的请求,其代码集成在 kubelet 中,这也是 k8s 急于摆脱 Docker 的原因之⼀;

真正的启动容器是通过 containerd-shim 去调⽤ runC 来启动容器的,runC 启动完成后会直接退出,containerd-shim 会成为容器进程的⽗进程,负责收集容器进程的状态,上报给 containerd,并在容器中 pid=1 的进程退出后接管容器中的⼦进程,确保不会出现僵⼫进程。同时也避免了宿主机上 containerd 进程挂掉的话,所有容器进程都退出;

containerd 和 Docker 细节差异

Docker 作为容器运⾏时,k8s 其实根本没有使⽤ docker 本身的存储、⽹络等功能,只是⽤了 Docker 的 Image 功能,来满⾜ CRI 中的镜像服务;

containerd 和 CRI-O

CRI-O 是由红帽发起并开源的⼀款容器运⾏时,本身⽐较新,没有太多的⽣产实践。⽽且在社区的测试结果中,在操作容器⽅⾯的性能以及延时都没有 containerd 优秀