技术指南

systemd 服务重启循环:从退出原因到受控恢复

先止住 systemd 重启循环,再用退出结果、有效 unit、journal、core 与资源证据定位根因,完成受控恢复。

systemd 服务不断重启时,先阻止故障继续放大,再判断每次退出的真正原因。`Restart=` 只决定退出后是否重启,`StartLimitIntervalSec=` 与 `StartLimitBurst=` 只限制启动频率;两者都不能修复配置、权限、依赖、崩溃、watchdog 或资源问题。可靠恢复要把同一时间窗内的 unit 状态、退出结果、有效配置、journal 和业务症状对齐。

先把故障分到一条路径

观察优先解释下一步
Result=exit-code,ExecMainStatus 非零应用主动报错,常见于参数、配置、权限或依赖读取该次退出前后的完整应用日志,并用服务身份核对输入
Result=signal 或出现 core dump进程崩溃、被外部 signal 终止或触发断言保存 signal、可执行文件版本、core 元数据和同窗部署变更
Result=watchdog 或启动/停止 timeout通知协议、事件循环或生命周期钩子未在期限内完成核对 Type=、WatchdogSec=、TimeoutStartSec= 与程序实现
Result=oom-kill 或内核记录 OOMunit 或宿主发生内存处置对齐 cgroup 事件、内核日志和资源边界,不只提高 RestartSec
start request repeated too quickly启动次数撞到 rate limit先找前几次失败;限流是保护结果,不是根因
进程退出码为 0 但仍循环Restart=always/on-success、Type= 或前台/后台模型不匹配核对主进程是否按 unit 类型保持预期生命周期

先保存状态、计数和时间窗

把 example.service 和时间窗换成真实值。较旧 systemd 可能不提供部分属性;缺项要记录为版本差异,不要补写推测值
unit=example.service
stamp=$(date --utc --iso-8601=seconds)
printf 'incident_utc=%s\nunit=%s\n' "$stamp" "$unit"
systemctl status "$unit" --no-pager --full
systemctl show "$unit" \
  --property=ActiveState,SubState,Result,ExecMainCode,ExecMainStatus \
  --property=MainPID,NRestarts,Restart,RestartUSec \
  --property=FragmentPath,DropInPaths
journalctl --unit="$unit" --boot=0 --since='30 minutes ago' \
  --output=short-iso-precise --no-pager

`systemctl status` 适合人工阅读,`systemctl show --property=` 适合保存稳定的键值证据。`Result` 描述最近一次服务结果,`ExecMainCode` 与 `ExecMainStatus` 保留主进程退出方式,`NRestarts` 是当前管理器周期内的重启计数。计数是现场线索,不是长期 SLA;管理器重启或 `reset-failed` 都会改变可观察状态。

读取真正生效的 unit,而不是只看一个文件

cat 会按加载顺序显示主文件和 drop-in;show 的运行时属性通常是规范化后的值,不一定与原始指令逐字相同
systemctl cat "$unit"
systemctl show "$unit" \
  --property=FragmentPath,DropInPaths,User,Group,WorkingDirectory \
  --property=EnvironmentFiles,ExecStart,Type \
  --property=Restart,RestartUSec,StartLimitIntervalUSec,StartLimitBurst

优先检查刚发生变化的层:部署包、drop-in、EnvironmentFile、服务用户、工作目录、路径权限、监听端口、DNS/TLS 和上游依赖。不要通过 `ps e`、`/proc/<pid>/environ` 或工单截图收集全部环境;它们可能含凭据。需要确认某个变量时,只验证名称、来源和是否存在,秘密值留在受控边界内。

确认 Type= 与进程生命周期一致

  1. `Type=simple` 或 `exec` 通常要求 ExecStart 进程保持前台运行;程序自行 daemonize 后主进程退出,systemd 可能把生命周期判断错。
  2. `Type=forking` 依赖传统后台化行为,PID 识别错误会让 systemd 追踪到错误进程;优先使用程序支持的前台模式。
  3. `Type=notify` 需要程序发送 READY=1;设置 WatchdogSec= 后还必须按约定持续发送 watchdog 通知。
  4. oneshot 任务的成功退出就是完成,不应借 `Restart=always` 把一次性动作伪装成长驻服务。

重启策略还会受到 `SuccessExitStatus=`、`RestartPreventExitStatus=` 和 `RestartForceExitStatus=` 影响。先明确哪些退出代表短暂故障、永久配置错误、正常停机和需要人工介入,再选择策略;不要把所有非零退出都无限重试。

把崩溃、watchdog 与 OOM 分开取证

使用真实 UTC 时间窗。coredumpctl 结果可能包含敏感路径和参数;只在授权边界内进一步运行 coredumpctl info 或调试器
since='2026-08-18 22:00:00 UTC'
until='2026-08-18 22:20:00 UTC'
journalctl -u "$unit" --since "$since" --until "$until" \
  --output=short-iso-precise --no-pager
coredumpctl list --since "$since" --until "$until"
journalctl --kernel --since "$since" --until "$until" \
  --output=short-iso-precise --no-pager \
  | grep -Ei 'oom|killed process|segfault|watchdog'

core dump 只证明进程异常终止的一个证据面,不能单独解释触发输入;没有 core 也不证明未崩溃,因为存储、大小、权限或容器边界可能禁用了采集。`Result=watchdog` 要回到程序通知实现和当时阻塞点,`Result=oom-kill` 要与 cgroup `memory.events` 和内核日志交叉验证。

重启策略要限制影响,而不是隐藏失败

设置作用设计问题
Restart=按退出原因决定是否再次启动永久配置错误、正常退出和瞬时依赖故障是否能区分
RestartSec=每次自动重启前等待依赖恢复时间、外部限流和告警响应需要多长
StartLimitIntervalSec=/Burst=限制时间窗内允许的启动次数达到上限后谁接管,是否避免持续冲击依赖
RestartSteps=/RestartMaxDelaySec=较新 systemd 可逐步增加重启间隔目标发行版版本是否支持,最大延迟是否符合恢复预算
RestartPreventExitStatus=指定不应自动重启的退出码或 signal应用是否有稳定、已文档化的永久失败码

上游默认 `RestartSec=` 很短,直接沿用可能形成高频循环。具体等待和次数应来自依赖的恢复特性、任务幂等性、外部配额与告警响应时间。不要禁用 start limit 来追求“高可用”;没有退避和停止条件的自动恢复会把单点错误变成持续事故。

先静态检查,再做一次受控启动

verify 会加载 unit 及其引用并报告文件错误,但不能证明二进制、权限、网络、数据或业务健康。reset-failed 只应在根因修复后使用
sudo systemd-analyze verify /etc/systemd/system/example.service
sudo systemctl daemon-reload
sudo systemctl reset-failed "$unit"
sudo systemctl start "$unit"
systemctl show "$unit" \
  --property=ActiveState,SubState,Result,ExecMainCode,ExecMainStatus,NRestarts
journalctl -u "$unit" --since='5 minutes ago' \
  --output=short-iso-precise --no-pager

`reset-failed` 会同时清除 failed 状态、start rate-limit 计数和服务 restart 计数,因此要在证据保存后执行。一次 `active` 也不等于恢复:某些服务会在启动后才连接依赖、加载数据或进入下一轮崩溃。恢复流量前先执行应用自己的只读健康检查,再逐步放量。

用稳定窗口和业务结果验收

  • 在至少一个真实故障周期内,`NRestarts` 不再增长,Result、退出码和 journal 没有新失败。
  • 真实健康检查、请求错误率、延迟、队列积压和依赖侧限流回到事先定义的范围。
  • 服务使用预期的 FragmentPath、drop-in、用户、工作目录和部署版本,没有从旧文件或临时覆盖启动。
  • CPU、内存、文件描述符、任务数和日志速率没有把故障转移到宿主或依赖。

回滚配置和版本,不回滚证据

变更前保存 unit、drop-in、应用版本和配置管理提交。新版本仍循环时,先 stop,恢复已知可用的应用与配置,执行 daemon-reload 和一次受控 start,再重复同一组业务验收。`systemctl revert` 可能移除本地 override 和 drop-in,只在明确理解发行版 unit 与本地定制边界时使用;更稳妥的是由配置管理精确恢复本次改动。

让下一次失败在失控前可见

监控 restart 计数增量、failed 状态、启动耗时、watchdog、OOM、日志速率和真实业务 SLO,并把发布版本与 unit 配置版本写入同一事件时间线。为瞬时故障设置有限重试、退避和人工接管;为永久配置错误设计明确退出码和不重试路径。发行版或 systemd 升级后重新核对新增指令与运行时属性,不能用 latest 手册替代目标主机版本。

本文命令在 2026-08-18 按 systemd 当前上游 service、unit、exec、systemctl、journalctl、coredumpctl、resource-control 与 systemd-analyze 手册完成语义和语法审阅,没有在读者的生产 unit 或状态型服务上执行。旧 systemd、用户级 unit、容器、发行版补丁和应用自身 supervisor 可能改变属性、路径与恢复顺序;执行前先确认 `systemctl --version`、本机手册和服务数据一致性要求。

返回知识库