用 systemd 把 Python 小服务做成开机自启的守护进程:一份可复用的实践笔记
背景
很多同学在云服务器上跑 Python 小服务时,习惯用 nohup python app.py & 或 screen 挂着。这种方式在临时调试时够用,但一旦服务器重启、进程崩溃或 SSH 断线,服务就会悄悄消失,而且出了问题很难查。本文记录一次把 Python 服务改造成 systemd 托管守护进程的完整过程,覆盖单元文件编写、日志查看、平滑重启和常见坑,适合刚接触 Linux 运维的开发者参考。
为什么选 systemd
systemd 是主流 Linux 发行版默认的初始化系统,它自带进程守护、开机自启、失败重启、日志收集(journald)和资源限制能力。相比自己写 shell 守护脚本,systemd 的好处是配置声明式、状态可查询、日志有统一入口,而且不需要额外安装任何软件。
第一步:准备一个可运行的服务
假设我们有一个最小的 Flask 服务 /opt/demo/app.py:
from flask import Flask
app = Flask(__name__)
@app.get("/healthz")
def healthz():
return {"status": "ok"}
if __name__ == "__main__":
app.run(host="127.0.0.1", port=8000)
建议同时准备一个独立的虚拟环境,避免依赖污染系统 Python:
cd /opt/demo
python3 -m venv .venv
.venv/bin/pip install flask
关键点:服务只监听 127.0.0.1,对外暴露交给 Nginx 或安全组控制,这样即使程序有漏洞也不会直接暴露在公网。
第二步:编写 service 单元文件
新建 /etc/systemd/system/demo.service:
[Unit]
Description=Demo Flask Service
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=demo
Group=demo
WorkingDirectory=/opt/demo
ExecStart=/opt/demo/.venv/bin/python /opt/demo/app.py
Restart=on-failure
RestartSec=3
Environment=PYTHONUNBUFFERED=1
NoNewPrivileges=true
PrivateTmp=true
[Install]
WantedBy=multi-user.target
几个容易忽略的细节:
Type=simple适合前台运行的进程;如果是自己 fork 到后台的老式程序,才需要用forking。Restart=on-failure配合RestartSec能实现崩溃自动拉起,但不要设得太激进,否则会掩盖真实故障。Environment=PYTHONUNBUFFERED=1让 Python 的输出立即刷到 journal,否则日志会延迟出现,排查时很误导。NoNewPrivileges和PrivateTmp是低成本的安全加固,几乎不影响业务。
第三步:启用并验证
sudo systemctl daemon-reload
sudo systemctl enable --now demo.service
sudo systemctl status demo.service
curl -s http://127.0.0.1:8000/healthz
enable 负责写入开机自启的软链接,--now 表示同时立即启动。status 里重点看三件事:Active: active (running)、Main PID 是否存在、最近几行日志有没有异常。
第四步:日志与排错
systemd 服务不要自己写 app.log,直接用 journald 查询:
journalctl -u demo.service -n 100 --no-pager
journalctl -u demo.service -f # 实时跟踪
journalctl -u demo.service --since "10 min ago"
如果想限制日志体积,可以在单元文件里加 SystemMaxUse 相关的 journald 配置,或者给服务加 StandardOutput=journal 明确输出方向。排查"服务起不来"时,先看 systemctl status 的退出码,再看 journalctl 的最后几十行,通常就能定位到是路径写错、权限不足还是端口被占用。
第五步:平滑重启与配置变更
改完代码后:
sudo systemctl restart demo.service
如果只是改了单元文件,记得先 daemon-reload 再 restart,否则新配置不会生效。这是新手最常踩的坑之一。
常见问题清单
- Permission denied:检查
User是否有权限读取/opt/demo,以及虚拟环境路径是否写对。 - 服务启动后马上退出:多半是前台进程被写成了后台运行,或
ExecStart的路径不存在。 - 端口冲突:用
ss -tlnp | grep 8000确认端口占用情况。 - 环境变量丢失:systemd 不会加载
.bashrc,所有变量都要在Environment或EnvironmentFile里显式声明。 - 开机没自启:确认执行过
enable,并且WantedBy=multi-user.target没有被误改。
小结
把 Python 服务交给 systemd 托管,本质上是用平台能力替代手工运维:开机自启、崩溃重启、统一日志、权限收敛,全部通过一个声明式单元文件完成。配置一次之后,后续的部署和排错都会简单很多。建议把单元文件纳入版本管理,和代码一起评审,这样服务配置的变更也能被追溯。
- 点赞
- 收藏
- 关注作者
评论(0)