systemd多服务并行启动依赖错乱需用after=+requires=组合控制顺序与失败传播;supervisord日志混杂须为各服务单独配置stdout/err日志文件;shell并发启动应改用start-stop-daemon或systemd单元;docker-compose健康检查需配合condition: service_healthy且避免用localhost。

systemd 多服务并行启动时依赖顺序错乱
systemd 默认按 WantedBy=multi-user.target 汇总服务,但「同时启用」不等于「同时就绪」。常见现象是 A 服务已启动,B 服务却因连不上 A 的端口而反复 restart——不是没启动,是启动太快、依赖没 ready。
实操建议:
- 用
After=+Requires=组合,而非只写Wants=;After=控制顺序,Requires=触发失败传播 - 对网络服务(如 redis、postgresql),加
ExecStartPost=/bin/sh -c 'until nc -z %H 6379; do sleep 1; done'做轻量级就绪等待 - 避免在
ExecStart=里写长命令链,systemd 对 shell 解析有限,推荐封装为独立脚本再调用
supervisord 启动多个 Python 服务时 stdout 互相污染
supervisord 把所有子进程的 stdout 和 stderr 默认重定向到同一日志文件,导致多服务日志混在一起,grep 时分不清哪条是 flask 的 500 错误、哪条是 Celery 的连接超时。
实操建议:
- 每个
[program:xxx]段必须显式配置stdout_logfile=/var/log/xxx-out.log和stderr_logfile=/var/log/xxx-err.log - 加上
stdout_logfile_maxbytes=10MB和stdout_logfile_backups=5,防止单个日志无限增长 - 若需实时查看聚合日志,用
tail -f /var/log/xxx-*.log而非依赖 supervisord 自带的supervisorctl tail,后者不保证多路输出时序
shell 脚本并发启动服务时进程失控
写一个 for 循环起 5 个 nohup python app.py & 看似简单,实际会留下孤儿进程、无法统一 stop、信号传不进子进程、ps aux | grep app.py 查到一堆 PID 难以区分。
实操建议:
- 改用
start-stop-daemon:它能记录 PID 文件、自动处理用户权限、支持 –retry 实现优雅停止 - 每个服务单独配 systemd service 文件,哪怕只是临时用,也比裸跑 shell 可控得多;最小化 unit 示例:
[Service] Type=simple User=appuser ExecStart=/usr/bin/python3 /opt/myapp/main.py PIDFile=/run/myapp.pid - 绝对不要在脚本里用
sleep 2来“等前一个启动完”——系统负载高时 2 秒根本不够,且不可靠
docker-compose up -d 启动多容器后健康检查失效
docker-compose.yml 里写了 healthcheck,但 up -d 后立刻 curl 还是 connection refused,原因是 compose 默认不等 healthcheck 成功就返回,depends_on 只管容器启动,不管服务就绪。
实操建议:
-
depends_on必须配合condition: service_healthy才有效,否则形同虚设 - 健康检查命令别用
curl -f http://localhost:8000/health,容器内 localhost 不一定通;改用curl -f http://web:8000/health(web 是服务名) - 生产环境别全靠 compose 做依赖编排,把强依赖逻辑(比如 DB 连接池初始化)移到应用启动阶段,而不是靠外部轮询
真正难的不是让服务“跑起来”,是让它们按需就绪、出问题时有清晰边界、重启时不互相拖垮。这些细节藏在 After= 的写法里、healthcheck 的 URL 里、stdout_logfile 的路径里——漏掉一个,排查时间就翻倍。