4、CRI-O架构与核心组件:CRI-O守护进程、conmon、OCI运行时(runc/crun)、容器存储管理

CRI-O 是 Kubernetes 控制面与 OCI 兼容容器运行时之间的轻量级桥梁。其设计哲学是“只做必要的事”,将镜像管理、容器生命周期、存储挂载与监控职责分离到四个核心组件中。理解这些组件的交互关系,是定位容器运行时故障的基础。

核心要点:CRI-O 不直接运行容器,而是通过 conmon 监控容器进程,并委托 runc 或 crun 完成实际的命名空间创建与 cgroup 配置。存储管理则依赖 containers/storage 库与 OverlayFS 驱动。

4.1 整体架构与数据流

下图展示了 CRI-O 守护进程接收 kubelet 请求后,如何协调 conmon、OCI 运行时和存储层完成 Pod 启动的完整链路。

CRI-O 核心组件架构与数据流 CRI-O 核心组件架构与数据流 kubelet (CRI 客户端) gRPC CreateContainer CRI-O 守护进程 (crio) 容器生命周期 + 镜像管理 conmon (容器监控) stdout/stderr + 退出信号 OCI 运行时 (runc/crun) 命名空间 + cgroup + 挂载 容器存储管理 containers/storage + OverlayFS 关键数据流说明: 1. kubelet 通过 CRI gRPC 接口向 crio 发送 RunPodSandbox / CreateContainer 请求。 2. crio 解析请求,调用 containers/storage 准备 rootfs,并生成 OCI 规范 JSON。 3. crio 启动 conmon 作为中间进程,conmon 再 fork-exec 执行 runc/crun 创建容器。 4. conmon 负责接管容器进程的 stdio,并在容器退出时通知 crio 清理资源。

4.2 CRI-O 守护进程 (crio)

crio 是常驻后台的守护进程,通过 Unix Socket 暴露 CRI gRPC 服务。它不直接执行容器,而是扮演“编排调度器”的角色。

职责模块 说明 故障排查入口
gRPC 服务端 监听 /var/run/crio/crio.sock,处理 kubelet 的 Pod/Container 操作请求 crictl info、journalctl -u crio
镜像管理 拉取、缓存、垃圾回收容器镜像,支持镜像层复用 crictl images、crictl stats
OCI 规范生成 将 CRI 参数(如 CPU/Memory limits、Mounts)转换为 OCI runtime-spec JSON 检查 /var/run/containers/storage/ 下的 bundle 目录
沙箱管理 管理 Pod 级别的命名空间(Network、PID、IPC)生命周期 crictl pods、crictl inspectp
排查提示:当 Pod 一直处于 ContainerCreating 状态时,优先检查 crio 日志中的 level=error 或 level=warning 条目。常见错误包括镜像拉取超时、OCI 运行时路径错误、存储空间不足。

4.3 conmon:容器监控中间件

conmon 是 CRI-O 架构中容易被忽视但极其关键的组件。它作为 crio 与 OCI 运行时之间的“中间人”,解决了一个核心问题:如何可靠地捕获容器进程的 stdout/stderr 和退出码。

  • 进程模型:crio 先 fork-exec 启动 conmon,conmon 再 fork-exec 启动 runc/crun。conmon 成为容器进程的父进程。
  • I/O 重定向:conmon 通过 Unix Domain Socket 或管道将容器内的 stdout/stderr 传递给 crio,再转发给 kubelet 的 streaming API。
  • 退出信号处理:当容器主进程退出时,conmon 捕获其退出码,并通过附加的通信管道通知 crio 执行清理。
  • OOM 检测:conmon 监控容器进程的 cgroup 事件,在发生 OOM Kill 时生成特定日志。
常见故障模式:如果 conmon 进程异常退出(如被 cgroup 内存限制杀死),容器会变成“僵尸”状态。此时 crictl ps 可能显示容器仍在运行,但实际进程已消失。排查时需检查 ps aux | grep conmon 的进程树完整性。

4.4 OCI 运行时:runc 与 crun

OCI 运行时负责真正“创建容器”——即调用 Linux 内核的系统调用建立隔离环境。CRI-O 默认使用 runc,但 crun 因其更低的延迟和内存占用正逐渐成为热门选择。

特性 runc (默认) crun (可选)
实现语言 Go C
启动延迟 较高(Go 运行时 + GC 开销) 低(无 GC,直接系统调用)
内存占用 每个容器约 5-15 MB 每个容器约 0.5-2 MB
cgroup v2 支持 完整支持 完整支持
适用场景 通用场景、兼容性优先 高密度部署、边缘计算、延迟敏感

CRI-O 通过配置文件 /etc/crio/crio.conf 中的 runtime_path 指定使用哪个运行时。切换时只需修改路径并重启 crio 服务。

# /etc/crio/crio.conf 示例
[crio.runtime]
# 默认使用 runc
runtime_path = "/usr/bin/runc"
# 或切换为 crun
# runtime_path = "/usr/bin/crun"
# 运行时类型:oci
runtime_type = "oci"
# 监控路径
conmon_path = "/usr/bin/conmon"

4.5 容器存储管理

CRI-O 使用 containers/storage 库管理镜像层和容器 rootfs。默认驱动为 OverlayFS,支持分层复用和写时复制。

存储管理的核心流程如下:

  1. 镜像拉取:crio 调用 containers/storage 从镜像仓库拉取 manifest 和层数据,解压到 /var/lib/containers/storage/。
  2. 镜像挂载:创建容器时,存储层将镜像的只读层与一个可写层(upperdir)组合,通过 OverlayFS 挂载为容器的 rootfs。
  3. 卷挂载:crio 根据 CRI 的 mounts 参数,将 hostPath、emptyDir 或 PV 挂载到容器内指定路径。
  4. 垃圾回收:crio 定期扫描未使用的镜像层和容器存储,释放磁盘空间。
存储故障排查清单:
  • 磁盘空间不足:检查 df -h /var/lib/containers,使用 crictl rmi --prune 清理未使用的镜像。
  • OverlayFS 冲突:检查 dmesg | grep overlay 是否有文件系统错误,确保内核支持 OverlayFS。
  • 存储驱动不一致:确认 crio.conf 中的 storage_driver 与内核模块匹配(默认 overlay2)。
  • 挂载泄漏:使用 mount | grep containers 检查是否有残留的 Overlay 挂载点。

4.6 组件交互故障排查速查表

下表总结了当容器启动失败时,各组件可能出现的典型症状与排查方向:

故障现象 可能原因组件 排查命令/日志
Pod 一直 Pending CRI-O 守护进程 crictl pods、journalctl -u crio --no-pager | grep -i error
容器创建超时 OCI 运行时 (runc/crun) crictl inspect <container-id> 查看 state,检查 /var/log/crio.log
容器日志为空 conmon ps aux | grep conmon 确认进程存在,检查 /var/log/pods/ 下的日志文件
镜像拉取失败 存储管理 crictl images、crictl inspecti、检查 /var/lib/containers/storage/ 磁盘空间
容器启动后立即退出 OCI 运行时 + conmon crictl logs <container-id>、crictl inspect <container-id> | jq .info.exitCode

掌握 CRI-O 的组件分工与交互边界,能帮助 SRE 在故障发生时快速定位“问题出在哪个环节”,从而避免盲目重启整个节点或重建 Pod。