Python进程不处理SIGTERM会立即退出(退出码143),导致请求中断、连接泄漏、日志截断和事务丢失;Kubernetes默认发送SIGTERM而非SIGKILL,但Python解释器对其默认行为是静默终止,需显式注册处理器或利用框架生命周期钩子协调优雅停机。

Python进程不处理SIGTERM就会被立刻杀掉
Kubernetes 删除 Pod 时,kubelet 默认向容器 PID 1 发送 SIGTERM,而不是直接 SIGKILL。但 Python 解释器对 SIGTERM 的默认行为是**立即退出**——它不会等待正在运行的线程、协程、数据库连接或文件写入完成。结果就是:请求中断、连接池泄漏、日志截断、未提交事务丢失。
这和你在本地按 Ctrl+C(触发 SIGINT)不同:SIGINT 在交互式终端中会抛出 KeyboardInterrupt 异常,容易被捕获;而 SIGTERM 在容器里默认静默终止,连 traceback 都不会打。
- 不注册处理器 → 进程收到
SIGTERM后秒退,退出码通常是143(128 + 15) - 只处理
SIGINT不处理SIGTERM→ K8s 滚动更新时照样崩 - 在信号处理器里做耗时操作(如同步 HTTP 调用、阻塞 I/O)→ 可能被
kubelet超时后补发SIGKILL
Uvicorn/FastAPI这类ASGI服务器已经接管了SIGTERM,你别抢活干
像 uvicorn、hypercorn 这类 ASGI 服务器,启动后会自动注册 SIGTERM 处理器:停止接受新连接、等待活跃请求完成、关闭监听 socket。如果你再手动用 signal.signal(signal.SIGTERM, ...) 注册一个全局处理器,反而可能干扰它的 draining 逻辑,导致请求被提前切断或 hang 住。
真正该你做的,是在框架提供的生命周期钩子中收尾业务资源:
立即学习“Python免费学习笔记(深入)”;
-
FastAPI用@asynccontextmanager的yield后代码段 -
Starlette用on_shutdown事件回调 - 不要在钩子里调
sys.exit()或os._exit()—— 服务器自己会退出 - 避免在关闭阶段执行长耗时任务(比如上传监控快照),应设超时或降级为异步 fire-and-forget
多线程/多进程场景下,SIGTERM只发给PID 1,子进程/线程不会自动响应
Python 主进程(PID 1)收到 SIGTERM 后,不会自动转发给子线程或 multiprocessing.Process。如果你开了后台线程拉消息、定时刷缓存、或者用 concurrent.futures.ProcessPoolExecutor 做计算,它们会继续跑,直到主进程退出后被系统强制回收 —— 此时没机会执行 atexit 或 __del__ 里的清理逻辑。
正确做法是用共享状态协调退出:
- 主线程收到信号后,设置
threading.Event或asyncio.Event标志位 - 工作线程/进程定期检查该标志,主动退出并清理(如
redis.connection.close()、producer.flush()) - 主进程调用
join(timeout=...)等待,超时则记录告警,不强行kill() - 如果用了
gunicorn,注意它默认把 worker 进程 fork 出来,SIGTERM不会透传到 worker,得靠gunicorn自己的信号转发机制
容器里PID 1不是Python,信号可能根本收不到
很多 Dockerfile 写成 CMD ["python", "app.py"],看似没问题,但实际容器中 python 是 PID 1。问题在于:Linux 对 PID 1 进程有特殊信号处理规则 —— 它不会继承父进程的信号处理器,且某些 init 系统(如旧版 systemd)甚至会屏蔽部分信号。
更糟的是,如果你用了 shell 封装,比如 CMD ["sh", "-c", "python app.py"],那 sh 是 PID 1,而 python 是子进程,SIGTERM 只会发给 sh,不会自动转发给 python。
- 解决方案:用
tini或dumb-init作为容器 entrypoint,它会正确转发信号 - Dockerfile 中加:
ENTRYPOINT ["/sbin/tini", "--"],再CMD ["python", "app.py"] - K8s 中可配
securityContext.runAsNonRoot: true,但别忘了同时给tini加可执行权限 - 验证是否生效:进容器
ps -eo pid,ppid,comm | grep -E "(tini|python)",确认python的 PPID 是tini的 PID
最常被忽略的一点:K8s 的 terminationGracePeriodSeconds 是从 SIGTERM 发出那一刻开始倒计时的,preStop 钩子、应用内清理、连接 draining 全部挤在这段时间里。别假设“反正有30秒”,要实测你的服务从收到信号到彻底 quiet 所需的真实时间,并留出缓冲。


















