Supervisor 部署非容器应用
有些进程天生不适合放进容器:老项目依赖系统里的库、需要在同一台机上按 CPU 核数跑多个 worker、运维同事习惯用 Web 界面点一下重启。Supervisor 就是这类场景的工具——它是用 Python 写的进程管理守护进程,能拉起程序、崩溃自动重启、统一看日志。但能用 systemd 就别装 supervisor:发行版自带、不引入额外 Python 依赖、和 journalctl 打通,除非你确实需要多进程批量管理或 Web 界面。
什么情况下用 Supervisor
| 场景 | 建议 | 理由 |
|---|---|---|
| Ubuntu / Debian / RHEL 等主流发行版 | 用 systemd | 自带、无需额外依赖、日志由 journald 统一管理 |
| CentOS 6 等老系统、无 systemd | 用 Supervisor | 系统没有可用的服务管理器 |
| 容器里需要管多个进程 | 用 Supervisor | 容器 PID 1 不适合直接管一堆子进程 |
| 同一程序要按核数跑多个 worker | 用 Supervisor | numprocs 一行搞定 |
| 运维需要 Web 界面启停进程 | 用 Supervisor | 开 [inet_http_server] 即可 |
安装与目录结构
sudo apt -y install supervisor # Debian / Ubuntu
sudo dnf -y install supervisor # Alibaba Cloud Linux / RHEL 系
sudo systemctl enable --now supervisor
supervisord -v
| 路径 | 用途 | 备注 |
|---|---|---|
/etc/supervisor/supervisord.conf | 主配置 | 末尾通过 [include] 引入应用配置 |
/etc/supervisor/conf.d/*.conf | 每个应用一个文件 | Debian/Ubuntu 的默认引入目录 |
/etc/supervisord.d/*.ini | RHEL 系的引入目录 | 后缀是 .ini,容易踩坑 |
/var/run/supervisor.sock | 控制用 unix socket | supervisorctl 通过它通信 |
主配置里必须确认这一行存在,否则放进 conf.d 的文件不会生效:
[include]
files = /etc/supervisor/conf.d/*.conf
[program:xxx] 关键项
| 配置项 | 含义 | 常用值 |
|---|---|---|
command | 启动命令,必须用绝对路径 | /home/deploy/apps/myapi/.venv/bin/gunicorn ... |
directory | 工作目录 | /home/deploy/apps/myapi/current |
user | 以哪个用户运行 | deploy(绝不要用 root) |
autostart | 随 supervisord 启动 | true |
autorestart | 退出后自动重启 | true;调试时可设 unexpected |
startsecs | 启动后存活多久算成功 | 5(太快会陷入重启循环) |
startretries | 连续失败多少次放弃 | 3 |
stopwaitsecs | 发信号后等多久再 SIGKILL | 30(要给优雅停机留时间) |
stopsignal | 用哪个信号停进程 | TERM;Gunicorn 用 TERM,uWSGI 用 QUIT |
stdout_logfile | 标准输出日志路径 | /var/log/myapi/app.log |
stdout_logfile_maxbytes / _backups | 单文件上限与保留份数 | 50MB / 5 |
redirect_stderr | 把 stderr 并入 stdout | true(日志集中在一个文件里好查) |
environment | 注入环境变量 | APP_ENV="prod",TZ="Asia/Shanghai" |
一个完整配置
# /etc/supervisor/conf.d/myapi.conf
[program:myapi]
command=/home/deploy/apps/myapi/.venv/bin/gunicorn app.main:app -k uvicorn.workers.UvicornWorker -b 127.0.0.1:8000 --access-logfile -
directory=/home/deploy/apps/myapi/current
user=deploy
autostart=true
autorestart=true
startsecs=5
startretries=3
stopwaitsecs=30
stopsignal=TERM
stdout_logfile=/var/log/myapi/app.log
stdout_logfile_maxbytes=50MB
stdout_logfile_backups=5
redirect_stderr=true
environment=APP_ENV="prod",TZ="Asia/Shanghai"
[group] 与 numprocs:一次管多个进程
[program:worker]
command=/home/deploy/apps/myapi/.venv/bin/python -m app.worker
directory=/home/deploy/apps/myapi/current
user=deploy
numprocs=4
process_name=%(program_name)s_%(process_num)02d
autostart=true
autorestart=true
redirect_stderr=true
stdout_logfile=/var/log/myapi/worker.log
[group:myapp]
programs=myapi,worker
numprocs=4 会拉起 worker_00 到 worker_03;[group:myapp] 让 supervisorctl 可以用 myapp:* 一次操作整组进程。
supervisorctl 常用命令
sudo supervisorctl status # 全部程序状态
sudo supervisorctl status myapp:* # 只看某个组
sudo supervisorctl reread # 重新读取配置文件(不改动进程)
sudo supervisorctl update # 应用配置变更(新增/重启/停止)
sudo supervisorctl restart myapp:* # 重启整组
sudo supervisorctl stop myapi # 停止单个程序
sudo supervisorctl tail -f -100 myapi # 跟随查看日志尾部
reread 与 update 的正确姿势
sudo supervisorctl reread && sudo supervisorctl update # 标准姿势,两步都要执行
# 只 reread 不 update:配置被读了但进程没变,属于「改了没生效」的典型原因
sudo supervisorctl status && sudo supervisorctl tail -20 myapi
只有在主配置本身改动时,才需要 sudo systemctl restart supervisor——注意它会重启所有被管理的程序。
为什么 stop 会卡住
- 应用没处理
SIGTERM,进程不退出,只能等满stopwaitsecs后被SIGKILL强杀,正在处理的请求会丢。 stopsignal选错:Gunicorn 需要TERM,uWSGI 需要QUIT,选错等于发了它直接忽略的信号。- 程序自己 fork 了不受管的子进程(例如用 shell 脚本启动且没有
exec),父进程退了子进程还在。
排查顺序:supervisorctl status 看状态 → supervisorctl tail -100 <name> 看应用有没有打印退出日志 → ps -ef | grep <name> 看是否残留进程 → df -h 检查磁盘是否写满(磁盘满了进程会阻塞在写日志上)。
验证方法
sudo supervisorctl status # 期望 RUNNING
ps -ef | grep -v grep | grep myapi # 期望以 deploy 用户运行
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8000/healthz
kill -9 $(sudo supervisorctl pid myapi) && sleep 10 && sudo supervisorctl status # 期望自动拉起
最后一条是核心验证:杀掉进程后能自动恢复,才说明 autorestart 真的生效。
常见坑
| 坑 | 现象 | 做法 |
|---|---|---|
主配置 [include] 没配或路径不对 | 放进 conf.d 的配置完全不生效 | 确认 files = /etc/supervisor/conf.d/*.conf |
RHEL 系用了 .conf 后缀 | 配置被忽略 | RHEL 系默认只读 /etc/supervisord.d/*.ini |
command 里用相对路径 | 报找不到命令 | 写绝对路径,或把 venv 的 bin 放进 environment 的 PATH |
user 没设 | 以 root 跑业务进程,风险极高 | 显式声明 user=deploy |
stopwaitsecs 设得太小 | 收到请求处理一半被 SIGKILL | 按最长请求耗时设置,30~60 秒起步 |
忘了 update | 改了配置没生效,查半天 | 固定套路 reread + update |
| 日志不轮转 | 磁盘写满,进程卡死 | 配 maxbytes + backups,或交给 logrotate |
小结:Supervisor 适合老系统、容器内多进程与需要 Web 界面的场景,主流发行版上应优先用 systemd;配置的关键项是 command(绝对路径)、user、startsecs、stopwaitsecs 与 stopsignal;改完配置固定走 reread + update;stop 卡住先看应用是否忽略信号、再查残留子进程与磁盘,最后用「杀掉进程看是否自动拉起」验证 autorestart 真的生效。