Kubernetes 容器运行时接口 (CRI) 详解

Kubernetes 作为业界领先的容器编排平台,其核心组件 Kubelet 需要与节点上的容器运行时进行交互,以管理 Pod 和容器的生命周期。为了实现 Kubernetes 与不同容器运行时的解耦,社区定义了容器运行时接口(Container Runtime Interface, CRI)。本文将深入探讨 CRI 的概念、容器运行时的分层架构、OCI 标准以及主流容器运行时的对比。

1. 为什么需要 CRI?

在 CRI 标准出现之前,Kubernetes 代码直接与 Docker Engine 交互。这种紧耦合的方式存在以下问题:

  • 限制了运行时的选择: Kubernetes 难以支持 Docker 之外的其他容器运行时。
  • 增加了维护成本: Kubernetes 核心代码需要维护与特定运行时相关的逻辑。
  • 阻碍了创新: 新的容器运行时技术难以快速集成到 Kubernetes 生态中。

为了解决这些问题,Kubernetes 引入了 CRI。CRI 定义了一套标准的 gRPC 接口,Kubelet 通过这套接口与任何实现了 CRI 的容器运行时进行通信,从而实现了 Kubernetes 平台与具体容器运行时实现的解耦。

2. CRI (Container Runtime Interface) 定义

CRI 本质上是 Kubernetes 定义的一组 gRPC API 规范。它包含两个主要的服务:

  • ImageService: 负责管理容器镜像,如拉取、查看和删除镜像。
  • RuntimeService: 负责管理 Pod 和容器的生命周期,如创建、启动、停止和删除 PodSandbox(Pod 的隔离环境)和容器。

Kubelet 作为 Kubernetes 在每个 Node 上的代理,充当 CRI 的客户端。它根据 API Server 的指令,调用 CRI 接口,要求容器运行时执行相应的操作(例如,为新的 Pod 创建 PodSandbox,拉取所需的镜像,然后在 PodSandbox 内创建并启动容器)。

图 1: Kubelet 通过 CRI 与容器运行时交互

3. 容器运行时详解

容器运行时是实际负责运行容器的软件。为了更好地理解其工作方式,通常将其分为两个层级:高级运行时和低级运行时。

3.1 高级运行时 (High-level Runtime)

  • 职责: 主要负责容器镜像的管理(下载、存储、分发)、容器生命周期的管理(通过调用低级运行时),以及实现 CRI 接口与 Kubelet 通信。它们提供了一个更上层的抽象,隐藏了与操作系统内核交互的细节。
  • 代表: containerdCRI-O。(注意:Docker Engine 本身包含高级运行时功能,但在 Kubernetes CRI 场景下,通常通过 dockershim(已废弃)或直接使用 containerd)。
  • 工作流程(以 containerd 为例):
    1. Kubelet 通过 CRI gRPC 接口向 containerd 发送请求(例如,RunPodSandbox)。
    2. containerd 接收请求,进行必要的准备工作(如创建网络命名空间等)。
    3. 如果需要创建容器(CreateContainer 请求),containerd 会确保镜像存在(调用 ImageService 拉取),然后准备容器的根文件系统(rootfs)和 OCI 运行时规范文件(config.json)。
    4. containerd 调用低级运行时(如 runC)来创建和运行容器进程。
    5. containerd 持续监控容器状态,并通过 CRI 接口向 Kubelet 汇报。

3.2 低级运行时 (Low-level Runtime)

  • 职责: 直接与操作系统内核交互,负责创建和管理容器的隔离环境。它专注于容器进程的实际运行,不关心镜像管理或 API 交互。
  • 标准: 遵循 OCI(Open Container Initiative)运行时规范。该规范定义了容器配置(config.json)和状态,以及低级运行时需要实现的命令行接口(如 create, start, kill, delete 等)。
  • 代表: runC(最常用)、crunkata-runtime(用于 Kata Containers 安全容器)等。
  • 工作流程(以 runC 为例):
    1. 接收来自高级运行时的指令,通常包括容器的根文件系统路径和 OCI 配置文件(config.json)。
    2. 根据 config.json 中的定义,利用 Linux 内核特性创建容器的隔离环境:
      • 命名空间 (Namespaces): PID, Net, IPC, Mount, UTS, User 等,隔离进程视图、网络、进程间通信、挂载点、主机名和用户。
      • 控制组 (Cgroups): 限制容器可使用的资源(CPU、内存、磁盘 I/O 等)。
      • 文件系统: 设置容器的根文件系统(rootfs),通常使用 chrootpivot_root
      • 能力 (Capabilities): 限制容器内进程的特权。
      • Seccomp/AppArmor: 应用安全策略。
    3. 在创建好的隔离环境中启动容器指定的初始进程。
    4. 将容器进程的 PID 等信息返回给高级运行时。

4. OCI (Open Container Initiative)

OCI 是一个旨在制定容器格式和运行时开放标准的组织,由 Linux 基金会托管。它的目标是促进容器生态系统的互操作性,避免厂商锁定。OCI 定义了两个核心规范:

  • 镜像规范 (Image Specification): 定义了容器镜像的格式,包括镜像层、manifest 文件和配置文件的结构。这使得符合 OCI 标准的镜像可以在不同的容器引擎和运行时之间共享和使用。
  • 运行时规范 (Runtime Specification): 定义了容器的配置 (config.json)、执行环境和生命周期管理。它规定了如何从一个 OCI 文件系统包(filesystem bundle,包含 rootfs 和 config.json)运行一个容器。低级运行时(如 runC)是该规范的具体实现。

CRI 和 OCI 是相辅相成的:CRI 定义了 Kubelet 与高级运行时之间的接口,而 OCI 定义了高级运行时与低级运行时之间的接口以及容器镜像的格式。

5. 主流 CRI 运行时对比

目前,Kubernetes 生态中最常用的 CRI 运行时主要是 containerdCRI-O。Docker Engine 通过 dockershim 的方式已被 Kubernetes 废弃(自 v1.24 起移除)。

图 2: Docker, containerd, CRI-O 架构对比

1. 架构对比

  • Docker (via dockershim): 架构链路较长(Kubelet -> dockershim -> Docker Engine -> containerd -> runC)。dockershim 作为 Kubelet 和 Docker Engine 之间的适配层,增加了复杂性和潜在的故障点。由于 dockershim 已被移除,直接使用 Docker Engine 作为 Kubernetes 运行时的方式不再被推荐或支持。
  • Containerd: 架构相对简洁(Kubelet -> containerd (CRI Plugin) -> runC)。containerd 本身是一个专注于容器核心功能的守护进程,通过内置的 CRI 插件直接实现了 CRI 接口。它是 CNCF 的毕业项目,社区活跃,被广泛应用于生产环境,也是许多云厂商托管 Kubernetes 服务的默认运行时。
  • CRI-O: 架构最为精简(Kubelet -> CRI-O -> runC)。CRI-O 是专门为 Kubernetes 设计的轻量级 CRI 实现,其唯一目标就是满足 CRI 规范。它不包含构建镜像等额外功能,紧密跟随 Kubernetes 的发布周期。

2. 关键组件

  • dockershim: Kubernetes 为了兼容 Docker 而开发的 CRI 实现,现已废弃。
  • containerd: 包含 CRI 插件,提供完整的容器生命周期管理、镜像管理等功能。通过 containerd-shim 进程来管理具体的容器实例(runC 进程),即使 containerd 主进程重启,运行中的容器也不会受影响。
  • CRI-O: 轻量级 CRI 守护进程。使用 conmon 工具来监控每个容器,conmon 负责处理容器的日志、TTY 和退出码等,并将容器进程与 CRI-O 主进程解耦。
  • OCI Runtime (runC): 所有这三种运行时最终都依赖符合 OCI 规范的低级运行时(通常是 runC)来创建和运行容器。

3. Kubernetes 集成

  • Docker: 依赖外部的 dockershim 组件,已被移除。
  • Containerd: 通过内置的 CRI 插件原生集成。是 Kubernetes 当前推荐和广泛使用的运行时。
  • CRI-O: 原生实现 CRI 接口,与 Kubernetes 紧密集成。

4. 性能考虑

虽然架构不同,但在典型的 Pod 创建/销毁等操作上,containerdCRI-O 的性能通常优于通过 dockershim 的 Docker,因为它们的调用链更短。实际性能差异可能因具体负载和环境而异。

图 3: 不同运行时在 Pod 启动延迟等方面的性能比较(示例性,具体数据可能变化)

6. 总结

CRI 作为 Kubernetes 与容器运行时之间的标准接口,极大地促进了 Kubernetes 生态的开放性和灵活性。通过将 Kubelet 与具体的运行时实现解耦,用户可以根据需求选择最合适的容器运行时。containerdCRI-O 作为遵循 CRI 和 OCI 标准的现代容器运行时,凭借其简洁的架构、高效的性能和良好的社区支持,已成为 Kubernetes 环境下的主流选择。理解 CRI、OCI 以及不同运行时的特点,对于深入掌握 Kubernetes 的工作原理和进行技术选型至关重要。


Kubernetes 容器运行时接口 (CRI) 详解
https://mfzzf.github.io/2025/03/22/kubernetes-CRI/
作者
Mzzf
发布于
2025年3月22日
许可协议