Django+uWSGI 重启 502:双 master 与取 PID 失灵排障 SOP
背景
给生产站(Django + uWSGI 生产部署)打 API 补丁后需要重启 uWSGI 使代码生效。
第一版补丁脚本因 r''' 里又嵌套了 """ 文档字符串,直接 SyntaxError 失败;
随后重启命令又误用了 ./venv/bin/uwsgi(该文件不存在),结果旧实例被 kill、新实例起不来,站点直接 502。
一、现象:uwsgi 重启 502 的典型现场
补丁打完、执行重启命令,站点返回 502。登录服务器排查,发现 ps 里同时存在两个 uWSGI master:
2434688 ? 12:00:00 uwsgi # 旧实例(仍在服务旧代码),日志 /tmp/uwsgi_new.log
2607020 ? 3:00:00 uwsgi # 刚误起的实例,日志 /var/log/uwsgi/weiserv-new.log
两个 master 同时去绑 weiserv.sock → socket 争抢 / 旧代码仍在服务 → 对外 502。
二、根因:双 master 并存 + 取 PID 命令失灵
坑 1:uwsgi reload 只重载 pidfile 记录的那个实例。
uwsgi --reload pidfile 读 pidfile 里的 PID 去 reload,但真正在对外服务的旧 master 并不在那个 pidfile 里
(它是更早的另一次启动留下的),于是模板/代码改动「看起来没生效」,容易误判为缓存问题。
坑 2:常规取 PID 命令在本机失灵。
$ pgrep -x uwsgi # 时而返回空(本机 PATH 不稳定)
$ ps -C uwsgi # 过滤失效,返回的是全部进程而非 uwsgi
两个「标准」命令都不可靠,手动 kill 极易漏杀,残留 master 继续占着 socket。
三、干净重启 SOP(uwsgi 杀进程 重启 的正确姿势)
唯一可靠的取 PID 方式是按 comm 精确匹配,避开 pgrep / ps -C 的坑:
# 1) 可靠取全部 uwsgi PID(按 comm 精确匹配)
PIDS=$(ps -eo pid,comm | awk '$2=="uwsgi"{print $1}')
# 2) 强杀全部残留(双 master 必须全清,否则 socket 冲突 / 旧代码仍在服务)
kill -9 $PIDS
# 3) 删残留 socket(属组 [部署用户]:www-data 才会被 nginx 访问)
rm -f /home/[部署用户]/wwwroot/weiserv/weiserv.sock
# 4) 单实例重启——用 PATH 上的 uwsgi,勿用 ./venv/bin/uwsgi
cd /home/[部署用户]/wwwroot/weiserv
/usr/local/bin/uwsgi --ini uwsgi_conf.ini --chown [部署用户]:www-data \
--daemonize /var/log/uwsgi/weiserv-new.log
注:
uwsgi_conf.ini中daemonize为禁用状态,日志路径由启动命令行的--daemonize指定(即上行的/var/log/uwsgi/weiserv-new.log)。「现象」段旧实例的/tmp/uwsgi_new.log为更早一次手工启动遗留,非 ini 默认。关键点:永远用
which uwsgi确认的那个二进制(/usr/local/bin/uwsgi),
而不是想当然的./venv/bin/uwsgi。venv 里通常没有 uwsgi 可执行文件。
四、验证
# 进程只剩单 master + workers
$ ps -eo pid,ppid,comm | awk '$3=="uwsgi"'
2609296 ? uwsgi # 单 master
$ ls -l /home/[部署用户]/wwwroot/weiserv/weiserv.sock
srw-rw-r-- 1 [部署用户] www-data ... weiserv.sock
# 站点恢复
$ curl -s -o /dev/null -w '%{http_code}' https://weiserv.com/
200
五、避坑清单
- 补丁脚本别在
r'''里再嵌"""——先本地python -c "compile(...)"或py_compile过一遍语法。 - 重启前先
which uwsgi,确认二进制路径,别用./venv/bin/uwsgi。 - 取 PID 用
ps -eo pid,comm | awk '$2=="uwsgi"',别信pgrep -x/ps -C。 - 双 master 必须
kill -9全清 + 删 sock,再单实例重启,否则 socket 冲突。 - 重启后
curl验 200 再离场,别只看进程起来了。