用 systemd 托管 Python 脚本:开机自启、崩溃重启与日志查看
nohup 和 screen 的问题
临时跑一个 Python 脚本,用 nohup python3 job.py & 或 screen 就够了。但这类方式有几个明显短板:机器重启后不会自动拉起,脚本崩了没人管,标准输出散落在 nohup.out 里,时间一长很难查。把它交给 systemd,开机自启、崩溃重启、日志收集三件事一次解决。
一个最小可用的 unit
假设脚本在 /opt/prize-watch/monitor.py,用虚拟环境 /opt/prize-watch/.venv,运行用户是 ubuntu。新建 /etc/systemd/system/monitor.service:
[Unit]
Description=Prize watch monitor
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=ubuntu
WorkingDirectory=/opt/prize-watch
Environment=PYTHONUNBUFFERED=1
ExecStart=/opt/prize-watch/.venv/bin/python /opt/prize-watch/monitor.py --loop
Restart=on-failure
RestartSec=5
TimeoutStopSec=20
[Install]
WantedBy=multi-user.target
几个参数值得说明:Type=simple 适合前台常驻的脚本;Restart=on-failure 只在退出码非 0 或被信号杀掉时重启;RestartSec=5 避免崩溃后立刻反复拉起;PYTHONUNBUFFERED=1 让日志实时进入 journal,而不是卡在缓冲区里。
启用与查看状态
sudo systemctl daemon-reload
sudo systemctl enable --now monitor.service
systemctl status monitor.service
改过 unit 文件后必须再执行一次 daemon-reload,否则 systemd 用的还是旧配置。这一步漏掉,会出现“明明改了却没生效”的情况。
日志用 journalctl 看
journalctl -u monitor.service -n 100 --no-pager
journalctl -u monitor.service -f
journalctl -u monitor.service --since "1 hour ago"
脚本里直接 print 的内容会进入 journal,不需要自己再重定向到文件。如果日志量很大,可以在 unit 里加 StandardOutput=append:/var/log/monitor.log,或者用 journald 的 SystemMaxUse 限制总量。
三个常见坑
- ExecStart 用了相对路径。systemd 不经过 shell,
python3可能找不到,必须写绝对路径。 - 环境变量没有继承。登录 shell 里的
PATH、代理、API Key 不会自动带进来。用Environment=或EnvironmentFile=显式声明,后者权限建议 600。 - 虚拟环境没激活。直接调用
.venv/bin/python,不要依赖source activate,systemd 不会执行 shell 初始化脚本。
定时任务也可以用 timer
如果脚本是“跑一次就退出”,比起常驻循环,更推荐 systemd timer:monitor.timer 里写 OnCalendar=*:0/10,对应的 service 写 Type=oneshot。它和 cron 的区别是自带日志、支持依赖关系和错过补偿(Persistent=true)。
排错顺序
systemctl status看退出码和最后一次日志;journalctl -u看完整报错堆栈;- 手动用同样的用户和绝对路径执行一次
ExecStart; - 确认 unit 改完后执行过
daemon-reload。
把脚本从 nohup 迁到 systemd 只需要十几分钟,之后重启服务器、脚本崩溃都不用人盯着,日志也有统一入口,长期维护成本会低很多。
- 点赞
- 收藏
- 关注作者
评论(0)