📖目录
- [1. 前言:你有没有想过,Pod 到底是怎么"跑起来"的?](#1. 前言:你有没有想过,Pod 到底是怎么“跑起来”的?)
- [2. 先上专业定义,再用大白话翻译](#2. 先上专业定义,再用大白话翻译)
-
- [2.1 专业定义(来自官方)](#2.1 专业定义(来自官方))
- [2.2 大白话翻译](#2.2 大白话翻译)
- [3. 架构图解:CRI + containerd + OCI 的协作流程](#3. 架构图解:CRI + containerd + OCI 的协作流程)
-
- [3.1 整体调用链路(高层视角)](#3.1 整体调用链路(高层视角))
- [3.2 containerd 内部模块分解](#3.2 containerd 内部模块分解)
- [3.3 从 Pod 到进程的完整路径(带数据流)](#3.3 从 Pod 到进程的完整路径(带数据流))
- [4. 代码示例:CRI 接口长什么样?](#4. 代码示例:CRI 接口长什么样?)
- [5. 公式化理解:CRI + OCI = 可移植的容器抽象](#5. 公式化理解:CRI + OCI = 可移植的容器抽象)
- [6. 为什么 containerd 能成为 Kubernetes 的"默认选择"?](#6. 为什么 containerd 能成为 Kubernetes 的“默认选择”?)
-
- [6.1 技术优势](#6.1 技术优势)
- [6.2 与竞品对比](#6.2 与竞品对比)
- [7. 日常开发中的应用:如何调试 containerd?](#7. 日常开发中的应用:如何调试 containerd?)
-
- [7.1 场景:Pod 卡在 `ContainerCreating`](#7.1 场景:Pod 卡在
ContainerCreating)
- [7.1 场景:Pod 卡在 `ContainerCreating`](#7.1 场景:Pod 卡在
- [8. 结语:理解 containerd,就是理解容器的"物理落地"](#8. 结语:理解 containerd,就是理解容器的“物理落地”)
1. 前言:你有没有想过,Pod 到底是怎么"跑起来"的?
我们写一个 YAML 文件:
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 要创建容器,会调用:
proto
// 来自 runtime/v1/api.proto
service RuntimeService {
rpc RunPodSandbox(RunPodSandboxRequest) returns (RunPodSandboxResponse);
rpc CreateContainer(CreateContainerRequest) returns (CreateContainerResponse);
rpc StartContainer(StartContainerRequest) returns (StartContainerResponse);
}
而 containerd 的 CRI 插件实现了这些方法。以 CreateContainer 为例(简化版逻辑):
go
// 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 Spec runcLinux Syscall Isolated 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 工具)查看:
bash
# 列出所有 Pod
crictl pods
# 查看某个容器的日志
crictl logs <container-id>
# 拉取镜像(模拟 kubelet 行为)
crictl pull nginx:latest
# 查看容器状态
crictl inspect <container-id>
📌
crictl的配置文件通常在/etc/crictl.yaml,指向 containerd 的 socket:
yamlruntime-endpoint: unix:///run/containerd/containerd.sock
8. 结语:理解 containerd,就是理解容器的"物理落地"
如果说 Kubernetes 是云原生的"操作系统",
那么 kubelet 是内核 ,
containerd 就是驱动程序 ,
而 runc 是最终操作硬件(CPU/内存)的汇编指令。
只有打通这一整条链路,你才能真正明白:
- 为什么容器能隔离?
- 为什么镜像能跨平台?
- 为什么 K8s 能做到"一次定义,随处运行"?
下一篇文章,我们将深入 Pod 的网络模型------为什么每个 Pod 有独立 IP?Calico、Flannel、Cilium 到底做了什么?Service 的虚拟 IP 又是如何被 iptables 或 eBPF 实现的?
敬请期待!