容器里的服务收不到 SIGTERM?先查 PID 1 与 shell 包装

举报
shijiu_dev 发表于 2026/11/02 09:07:27 2026/11/02
【摘要】 容器停止总是等满超时、应用日志没有优雅退出记录,问题常出在 PID 1 和 shell 包装层。本文用最小示例复现信号丢失,并给出 exec、ENTRYPOINT 与 Kubernetes 的修复和排查清单。

现象

容器执行 docker stop,或在 Kubernetes 中删除 Pod 时,应用没有打印退出日志,进程总是撑到宽限时间后收到强制信号。此时 docker inspect 的 ExitCode 可能显示 137,很容易被误判为 OOM。137 只代表进程被信号终止,不等于一定是内存问题。

最小复现

下面用一个 shell 包装业务进程,并在启动前打印环境信息:

#!/bin/sh
echo start
python app.py

如果镜像写成 CMD ./start.sh,容器的 PID 1 往往就是 shell 或入口脚本,而不是 python。停止信号被发给 PID 1 后,shell 不再把信号转给子进程,应用便没有机会执行连接关闭、任务收尾等清理动作。

先在容器里核实进程关系:

cat /proc/1/cmdline
ps -eo pid,ppid,comm,args
docker inspect -f '{{.Config.Cmd}}' <容器名>

如果 PID 1 是 sh、bash 或某个 entrypoint 脚本,而业务进程是它下面的子进程,就应该先怀疑包装层。再观察日志中是否同时出现“优雅退出”和“强杀”痕迹:应用自己打印了关闭日志,说明信号已收到,问题转向清理超时;完全没有关闭日志,则重点查 PID 1 和进程树。

修复方式

启动脚本的最后一行用 exec 启动业务,让业务进程替换 shell 并成为 PID 1:

exec python app.py

Dockerfile 中的命令优先使用 exec 形式,避免多套一层 shell:

CMD ["python", "app.py"]

exec 形式并不是万能药。入口脚本前面如果有初始化动作,最后仍要写成 exec "$@";业务本身会大量 fork 子进程时,可以用 tini 或容器运行时的 init 选项充当 PID 1,负责回收孤儿进程。

验证与预防

修复后用 docker stop --time 5 测退出耗时,并让应用显式处理 SIGTERM 和 SIGINT:停止接收新请求、等待在途请求、释放连接,超过内部截止时间再退出。Kubernetes 中还要检查 terminationGracePeriodSeconds 和 preStop 是否设置了不合理的长等待。

排查顺序可以固定为:先区分 OOM 与普通强杀,再确认 PID 1 是谁,接着检查 shell 和脚本是否转发信号,最后看应用 handler 与清理超时。直接加大宽限期会掩盖问题,短期看似稳定,长期仍会制造发布抖动。

把这个检查加入镜像模板和启动脚本评审,通常比事故后再追 ExitCode 更有效。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。