bigywater.tech
Docker · 11

exec 与 shell 形式:PID 1 与信号能不能传到你的程序

“为什么我的容器 docker stop 要等那么久?”(默认的等待时间随环境配置而不同,这台 Docker Desktop 是 3 秒,见 c30)几乎总是因为 SIGTERM 没有到达你的程序,或者你的程序作为 PID 1 没有处理函数。这篇把几种写法并排跑一遍,测 docker stop 的用时和退出码,再验证 PID 1 对 SIGTERM 的特殊对待。

3.13 秒 / 137
复合 shell 命令的 ENTRYPOINT:PID 1 是 sh,docker stop -t 3 等满超时后被 SIGKILL
0.10 秒 / 0
exec 形式的 ENTRYPOINT:信号直达程序,干净退出
--init:0.13 秒 / 143
没有信号处理的 sleep 加 --init 后,docker stop 立刻结束

谁是 PID 1

实测 · 本机执行输出
  [exec 形式 ENTRYPOINT ["/app.sh"]]
      docker stop -t 3 用时 0.10 秒;退出码 0;日志:app 启动,PID=1 / 收到 SIGTERM,清理后退出
  [shell 形式、单条命令 ENTRYPOINT /app.sh]
      docker stop -t 3 用时 0.08 秒;退出码 0;日志:app 启动,PID=1 / 收到 SIGTERM,清理后退出
  [shell 形式、复合命令 ENTRYPOINT /app.sh && echo done]
      docker stop -t 3 用时 3.13 秒;退出码 137;日志:app 启动,PID=7
  [shell 形式、复合命令,但用 exec 接管:ENTRYPOINT exec /app.sh]
      docker stop -t 3 用时 0.13 秒;退出码 0;日志:app 启动,PID=1 / 收到 SIGTERM,清理后退出
  [exec 形式 ENTRYPOINT ["sleep","300"](没有信号处理)]
      docker stop -t 3 用时 3.16 秒;退出码 137;日志:
  [同上,docker run --init]
      docker stop -t 3 用时 0.13 秒;退出码 143;日志:

exec 形式和 exec /app.sh 接管后,程序自己就是 PID 1,docker stop 发的 SIGTERM 它能收到;复合 shell 命令(/app.sh && echo done)让 /bin/sh 当 PID 1,程序成了 PID 7 的子进程,sh 不转发信号,日志里程序根本没收到 SIGTERM,等满超时后被强杀(137)。

PID 1 不会被默认动作杀死

实测 · 本机执行输出
  容器内对 PID 1(sleep 300)发 kill -TERM 1 后 1 秒,ps 看到:PID COMMAND | 1 sleep 300 | 7 sh -c kill -TERM 1; sleep 1; ps -o pid,args | head -4
  在同一个容器里另起一个 sleep 300(PID 不是 1),对它发 kill -TERM:退出码 143(143 = 128 + 15,即被 SIGTERM 终止)

容器里对 PID 1 发 kill -TERM 1,没有信号处理函数的 sleep 300 1 秒后还在;同一个容器里另一个 sleep 300(PID 不是 1)被同样的信号杀死(退出码 143)。内核对 PID 1 的默认信号动作做了保护。我没有读内核源码,这是现象。

docker kill 与 docker stop

实测 · 本机执行输出
  docker kill --signal TERM(sleep 300 作为 PID 1,没有信号处理):1 秒后容器状态 = running
  接着 docker stop -t 2:用时 2.13 秒,退出码 137

从宿主机用 docker kill --signal TERM 发信号,PID 1 的 sleep 1 秒后仍是 running;docker stop -t 2 等满 2 秒后发 SIGKILL,退出码 137。加 --init(容器里多一个 init 进程负责转发信号并回收子进程)后,同样的 sleep 在 0.13 秒内以 143 退出。

分步演示

exec 与 shell 形式
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / Docker Desktop(Engine 29.8.2,容器跑在 Docker Desktop 自带的 Linux 虚拟机里,内核与宿主机的 macOS 不同);镜像为 linux/arm64;需要看虚拟机内部时用特权容器 + nsenter;计时用 time / date +%s%N,数字每次运行会有小幅抖动。app.sh 是一个 trap 了 SIGTERM 的 sh 脚本;docker stop -t 3(或 -t 2)的用时用 Python time.time 测;退出码取 docker inspect 的 State.ExitCode;日志取 docker logs;每种写法只运行一次;实验后已删除所有镜像和容器
    • 没有验证:bash / dash 作为 PID 1 的差别、Python / Node / Java 程序作为 PID 1 时的默认信号行为、tini 与 docker run --init 的内部差异(后面有专门一篇)、docker stop --signal / STOPSIGNAL 指令(后面也有专门一篇)、子进程孤儿的回收
    京ICP备2026067502号-1京公网安备11010502063487号