containerd (管理) 和 runc (执行)分离

要理解 containerd 与 runc 的架构和功能,需先明确二者的定位:containerd 是容器运行时的 "管理层" ,负责容器生命周期的编排、镜像管理、存储与网络配置等;runc 是容器运行时的 "执行层" ,是最贴近 Linux 内核的轻量级工具,仅负责将容器 "跑起来"(即创建符合 OCI 标准的容器进程)。二者共同构成了 Docker、Kubernetes 等容器平台的核心运行时基础,且严格遵循 OCI(Open Container Initiative,开放容器倡议)标准,确保容器生态的兼容性。

一、背景与 OCI 标准:理解二者关系的前提

在容器生态早期,Docker 是 "一站式" 解决方案(包含镜像、运行时、编排等),但为了推动生态标准化,Docker 于 2015 年主导成立 OCI,并将容器运行时的核心部分拆分为:

  • OCI 镜像规范:定义容器镜像的格式(如层结构、配置文件 config.json),确保不同工具构建的镜像可互通。
  • OCI 运行时规范:定义容器的生命周期(创建、启动、停止、删除)和运行时环境(如命名空间、控制组、根文件系统),确保不同运行时工具可创建兼容的容器。

containerd 和 runc 均是 OCI 标准的实现者:

  • runc 是 OCI 运行时规范的参考实现(官方指定),是最基础的容器执行工具。
  • containerd 是更高层的运行时管理器,内部依赖 runc 完成容器的实际创建,同时扩展了镜像管理、存储、网络等能力。

二、整体架构:从 "管理层" 到 "执行层" 的分层设计

containerd 与 runc 并非平级关系,而是 "上层编排 + 下层执行" 的分层架构,中间通过 OCI 规范衔接。整体架构可拆解为 3 层,从下到上分别是:

markdown 复制代码
Linux 内核层(Namespace/Cgroups/Seccomp)
        ↓
执行层:runc(OCI 运行时规范实现,创建容器进程)
        ↓
管理层:containerd(容器生命周期、镜像、存储、网络管理)
        ↓
应用层:Docker/Kubernetes(用户交互层,调用 containerd 接口)

各层核心职责

层级 核心组件 依赖关系 核心职责
管理层 containerd 依赖 runc 执行容器 镜像拉取 / 推送、容器生命周期管理(启停 / 删除)、存储卷管理、网络配置、元数据记录
执行层 runc 依赖 Linux 内核特性 解析 OCI 配置文件、创建容器进程(配置 Namespace/Cgroups)、启动容器内初始化进程
内核层 Linux 内核 无(底层支撑) 提供容器隔离(Namespace)、资源限制(Cgroups)、安全限制(Seccomp)等基础能力

三、containerd 架构与核心功能

containerd 采用模块化设计,内部通过多个独立组件协作完成功能,核心目标是 "稳定、高效地管理容器生命周期",同时向上提供统一的 API(gRPC)供上层平台(如 Docker、K8s)调用。

1. containerd 核心子组件

containerd 的架构可拆解为以下关键子组件(从外到内):

组件 核心功能
API 层(gRPC) 对外提供标准化 gRPC API(如 ContainerService、ImageService),是上层调用的入口(如 K8s 的 cri-plugin 就是通过此 API 与 containerd 交互)。
元数据存储(Metadata Store) 存储容器、镜像、存储卷的元数据(如容器 ID、镜像哈希、创建时间),默认使用 BoltDB 数据库。
镜像管理器(Image Manager) 负责镜像的拉取(从 Registry)、推送、解压(将镜像层挂载为容器根文件系统)、镜像索引管理,遵循 OCI 镜像规范。
容器管理器(Container Manager) 管理容器的全生命周期:创建容器配置、调用 runc 执行容器、监控容器状态、处理容器停止 / 删除。
存储管理器(Storage Manager) 管理容器的根文件系统(RootFS)和存储卷(Volume),支持多种存储驱动(如 overlay2、devmapper),负责镜像层的挂载与合并。
网络管理器(Network Manager) 配置容器的网络(如创建网络命名空间、分配 IP、配置网桥),通常依赖 CNI(Container Network Interface) 插件实现(如 Calico、Flannel)。
运行时封装层(Runtime Shim) 关键组件:作为 containerd 与 runc 之间的 "中间层",负责:1. 启动 runc 进程并监控其状态;2. 转发容器的标准输入 / 输出(STDIO);3. 当 runc 退出后(创建容器后 runc 会退出), Shim 继续维持与容器进程的连接,避免 containerd 直接依赖 runc 进程。

2. containerd 核心功能详解

(1)容器生命周期管理

这是 containerd 最核心的功能,覆盖容器从 "创建" 到 "删除" 的全流程,具体步骤如下:

  1. 接收请求:通过 gRPC API 接收上层(如 K8s)的容器创建请求(包含镜像名、资源限制、网络配置等)。
  1. 准备镜像:调用镜像管理器拉取指定镜像(若本地不存在),并将镜像的多层文件系统通过存储驱动(如 overlay2)合并为容器的根文件系统(RootFS)。
  1. 生成 OCI 配置:将上层请求转换为符合 OCI 运行时规范的 config.json(包含 Namespace 配置、Cgroups 资源限制、Seccomp 安全策略、容器内命令等)。
  1. 启动 Runtime Shim:创建 containerd-shim 进程,由 Shim 负责后续与 runc 的交互。
  1. 调用 runc 创建容器:Shim 调用 runc create 命令,传入 config.json 和 RootFS 路径,由 runc 完成容器进程的创建。
  1. 启动容器:Shim 调用 runc start 命令,启动容器内的初始化进程(如 init 或用户指定的命令)。
  1. 监控与维护:Shim 持续监控容器进程状态(如 PID、退出码),并将状态同步给 containerd;若容器异常退出,根据配置执行重启策略。
  1. 删除容器:接收删除请求后,调用 runc delete 清理容器进程和资源,再通过存储管理器删除 RootFS 和元数据。

(2)镜像管理

containerd 独立完成镜像的全生命周期管理,不依赖外部工具:

  • 镜像拉取 / 推送:支持从 Docker Hub、私有 Registry 等拉取镜像,支持 HTTPS/TLS 加密,可配置镜像代理、认证信息(如用户名密码)。
  • 镜像解压与挂载:拉取的镜像为压缩格式(如 layer.tar),存储管理器会将其解压到本地存储目录(如 /var/lib/containerd/io.containerd.snapshotter.v1.overlay2/),并通过 overlay2 等驱动合并为可读写的 RootFS(镜像层为只读,容器层为可写)。
  • 镜像清理:提供 containerd images prune 命令,可清理未被容器使用的镜像,释放磁盘空间。

(3)存储与网络扩展

  • 存储扩展:支持多种存储驱动(overlay2、devmapper、zfs 等),适配不同的底层存储场景;同时支持通过 CSI(Container Storage Interface) 插件对接外部存储(如 AWS EBS、阿里云云盘),满足持久化存储需求。
  • 网络扩展:通过 CNI 插件实现网络配置,支持桥接(bridge)、host 网络、overlay 网络等模式,可无缝集成 K8s 的网络插件(如 Calico、Flannel),实现容器跨节点通信。

四、runc 架构与核心功能

runc 是 OCI 运行时规范的参考实现,是一个轻量级的命令行工具(无后台进程),仅负责 "执行容器" 这一单一任务 ------ 即根据 OCI 配置文件,调用 Linux 内核接口创建容器进程。

1. runc 核心架构

runc 的架构非常简洁,核心是 "解析配置 → 配置内核资源 → 创建容器进程" 的线性流程,无复杂的模块化设计,具体可分为 3 个阶段:

阶段 核心操作
1. 解析 OCI 配置 读取 config.json 文件(由 containerd 生成),提取关键配置:- 根文件系统路径(RootFS);- 容器命名空间(PID、Network、Mount 等);- Cgroups 资源限制(CPU、内存、磁盘 IO);- Seccomp 安全策略(限制容器可调用的系统调用);- 容器内初始化命令(如 sh、nginx)。
2. 配置内核资源 通过 Linux 系统调用配置容器的隔离与资源限制:- 调用 unshare() 创建新的 Namespace(PID、Network、Mount 等);- 挂载 RootFS 到指定目录,并设置挂载权限(如 /proc、/sys 等虚拟文件系统);- 创建 Cgroups 目录(如 /sys/fs/cgroup/cpu/runc/<容器ID>),并写入资源限制参数;- 加载 Seccomp 规则,限制容器的系统调用(如禁止 fork、mount 等危险调用)。
3. 创建容器进程 调用 execve() 执行容器内的初始化命令,启动容器进程;此时容器进程已处于隔离的 Namespace 和受限制的 Cgroups 中,与主机其他进程隔离。

2. runc 核心功能详解

(1)容器进程的创建与隔离

runc 是容器 "隔离性" 的直接实现者,依赖 Linux 内核的三大核心特性:

  • Namespace 隔离:为容器创建独立的 "系统视图",确保容器内进程无法感知主机或其他容器的资源:
    • PID Namespace:容器内的 PID 从 1 开始(初始化进程),与主机 PID 不冲突。
    • Network Namespace:容器拥有独立的网络栈(网卡、IP、端口),与主机网络隔离。
    • Mount Namespace:容器拥有独立的文件系统挂载点(仅能看到 RootFS 内的文件)。
    • 其他 Namespace:UTS(主机名隔离)、IPC(进程间通信隔离)、User(用户 ID 映射,容器内 root 不等于主机 root)。
  • Cgroups 资源限制:限制容器对 CPU、内存、磁盘 IO、网络带宽等资源的使用,避免单个容器耗尽主机资源:
    • 例如:通过 cpu.cfs_quota_us 限制容器 CPU 使用率不超过 50%,通过 memory.limit_in_bytes 限制容器内存不超过 1GB。
  • Seccomp 安全限制:通过白名单机制限制容器可调用的系统调用,减少容器被攻击的风险:
    • 例如:禁止容器调用 mount(避免挂载主机文件系统)、unshare(避免创建新的 Namespace)等系统调用。

(2)轻量级设计:无后台进程

runc 是 "一次性工具",无长期运行的后台进程:

  • 当执行 runc create 时,runc 配置完内核资源后立即退出,仅留下容器进程和 containerd-shim 进程。
  • 当执行 runc start 时,runc 启动容器进程后也立即退出,后续由 containerd-shim 监控容器进程状态。
  • 这种设计让 runc 更轻量、更稳定,避免因自身后台进程崩溃导致容器失控。

(3)OCI 兼容性

runc 是 OCI 运行时规范的参考实现,任何符合 OCI 规范的 config.json 都可通过 runc 运行,确保容器生态的兼容性:

  • 例如:用 Podman 构建的 OCI 镜像,可通过 runc run 直接启动;用 containerd 生成的 config.json,也可通过 crun(另一个 OCI 运行时实现)启动。

五、containerd 与 runc 的交互流程(实例)

以 "启动一个 Nginx 容器" 为例,完整展现 containerd 与 runc 的协作过程:

  1. 上层请求:用户通过 K8s 或 Docker 发送 "启动 Nginx 容器" 的请求(指定镜像 nginx:latest、内存限制 512MB)。
  1. containerd 接收请求:通过 gRPC API 接收请求,调用镜像管理器拉取 nginx:latest 镜像(若本地不存在)。
  1. 准备 RootFS:存储管理器将镜像的多层文件系统(如 layer1.tar、layer2.tar)通过 overlay2 合并为可读写的 RootFS,路径为 /var/lib/containerd/io.containerd.snapshotter.v1.overlay2/<快照ID>/fs。
  1. 生成 OCI 配置:containerd 将请求参数转换为 config.json,核心内容包括:
    • root.path:RootFS 路径;
    • process.args:["nginx", "-g", "daemon off;"](容器内命令);
    • linux.namespaces:配置 6 个 Namespace;
    • linux.resources:设置内存限制 512MB。
  1. 启动 containerd-shim:containerd 创建 containerd-shim-runc-v2-<容器ID>.mount 进程,作为与 runc 交互的中间层。
  1. runc 创建容器:shim 调用 runc create --bundle <配置目录> <容器ID>,runc 解析 config.json,执行以下操作:
    • 调用 unshare() 创建 6 个 Namespace;
    • 挂载 RootFS 及 /proc、/sys 等虚拟文件系统;
    • 在 /sys/fs/cgroup/memory/ 下创建容器专属目录,写入 memory.limit_in_bytes = 536870912(512MB);
    • 加载 Seccomp 规则,限制系统调用。
  1. runc 启动容器:shim 调用 runc start <容器ID>,runc 调用 execve() 执行 nginx 命令,容器进程启动(PID 为容器内的 1 号进程)。
  1. 监控与维护:runc 退出,shim 持续监控容器进程状态(如 CPU 使用率、内存占用),并将状态同步给 containerd;若容器异常退出,shim 通知 containerd 执行重启策略。

六、总结:核心差异与协同价值

维度 containerd runc
定位 容器管理层(全生命周期管理) 容器执行层(仅创建 / 启动容器进程)
功能范围 镜像、存储、网络、容器生命周期、元数据管理 解析 OCI 配置、创建容器进程(内核资源配置)
运行形态 长期后台进程(containerd 服务) 一次性命令行工具(无后台进程)
依赖关系 依赖 runc 执行容器 依赖 Linux 内核特性,不依赖其他工具

协同价值

  • 分层解耦:containerd 负责 "管理",runc 负责 "执行",各司其职,便于维护和扩展(例如:可将 runc 替换为 crun(更快)或 kata-runtime(更强隔离),不影响 containerd 的管理功能)。
  • 标准化兼容:二者均遵循 OCI 标准,确保容器可在不同平台(Docker、K8s、Podman)间无缝迁移。
  • 稳定性与轻量:runc 的轻量级设计减少了故障点,containerd 的模块化设计确保了管理功能的稳定与可扩展。

正是这种 "管理层 + 执行层" 的协作架构,让 containerd 和 runc 成为现代容器生态(尤其是 K8s)的核心运行时基础。

相关推荐
小猪咪piggy1 小时前
【JavaEE】(20) Spring Boot 统一功能处理
java·spring boot·后端
bobz9659 小时前
ubuntu install NVIDIA Container Toolkit
后端
绝无仅有9 小时前
Go Timer 面试指南:常见问题及答案解析
后端·算法·架构
绝无仅有9 小时前
Go 语言面试指南:常见问题及答案解析
后端·面试·go
bobz96510 小时前
Docker 与 containerd 的架构差异
后端
程序猿阿伟10 小时前
《跳出“技术堆砌”陷阱,构建可演进的软件系统》
后端
就叫飞六吧10 小时前
基于Spring Boot的短信平台平滑切换设计方案
java·spring boot·后端
bobz96511 小时前
NVIDIA Container Toolkit(容器运行时依赖)
后端
bobz96511 小时前
NVIDIA Container Toolkit 架构上下文
后端