经典结构图
containerd & kubernetes & runc
角色分工和层级
-
Kubernetes:部署容器的平台,属于最上层与用户交互的角色。
- 主要承担的功能:负责集群成员管理,以及自动化的 Pod 的调度维护。
-
containerd:容器平台(Docker、Kubernetes)的低一层,底层运行时(runc、kata)的高一层,属于中间负责 Pod 级别管理的角色。
- 主要承担的功能:镜像管理(镜像导入导出删除)、容器管理(容器创建删除停止),文件系统的快照管理。
-
runc:底层运行时,属于直接与底层操作系统交互的角色;类似的 runtime 还有 kata、Firecracker、gVisor 等,用于不同的操作系统平台且遵循 OCI 规范。
- 主要承担的功能:根据镜像配置,使用系统提供的 cgroups 之类的资源隔离组件,为不同的容器创建运行环境,然后调起 endpoint。
-
containerd 和 kubernetes 都通过
systemd
维护进程,二者之间通过 gRPC 进行通信(遵循 OCI 规范) -
containerd 和 runc 是直接通过进程调用的方式进行交互和绑定。大白话就是 containerd 为每个容器开了一个 shim 进程;然后 shim 进程拼了一个 runc 命令跑容器,并作为主进程接管所有的 runc 僵尸进程,然后监控容器的状态。
从 kubernetes 到 runc 的流程
- kuberlet 通过 gRPC 向 containerd 发送命令调用
- 以前是 kuberlet 调用 CRI-containerd,然后 CRI-containerd 再调用 contaienrd;后来 cri 直接作为一个插件集成进了 containerd,调用链缩短为了
kubelet -> containerd -> runc
- 源码也可以看到,cri 现在在 containerd 的内部是作为插件进行加载的。
- 以前是 kuberlet 调用 CRI-containerd,然后 CRI-containerd 再调用 contaienrd;后来 cri 直接作为一个插件集成进了 containerd,调用链缩短为了
containerd
收到请求,创建containerd-shim
实例containerd-shim
实例才会真正操作容器,负责管理一个容器的整个声明周期,并且对其状态进行监控和上报;容器进程需要一个父进程来做状态收集、维持stdin
等fd
打开等工作,假如这个父进程就是containerd
,那如果containerd
挂掉的话,整个宿主机上所有的容器都得退出了,引入containerd-shim
可以规避这个问题。
- 创建
shim
进程(通过runtime/shim
的newInit()
初始化)newInit()
中会调用process.NewRunc()
创建新的go-runc
实例,然后go-runc
去拼runc
命令并执行
runc
启动容器,然后runc
本身会直接退出,containerd-shim
则会接管容器进程,并负责上报容器进程的状态给containerd
。同时开 subReaper 在容器中pid
为1
的进程退出后接管容器中的子进程进行清理, 确保无僵尸进程。- 创建容器的 RPC 调用流程
cmd\ctr\commands\tasks\tasks_unix.go
NewTask() 调用 container.NewTask()container.NewTask()
c.client.TaskService().Create(ctx, request)- 创建一个 GRPC 客户端然后进行通信
协议规范
CRI(Container Runtime Interface 容器运行时接口)
CRI 是由 Kubernetes 定义的一套对接容器运行时的接口标准,然后 containerd(或者 dockerdhim)等容器运行时会有一套自己的实现。目前官方主流提供 Kubernetes + containerd 的方案,即 containerd 内有一个 CRI 插件实现,提供给 Kubernetes 进行调用。
可以在 Kubernetes 的配置中指定不同的 CRI 实现组件,来达到对接不同的容器运行时的目的。
OCI(Open Container Initiative 开放容器倡议)
围绕容器镜像和运行时创建指定的开放行业标准,维护容器镜像格式的规范,以及容器应该如何运行。也相当与一套标准的接口,然后不同的平台(如 Linux、Windows)上面有不同的组件实现。
CNI(Container Network Interface 容器网络接口)
由一组用于配置 Linux 容器的网络接口的规范和库组成,同时还包含了一些插件。CNI 只提供了两个接口:容器创建分配网络资源、容器删除释放网络资源。
CSI(Container Storage Interface 容器存储接口)
主要用于 Kubernetes 中,旨在能为容器编排引擎和存储系统间建立一套标准的存储调用接口,为容器编排引擎提供存储服务。
开源组件实现
containerd
开源的容器运行时之一,从 docker 中的 libcontainerd 子项目发展而来;目前是 Kubernetes 社区官方使用的 runtime。
CRI-O
开源的容器运行时之一(可能等价为另一种类似 containerd 的中间件),由 RedHat、IBM、英特尔、SUSE、Hyper 等公司从头开始创建的另一套 runtime。
runc
Linux 平台上最底层的 OCI 组件实现,用来根据镜像创建容器的运行环境并拉起容器进程,功能上与 cgroups 和 systemd 高度绑定。
其他子组件
containerd-shim
containerd-shim 是 containerd 的一个子组件。实际的运行环境中的 contaienrd 进程只是充当了一个管理角色(一个节点只有一个),而 containerd-shim 才是真正创建和操作容器的组件,每一个容器都会对应一个 containerd-shim 作为父进程。它的主要职责是做容器的状态收集、维持 stdin
、fd
等操作。
containerd-shim 有 v1 和 v2 两个版本,在实现上有着非常大的差异;目前官方版本已经在淘汰 v1 版本。
dockershim
dockershim 和 containerd-shim 承担的角色完全不同。dockershim 是 Kubernetes 为了让 docker 的接口适配 OCI 规范而构建的适配器。
目前市面上以 docker 作为 endpoint 的 Kubernetes 集群环境还有很多,所以 dockershim 相关的信息还是很常见。