文章目录
核心观点
docker stop 本质上不是一个"关机"指令,而是一次"限时谈判":它先向容器主进程发送 SIGTERM 信号请求优雅退出,若超时未响应,才会发送 SIGKILL 强制抹杀。
正如 Docker 官方文档所描述的,理解这一机制的核心价值在于:数据完整性。如果你不懂这背后的信号传递原理,你的数据库可能还没来得及落盘就被强制杀死,你的 Web 服务可能在处理请求的一半突然断连。掌握它,是为了让你的应用能像绅士一样体面离场,而不是像逃犯一样被当场击毙。
一、 标准流程:先礼后兵的艺术
当你敲下 docker stop 命令时,Docker 并没有立即切断容器的电源。相反,它启动了一个精心设计的"停机协议"。
首先,Docker 会向容器内的 PID 1 进程 (也就是容器的主进程)发送一个 SIGTERM 信号。这个信号的含义是"请停止工作"。这就好比你在餐厅打烊前礼貌地告诉顾客:"我们要关门了,请您吃完尽快离开。"
此时,一个设计良好的应用程序会捕获这个信号,开始执行清理工作:保存内存中的数据、关闭数据库连接、完成正在处理的 HTTP 请求。Docker 默认会给容器 10 秒钟(Linux 容器)的"宽限期"(Grace Period)。
然而,如果 10 秒过后容器还在运行,Docker 就会失去耐心。它会发出第二个信号:SIGKILL。这个信号是内核级别的"强制终止",应用程序无法捕获也无法忽略它。这就像是保安直接把赖着不走的顾客架出了大门。如果你的程序依赖这一步来停止,那么数据丢失和文件损坏的风险将大大增加。
二、 "PID 1" 陷阱:为什么你的容器对信号置若罔闻?
很多初级开发者会发现一个奇怪的现象:执行 docker stop 后,容器总是雷打不动地卡住 10 秒,然后才退出。这通常不是因为程序清理工作太慢,而是因为你的程序压根就没收到信号。
这通常归咎于 Dockerfile 中 ENTRYPOINT 或 CMD 的写法。
根据 Dockerfile 参考文档,指令有两种写法:Shell 格式 和Exec 格式 。如果你使用 Shell 格式,例如 ENTRYPOINT command param1,Docker 实际上会以 /bin/sh -c command param1 的方式运行你的程序。
这意味着,容器内的 PID 1 进程变成了 Shell (/bin/sh),而你的应用程序只是 Shell 的一个子进程。问题在于,Unix 的 Shell 通常不会将收到的信号转发给它的子进程。
当 Docker 发送 SIGTERM 给 PID 1(Shell)时,Shell 收到信号但什么也不做,也不会告诉你的应用该停了。你的应用还在快乐地运行,完全不知道外面发生了什么。直到 10 秒超时,SIGKILL 袭来,Shell 和你的应用一起被强制杀死。这就是那神秘"卡顿 10 秒"的真相。
要解决这个问题,你应该使用 Exec 格式 ,即 ENTRYPOINT ["executable", "param1", "param2"]。这种写法直接将你的应用作为 PID 1 运行,从而能直接接收并处理 Docker 发出的退出信号。如果必须使用 Shell,可以使用 exec 命令(如 ENTRYPOINT exec command param1),它会用你的命令替换掉 Shell 进程,使其接管 PID 1。
三、 掌控权在你:自定义停机策略
虽然 SIGTERM 和 10 秒超时是默认标准,但真实世界的应用往往更复杂。Docker 提供了灵活的机制来适应这些需求。
并不是所有应用都把 SIGTERM 当作停止信号。例如,Nginx 传统上使用 SIGQUIT 来进行优雅停机。在这种情况下,你如果不做配置,Docker 发送默认的 SIGTERM 可能会导致 Nginx 立即退出而不是优雅停止。你可以在 Dockerfile 中使用 STOPSIGNAL SIGQUIT 指令,或者在启动时通过 --stop-signal 选项,告诉 Docker:"在这个容器里,请用 SIGQUIT 来代替 SIGTERM 进行谈判。"
同样,10 秒的宽限期对于一个正在处理大量事务的数据库或微服务来说可能太短了。你可以通过 --timeout(或 -t)参数来延长这个时间。如果你在 Docker Compose 中管理服务,也可以在 docker-compose.yml 中配置 stop_grace_period。甚至,你可以将超时设置为 -1,这意味着 Docker 会无限期等待,直到程序自己退出------当然,这需要你对程序的退出逻辑有绝对的信心。
总结
docker stop 的背后,是操作系统信号传递的精密舞蹈。记住:优雅的停机 = 正确的信号接收 (Exec Form) + 充足的清理时间 (Timeout) + 合适的信号类型 (StopSignal)。不要让你的应用死于无声的误解中。
参考资料: