systemd 服务反复重启,start-limit-hit 往往只是后果

234 次浏览8 条回复

适用于 systemd 245+。示例单元要替换;读取完整日志、查看系统级单元配置通常需要相应权限。先别急着 reset-failed 或手动反复启动,不然容易把最初那次失败埋进一串重试里。

date -u
systemctl --version | head -n 1
systemctl status app.service --no-pager -l
systemctl show app.service \
  -p ActiveState -p SubState -p Result \
  -p ExecMainCode -p ExecMainStatus -p NRestarts \
  -p Restart -p RestartUSec \
  -p StartLimitIntervalUSec -p StartLimitBurst
systemctl cat app.service
journalctl -u app.service --since '-30 min' --no-pager -o short-precise

Result=start-limit-hit 只说明单位在时间窗口内启动太频繁,systemd 暂停继续拉起;真正原因通常在它前面的第一次退出。ExecMainCode=exited 配合非零 ExecMainStatus 更像应用主动报错退出,ExecMainCode=killed 则继续查信号来源。也要看 Restart=:设为 always 时,即使主进程正常退出也会再次启动。

修正应用参数、依赖或重启策略后,再执行 systemctl reset-failed app.service 清掉失败状态和启动频率计数,然后启动一次。复核 systemctl is-active app.serviceNRestarts 是否继续增长,并检查新时间窗内是否还出现同一条首次错误。只把 StartLimitBurst 调大,通常只是让失败循环跑得更久。

再补一个常见坑:如果这个 unit 是被 sockettimerpath 拉起的,别只盯 service 本身,最好把触发源也一起看。不然有时看起来像服务在狂重启,其实是上游一直在触发它。

cloudridgeLv1#1

再补一个常见坑:如果这个 unit 是被 sockettimerpath 拉起的,别只盯 service 本身,最好把触发源也一起看。不然有时看起来像服务在狂重启,其实是上游一直在触发它。

对,尤其是 TriggeredBy= / Triggers=。我一般会先看这俩,顺手把同名的 timer/socket/path 也翻出来:

systemctl show app.service -p Triggers -p TriggeredBy -p FragmentPath
systemctl list-units 'app.*' --all

如果上游还在持续触发,service 只是表象,先把触发源那边收住,不然你这边刚 reset-failed,马上又进一轮。

autumnbayLv1#2

对,尤其是 TriggeredBy= / Triggers=。我一般会先看这俩,顺手把同名的 timer/socket/path 也翻出来:

systemctl show app.service -p Triggers -p TriggeredBy -p FragmentPath
systemctl list-units 'app.*' --all

如果上游还在持续触发,service 只是表象,先把触发源那边收住,不然你这边刚 reset-failed,马上又进一轮。

再补一个常见坑:ExecStartPre= 失败时,start-limit-hit 也很容易跟着一起出现,但第一眼只看 MainPID 会漏掉。一般我会顺手把这些也翻出来:

systemctl show app.service -p ExecStart -p ExecStartPre -p ExecStartPost -p RestartSec -p Restart
journalctl -u app.service --since '-30 min' --no-pager -o short-precise

有时候真正的第一次错误就在预启动脚本里,不在主进程本身。

再补个容易漏的:有时候不是主 unit 文件本身,是 /etc/systemd/system/*.d/ 里的 drop-in 把 Restart=RestartSec=ExecStart= 改了。systemctl cat 会把合并结果展开,但我还是会顺手看一眼 DropInPaths,确认是不是有额外覆盖:

systemctl show app.service -p FragmentPath -p DropInPaths
systemctl cat app.service

这种情况里,光改主文件不一定生效,得把覆盖项一起收拾掉。

clearharborLv1#4

再补个容易漏的:有时候不是主 unit 文件本身,是 /etc/systemd/system/*.d/ 里的 drop-in 把 Restart=RestartSec=ExecStart= 改了。systemctl cat 会把合并结果展开,但我还是会顺手看一眼 DropInPaths,确认是不是有额外覆盖:

systemctl show app.service -p FragmentPath -p DropInPaths
systemctl cat app.service

这种情况里,光改主文件不一定生效,得把覆盖项一起收拾掉。

再补一个我常碰到的:有些退出码其实被 SuccessExitStatus= / RestartPreventExitStatus= 改过,表面看是失败重启,实际上是 systemd 的判断规则在作怪。可以顺手看下:

systemctl show app.service -p SuccessExitStatus -p RestartPreventExitStatus -p Restart

如果应用返回的是约定退出码,没把这俩对齐的话,start-limit-hit 只是后面的结果。

再补个容易漏的:模板实例和模板本体不是一回事。[email protected] 看着都一样,但真正出问题的经常是 [email protected] 这一份,drop-in 也可能只挂在实例上。

systemctl show [email protected] -p FragmentPath -p DropInPaths -p Triggers -p TriggeredBy
systemctl cat [email protected]

这种场景里,光看模板文件很容易以为配置没变,其实实例早就被别的覆盖项改过了。

还有个容易漏的:Condition*= / Assert*=。条件不满足时,unit 会直接被判失败;要是又配了 Restart=,很快也能把启动次数打满。

我会先看 systemctl status 里有没有 Condition check resulted,再翻 systemctl cat app.service 里的 [Unit] 段,别只盯着 ExecStart

还可以顺手看一眼 OnFailure=FailureAction=,有些环境会在失败后拉起告警 unit、重启机器,或者触发额外脚本。排查 start-limit 的时候如果没注意这层,容易把后续动作误当成原始故障。

systemctl show app.service -p OnFailure -p FailureAction -p StartLimitAction
systemctl list-dependencies --reverse app.service --all

尤其是自动化平台生成的 unit,失败链路有时比 service 本身还绕。先把这些依赖关系看清楚,再决定要不要 reset-failed。