Linux containerd 的 shim 与 runc 的 OCI 运行时调试方法

17次阅读

查 containerd shim 卡住需先用 ps 找 pid,再通过 ls -l /proc//fd/ 检查残留 fd,最后用 strace -p 观察阻塞在 wait4/epoll_wait/recvfrom;若 runc 已退出但 shim 仍持 socket,需手动清理对应 runtime 目录。

Linux containerd 的 shim 与 runc 的 OCI 运行时调试方法

containerd shim 进程卡住,怎么查它在等什么?

shim 进程本身不执行容器,只做 containerd 和 runc 之间的“传话员”;它卡住,通常不是 shim 本身坏了,而是它在等 runc 返回、或等容器进程退出、或等 cgroup 状态就绪。最直接的判断方式是看它的子进程和状态:

  • ps -o pid,ppid,comm,state -C containerd-shim 找出卡住的 shim PID
  • ls -l /proc/<shim-pid>/fd/</shim-pid> 看它是否还持着 initctlconsole-socket 的 fd(常见于容器没正确 detach)
  • strace -p <shim-pid></shim-pid> 观察最后阻塞在哪个系统调用(通常是 wait4epoll_waitrecvfrom
  • 如果 shim 持有 runc 的 socket 连接但 runc 已退出,说明 runc 崩溃后没清理 socket,需手动 kill 并清理 /run/containerd/io.containerd.runtime.v2.task/<namespace>/<id>/</id></namespace> 下残留目录

runc exec 进入容器失败,报“no such process”或“container not running”

这不是权限或路径问题,而是 runc 查找容器状态时依赖 state.json 文件,而这个文件由 shim 维护——如果 shim 挂了但容器进程还在,runc exec 就会找不到合法状态。关键点在于:runc 不直接跟内核打交道,它只读 /run/containerd/io.containerd.runtime.v2.task/<ns>/<id>/state.json</id></ns>,并据此生成 exec.fifo 和绑定到容器 init 进程的 stdin/stdout/stderr

  • 先确认容器是否真在跑:ps -eo pid,ppid,comm,args | grep <container-pid></container-pid>
  • 检查 state.json 是否存在且 "status": "running";若文件损坏或 status 是 created,说明 shim 没完成启动流程
  • runc exec 必须指定 --pid-file 或通过 --root 指向正确的 runc root(默认 /var/run/runc),否则会去错地方找 state
  • 不要用 runc --root /run/containerd/runc/<ns> exec ……</ns> 直接操作——containerd v2 runtime 要求 runc 使用自己的 bundle layout,路径必须匹配 shim 创建时的 bundle 字段

调试 OCI runtime 时,如何让 runc 输出详细日志?

runc 默认静默,但它的日志开关不在命令行参数里,而在环境变量和配置中。真正生效的是 RUNC_LOG + RUNC_LOG_LEVEL,且仅当 runc 编译时启用了 debug 支持(主流发行版包通常关闭了)。

  • 临时启用:运行前加 RUNC_LOG=/tmp/runc.log RUNC_LOG_LEVEL=debug runc --debug run -b <bundle><id></id></bundle>
  • --debug 参数必须显式带上,否则 RUNC_LOG 被忽略
  • 注意日志路径需 shim 进程有写权限(比如 containerd 启动 shim 时用的是 containerd 用户,不是 root)
  • 如果看到 failed to load OCI config: invalid character,大概率是 config.json 里多了注释或用了 tab 缩进——OCI spec 严格要求纯 JSON,不能有注释、尾逗号、tab

containerd 重启后容器消失,但 runc list 还能看到?

这是典型的生命周期管理错位:runc list 只扫 /var/run/runc 下的 state 文件,而 containerd v2 runtime 把容器状态存在 /run/containerd/io.containerd.runtime.v2.task/<ns>/<id>/state.json</id></ns>,两者完全不互通。runc 看到的是“孤儿容器”,containerd 认为它们已丢失。

  • 不要直接 runc delete 这些容器——可能残留 cgroup、网络命名空间、mount ns,导致后续创建失败
  • 正确做法是先用 containerd-stress cleanup(如安装了 containerd-devel 包),或手动清理:find /run/containerd/io.containerd.runtime.v2.task -name state.json -exec dirname {} ; | xargs -r rm -rf
  • 更稳妥的是启用 containerd 的 oom_score_adjreclaimable 配置,避免因 OOM 导致 shim 异常退出却不通知 containerd
  • 所有生产环境务必开启 [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options] 中的 SystemdCgroup = true,否则 cgroup v2 下 runc 和 containerd 对 cgroup 路径解析不一致,重启后状态无法对齐

OCI runtime 调试真正的难点不在命令怎么敲,而在于每个组件只认自己那一小段契约:shim 信 state.json,runc 信 bundle 目录结构,containerd 信 shim 的 gRPC 响应。一旦其中一环状态没同步(比如 shim panic 但没删 state.json),整个链就断成两截,得靠交叉验证才能定位真实断点。

text=ZqhQzanResources