systemd 服务化与开机自启

线上进程必须解决三件事:崩溃了自动拉起、机器重启后自动回来、日志有地方统一查看。这三件事 systemd 天生就做得到,而且几乎所有发行版都自带。能用 systemd 就别装 supervisor——少一个 Python 依赖、少一套配置文件、与 journalctl 天然打通,是最省心的选择。

为什么优先用 systemd

维度systemdSupervisor
日志journalctl -u app -f,自带轮转与时间过滤落文件,需自己配轮转
启动依赖After/Wants 声明式排序[group] 与 priority,表达力弱
资源限制LimitNOFILEMemoryMaxCPUQuota 直接写需额外脚本配合
定时任务timer 替代 cron,带补跑与随机延迟不支持

unit 文件放在哪

路径用途注意
/etc/systemd/system/*.service自己写的服务,首选这里优先级最高,daemon-reload 后生效
/usr/lib/systemd/system/*.service发行版包安装的服务不要手改,升级包会覆盖
~/.config/systemd/user/*.service用户级服务,无需 rootsystemctl --user 且开启 enable-linger

unit 文件三段

作用关键项
[Unit]元信息与依赖关系DescriptionAfterWantsStartLimitBurst
[Service]怎么起、怎么停、怎么重启TypeUserExecStartRestartEnvironmentFile
[Install]开机自启WantedBy=multi-user.target

一个完整的 service 示例

# /etc/systemd/system/myapi.service
[Unit]
Description=MyAPI service
After=network-online.target mysql.service
# 新版 systemd 把启动频率限制放在 [Unit],老版本写在 [Service] 里
StartLimitIntervalSec=60
StartLimitBurst=5

[Service]
Type=simple
User=deploy
WorkingDirectory=/home/deploy/apps/myapi/current
EnvironmentFile=/home/deploy/apps/myapi/shared/.env
# 启动前跑一次数据迁移,失败则不会启动新版本
ExecStartPre=/home/deploy/apps/myapi/current/app --migrate
ExecStart=/home/deploy/apps/myapi/current/app --config shared/config.yaml
Restart=always
RestartSec=3
TimeoutStopSec=30
KillSignal=SIGTERM
LimitNOFILE=65535
StandardOutput=journal

[Install]
WantedBy=multi-user.target

关键项逐条解释

配置项含义生产要点
ExecStart启动命令,必须绝对路径不支持管道与重定向,要用就写 sh -c '...'
ExecStartPre启动前执行的命令用于迁移与配置校验,失败则不启动主进程
WorkingDirectory工作目录相对路径的配置与日志都以它为准
User / Group运行身份绝不用 root,配好后确认文件属主正确
EnvironmentFile从文件读环境变量权限 600,只写 KEY=value,不要带 export
Restart=always任何退出都重启on-failure 只在非 0 退出时重启
RestartSec重启前等待秒数3~5 秒,避免疯狂重启刷满日志
StartLimitBurst窗口内最多重启次数超限后进入 failed,需要人工介入
TimeoutStopSec / KillSignal停机等待与信号默认 SIGTERM,等超时后 SIGKILL

常用命令、日志与验证

sudo systemctl daemon-reload          # 改了 unit 文件必须先执行,否则不生效
sudo systemctl enable --now myapi     # 开机自启 + 立即启动
systemctl status myapi --no-pager     # 状态、主 PID、最近日志
systemctl is-enabled myapi && systemctl is-active myapi   # 期望 enabled / active

sudo journalctl -u myapi -f                      # 实时跟随
sudo journalctl -u myapi -n 200 --no-pager       # 最近 200 行
sudo journalctl -u myapi --since "1 hour ago"    # 时间范围
sudo journalctl -u myapi -p err -b               # 本次开机以来的错误日志

curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8000/healthz
# 杀掉进程,验证 Restart=always 真的生效
sudo kill -9 "$(systemctl show -p MainPID --value myapi)" && sleep 5 && systemctl is-active myapi

journal 会无限增长,记得在 /etc/systemd/journald.conf 里设 SystemMaxUse=500M,或定期 sudo journalctl --vacuum-size=500M

优雅停机

sudo systemctl stop myapi
sudo journalctl -u myapi -n 50 --no-pager   # 看应用有没有打印优雅退出日志
time sudo systemctl restart myapi           # 明显快于 TimeoutStopSec,才说明信号被正确处理

停止流程是:给主进程发 SIGTERM,等满 TimeoutStopSecSIGKILL 强杀。默认 KillMode=control-group,整个进程组都会被清理;若应用需要在停机时落盘或结算,务必把超时调够并在代码里注册信号处理。

用 timer 替代 cron

# /etc/systemd/system/backup.service —— 真正执行的任务
[Unit]
Description=Daily database backup
[Service]
Type=oneshot
User=deploy
ExecStart=/home/deploy/apps/myapi/bin/backup.sh
# /etc/systemd/system/backup.timer —— 调度器
[Unit]
Description=Run backup every day at 03:30
[Timer]
OnCalendar=*-*-* 03:30:00
Persistent=true
RandomizedDelaySec=300
[Install]
WantedBy=timers.target
sudo systemctl daemon-reload && sudo systemctl enable --now backup.timer
systemctl list-timers --all | head
sudo journalctl -u backup.service -n 50 --no-pager

Persistent=true 让关机期间错过的任务在开机后补跑一次,RandomizedDelaySec 让多台机器错开执行;比 cron 强在日志进 journal、失败能在 systemctl status 里看到、依赖与环境变量写法与普通服务一致。

常见坑

现象做法
改完 unit 不 daemon-reload配置不生效,改了像没改每次改 unit 都执行一次
ExecStart 用相对路径或带管道启动直接报错绝对路径,或用 sh -c '...'
不写 [Install]enable 失败,开机不自启WantedBy=multi-user.target
EnvironmentFile 里写 export变量解析失败只写 KEY=value,权限 600
Restart=always 但启动即失败疯狂重启刷满日志RestartSecStartLimitBurst
journal 无限增长系统盘被日志写满SystemMaxUse 并定期 vacuum

小结:主流发行版上服务化首选 systemd,unit 按 [Unit][Service][Install] 三段写全,ExecStart 用绝对路径、User 指定普通用户、Restart=alwaysRestartSec 与启动频率限制、日志交给 journal;改完 unit 必须 daemon-reload,用 kill -9 验证自动重启、用 systemctl is-enabled 验证开机自启,定时任务用 timer 替代 cron。

笔记加载中…