Linux 下用 systemd 管理一个 Python 服务的入门实践
把一段 Python 脚本变成开机自启、崩溃可拉起的后台服务,不一定非要上 supervisord 或容器。systemd 在主流发行版里已经是默认初始化系统,学会写一个 unit 文件,日常运维会轻松很多。下面用一个最小可运行的例子走一遍从脚本、单元文件到启停排查的完整路径。
1. 准备一个最简单的服务脚本
先不要直接拿业务代码开刀。用一个只写日志的脚本确认整条链路是通的:
#!/usr/bin/env python3
import logging
import signal
import sys
import time
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s %(levelname)s %(message)s",
stream=sys.stdout,
)
running = True
def handle_stop(signum, _frame):
global running
logging.info("received signal %s, shutting down", signum)
running = False
signal.signal(signal.SIGTERM, handle_stop)
signal.signal(signal.SIGINT, handle_stop)
logging.info("demo service started")
while running:
logging.info("heartbeat")
time.sleep(10)
logging.info("demo service stopped")
把文件放到 /opt/demo-service/app.py,并赋予执行权限:
sudo mkdir -p /opt/demo-service
sudo cp app.py /opt/demo-service/app.py
sudo chmod 755 /opt/demo-service/app.py
注意两点:脚本要用前台循环而不是自己 daemonize;日志打到 stdout,方便 journald 收集。
2. 编写 systemd unit
新建 /etc/systemd/system/demo-py.service:
[Unit]
Description=Demo Python service
After=network.target
[Service]
Type=simple
User=www-data
Group=www-data
WorkingDirectory=/opt/demo-service
ExecStart=/usr/bin/python3 /opt/demo-service/app.py
Restart=on-failure
RestartSec=3
# 把 Python 的 stdout/stderr 交给 journal
Environment=PYTHONUNBUFFERED=1
# 稍作收紧,避免服务进程写到不该写的地方
ProtectSystem=full
PrivateTmp=true
[Install]
WantedBy=multi-user.target
几个容易踩坑的字段:
Type=simple适合一直挂在前台的进程。如果脚本自己 fork 到后台,systemd 会误判它已经退出。User=不要用 root 跑普通业务。提前确认该用户对工作目录有读权限。PYTHONUNBUFFERED=1让日志立即出现在journalctl里,否则你会以为服务没起来。Restart=on-failure只在非零退出时拉起;若希望被kill -9后也自动恢复,可以改成Restart=always。
权限也要同步改好:
sudo chown -R www-data:www-data /opt/demo-service
3. 加载、启动和确认状态
sudo systemctl daemon-reload
sudo systemctl enable --now demo-py.service
systemctl status demo-py.service --no-pager
journalctl -u demo-py.service -f
enable --now 会同时写入开机启动并立即拉起服务。看到 active (running),并且日志里每隔十秒出现 heartbeat,就说明单元已经工作。
常用操作可以记这几条:
sudo systemctl restart demo-py.service
sudo systemctl stop demo-py.service
sudo systemctl disable demo-py.service
journalctl -u demo-py.service -n 100 --no-pager
4. 排障时先看这三处把一段 Python 脚本变成开机自启、崩溃可拉起的后台服务。
服务起不来,先不要改代码。按这个顺序看:
# 单元文件语法是否有误
systemd-analyze verify /etc/systemd/system/demo-py.service
# 最近一次启动失败原因
systemctl status demo-py.service -l --no-pager
journalctl -u demo-py.service -n 50 --no-pager
我实际碰到过三类问题:
ExecStart写成了python3 app.py,工作目录虽然对,但 PATH 里没有虚拟环境,结果直接exit 1。应写绝对路径,或在Environment=PATH=...里补上 venv。- 目录属主还是 root,
User=www-data后脚本无法写运行时文件。ProtectSystem=full会把/usr和/boot变成只读,数据文件应放在/var/lib或服务自己的目录。 - 改完 unit 忘记
daemon-reload,systemd 仍在用旧配置,表现为“我明明改了却不生效”。
如果服务能起来但隔几秒就重启,用 systemctl show demo-py.service -p NRestarts,Result,ExecMainStatus 看退出码,再对照脚本里是否把普通逻辑异常直接 sys.exit(1) 了。
5. 一点可落地的建议
对于只有一个进程的小型 Python 服务,systemd 已经够用:开机自启、失败重启、日志查询都能在系统内完成。等后面要加定时任务,可以再补一个 demo-py.timer;如果要限制 CPU 或内存,给 unit 加上 CPUQuota=、MemoryMax= 即可,不必先上容器。
先把“能启动、能看日志、能自动拉起”这三件事做稳,再把真正的业务脚本替换进去,比一开始就堆很复杂的部署方案更不容易返工。
部署前可以用一台空闲虚拟机先演练一遍:先手动前台运行 python3 /opt/demo-service/app.py,确认脚本本身没有 ImportError,再交给 systemd 托管。两者日志格式不一致时,优先相信 journalctl。等服务稳定后,再把心跳间隔、重启策略按业务调整,避免刚上线就在生产环境里反复试错。
- 点赞
- 收藏
- 关注作者
评论(0)