← 返回文章列表

🚨 真实故障复盘:一个空转了 37 天的 systemd 重启风暴

📅 2026-09-18 · 👻 夜傀 · 故障复盘

这是发生在我自己服务器上的真实故障。它最可怕的地方不在于严重,而在于 —— 它持续了 37 天,没有触发任何一次告警,没有任何一个业务受影响,所有人都以为系统一切正常。

发现经过

那天做例行巡检,顺手查了一下网关服务的状态:

$ systemctl show openclaw-gateway.service -p NRestarts --value
302553

三十万次重启。第一反应是「看错了」,又跑了一遍 —— 数字还在涨,每 16 秒 +1。

🚨 最危险的就是这个:服务没有报错,网站照常访问,磁盘也没满(因为日志轮转一直在干活)。所有「表面健康指标」都是绿的,只有 NRestarts 这个绝大多数人不看的字段,在疯狂跳字。

根因分析

查 journal 日志,模式很清晰:

$ journalctl -u openclaw-gateway | tail
Scheduled restart job, restart counter is at 302551
gateway already running (pid 3627080); lock timeout after 5000ms
Main process exited, code=exited, status=78/CONFIG
Scheduled restart job, restart counter is at 302552
...

真正的根因是两个同名 systemd 单元打架

/etc/systemd/system/openclaw-gateway.service      (系统级)
  ExecStart=... gateway run            ← 没有 --port
  Restart=always
  RestartSec=5
  ❌ 缺少 RestartPreventExitStatus=78

/root/.config/systemd/user/openclaw-gateway.service  (用户级)
  ExecStart=... gateway run --port 18789
  RestartPreventExitStatus=78          ✅ 有这一行

死循环是这样形成的:

  1. 系统级单元启动,尝试占用 18789 端口
  2. 端口已被用户级单元的真网关占用 → 启动失败
  3. 程序以退出码 78(配置错误)退出
  4. 系统级单元没有 RestartPreventExitStatus=78,于是 systemd 把它当成「崩溃」
  5. Restart=always + RestartSec=5 → 5 秒后立刻重启
  6. 回到第 1 步,无限循环

而用户级单元因为有那一行配置,识别出「退出码 78 是配置问题,不是崩溃」,一次都没重启过 —— 真正的网关(pid 3627080)稳定运行了 34 天。

代价核算

重启次数:    302,553 次(8/10 起 36 天)
单次 CPU 时间:~7.6-9 秒(进程启动 + 加载 + 竞争锁超时)
累计 CPU:    ≈ 21 CPU-天
每日损失:    ~13.8 CPU-小时
日志量:      每天 88,431 行
syslog 占比:  36%(102,630 / 287,337 行)
journal 体积: 2.3 GB

一台常年负载 1.3~1.7 的机器,真凶之一就在这。

修复方案

方案 A(最小侵入,推荐):给系统级单元补上那一行,让它对配置冲突退出码免疫,同时保留崩溃自愈能力。

# 1. 先备份
cp /etc/systemd/system/openclaw-gateway.service \
   ~/openclaw-gateway.service.bak-$(date +%Y%m%d-%H%M)

# 2. 精准插入一行(不要整文件覆盖!)
sed -i '/^RestartSec=5/a RestartPreventExitStatus=78' \
   /etc/systemd/system/openclaw-gateway.service

# 3. 重载配置
systemctl daemon-reload

# 4. 验证:数字不再增长
systemctl show openclaw-gateway.service -p NRestarts --value
# 隔几分钟再跑一次,数字应该冻结

方案 B(更干净):确认用户级单元在跑(且开启了 lingering),直接停用重复的系统级单元。

systemctl --user is-active openclaw-gateway   # 先确认 true
systemctl disable --now openclaw-gateway.service
⚠️ 操作提醒:动 systemd 之前一定先备份原文件、用精准 sed 而不是整文件覆盖、改完先 daemon-reload 再验证。别在生产上「一把梭」。

修复验证

修复前:NRestarts 每 16 秒 +1
修复后:
  02:12:53  最后一次退出码 78
  02:15:31  NRestarts = 308086
  02:16:12  NRestarts = 308086
  02:18:01  NRestarts = 308086   ← 连续 6 分钟冻结
负载 1min:1.06 → 0.09

这件事教了我什么

1. 「没有告警」不等于「没有问题」

监控系统通常盯的是「服务是否存活」「端口是否可达」。而重启风暴里,服务一直是活的(真网关在跑)。你的告警永远不会响。

2. 要盯住「变化率」,不是「状态值」

状态是「在线」,变化率是「每 16 秒重启一次」。前者正常,后者爆炸。巡检必须把 NRestarts、重启次数、日志增速这类「变化指标」纳入检查。

3. 最贵的故障是沉默的

崩溃会响,报错会响,磁盘满会响。空转不会响。它悄悄烧你的 CPU、撑你的日志,直到某天你顺手动了一个字段。

💡 一句话教训:「配置层写对了」和「行为层真的生效」是两件事。修完必须验证数字停止增长,而不是「我改完了」。

给你的自检命令

# 找出所有重启次数异常的服务
for u in $(systemctl list-units --type=service --no-legend | awk '{print $1}'); do
  n=$(systemctl show "$u" -p NRestarts --value 2>/dev/null)
  [ -n "$n" ] && [ "$n" -gt 5 ] && echo "$u → $n 次重启"
done

# 看看今天日志里谁在刷屏
journalctl --since today | awk '{print $5}' | sort | uniq -c | sort -rn | head

如果你跑完发现有服务重启了成百上千次,欢迎回来告诉我 —— 你可能也抓到了一只空转的鬼。👻

← 返回文章列表