1. 容器运行时概述

容器运行时(Container Runtime)是容器化技术栈中最底层的执行引擎,负责管理容器的完整生命周期:从镜像拉取、容器创建、进程启动,到资源隔离与终止回收。理解容器运行时的定义、标准化接口以及不同层级的分工,是掌握基于 containerd 的控制面架构设计的前提。

1.1 容器运行时定义

容器运行时是一段直接与操作系统内核交互的软件组件,它利用 Linux 内核特性(如 cgroups、namespace、UnionFS)为容器进程提供隔离的执行环境。从功能边界来看,容器运行时承担以下核心职责:

  • 镜像管理:拉取、存储、解包容器镜像,将分层文件系统挂载为容器的 rootfs。
  • 容器生命周期:创建、启动、停止、删除容器进程,处理信号与退出码。
  • 资源隔离:通过 cgroups 限制 CPU、内存、IO,通过 namespace 隔离进程、网络、挂载点等视图。
  • 存储与网络:挂载数据卷、配置网络接口(通常委托给 CNI 插件)。
关键认知:容器运行时并不等同于 Docker。Docker 是一个包含 CLI、API、镜像构建、编排等功能的完整平台,而容器运行时仅负责“运行容器”这一核心动作。Kubernetes 通过 CRI(Container Runtime Interface)解耦了编排层与运行时层。

1.2 OCI 规范简介

Open Container Initiative(OCI)是 Linux 基金会旗下的开放标准组织,旨在制定容器格式和运行时的通用规范。OCI 目前维护三个核心规范:

规范名称 版本 核心内容
OCI Image Spec v1.1 定义镜像的索引、清单、层、配置的 JSON 结构与哈希验证方式
OCI Runtime Spec v1.1 定义容器运行时必须遵循的 config.json 格式、生命周期钩子、文件系统挂载标准
OCI Distribution Spec v1.1 定义镜像仓库的推送、拉取、标签、删除等 HTTP API 协议

OCI 规范的出现终结了早期容器生态的碎片化。任何符合 OCI Runtime Spec 的运行时(如 runc、crun、youki)都可以运行由任何符合 OCI Image Spec 的工具(如 BuildKit、Kaniko)构建的镜像。这种“一次构建,到处运行”的标准化能力是容器生态繁荣的基石。

实践要点:在 containerd 中,镜像拉取后会被解包为 OCI 标准的 rootfs 和 config.json,然后传递给底层的 runc 执行。containerd 本身是 OCI 规范的忠实实现者。

1.3 runc 与 containerd 的关系

runc 和 containerd 是容器运行时生态中两个不同层次的组件,它们的分工常被混淆。下面这张架构图清晰展示了二者的定位:

runc 与 containerd 在容器栈中的分层关系 Kubernetes (CRI 客户端) containerd(高级运行时 / 控制面) 镜像管理 · CRI shim · 快照器 · 元数据存储 runc(低级运行时 / 执行引擎) Linux Kernel (cgroups + namespace) containerd 通过 CRI 接收请求,管理镜像与元数据,最终调用 runc 创建容器进程

runc 是 OCI Runtime Spec 的参考实现,由 Docker 公司捐赠给 OCI 社区。它是一个轻量级的 CLI 工具,功能单一:读取 config.json 和 rootfs,通过 clone 系统调用创建容器进程,然后退出。runc 本身不管理镜像、不处理网络、不维护持久化状态。

containerd 则是一个工业级的守护进程,最初从 Docker Engine 中拆分出来,现已成为 CNCF 毕业项目。containerd 在 runc 之上提供了完整的控制面能力:

  • CRI 实现:实现了 Kubernetes CRI gRPC 接口,可以直接被 kubelet 调用。
  • 镜像管理:支持 OCI 镜像的拉取、存储、挂载,内置内容寻址存储(CAS)。
  • 快照器:管理容器文件系统的 overlayfs 快照链。
  • 任务管理:通过 shim 进程(containerd-shim-runc-v2)隔离容器进程与 containerd 守护进程的生命周期。
常见误区:很多人认为“containerd 就是容器运行时”,严格来说 containerd 是运行时管理控制面,而 runc 才是真正的容器运行时执行体。在 Kubernetes 节点上,kubelet → containerd → containerd-shim → runc 构成了完整的调用链。

1.4 容器运行时分类:低级与高级

根据功能边界和抽象层级,容器运行时通常分为两大类:

类别 代表实现 核心职责 是否直接操作内核 是否管理镜像
低级运行时 runc, crun, youki, gVisor (部分) 创建容器进程、配置 cgroups/namespace、执行 mount 是 否
高级运行时 containerd, CRI-O, Docker Engine 镜像管理、CRI 接口、存储编排、shim 生命周期 否(委托给低级运行时) 是

低级运行时直接与内核交互,负责“跑起一个进程并隔离它”。高级运行时则提供更上层的编排能力,它们通常调用低级运行时来完成实际的容器创建。这种分层设计使得 Kubernetes 可以灵活切换底层运行时——例如从 runc 切换到 gVisor 或 Kata Containers 来增强安全隔离,而无需修改上层编排逻辑。

下面这张图展示了高级运行时与低级运行时在 Kubernetes 节点上的协作关系:

高级运行时与低级运行时的协作架构 kubelet CRI containerd shim containerd-shim runc Linux Kernel 调用链说明 kubelet 通过 CRI 调用 containerd → containerd 启动 shim 进程 → shim 调用 runc 创建容器 → runc 通过 clone() 进入内核隔离

从架构图中可以清晰看到:高级运行时(containerd)负责与编排系统对接、管理镜像和元数据;低级运行时(runc)专注于执行容器进程;中间的 shim 进程则起到解耦作用——即使 containerd 守护进程重启,已运行的容器也不会受到影响。

选型建议:在生产环境中,containerd 是目前 Kubernetes 最主流的 CRI 实现(2024 年市场占有率超过 70%)。低级运行时通常默认使用 runc,但在多租户安全场景下可替换为 gVisor 或 Kata Containers,containerd 通过 RuntimeClass 机制支持这种灵活切换。