Containerd
Containerd 是一款容器运行时,其功能是高度模块化的,启动后,主进程会在 grpc server 下注册一系列的 grpc service,如 CRIService、TasksService 等
Containerd 的具体功能基本都是通过调用这些 gRPC service 完成的,启动后会启动一个 gRPC server,默认监听位置为 /run/containerd/containerd.sock
CRI Service
kubelet 通过 CRI 调用 containerd 来创建 sandbox(pod)、container 等。这里的 CRI 指 containerd 的 CRIService 暴露出来的 gRPC 接口
Runtime
containerd 不负责创建或删除容器等操作,这些操作交给下层的 runtime 来做
创建容器的顺序为:
- containerd daemo 接收来自 client 的创建 container 的请求(可能是 kubelet 通过 cri 发起的,或者 ctr、nerdctl 这些 client 发起的)
- containerd daemon 准备容器所需的 rootfs 和 container 配置信息(OCI runtime config)
- containerd daemon 将 rootfs、配置信息发送给 runtime
- runtime 接收来自 containerd daemon 的创建容器的请求。runtime 执行系统调用,创建容器进程,并启动一个守护进程(containerd shim),暴露并监听一个 unix socket,同时将这个 socket 信息写到 stdout
- containerd daemon 从 stdout 中读到这个 unix socket 信息,并在后续通过这个 socket 与 runtime 交流,以获取 container 中的状态信息等
在具体实现中 containerd 的 runtime 分为两个部分
- runtime shim
- shim 是与 containerd daemon 直接沟通的结构。是一个二进制文件,在 v1.7 版本中为
containerd-shim-runc-v2
,containerd daemon 会直接调用这个二进制文件,创建一个 containerd shim 进程
- shim 是与 containerd daemon 直接沟通的结构。是一个二进制文件,在 v1.7 版本中为
- runtime engine
- 即我们常说的狭义上的容器运行时,如 runc。以二进制形式存在,runtime engine 是真正负责调用操作系统接口以创建、启动和删除容器的模块。它只会和 runtime shim 通信
创建 pod 流程
RunPodSandbox
- kubelet 通过内置的 CRI client 调用 containerd 的 cri service 的
RunPodSandbox
(gRPC call) - cri service 根据 gRPC Request(
RunPodSandboxRequest
)中描述创建SandboxInfo
,存储到本地的 metadata 数据库中(blot 数据库,一个本地键值对存储) - cri service 创建 network namespace,并调用 CNI 插件初始化 container network
- cri service 启动 sandbox,主要把 pause container 跑起来。这里启动 pause container 的步骤与后续启动业务 container 的步骤基本相同
CreateContainer
准备 container 的运行环境,并不会在系统中真正创建 container
- kubelet 通过其内置的 CRI Client 调用 containerd 的 cri service 的
CreateContainer
(gRPC call) - cri service 从 gRPC Request (CreateContainerRequest)的描述中,得到 sandbox id,并据此从 metadata 数据库中找到之前创建的 sandbox 对象
- cri service 根据
CreateContainerRequest
中的描述,创建并配置 Container 对象。其中重要的配置步骤包括- 根据 snapshotter 的配置,对镜像 unpack 以构建 container 的 rootfs
- 生成 oci runtime spec
- cri service 将 Container 对象写入到 metadata 数据库中
StartContainer
- kubelet 通过其内置的 CRI client 调用 containerd 的 cri service 的
startContainer
- cri service 从 gRPC Request(
StartContainerRequest
)的描述,得到 sandbox id,并据此从 metadata 数据库中找到之前创建的 sandbox 对象 - cri service 从 metadata 数据库中找到之前创建的 container 对象
- cri service 创建
Task
对象。一个运行态的 container 在 containerd 的内存中是使用Task
对象来描述的。所以。创建实际运行的 container 的过程,其实就是创建并启动Task
的过程。具体过程:- cri service 会通过 gRPC call 调用 TaskService(也是一个监听
/run/containerd/containerd.sock
的 gRPC service)尝试创建Task
对象 - TaskService 调用 PlatformRuntime(PlatformRuntime 是个接口,具体的实现是 TaskManager)创建
runtime.Task
- TaskManager 尝试启动一个 containerd shim 进程
- 如果当前要创建的 container(
Task
)属于某个已创建的 Sandbox(比如,创建业务 container 时,本质上是向之前已经创建了 pause container 的 sandbox 中添加 container,此时,已经存在当时创建 pause container 时创建的 shim 进程),那么不创建新的 shim 进程(即,同一个 Pod sandbox 下的 container 会复用同一个 shim 进程) - 否则,执行
containerd-shim-runc-v2
这个二进制,启动一个 shim 进程,该 shim 进程会启动一个 ttrpc service,并把其所监听的 unix socket 地址返回给 TaskManager
- 如果当前要创建的 container(
- TaskManager 通过 ttrpc 接口调用 shim 进程
- shim 进程调用 runc(或者其他的 OCI runtime engine)以创建容器进程
- cri service 会通过 gRPC call 调用 TaskService(也是一个监听
Snapshotter
snapshotter