用 systemd 托管一个常驻服务
背景
手上有一个小程序需要长期运行,比如一个监听本地端口的 Python 脚本。直接用
nohup 放到后台虽然能跑,但进程崩溃后不会自动拉起,重启机器后也要手动启动,日志还散落在各处。
现代 Linux 发行版大多使用 systemd 作为初始化系统,把程序交给它管理,就能一次性解决
开机自启、异常重启、统一日志这三个问题。下面以 /opt/hello/app.py 为例。
1. 创建专用用户
服务不需要 root 权限时,就不要用 root 运行。先创建一个不能登录的系统用户:
sudo useradd --system --no-create-home --shell /usr/sbin/nologin hello
sudo chown -R hello:hello /opt/hello
2. 编写 unit 文件
自定义的 unit 文件放在 /etc/systemd/system/ 下,文件名就是服务名:
# /etc/systemd/system/hello.service
[Unit]
Description=Hello demo service
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=hello
Group=hello
WorkingDirectory=/opt/hello
ExecStart=/usr/bin/python3 /opt/hello/app.py
Environment=PORT=8080
Restart=on-failure
RestartSec=5s
# 基础加固
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
PrivateTmp=true
[Install]
WantedBy=multi-user.target
几个关键字段:
ExecStart:必须写绝对路径,systemd 不会读取你的 Shell 环境变量PATH。Restart=on-failure:进程非正常退出时自动重启,RestartSec控制间隔。ProtectSystem=strict:整个文件系统对该服务只读。程序如果需要写文件,用ReadWritePaths=/var/lib/hello单独放行。WantedBy=multi-user.target:决定执行enable时服务挂在哪个启动目标下,普通服务用这个即可。
3. 启动与开机自启
sudo systemctl daemon-reload # 让 systemd 读取新文件
sudo systemctl enable --now hello # 设为开机自启并立即启动
systemctl status hello # 查看运行状态
status 输出里重点看 Active: 这一行,active (running) 表示正常运行;下方还会附带最近几行日志,排错时很有用。
4. 查看日志
程序写到标准输出和标准错误的内容,会被 journald 自动收集:
journalctl -u hello -f # 实时跟踪
journalctl -u hello --since "1 hour ago" # 最近一小时
journalctl -u hello -b # 本次开机以来
journalctl -u hello -p err # 只看错误级别
Python 默认会缓冲标准输出,日志可能迟迟不出现。可以在 unit 中加上
Environment=PYTHONUNBUFFERED=1,或者用 python3 -u 启动。
5. 修改配置的正确姿势
对于发行版软件包自带的服务,不要直接改 /usr/lib/systemd/system/ 下的文件,升级时会被覆盖。推荐使用 drop-in 覆盖:
sudo systemctl edit hello # 打开编辑器,写入要覆盖的片段
sudo systemctl restart hello
保存后会生成 /etc/systemd/system/hello.service.d/override.conf,并自动重新加载配置。需要覆盖 ExecStart 时要先写一行空的 ExecStart= 清空原值,再写新值。用 systemctl cat hello 可以看到合并后的最终配置。
常见问题
| 现象 | 常见原因 |
|---|---|
status=203/EXEC | ExecStart 路径错误、不是绝对路径,或文件没有执行权限。 |
status=217/USER | User= 指定的用户不存在。 |
status=200/CHDIR | WorkingDirectory 不存在或没有访问权限。 |
| 反复重启后停止 | 触发了启动频率限制,修复问题后执行 systemctl reset-failed hello 再启动。 |
| 改了文件但不生效 | 忘记执行 daemon-reload,或者被某个 drop-in 覆盖了。 |
小结
一个常驻服务交给 systemd 管理,最少只需要 [Unit]、[Service]、[Install] 三段配置。养成“专用用户 + 绝对路径 + 自动重启 + 基础加固”的习惯,后续维护会轻松很多。更多字段可以查阅 man systemd.service 和 man systemd.exec。