【Docker】Docker Stop 后到底发生了什么?——从信号机制到优雅停机
文章目录核心观点一、 标准流程先礼后兵的艺术二、 PID 1 陷阱为什么你的容器对信号置若罔闻三、 掌控权在你自定义停机策略总结核心观点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 param1Docker 实际上会以/bin/sh -c command param1的方式运行你的程序。这意味着容器内的 PID 1 进程变成了Shell/bin/sh而你的应用程序只是 Shell 的一个子进程。问题在于Unix 的 Shell 通常不会将收到的信号转发给它的子进程。当 Docker 发送SIGTERM给 PID 1Shell时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)。不要让你的应用死于无声的误解中。参考资料Docker CLI Reference: docker container stopDockerfile Reference: Shell form ENTRYPOINT exampleDocker Compose FAQ