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

📖目录

  • [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)
  • [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:

yaml 复制代码
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 实现的?

敬请期待!

相关推荐
saber_andlibert1 小时前
【docker】入门基础和镜像、容器
linux·运维·docker·容器
a***11351 小时前
使用Kubernetes部署Spring Boot项目
spring boot·容器·kubernetes
退役小学生呀1 小时前
二十五、基于ArgoCD的K8s多集群管理方案及落地
运维·云原生·容器·kubernetes·devops
leo__5201 小时前
在Kubernetes环境中引用变量的方法
云原生·容器·kubernetes
来自于狂人1 小时前
HCIE云计算考点精析
云计算
羑悻的小杀马特1 小时前
Docker Compose高手实践之路:简单拓扑、数据库代理、基于WordPress的个人博客站点一键搞定!
数据库·docker·容器·wordpress·docker compose
k***21601 小时前
使用 Docker 部署 RabbitMQ 的详细指南
docker·容器·rabbitmq
The star"'2 小时前
docker
docker·云计算
Lynnxiaowen2 小时前
今天我们开始学习Docker概述与安装
linux·学习·docker·容器·云计算