2. runc 架构深度解析:启动流程、生命周期管理与交互关系
runc 是 OCI(Open Container Initiative)运行时规范的参考实现,也是绝大多数容器引擎(Docker、containerd、CRI-O)的底层容器创建与执行引擎。理解 runc 的架构,是构建容器运行时治理控制面的前提。本章从启动流程、生命周期管理以及与 containerd/CRI-O 的交互关系三个维度,深度解析 runc 的内部机制。
2.1 runc 的启动流程
runc 的启动流程遵循 OCI 运行时规范,分为 创建(create)、启动(start) 和 执行(exec) 三个阶段。其核心在于利用 Linux 内核的 namespace、cgroups、chroot 等技术,构建一个隔离的容器环境。
关键概念: runc 本身是一个 CLI 工具,它通过解析 OCI 标准的 bundle(包含 config.json 和 rootfs)来启动容器。容器进程的创建与父进程分离,通过
clone() 系统调用实现。
2.1.1 启动流程步骤
- 解析 Bundle: runc 读取
config.json文件,获取容器配置(namespace、cgroups、mounts、capabilities 等)。 - 创建容器进程: 通过
clone()系统调用创建新的子进程,并为其设置新的 namespace(PID、NET、IPC、UTS、MNT、USER)。 - 设置 cgroups: 根据配置,将容器进程加入指定的 cgroup 控制器(cpu、memory、pids、blkio 等),实现资源限制。
- 挂载文件系统: 切换 rootfs,挂载 proc、sys、dev 等虚拟文件系统,并执行 config.json 中定义的 mounts。
- 执行用户进程: 在容器内执行
config.json中指定的process.args,即容器的主进程(如/bin/bash或/usr/local/bin/app)。
治理要点: 在启动流程中,
config.json 的完整性校验是安全治理的第一道防线。建议在 runc 调用前,对 bundle 进行签名验证和配置审计。
2.2 生命周期管理
runc 对容器的生命周期管理遵循 OCI 规范定义的状态机。容器状态包括 creating、created、running、stopped 等。runc 通过 state、list、kill、delete 等命令进行管理。
2.2.1 状态转换表
| 当前状态 | 触发操作 | 下一状态 | 说明 |
|---|---|---|---|
| creating | create 完成 | created | 容器进程已创建,但未启动 |
| created | start | running | 容器主进程开始执行 |
| running | 进程退出 / kill | stopped | 主进程终止,容器停止 |
| stopped | delete | (已删除) | 清理容器资源 |
治理风险: 在
stopped 状态时,如果 delete 操作失败,可能导致僵尸容器残留,占用 cgroups 和挂载点资源。治理控制面应定期扫描 stopped 状态容器并强制清理。
2.2.2 生命周期管理命令
- runc create <container-id>: 创建容器,但不启动主进程。容器进入
created状态。 - runc start <container-id>: 启动已创建的容器,执行主进程。容器进入
running状态。 - runc kill <container-id> <signal>: 向容器主进程发送信号(如 SIGTERM、SIGKILL)。
- runc delete <container-id>: 删除已停止的容器,释放所有资源。
- runc exec <container-id> <command>: 在运行中的容器内执行新进程(如
runc exec mycontainer /bin/sh)。
2.3 与 containerd / CRI-O 的交互关系
在现代 Kubernetes 容器运行时栈中,runc 通常不直接暴露给用户,而是作为 低级运行时(low-level runtime) 被 containerd 或 CRI-O 调用。它们之间的交互遵循 OCI 运行时规范,通过 runc 二进制文件或 containerd-shim-runc-v2 进程进行通信。
2.3.1 交互架构
Kubernetes 通过 CRI(Container Runtime Interface)与 containerd 或 CRI-O 通信。这些高级运行时再调用 runc 来创建和管理容器。典型的调用链如下:
Kubelet --(CRI)--> containerd/CRI-O --(OCI)--> runc --(Linux syscalls)--> 容器进程
2.3.2 交互方式对比
| 特性 | containerd + runc | CRI-O + runc |
|---|---|---|
| 调用方式 | 通过 containerd-shim-runc-v2 进程 |
直接调用 runc 二进制 |
| 进程模型 | 每个容器对应一个 shim 进程,管理 runc 实例 | CRI-O 直接管理 runc 进程 |
| 标准输出处理 | shim 进程负责 IO 多路复用和日志转发 | CRI-O 直接处理容器 IO |
| 生命周期管理 | shim 进程保持 runc 容器状态,支持原地重启 | CRI-O 直接管理 runc 状态 |
| 治理复杂度 | 需要监控 shim 进程和 runc 实例 | 直接监控 runc 进程 |
治理洞察: 在 containerd 架构中,
containerd-shim-runc-v2 进程是治理的关键点。它作为 runc 的父进程,负责容器的 IO 和状态保持。如果 shim 进程异常退出,可能导致 runc 容器成为孤儿。治理控制面应监控 shim 进程的健康状态。
2.3.3 交互流程示例(containerd)
- Kubelet 请求: Kubelet 通过 CRI 调用 containerd 的
RunPodSandbox或CreateContainer接口。 - containerd 处理: containerd 解析请求,准备 OCI bundle(config.json + rootfs),并启动
containerd-shim-runc-v2进程。 - shim 调用 runc: shim 进程调用
runc create和runc start,创建并启动容器。 - 状态上报: shim 进程持续监控 runc 容器状态,并通过 containerd 上报给 Kubelet。
优化建议: 在治理控制面中,可以通过监听
containerd-shim-runc-v2 进程的 stdout/stderr,实时获取 runc 容器的启动日志和错误信息,用于样本留存和异常检测。