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 启动的完整链路。
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,支持分层复用和写时复制。
存储管理的核心流程如下:
- 镜像拉取:crio 调用
containers/storage从镜像仓库拉取 manifest 和层数据,解压到/var/lib/containers/storage/。 - 镜像挂载:创建容器时,存储层将镜像的只读层与一个可写层(
upperdir)组合,通过 OverlayFS 挂载为容器的 rootfs。 - 卷挂载:crio 根据 CRI 的
mounts参数,将 hostPath、emptyDir 或 PV 挂载到容器内指定路径。 - 垃圾回收: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。