【云计算】【Kubernetes】 ③ 深入 containerd - CRI 插件如何驱动 OCI 容器? - 详解


1. 前言:你有没有想过,Pod 到底是怎么“跑起来”的?

我们写一个 YAML 文件:

apiVersion: v1
kind: Pod
spec:
containers:
- name: nginx
image: nginx:latest

然后执行 kubectl apply -f pod.yaml,几秒钟后,一个 Nginx 容器就在某个节点上运行起来了。

但你有没有想过:Kubernetes 本身并不直接创建容器!

它只是“发号施令”的指挥官,真正动手干活的是底层的容器运行时(Container Runtime)。而今天我们要聊的主角——containerd,就是目前 Kubernetes 默认且最主流的容器运行时。

更关键的是:Kubernetes 和 containerd 之间,并不直接对话。它们靠一个叫 CRI(Container Runtime Interface) 的“翻译官”来沟通。

那么问题来了:

  • CRI 到底是什么?
  • containerd 如何通过 CRI 插件响应 kubelet 的指令?
  • OCI 标准又在其中扮演什么角色?
  • 整个调用链路是如何串联起来的?

别急,今天我们就一层层剥开这个“洋葱”,用大白话讲清楚这套云原生世界的“肌肉系统”。


2. 先上专业定义,再用大白话翻译

2.1 专业定义(来自官方)

containerd is an industry-standard container runtime with an emphasis on simplicity, robustness, and portability. It is available as a daemon for Linux and Windows, which can manage the complete container lifecycle of its host system: image transfer and storage, container execution and supervision, low-level storage and network attachments, etc.

—— containerd 官网

CRI (Container Runtime Interface) is a plugin interface which enables kubelet to use a wide variety of container runtimes, without the need to recompile.

—— Kubernetes 官方文档

OCI (Open Container Initiative) defines open standards for container formats and runtime specifications, including the Image Spec and Runtime Spec.


2.2 大白话翻译

想象一下:

  • Kubernetes(kubelet) 是餐厅的“店长”,只负责接单、安排座位、告诉厨房“我要一份宫保鸡丁”。
  • containerd 是“主厨”,负责真正切菜、炒菜、装盘。
  • CRI 就是店长和主厨之间的“对讲机”——店长说“上菜”,主厨听懂后开始操作。
  • OCI 则是“菜谱标准”:规定了“宫保鸡丁”必须用哪些原料、火候多少、摆盘样式。所有符合 OCI 的容器,就像按同一菜谱做的菜,可以在任何支持 OCI 的“厨房”里运行。

所以:

  • 没有 CRI,kubelet 就没法指挥 containerd
  • 没有 OCI,不同容器就无法通用
  • containerd 是那个既懂 CRI 又会做 OCI 菜的全能主厨

3. 架构图解:CRI + containerd + OCI 的协作流程

3.1 整体调用链路(高层视角)

+----------------+       gRPC (CRI)        +---------------------+
|                | -----------------------> |                     |
|   kubelet      |                         |  containerd (with   |
|                | <----------------------- |   CRI plugin)       |
+----------------+       gRPC (CRI)        +----------+----------+
                                                        |
                                                        | exec/run
                                                        v
                                               +------------------+
                                               |   runc (OCI)     |
                                               | (creates actual  |
                                               |   Linux process) |
                                               +------------------+

关键点:containerd 内置了 CRI 插件(从 v1.1 开始),所以它可以直接接收 kubelet 的 gRPC 请求。


3.2 containerd 内部模块分解

+--------------------------------------------------+
|                  containerd daemon               |
|                                                  |
|  +----------------+    +----------------------+  |
|  |   CRI Plugin   |<-->|  Container Lifecycle |  |
|  +----------------+    |  Manager (Task API)  |  |
|                        +-----------+----------+  |
|                                    |             |
|                        +-----------v----------+  |
|                        |      Image Service   |  |
|                        +-----------+----------+  |
|                                    |             |
|                        +-----------v----------+  |
|                        |       Snapshotter    |  |
|                        +-----------+----------+  |
|                                    |             |
|                        +-----------v----------+  |
|                        |        runc          | <-- OCI Runtime
|                        +----------------------+  |
+--------------------------------------------------+

说明

  • CRI Plugin:暴露 /run/containerd/containerd.sock,供 kubelet 调用。
  • Task API:管理容器的生命周期(start/pause/kill)。
  • Snapshotter:基于 overlayfs 等实现镜像分层(类似 Docker 的 layer)。
  • runc:真正的 OCI 运行时,调用 clone()mount() 等系统调用创建容器进程。

3.3 从 Pod 到进程的完整路径(带数据流)

kubectl apply → API Server → etcd → Scheduler → kubelet
                                                      ↓
                                              CRI.RunPodSandbox()
                                                      ↓
                                          containerd.CRIPlugin
                                                      ↓
                                         Create sandbox (pause container)
                                                      ↓
                                           CRI.CreateContainer()
                                                      ↓
                                      containerd pulls image (if needed)
                                                      ↓
                                       containerd creates task via runc
                                                      ↓
                                     runc executes OCI bundle → fork() + exec()
                                                      ↓
                                            Actual container process!

4. 代码示例:CRI 接口长什么样?

CRI 是一套 gRPC 接口,定义在 kubernetes/cri-api 中。

比如,kubelet 要创建容器,会调用:

// 来自 runtime/v1/api.proto
service RuntimeService {
  rpc RunPodSandbox(RunPodSandboxRequest) returns (RunPodSandboxResponse);
  rpc CreateContainer(CreateContainerRequest) returns (CreateContainerResponse);
  rpc StartContainer(StartContainerRequest) returns (StartContainerResponse);
}

而 containerd 的 CRI 插件实现了这些方法。以 CreateContainer 为例(简化版逻辑):

// containerd/pkg/cri/server/container_create.go
func (c *criService) CreateContainer(ctx context.Context, r *runtime.CreateContainerRequest) (*runtime.CreateContainerResponse, error) {
// 1. 解析容器配置(来自 Pod Spec)
config := r.GetConfig()
// 2. 拉取镜像(如果本地没有)
image, err := c.ensureImageExists(ctx, config.Image.Image)
// 3. 构建 OCI spec(符合 OCI Runtime Spec)
spec, err := c.generateContainerSpec(...)
// 4. 创建 containerd container 对象
container, err := c.client.NewContainer(ctx, containerID, ...)
// 5. 返回容器 ID
return &runtime.CreateContainerResponse{ContainerId: containerID}, nil
}

⚙️ 注意:这里生成的 spec 是一个符合 OCI Runtime Spec 的 JSON 结构,最终会被 runc 使用。


5. 公式化理解:CRI + OCI = 可移植的容器抽象

我们可以用一个“公式”来理解整个体系:

Kubernetes Pod → CRI (gRPC) containerd → OCI Spec runc → Linux Syscall Isolated Process \text{Kubernetes Pod} \xrightarrow{\text{CRI (gRPC)}} \text{containerd} \xrightarrow{\text{OCI Spec}} \text{runc} \xrightarrow{\text{Linux Syscall}} \text{Isolated Process} Kubernetes PodCRI (gRPC)containerdOCI SpecruncLinux SyscallIsolated Process

其中:

  • CRI控制平面协议(命令怎么下);
  • OCI数据平面规范(容器长什么样);
  • containerd中间协调者,把 CRI 命令翻译成 OCI 操作。

这就像:

  • CRI 是“菜单订单”(我要一个辣度5的宫保鸡丁);
  • OCI 是“标准化菜谱”(鸡肉100g、花生20g、辣椒油5ml…);
  • containerd 是“厨房调度系统”,把订单转成具体操作步骤。

6. 为什么 containerd 能成为 Kubernetes 的“默认选择”?

6.1 技术优势

特性说明
轻量去掉了 Docker 的高层功能(如 build、network CLI),只保留核心运行时能力
稳定CNCF 毕业项目,被 AWS EKS、Google GKE、Azure AKS 全面采用
模块化支持插件化 snapshotter、runtime(可替换 runc 为 Kata Containers 等)
符合标准原生支持 CRI + OCI,无 vendor lock-in

6.2 与竞品对比

项目类型是否开源Kubernetes 支持备注
containerd容器运行时✅ (CNCF)✅ 默认轻量、标准、云厂商首选
Docker Engine完整平台❌(需 dockershim,已废弃)功能全但臃肿
CRI-O容器运行时✅ (CNCF)专为 K8s 设计,更精简
Podman容器引擎间接(通过 CRI-O)无 daemon,rootless 友好
NVIDIA Container Runtime专用运行时✅(通过 containerd 插件)用于 GPU 容器

小知识:containerd 最初是从 Docker 中剥离出来的核心组件。Docker ≈ containerd + 高层工具(CLI、build、compose)。K8s 只需要“跑容器”的能力,所以直接用 containerd 更高效。


7. 日常开发中的应用:如何调试 containerd?

7.1 场景:Pod 卡在 ContainerCreating

你可以直接在节点上使用 crictl(CRI 的 CLI 工具)查看:

# 列出所有 Pod
crictl pods
# 查看某个容器的日志
crictl logs <container-id>
  # 拉取镜像(模拟 kubelet 行为)
  crictl pull nginx:latest
  # 查看容器状态
  crictl inspect <container-id>

crictl 的配置文件通常在 /etc/crictl.yaml,指向 containerd 的 socket:

runtime-endpoint: unix:///run/containerd/containerd.sock

8. 结语:理解 containerd,就是理解容器的“物理落地”

如果说 Kubernetes 是云原生的“操作系统”,
那么 kubelet 是内核
containerd 就是驱动程序
runc 是最终操作硬件(CPU/内存)的汇编指令

只有打通这一整条链路,你才能真正明白:

  • 为什么容器能隔离?
  • 为什么镜像能跨平台?
  • 为什么 K8s 能做到“一次定义,随处运行”?

下一篇文章,我们将深入 Pod 的网络模型——为什么每个 Pod 有独立 IP?Calico、Flannel、Cilium 到底做了什么?Service 的虚拟 IP 又是如何被 iptables 或 eBPF 实现的?

敬请期待!

posted @ 2026-01-02 16:45  clnchanpin  阅读(7)  评论(0)    收藏  举报