用 systemd 托管长期运行的服务

比 nohup 和额外的进程管理工具更省心的方式,是用系统自带的 systemd。一个 unit 文件、一条 enable,就能拿到开机自启、崩溃重启和统一日志。

最小可用的 unit

把文件放在 /etc/systemd/system/ 下,文件名就是服务名。下面这个例子启动一个监听 3000 端口的应用。

# /etc/systemd/system/notes-api.service
[Unit]
Description=Notes API
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=notes
Group=notes
WorkingDirectory=/srv/notes
EnvironmentFile=/etc/notes-api.env
ExecStart=/srv/notes/bin/notes-api --port 3000
Restart=on-failure
RestartSec=3
TimeoutStopSec=20

[Install]
WantedBy=multi-user.target

几个字段怎么选

  • Type=simple 适合前台运行、不主动 fork 的程序,绝大多数现代应用都是这一类。传统守护进程用 forking,支持 sd_notify 的用 notify。
  • Restart=on-failure 只在退出码非零或被信号杀死时重启,配置错误导致反复崩溃时不会无限循环。always 更适合必须常驻的网关类服务。
  • EnvironmentFile 用来放密钥和连接串,文件权限设为 600,不要写进 unit 文件本身。
  • User 与 Group 指定运行身份,永远不要用 root 跑业务进程。

顺手加上沙箱选项

systemd 自带一层轻量隔离,成本很低。注意 ProtectSystem=strict 会把整个文件系统设为只读,需要写入的目录必须用 ReadWritePaths 单独放开。

[Service]
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/srv/notes/data
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX
PrivateDevices=true

加上这些选项后先重启服务,确认日志里没有权限错误,再观察一天。某些需要调用外部命令或读取证书的程序可能要额外放开路径。

启动、开机自启与日志

改完 unit 文件必须 daemon-reload,否则 systemd 用的还是旧内容。这是最容易忘的一步。

sudo systemctl daemon-reload
sudo systemctl enable --now notes-api
sudo systemctl status notes-api

journalctl -u notes-api -f
journalctl -u notes-api -n 100 --no-pager
journalctl -u notes-api --since "1 hour ago"

默认日志放在 /run 下,重启就没了。需要长期保留时,把 /etc/systemd/journald.conf 里的 Storage 改为 persistent,并用 SystemMaxUse 限制占用。

[Journal]
Storage=persistent
SystemMaxUse=500M

用 timer 代替 cron

定时任务也交给 systemd,好处是日志和 systemctl list-timers 统一可见。先写一个一次性的 service,再写触发它的 timer。

# /etc/systemd/system/notes-backup.service
[Unit]
Description=Backup notes data

[Service]
Type=oneshot
ExecStart=/usr/local/bin/notes-backup.sh
# /etc/systemd/system/notes-backup.timer
[Unit]
Description=Daily notes backup

[Timer]
OnCalendar=*-*-* 03:30:00
Persistent=true
RandomizedDelaySec=300

[Install]
WantedBy=timers.target
sudo systemctl daemon-reload
sudo systemctl enable --now notes-backup.timer
systemctl list-timers --all

Persistent=true 会补跑关机期间错过的任务,RandomizedDelaySec 则避免所有机器在同一秒集中执行。

常用命令

目的命令
查看状态systemctl status notes-api
重启systemctl restart notes-api
停止并禁止自启systemctl disable --now notes-api
查看依赖关系systemctl list-dependencies notes-api
查看启动耗时systemd-analyze blame

写 unit 文件的时间通常不超过十分钟,但能省掉之后每一次“服务怎么又没了”的排查。值得。