Supervisor 必须直接 fork/exec 启动 Uvicorn 单进程(--workers 1),禁用所有后台化逻辑,否则会托管 shell 而非实际进程,导致控制失效、日志丢失;配置需指定绝对路径、正确用户权限、killasgroup=true 和 stopsignal=TERM,并通过 numprocs 实现水平扩展。

Supervisor 能管住 Uvicorn,但必须绕开它的 daemon 模式和多进程陷阱——否则进程会失控、日志丢失、重启失败。
为什么不能直接用 uvicorn --daemon 配合 Supervisor?
Uvicorn 本身不支持 --daemon 参数(加了会报错 unrecognized arguments: --daemon),但有人误套用 Gunicorn 习惯,用 nohup 或 & 包裹它再丢给 Supervisor,结果是:Supervisor 管的不是 uvicorn 主进程,而是 shell,导致 supervisorctl stop 失效、autorestart 不触发、日志全丢。
- Uvicorn 进程必须由 Supervisor 直接
fork/exec启动,不能间接托管 - 禁用任何后台化逻辑:删掉
nohup、&、screen,也不要用 systemd 的Type=forking - 确保
command=指向的是可执行文件绝对路径,比如/opt/venv/bin/uvicorn,不是uvicorn(避免 PATH 依赖)
supervisord.conf 里怎么写 Uvicorn 的 program 配置?
关键在三点:用 --workers 1(禁用 uvicorn 自身多进程)、关掉所有信号拦截、把日志收归 Supervisor 管理。以下是最简可用模板:
Uvicorn 0.52.1 是 Python ASGI Web 服务器,提供异步应用服务及 WebSocket 支持,常用于 FastAPI、Starlette 等应用的开发和部署。本下载为官方 PyPI 通用 wheel 包,可使用 pip 安装,并由包管理器处理所需依赖。
[program:myapp] command=/opt/venv/bin/uvicorn --workers 1 --host 0.0.0.0 --port 8000 main:app directory=/opt/myapp user=www-data autostart=true autorestart=true startretries=3 redirect_stderr=true stdout_logfile=/var/log/myapp/access.log stdout_logfile_maxbytes=10MB stdout_logfile_backups=5 stopwaitsecs=10 killasgroup=true stopsignal=TERM
-
--workers 1:强制单 worker,让 Supervisor 控制横向扩展(多实例靠numprocs,不是 uvicorn 内置) -
killasgroup=true+stopsignal=TERM:确保 Ctrl+C 或 stop 命令能真正终止 uvicorn 及其子线程(比如 asyncio 任务) - 不要用
--uds(Unix socket)除非你明确需要 Nginx 代理到 socket;用--host/--port更直白、调试方便 -
user=必须存在且有权限读代码、写日志,别设成 root
启动后 supervisorctl status 显示 STARTING 或 FATAL 怎么办?
常见原因就三个:路径错、权限错、Python 环境错。先看日志:
- 运行
supervisorctl tail myapp stderr,不是翻/var/log/supervisor/supervisord.log - 如果报
ModuleNotFoundError:确认directory=正确,并在command中用虚拟环境里的uvicorn,而非系统全局的 - 如果报
Permission denied:检查user=对应用户是否能读main.py、写access.log所在目录 - 如果卡在 STARTING 超过 10 秒:可能是端口被占,换
--port 8001测试;或 uvicorn 启动时加载模型太慢,调大startsecs=30
想水平扩展多个 Uvicorn 实例,该用 numprocs 还是 --workers?
用 numprocs,别碰 --workers。Uvicorn 的 --workers 是 spawn 模式,每个 worker 是独立 Python 进程,共享不了 asyncio event loop,反而容易引发资源竞争(比如多个 worker 同时写同一个 SQLite 文件)。而 Supervisor 的 numprocs 是真正隔离的进程组,配合 Nginx upstream 能做健康探活和负载分发。
- 正确做法:
numprocs=4+process_name=myapp-%(process_num)02d+ Nginx 配置upstream指向127.0.0.1:8000~127.0.0.1:8003 - 错误做法:留
--workers 4,但numprocs=1—— 这样 Supervisor 只监控一个主进程,worker 崩溃了它根本不知道 - 注意:每个实例要绑定不同端口,或改用
--fd 0+ Unix socket(需配合socket_type=stream和serverurl=unix:///var/run/supervisor.sock)
最易被忽略的一点:Supervisor 不会自动 reload 配置变更,哪怕你改了 ini 文件。每次改完必须手动 supervisorctl reread && supervisorctl update,否则新配置永远不生效——这个动作比重启 supervisord 安全得多,也快得多。
















