bigywater.tech
Flask · 189

容器信号处理

容器停止时 docker stop 发的是 SIGTERM,但收到它的是 1 号进程。如果 1 号进程是 sh 而不是 gunicorn,优雅关闭就根本没有机会发生。这篇用本机的真实进程模拟这件事:被发信号的进程分别是 gunicorn 本身还是外面包着的 sh。需要说明:没有真正的 Docker 容器(拉不到基础镜像),也不是 Linux,所以 1 号进程的特殊信号语义没有验证(Dockerfile 的写法见第 178 篇,优雅关闭见第 180 篇)。

exec
入口脚本里加 exec 让 gunicorn 接替 sh 成为被发信号的进程
孤儿
sh 被 SIGTERM 终止而 gunicorn 没收到信号继续运行
宽限期
优雅关闭时间必须小于容器的停止宽限期

谁收到 SIGTERM

实测 · 本机执行输出
启动方式                                          被发信号的进程就是 gunicorn?       它的退出      请求结果          gunicorn 变孤儿继续运行?
exec 形式:直接运行 gunicorn(CMD ["gunicorn", ...])  True                          2.56 s    200 3.01 s    False
shell 形式,单条命令:sh -c "gunicorn ..."            True                          2.59 s    200 3.01 s    False
shell 形式,多条命令:sh -c "gunicorn ...; echo done" True                          0.0 s     200 3.01 s    True
入口脚本最后一行是 gunicorn(没有 exec)                   False                         0.0 s     200 3.01 s    True
入口脚本最后一行是 exec gunicorn                       True                          2.72 s    200 3.01 s    False

上一篇:数据库连接健康检查;下一篇:Flask 应用启动耗时。

实验在 macOS 上用 /bin/sh(bash 3.2)与真实的 gunicorn 进程完成,没有使用 Docker。

shell 形式

实测 · 本机执行输出
  再跑一次 sh -c "...; echo done":sh 退出后 gunicorn 孤儿存在 = True

SIGKILL 补刀

实测 · 本机执行输出
  SIGTERM 之后 1.5 秒补 SIGKILL:请求 → RemoteDisconnected,2.02 s 后(这个请求本来要 3 秒才完成,被打断)

分步演示

容器信号处理
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / Python 3.14.7 / Flask 3.1.3 / Werkzeug 3.1.9 / Jinja2 3.1.6 / SQLAlchemy 2.1.1 / gunicorn 26.2.0(都装在自己的虚拟环境里);大多数实验用 Flask 的 test_client(直接调用 WSGI 应用,不经过网络),需要真实 HTTP 时另起 gunicorn 或开发服务器(端口 9600 以上,本机 127.0.0.1);计时用 time.perf_counter,数字每次运行会有小幅抖动。macOS /bin/sh(bash 3.2.57)、gunicorn、真实进程(不是 Docker 容器)
    • 没有验证:Linux 容器里 1 号进程不套用默认信号处理(没装 handler 的进程会忽略 SIGTERM)、dash/busybox ash 对 sh -c 单条命令是否 exec、tini 与 docker run --init、僵尸进程回收、docker stop 的 10 秒默认值与 Kubernetes terminationGracePeriodSeconds 的实际行为(只用 SIGKILL 延时做了模拟)
    京ICP备2026067502号-1京公网安备11010502063487号