systemd 服务反复重启:Type=、Restart= 与启动限流的配置逻辑

知识相关1小时前发布 一方
0 00

systemd 服务写完 unit、执行 enable,过一会儿再看状态却是 failed,日志里只有一行「Start request repeated too quickly」。这类问题多半不在程序本身,而在三个开关的配合:Type= 决定何时算作启动成功,Restart= 决定何时再次拉起,启动限流决定拉起的次数上限。本文按「是什么、原理、怎么配、怎么排查」的顺序讲完这三块。

三个开关各自管什么

一个 service 单元的行为可以拆成三段时间。启动阶段由 Type= 判定「起来了没有」;运行阶段由 Restart= 判定「挂了要不要再拉」;而每一次拉起都要经过 StartLimitIntervalSec= 与 StartLimitBurst= 组成的限流闸门。三者串在同一条链上,任何一环理解错,现象都会长得像「程序有 bug」。

先把默认值摆出来,后面的误区大多源于这些默认值:Type= 默认是 simple(在指定 ExecStart= 且未设 BusName= 时),Restart= 默认是 no,RestartSec= 默认 100 毫秒,启动限流默认 10 秒内允许 5 次。

把默认值串起来,就能还原一类常见故障。程序启动即退出,systemd 在 100 毫秒后再次拉起,十秒内连撞五次,限流生效,单元进入 failed,状态行只剩 start-limit-hit。表面看是「服务起不来」,实际是三层默认值叠加的结果。

Type=:什么时候算「启动成功」

Type= 的取值有 simple、exec、forking、oneshot、dbus、notify、notify-reload、idle。它们的差别只在一件事:systemd 在哪一刻认为单元已经起来,并开始启动后续单元。官方在 systemd.service 手册页里逐个给出了定义。

  • simple:主进程 fork 出来就判定启动完成,此时还没执行 execve,可执行文件甚至可能不存在。
  • exec:等主进程真正 exec 成功才算启动,文档明确写着这通常是更合适的选择。
  • oneshot:主进程退出后才算启动完成,适合一次性任务;未写 Type= 与 ExecStart= 时的隐含默认值也是它。
  • notify:等程序通过 sd_notify 发出 READY=1 才算启动完成,适合自己有初始化阶段的守护进程。
  • forking:父进程退出后就算启动完成,文档对该类型的评价是「不建议使用」。

simple 的副作用值得单独说。man 手册写明,simple 服务执行 systemctl start 时会报成功,即便二进制无法执行——比如 User= 指定的账户不存在,或者路径写错。启动日志里看不到报错,只有等到进程真的挂掉,才会暴露出来。
如果程序本身支持 sd_notify,用 Type=notify 可以把「初始化完成」显式告知 systemd。配合 WatchdogSec= 还能识别卡死但未退出的进程,代价是需要程序或在包装脚本里发送通知。

Restart=:七种取值与触发矩阵

Restart= 可取 no、on-success、on-failure、on-abnormal、on-watchdog、on-abort、always。区别在于哪些退出原因会触发重启,下面的表按官方文档的退出原因分类整理。

退出原因alwayson-failureon-abnormalon-aborton-watchdogon-success
干净退出(退出码 0 或 SIGHUP/SIGINT/SIGTERM/SIGPIPE)是否否否否是
非零退出码是是否否否否
异常信号(含核心转储)是是是是否否
操作超时(启动、重载、停止)是是是否否否
看门狗超时是是是否是否
因 OOM 被终止是是是否否否

对长期运行的服务,文档给出的建议是 on-failure。它覆盖非零退出、异常信号、超时、看门狗和 OOM,同时放过「程序自己正常退出」这种情况,避免一个设计上会退出的任务被反复拉起。若服务需要保留自行退出的能力,on-abnormal 更合适。

还有两个例外要记住。systemctl stop 主动停止不会触发重启;RestartPreventExitStatus= 里的退出码或信号也不会触发。反过来,RestartForceExitStatus= 里的状态会被强制重启。Type=oneshot 的服务则直接拒绝 always 与 on-success。

启动限流:为什么改完还是起不来

重启并不是无条件的。每一次启动都会计入限流窗口,默认窗口 10 秒、允许 5 次。超过之后单元进入 failed,日志出现 start-limit-hit,此时再手动 start 也会被拒。这就是「改完配置仍然起不来」的常见原因——不是配置没生效,而是窗口还没过去。限流的定义见 systemd.unit 手册页。

这里有两个容易忽略的细节。其一,限流对「启动」计数,不区分自动重启和手动 start。其二,systemctl reset-failed 会清空该服务的重启速率计数器,管理员想立刻手动拉起时,这是标准动作。

RestartSec= 默认只有 100 毫秒,配合 5 次/10 秒的限流,意味着一个必挂的程序在 0.5 秒内就能把额度用完。要观察真实的重启间隔,可以把 RestartSteps= 与 RestartMaxDelaySec= 一起用上。文档给的例子是 RestartSec=10s、RestartSteps=4、RestartMaxDelaySec=160s,实际间隔依次为 10、20、40、80、160 秒,之后稳定在 160 秒。RestartSteps= 默认 0,即关闭指数退避;RestartMaxDelaySec= 默认 infinity。

一份可套用的模板与验证步骤

下面这份配置把三段职责分开设置,参数值可直接替换成自己的路径与账户。

段落指令取值
[Unit]After= / Wants=network-online.target
[Unit]StartLimitIntervalSec= / StartLimitBurst=60 / 5
[Service]Type=exec
[Service]User= / WorkingDirectory=app / /opt/app
[Service]ExecStart=/opt/app/bin/app --config /etc/app/config.toml
[Service]Restart= / RestartSec=on-failure / 2s
[Service]RestartSteps= / RestartMaxDelaySec=4 / 30s
[Service]TimeoutStopSec=20s
[Install]WantedBy=multi-user.target

落地时按顺序走五步:用 systemd-analyze verify 检查语法;systemctl daemon-reload 让改动生效;systemctl enable --now 启动并设开机自启;systemctl show -p NRestarts -p Result 看重启次数与结果;journalctl -u 服务名 -b 查看本次引导的日志。若 Result 显示 start-limit-hit,先 reset-failed 再排查根因。

托管方式不止 systemd 一种。Windows 上让程序常驻要另走服务注册流程,站内 Syncthing 2.1 上手记录过完整步骤;想长期观察重启次数与资源曲线,可配合 Beszel 轻量服务器监控做告警。涉及网络的服务常在 After= 上出问题,WireGuard 隧道原理与配置那篇可作为启动顺序的参照。

还有一处默认值容易被忽略:KillMode= 默认是 control-group,停止时会清理整个 cgroup 的进程。若改成 process,只杀主进程,子进程可能残留并占着端口,表现为下一次启动报地址已被占用。TimeoutStopSec= 默认 90 秒,超时后才发 SIGKILL,缩短这个值能加快重启节奏。

五个常见误区

  1. 把 Type=simple 的启动成功当成程序正常运行。二进制缺失、User= 不存在时它同样报成功,换成 Type=exec 能提前暴露问题。
  2. 以为 Restart=always 会处理一切。主动 stop 不会重启,oneshot 也不接受 always,需要自行退出的服务更适合 on-failure。
  3. 保留 RestartSec= 默认的 100 毫秒。崩溃循环会瞬间耗尽限流额度,并把日志刷得难以阅读。
  4. 看到 start-limit-hit 就去改程序。先确认是不是限流拦住,reset-failed 后再观察一轮。
  5. 手改 unit 文件却不 daemon-reload。修改不会生效,状态看起来像「配置无效」。

本文面向需要长期托管后台进程的开发与运维人员。判断顺序可以简化成一句话:先确认 Type= 是否如实反映启动,再确认 Restart= 覆盖了你关心的失败类型,然后用限流与退避把重启频率压到可观测的范围内。


相关地址

以下为文中引用的规范与手册页,参数默认值以对应版本的 man 手册为准。

  • systemd.service 手册页(freedesktop.org):https://www.freedesktop.org/software/systemd/man/latest/systemd.service.html
  • systemd.unit 手册页(freedesktop.org):https://www.freedesktop.org/software/systemd/man/latest/systemd.unit.html
  • systemd.kill 手册页(freedesktop.org):https://www.freedesktop.org/software/systemd/man/latest/systemd.kill.html
  • systemd-system.conf 手册页(freedesktop.org):https://www.freedesktop.org/software/systemd/man/latest/systemd-system.conf.html
  • SUSE 知识库:Start request repeated too quickly 排查(suse.com):https://www.suse.com/support/kb/doc/?id=000019750
© 版权声明

相关文章