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 启动流程步骤

  1. 解析 Bundle: runc 读取 config.json 文件,获取容器配置(namespace、cgroups、mounts、capabilities 等)。
  2. 创建容器进程: 通过 clone() 系统调用创建新的子进程,并为其设置新的 namespace(PID、NET、IPC、UTS、MNT、USER)。
  3. 设置 cgroups: 根据配置,将容器进程加入指定的 cgroup 控制器(cpu、memory、pids、blkio 等),实现资源限制。
  4. 挂载文件系统: 切换 rootfs,挂载 proc、sys、dev 等虚拟文件系统,并执行 config.json 中定义的 mounts。
  5. 执行用户进程: 在容器内执行 config.json 中指定的 process.args,即容器的主进程(如 /bin/bash 或 /usr/local/bin/app)。
治理要点: 在启动流程中,config.json 的完整性校验是安全治理的第一道防线。建议在 runc 调用前,对 bundle 进行签名验证和配置审计。
runc 启动流程示意图 runc 启动流程 1. Bundle 解析 2. clone() 创建进程 3. 设置 cgroups 4. 挂载文件系统 5. 执行用户进程 说明:用户进程是容器的 1 号进程(PID 1)

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)。
runc 生命周期状态机 runc 生命周期状态机 creating created running stopped deleted create 完成 start 进程退出 / kill delete 说明:状态转换 由 runc 命令触发 (create/start/kill/delete)

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)

  1. Kubelet 请求: Kubelet 通过 CRI 调用 containerd 的 RunPodSandbox 或 CreateContainer 接口。
  2. containerd 处理: containerd 解析请求,准备 OCI bundle(config.json + rootfs),并启动 containerd-shim-runc-v2 进程。
  3. shim 调用 runc: shim 进程调用 runc create 和 runc start,创建并启动容器。
  4. 状态上报: shim 进程持续监控 runc 容器状态,并通过 containerd 上报给 Kubelet。
优化建议: 在治理控制面中,可以通过监听 containerd-shim-runc-v2 进程的 stdout/stderr,实时获取 runc 容器的启动日志和错误信息,用于样本留存和异常检测。

2.4 本章小结