HUP信号不能直接重启应用,只能重载配置并替换worker,因Gunicorn的handle_hup()仅读取新配置、启动新worker、通知旧worker退出,不重新导入Python模块或刷新代码;修改app.py后需重建进程才生效。

为什么 HUP 不能直接重启应用,只能重载配置
很多人误以为 kill -HUP 是“热重启”,其实它只触发配置重载和 worker 替换,不重新导入 Python 模块或 reload 应用代码。Gunicorn 的 handle_hup() 函数只做三件事:读取新配置、启动新 worker、通知旧 worker 优雅退出。如果你改了 app.py 本身(比如加了个路由),kill -HUP 完全无效——新 worker 加载的还是旧进程内存里的代码镜像。
常见错误现象:
- 修改 Flask 路由后发
kill -HUP,请求 404 依旧存在 - 日志里看到
[INFO] Hang up: Master,但新功能没生效 - 旧 worker 没及时退出,
ps aux | grep gunicorn显示双倍进程数
真正起效的前提是:--reload 未启用、且你用的是 gunicorn app:application 这类模块路径方式(非 --paste 或 --pythonpath 动态加载)。否则模块缓存不会刷新。
USR2 平滑升级必须满足的三个硬性条件
kill -USR2 看似一键升级,但失败率极高,根本原因常被忽略:它要求新旧二进制完全兼容、配置无破坏性变更、且 master 进程能 fork 出新实例。不满足任一条件,就会卡在“双 master 共存”状态,甚至导致请求 502。
必须检查:
- 新 Gunicorn 二进制是否与旧版 ABI 兼容?比如从
20.1.0升到21.3.0通常安全,但跳过主版本(如20.x→22.x)需查 release notes -
gunicorn.conf.py中是否有已弃用参数?例如preload = True在新版可能被忽略,导致新 master 启动后立即 exit - 磁盘空间是否足够?USR2 会复制整个进程镜像,临时占用双倍内存+少量磁盘(用于
.pid.oldbin)
验证是否成功:查看 /tmp/gunicorn-*.pid(或你配置的 pid 文件路径),应出现两个文件:gunicorn.pid(新 master PID)和 gunicorn.pid.oldbin(旧 master PID)。缺一不可。
TERM + USR2 组合操作才是生产环境的安全升级路径
单独用 kill -USR2 风险太大,真实线上应该走“先升再切”的两步法。USR2 启动新 master 后,必须手动确认新 worker 健康,再发 TERM 给旧 master——这步不能省,否则旧 master 可能持续处理请求数小时(取决于 graceful-timeout)。
标准操作链:
-
kill -USR2 $(cat gunicorn.pid)—— 启动新 master -
sleep 5 && curl -f http://localhost:8000/health—— 等待并验证新服务就绪 -
kill -TERM $(cat gunicorn.pid.oldbin)—— 主动终止旧 master(不是kill -QUIT,后者依赖 graceful-timeout)
关键细节:
-
gunicorn.pid.oldbin文件由新 master 创建,权限继承自原进程用户;若新 master 以不同用户运行(如降权),该文件可能不可读 - 旧 master 的 TERM 处理逻辑在
handle_term(),它会设置self.reexec_pid = None并等待所有 worker exit,比自动超时更可控 - 如果
gunicorn.pid.oldbin不存在,说明 USR2 失败,立刻kill -9 $(cat gunicorn.pid)回滚,不要硬等
nohup 和信号机制完全无关,别混用
nohup 只是屏蔽 SIGHUP 信号,防止终端断开时进程被杀。它对 Gunicorn 的 HUP/USR2 信号毫无影响——因为这些信号是发给 master 进程 PID 的,而 nohup 不改变进程的信号接收能力,只改了初始信号 handler。
典型误用场景:
- 用
nohup gunicorn ... &启动后,再kill -HUP失败:实际是 PID 文件没写或路径错,和nohup无关 - 以为
nohup能让 USR2 升级更“稳定”:完全错误,USR2 依赖父子进程关系,nohup会破坏 fork 层级(它用 double-fork 模式)
真正要保活,靠的是进程管理器(systemd、supervisord)或容器编排(k8s),不是 nohup。后者只适合一次性脚本,不该出现在 Web 服务部署链中。










