Supervisor 到底解决什么问题:当 systemd 不适用时的进程守护
个人站长跑的东西很杂:一个 Python 爬虫要常驻、一个 Node.js 小服务要自启、一个队列消费者断了要自动拉起。很多人第一反应是 systemd,但实际用起来常遇到几个坎——脚本跑在 Docker 容器里没有 systemd、进程是前台交互式的、或者你就想要一个能看日志能一键重启的 Web 界面。这时候 Supervisor 是更顺手的选择。
Supervisor 是一个用 Python 写的进程控制系统,核心能力就三样:把进程变成后台守护进程、进程挂了自动重启、统一管理日志与状态。它比 systemd 直观,配置全在一个 INI 文件里;又比 nohup/screen 靠谱,因为它是真正的守护进程管理器。本文从零开始,在一个 Debian/Ubuntu 服务器上搭一套能用的 Supervisor,并解决几个最常见的坑。
安装:系统包还是 pip,怎么选
Debian/Ubuntu 直接装最省心,会同时帮你配好 systemd 服务单元:
apt update apt install -y supervisor systemctl status supervisor
装完后你会得到两个关键路径:
- 主配置:
/etc/supervisor/supervisord.conf - 子配置目录:
/etc/supervisor/conf.d/(你的每个进程写一个.conf放这里)
如果你在容器里或想用最新版,才用 pip:pip install supervisor,然后手动生成配置 echo_supervisord_conf > /etc/supervisord.conf。但对绝大多数站长,apt 版足够,而且自带开机自启。
写第一个 program 配置块
假设你有一个 Python 脚本 /opt/myapp/worker.py,需要常驻运行。在 /etc/supervisor/conf.d/myapp.conf 里写:
[program:myapp] directory=/opt/myapp command=/opt/myapp/venv/bin/python worker.py autostart=true autorestart=true startsecs=5 startretries=3 user=www-data redirect_stderr=true stdout_logfile=/var/log/supervisor/myapp.out.log stderr_logfile=/var/log/supervisor/myapp.err.log stdout_logfile_maxbytes=50MB stdout_logfile_backups=5 environment=PYTHONUNBUFFERED="1",ENV="prod"
逐行拆解这些参数,它们决定了守护是否可靠:
directory:进程的工作目录,很多脚本用相对路径读文件,配错会直接启动失败。command:必须是绝对路径。Supervisor 不读取你的 shell 环境,PATH 里找不到命令是新人第一大坑。autostart/autorestart:开机自启 + 崩溃自动拉起,这是守护的核心价值。startsecs=5:进程启动后要稳定运行 5 秒才算"启动成功",防止那种秒退的假启动被误判。startretries=3:连续启动失败 3 次后放弃并标记为 FATAL,避免无限重启刷爆日志。user:用非 root 用户运行,安全基线。注意它不会自动创建用户,得先useradd。redirect_stderr=true:把 stderr 合并进 stdout 的日志文件,省一个文件,排查时也不用两头看。PYTHONUNBUFFERED=1:Python 必须加,否则输出被缓冲,日志半天不刷新,你会误以为程序卡死。
加载配置:update 与 reread 的区别
很多人改完配置发现没生效,是因为用错了命令。Supervisor 的配置重载分两步,理解它们的区别很关键:
# 只重新读取磁盘上的配置文件,不改变运行中的进程 supervisorctl reread # 应用改动:启动新增的程序、重启配置变化的程序,并显示发生了什么 supervisorctl update # 查看所有进程状态 supervisorctl status
reread 只是让 Supervisor 知道"文件变了",update 才真正执行变更。正常流程是 reread 接着 update。如果只是改了某个程序的参数(比如日志级别),update 会自动重启它;如果只是新增程序,update 会启动它。状态里几个常见的词要认识:
RUNNING:正常运行,这是你要的。FATAL:启动失败次数超限,一般是命令写错或依赖缺失,去看 err.log。BACKOFF:正在重试启动的过渡态。STOPPED:被手动停了,或autostart=false且还没启。
用 Web 界面统一管理(可选但香)
Supervisor 自带一个 HTTP 管理界面,对不熟悉命令行的站长或者想远程看状态的人很友好。开启方法是在主配置的 [inet_http_server] 段:
[inet_http_server] port=127.0.0.1:9001 username=admin password=你的强密码
注意默认是 127.0.0.1,只能本机访问。千万不要直接开 0.0.0.0 暴露公网——它只是个 HTTP 服务,没有 TLS,暴露出去等于把服务器控制权送人。正确做法是用 Nginx 反代并加 Basic Auth:
location /supervisor/ {
proxy_pass http://127.0.0.1:9001/;
proxy_set_header Host $host;
auth_basic "Restricted";
auth_basic_user_file /etc/nginx/.htpasswd;
}生成密码文件用 htpasswd -c /etc/nginx/.htpasswd admin,没装就 apt install apache2-utils。
日志管理:别让日志把磁盘撑满
Supervisor 不会自动 rotate 日志——stdout_logfile_maxbytes 和 stdout_logfile_backups 就是它的内置轮转机制。设 maxbytes=50MB、backups=5 意味着最多占 250MB 循环覆盖。如果你更习惯系统的 logrotate,也可以关掉内置轮转,改用 /etc/logrotate.d/ 配置。但二选一即可,两个都开容易互相打架。查日志时:
tail -f /var/log/supervisor/myapp.out.log # 或者用 supervisorctl 直接看(会抓最后一段) supervisorctl tail -f myapp stdout
用 eventlistener 做进程崩溃告警
Supervisor 有个很多人不知道的能力:事件监听器。当进程状态变化(如从 RUNNING 变 FATAL、或反复重启)时,Supervisor 会发出事件,你可以写一个监听器脚本,捕获后发通知。对个人站长来说,最实用的就是"进程挂了给我发邮件/发消息"。
主配置里定义监听器:
[eventlistener:crashmail] command=/usr/local/bin/crashmail.py events=PROCESS_STATE_FATAL,PROCESS_STATE_EXITED buffer_size=10
然后写 /usr/local/bin/crashmail.py。事件监听器的协议是:从 stdin 读取头部 ver:3.0 server:supervisor ...,然后回一行 READY\n 表示就绪,Supervisor 再推送事件体,处理完要回 RESULT 2\nOK。简化的脚本骨架:
#!/usr/bin/env python3
import sys, subprocess
def write_stdout(s):
sys.stdout.write(s); sys.stdout.flush()
def write_stderr(s):
sys.stderr.write(s); sys.stderr.flush()
def main():
while True:
write_stdout('READY\n') # 告诉 supervisor 我准备好了
line = sys.stdin.readline() # 读取事件头部
headers = dict(kv.split(':') for kv in line.strip().split(' '))
payload = sys.stdin.read(int(headers['len']))
if 'FATAL' in payload or 'EXITED' in payload:
subprocess.run(['/usr/bin/curl', '-s', '-m', '10',
'-d', 'process down: ' + payload[:200],
'你的告警webhook地址'])
write_stdout('RESULT 2\nOK') # 确认处理完成
if __name__ == '__main__':
main()有了它,你就不必天天盯着 supervisorctl status,进程一崩立刻收到风声。注意监听器脚本自身也必须由 Supervisor 管理(它就是一个 program),否则主进程重启时它也会丢。
资源限制:别让守护进程拖垮服务器
Supervisor 启动的子进程默认继承系统资源限制。如果你的应用有内存泄漏,它可能一路吃到 OOM。可以在 program 段里加限制(需要 Supervisor 较新版本支持 minfds 等):
[program:myapp] command=/opt/myapp/venv/bin/python worker.py minfds=1024 minprocs=200 # 用 systemd-run 包裹来限制内存(更细粒度) # command=/usr/bin/systemd-run --scope -p MemoryMax=512M /opt/myapp/venv/bin/python worker.py
更彻底的做法是配合 systemd-run 给进程加 cgroup 内存上限,这样即使程序泄漏也只会杀掉自己而不是整台机器。对 1G 内存的小 VPS 来说,这条保险非常值。
优雅停止:stopasgroup 与 killasgroup
很多程序会 fork 子进程(比如 Gunicorn、Celery)。如果只停父进程,子进程可能变成孤儿继续跑,占着端口导致下次启动失败。这两个参数就是解药:
[program:celery] command=/opt/app/venv/bin/celery -A app worker stopasgroup=true killasgroup=true stopsignal=TERM stopwaitsecs=30
stopasgroup=true:停止时把信号发给整个进程组,子进程一起收。killasgroup=true:stopwaitsecs 超时后,用 SIGKILL 强杀整组。stopsignal=TERM:先发 TERM 让程序优雅退出,配合stopwaitsecs给它 30 秒收尾。stopwaitsecs=30:等待优雅退出的秒数,处理长任务要调大。
没有这几个参数,Celery 这类挂着子进程的服务会反复出现"端口被占用"的诡异问题,排查起来极费时间。
从 systemd 迁移到 Supervisor 的对照表
如果你以前用 systemd 管理服务,迁移到 Supervisor 只需对照几个概念。下面这张表能帮你快速完成心智转换:
- systemd 的
[Service] ExecStart=→ Supervisor 的command=(都要求绝对路径)。 - systemd 的
Restart=always→ Supervisor 的autorestart=true。 - systemd 的
WorkingDirectory=→ Supervisor 的directory=。 - systemd 的
User=→ Supervisor 的user=(注意不会自动建用户)。 - systemd 的
systemctl status/restart→supervisorctl status/restart 名字。 - systemd 的
systemctl daemon-reload→supervisorctl reread加update。
理解了这个映射,你会发现 Supervisor 并不是 systemd 的替代品,而是它的一个更轻、更集中、更适合"管理一堆异构脚本"的互补工具。两者的日志模型也不同:systemd 默认把日志交给 journald,Supervisor 则自己写文件,所以前面讲的日志轮转配置格外重要。
五个高频故障排查
- 程序起不来,状态 FATAL:99% 是
command路径写错,或者用了相对路径。手动执行一遍那条完整命令,看能不能跑。 - Docker 容器里 Supervisor 不能自启:容器没有 systemd,需在启动脚本或 Dockerfile 的 ENTRYPOINT 里手动拉
supervisord -c /etc/supervisor/supervisord.conf。 - 改了配置 update 后没反应:检查缩进和拼写,Supervisor 的 INI 对空格敏感度不高但对键名严格。用
supervisorctl status确认状态,再看主配置里[include]段是否包含了你的 conf.d 目录。 - 日志文件不更新:Python 没加
PYTHONUNBUFFERED,输出还在缓冲区里。加环境变量重启即可。 - 进程显示 RUNNING 但实际没干活:进程可能卡死但没退出。Supervisor 不管"活着但僵死"的情况,可以加
stopasgroup=true并在程序内部加心跳自检,或者上healthchecks类的外部监控。
总结:什么时候该用 Supervisor
如果你在裸机上跑服务,systemd 通常是更第一优先、更现代的选择。但当你面临容器环境、需要 Web 界面、或管理一堆异构脚本任务时,Supervisor 的集中式配置和直观状态管理会让你省心很多。记住三个核心原则:command 用绝对路径、改配置走 reread + update、Web 界面绝不裸奔公网。把这三条守住,Supervisor 就能成为你服务器上一个安静可靠的"保姆"。