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

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/bash 或 nginx)已成功 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 里位置很关键
prestart 和 poststart 必须放在 hooks.prestart / hooks.poststart 数组中;poststop 则在 hooks.poststop。它们都属于 OCI runtime spec 的顶层 hooks 对象,不是 process 或 root 的子字段。
- 错误写法:
{"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










