Linux runc 的 spec hooks prestart / poststart / poststop 执行顺序

2次阅读

prestart hook在runc fork后、exec前执行,此时Namespace已生效但/proc/self/exe仍为runc;poststart在用户进程exec成功后运行,pid指向应用但不保证服务就绪;poststop在runc delete阶段、cgroup销毁前执行,可读取残留/proc信息但不可依赖网络或已退出进程。

Linux runc 的 spec hooks prestart / poststart / poststop 执行顺序

prestart hook 是容器进程 fork 之后、exec 之前执行的

它在 runc 已经调用 clone() 创建了容器命名空间,但还没用 execve() 启动用户指定的 init 进程时触发。这意味着:进程 PID 已分配、namespace 已生效、cgroup 已挂载,但 /proc/self/exe 还是 runc 自身,不是你的应用。

  • 典型用途:动态注入环境变量、修改 cgroup 参数(如 memory.max)、加载内核模块、绑定额外设备节点
  • 注意:不能依赖容器内完整的 rootfs 环境——/bin/sh 可能还不可用;建议用静态链接的二进制(如 busybox)或绝对路径调用 /usr/bin/env
  • 如果 hook 返回非零退出码,runc create 直接失败,容器不会进入 created 状态

poststart hook 在用户进程 exec 成功后才运行

此时容器 init 进程(比如 /bin/bashnginx)已成功 execve(),PID 和 /proc/[pid]/exe 都指向你的程序。但注意:它不等同于“服务就绪”——只是进程起来了,未必监听端口或完成初始化。

  • 常见误用:在这里做健康检查或等待服务 ready —— 不可靠,应交给上层编排器(如 systemd、k8s readiness probe)
  • 适合场景:记录 PID 到外部文件、通知监控系统“容器已启动”、启动配套的 sidecar 脚本(需确保不阻塞主进程)
  • hook 运行期间,runc start 命令会等待其退出;若 hang 住,容器状态卡在 running,但实际可能无响应

poststop hook 的执行时机常被误解

它不是在容器进程 exit 后立刻执行,而是在 runc delete(或 runc kill && runc delete)阶段、所有 namespace 解绑和 cgroup 销毁**之前**运行。此时容器进程大概率已终止,但 /proc 下仍可见残留、cgroup 目录尚存、mount namespace 也未完全清理。

  • 能做的事:读取容器最后的 /proc/[pid]/status(如果还在)、dump 内存快照、归档日志缓冲区、释放外部资源(如解绑 RDMA 设备)
  • 不能做的事:向已退出的容器进程发信号、依赖容器内网络(netns 通常已 unmounted)、假设 /tmp/dev/shm 仍可写(取决于 umount 顺序)
  • 一个坑:如果 poststop 执行过慢,runc delete 会阻塞,导致后续 runc run 因 cgroup busy 报错 failed to destroy cgroup: device or Resource busy

hooks 字段写在哪?spec.json 里位置很关键

prestartpoststart 必须放在 hooks.prestart / hooks.poststart 数组中;poststop 则在 hooks.poststop。它们都属于 OCI runtime spec 的顶层 hooks 对象,不是 processroot 的子字段。

  • 错误写法:{"process": {"hooks": {...}}} —— runc 完全忽略
  • 正确结构:{"hooks": {"prestart": [{"path": "/opt/hooks/pre-net.sh"}], "poststop": [...]}}
  • 路径必须是宿主机视角的绝对路径;hook 脚本内部看到的 rootfs 是容器的,但执行上下文是宿主机的 mount namespace(除非显式 unshare --user --pid --fork
  • 权限:脚本需对 runc 执行用户(通常是 root)可执行;若用 Go 编写,注意 CGO_ENABLED=0 静态编译,避免容器内缺失 libc

hook 的执行顺序是确定的,但每个 hook 的「副作用」是否完成、是否与其他组件(如 systemd、cni 插件)竞争资源,得靠你自己控制。最常出问题的不是顺序本身,而是假设 hook 里能看到某个状态——其实 namespace 解绑、cgroup 销毁、mount umount 都有细微时序差,别信“应该已经好了”。

text=ZqhQzanResources