OpenClaw Gateway 健康守护方案

一、背景

OpenClaw Gateway 作为 AI Agent 的核心服务,长期运行难免会遇到进程崩溃、假死、配置异常等问题。本文记录了搭建两级健康守护体系的完整方案。

二、问题分析

Gateway 异常主要分三种情况:

情况 表现 难度
进程崩溃 进程退出,端口释放 简单,systemd 自带
端口不通 进程在但端口未监听 中等
假死卡顿 进程在、端口在,但不响应请求 最难检测

前两种好处理,第三种最头疼——看起来一切正常,实际上已经不能工作了。

三、方案设计

(一)架构总览

采用两级守护,各司其职:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
┌─────────────────────────────────────────────────────┐
│ Gateway 异常 │
├──────────────────────┬──────────────────────────────┤
│ 进程崩溃 │ 进程在但卡死(假死) │
│ (异常退出) │ │
│ │ │ │ │
│ ▼ │ ▼ │
│ systemd │ cron 健康检查脚本 │
│ Restart=always │ 每 30 秒检测一次 │
│ 5 秒内自动拉起 │ │
│ │ 检测项: │
│ │ ├── 端口是否监听 │
│ │ ├── HTTP 是否响应 │
│ │ └── /health 接口是否正常 │
│ │ │ │
│ │ ▼ │
│ │ 指数退避重启 │
│ │ 5s → 10s → 20s → 强制清理 │
└──────────────────────┴──────────────────────────────┘

(二)检测流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
         cron 触发(每 30 秒)


┌─────────────┐
│ 维护模式? │
└──────┬──────┘
是 → 跳过退出
否 │

┌───────────────┐
│ 人为停止? │
│ (inactive) │
└──────┬────────┘
是 → 跳过退出
否 │

┌──────────────┐
│ 端口监听? │──── 否 ──→ 计数+1
└──────┬───────┘
│ 是

┌──────────────┐
│ HTTP 响应? │──── 否 ──→ 计数+1
└──────┬───────┘
│ 是

┌──────────────┐
│ /health OK? │──── 否 ──→ 计数+1
└──────┬───────┘
│ 是

备份配置,重置计数

(三)指数退避重启流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
   连续失败


第 1 次失败 → 等 5 秒 → 重启
第 2 次失败 → 等 10 秒 → 重启
第 3 次失败 → 等 20 秒 → 重启


┌──────────────────┐
│ systemctl stop │
│ 杀端口占用进程 │
│ pkill -9 残留 │
│ lsof 二次清理 │
│ systemctl start │
└──────────────────┘

(四)关键原则

  • 不修改 systemd service 文件——避免引入新的故障点
  • 职责分离——systemd 只管进程崩溃,cron 只管假死检测
  • 尊重人为操作——手动 stop 不会被自动拉起
  • 指数退避——避免连续失败时雪崩式重启

四、部署步骤

(一)创建健康检查脚本

脚本位置:~/.openclaw/scripts/openclaw-health-check.sh

1
2
mkdir -p ~/.openclaw/scripts
vim ~/.openclaw/scripts/openclaw-health-check.sh

完整代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
#!/bin/bash
# ============================================
# OpenClaw Gateway 健康守护脚本 v2
# 由 cron 每 30 秒调用(两条 cron 错开)
#
# 职责:
# - 假死检测(进程在但无响应)
# - 指数退避重启(5s → 10s → 20s → 强制清理)
# - 配置自动备份(每次健康时覆盖)
# - 日志清理(每天凌晨 3 点)
#
# 进程崩溃由 systemd Restart=always 兜底(秒级恢复)
# 本脚本只处理"进程在但卡死"的情况
#
# 维护模式:touch /tmp/openclaw-maintenance
# 紧急停止:crontab -r && systemctl --user stop openclaw-gateway.service && pkill -9 -f openclaw-gateway && fuser -k -9 18789/tcp
# ============================================

PORT=18789
SERVICE="openclaw-gateway.service"
STATE_FILE="/tmp/openclaw-health-state"
LOG_FILE="/tmp/openclaw/health-check.log"
MAINTENANCE_FLAG="/tmp/openclaw-maintenance"
CONFIG_FILE="$HOME/.openclaw/openclaw.json"
CONFIG_BAK="$HOME/.openclaw/openclaw.json.bak"

# 指数退避配置
MAX_FAIL=3

# cron 环境需要 D-Bus session
export XDG_RUNTIME_DIR="/run/user/$(id -u)"
export DBUS_SESSION_BUS_ADDRESS="unix:path=$XDG_RUNTIME_DIR/bus"

mkdir -p /tmp/openclaw

# ---------- 维护开关 ----------
if [ -f "$MAINTENANCE_FLAG" ]; then
exit 0
fi

# ---------- 跳过人为停止(inactive = 手动 systemctl stop) ----------
is_active=$(systemctl --user is-active "$SERVICE" 2>/dev/null)
if [ "$is_active" = "inactive" ]; then
exit 0
fi

log() {
echo "$(date '+%Y-%m-%d %H:%M:%S') $1" >> "$LOG_FILE"
}

# ---------- 读取状态 ----------
read_state() {
if [ -f "$STATE_FILE" ]; then
fail_count=$(grep -o 'fail=[0-9]*' "$STATE_FILE" | cut -d= -f2)
last_fail=$(grep -o 'last=[0-9]*' "$STATE_FILE" | cut -d= -f2)
fail_count=${fail_count:-0}
last_fail=${last_fail:-0}
else
fail_count=0
last_fail=0
fi
}

write_state() {
echo "fail=$1:last=$2" > "$STATE_FILE"
}

# ---------- 核心健康检测 ----------
check_health() {
# 1. 端口检测
if ! ss -tlnp 2>/dev/null | grep -q ":${PORT} "; then
echo "port"
return
fi

# 2. HTTP 探针(绕代理)
http_code=$(curl -s --noproxy '*' -o /dev/null -w "%{http_code}" --max-time 5 http://127.0.0.1:${PORT}/ 2>/dev/null)
if [ "$http_code" = "000" ]; then
echo "http"
return
fi

# 3. /health 接口(假死检测)
health=$(curl -s --noproxy '*' --max-time 10 http://127.0.0.1:${PORT}/health 2>/dev/null)
if ! echo "$health" | grep -q '"ok":true'; then
echo "stuck"
return
fi

echo "ok"
}

# ---------- 指数退避重启 ----------
do_restart() {
local fail=$1
local reason=$2

# 指数退避:5s → 10s → 20s
local wait=$((5 * (2 ** (fail - 1))))
log "[FAIL] ${reason}${wait}秒后重启 (${fail}/${MAX_FAIL})"

sleep "$wait"

# 停止服务
systemctl --user stop "$SERVICE" 2>/dev/null
sleep 2

# 强杀僵尸进程
fuser -k "${PORT}/tcp" 2>/dev/null
sleep 1
fuser -k -9 "${PORT}/tcp" 2>/dev/null
sleep 1
pkill -9 -f "openclaw-gateway" 2>/dev/null
sleep 1

# 确认端口释放
if ss -tlnp 2>/dev/null | grep -q ":${PORT} "; then
log "[FAIL] 端口仍被占用,lsof 清理..."
lsof -ti ":${PORT}" 2>/dev/null | xargs kill -9 2>/dev/null
sleep 1
fi

# 启动服务
systemctl --user start "$SERVICE"
log "[FAIL] 重启完成 (第${fail}次)"
}

# ---------- 主逻辑 ----------
result=$(check_health)

if [ "$result" = "ok" ]; then
# 正常:备份配置 + 重置状态
if [ -f "$CONFIG_FILE" ]; then
cp "$CONFIG_FILE" "$CONFIG_BAK"
fi
read_state
if [ "$fail_count" -gt 0 ]; then
log "[OK] 服务恢复正常"
fi
write_state 0 0
exit 0
fi

# 异常:读取状态
read_state
now=$(date +%s)

# 防抖:30 秒内不重复计数
if [ "$((now - last_fail))" -lt 30 ]; then
exit 0
fi

fail_count=$((fail_count + 1))
write_state "$fail_count" "$now"

if [ "$fail_count" -le "$MAX_FAIL" ]; then
do_restart "$fail_count" "$result"
fi

# ---------- 日志清理(凌晨3点) ----------
HOUR_MIN=$(date +%H:%M)
if [ "${HOUR_MIN:0:2}" = "03" ] && [ "${HOUR_MIN:3:2}" -lt 2 ]; then
find /tmp/openclaw -name "*.log" -mtime +7 -delete 2>/dev/null
find /tmp/openclaw -name "*.log" -size +50M -truncate 2>/dev/null
log "[MAINT] 日志清理完成"
fi

exit 0

(二)赋予执行权限

1
chmod +x ~/.openclaw/scripts/openclaw-health-check.sh

(三)设置 cron 定时任务

设置两条错开的 cron,实现 30 秒检测:

1
crontab -e

添加:

1
2
* * * * * /home/openclaw/.openclaw/scripts/openclaw-health-check.sh
* * * * * sleep 30 && /home/openclaw/.openclaw/scripts/openclaw-health-check.sh

(四)备份原版 systemd service(可选)

1
2
mkdir -p ~/.openclaw/backup
cp ~/.config/systemd/user/openclaw-gateway.service ~/.openclaw/backup/openclaw-gateway.service.bak

五、功能清单

功能 说明
进程崩溃恢复 systemd Restart=always,5 秒内拉起
假死检测 /health 接口探针,30 秒一次
指数退避重启 5s → 10s → 20s,避免频繁重启
僵尸进程清理 端口占用、残留进程强制 kill
人为停止不干预 检测到 inactive 状态直接跳过
配置自动备份 每次健康检查时覆盖备份 openclaw.json
防抖机制 30 秒内不重复计数
维护开关 一键暂停检测
日志自动清理 每天凌晨清理 7 天以上日志

六、常用命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 查看守护日志
cat /tmp/openclaw/health-check.log | tail -10

# 暂停检测(维护时使用)
touch /tmp/openclaw-maintenance

# 恢复检测
rm /tmp/openclaw-maintenance

# 紧急停止一切
crontab -r && systemctl --user stop openclaw-gateway.service && pkill -9 -f openclaw-gateway && fuser -k -9 18789/tcp

# 从备份恢复配置
cp ~/.openclaw/openclaw.json.bak ~/.openclaw/openclaw.json

# 备份原版 systemd service
cp ~/.config/systemd/user/openclaw-gateway.service ~/.openclaw/backup/openclaw-gateway.service.bak

七、测试方法

(一)测试进程崩溃

1
2
3
4
5
# 模拟崩溃(SIGABRT 信号)
kill -6 $(pgrep -f openclaw-gateway)
# 等 5 秒
sleep 5
systemctl --user status openclaw-gateway.service

(二)测试人为停止(不应自动拉起)

1
2
3
4
5
6
7
systemctl --user stop openclaw-gateway.service
# 等 1 分钟,确认没有自动拉起
systemctl --user status openclaw-gateway.service
cat /tmp/openclaw/health-check.log | tail -5

# 测试完手动恢复
systemctl --user start openclaw-gateway.service

(三)测试维护开关

1
2
3
4
5
6
7
touch /tmp/openclaw-maintenance
systemctl --user stop openclaw-gateway.service
# 等 1 分钟,确认 cron 没有自动拉起

# 恢复
rm /tmp/openclaw-maintenance
systemctl --user start openclaw-gateway.service

八、文件清单

文件 说明
~/.openclaw/scripts/openclaw-health-check.sh 健康守护脚本
~/.openclaw/backup/openclaw-gateway.service.bak 原版 systemd service 备份
~/.openclaw/openclaw.json.bak 配置自动备份(每分钟覆盖)
/tmp/openclaw-health-state 失败计数状态
/tmp/openclaw/health-check.log 守护日志
/tmp/openclaw-maintenance 维护开关文件(存在=暂停检测)

九、总结

这个方案的核心理念是简单可靠

  1. 不碰 systemd service——避免引入新的故障点
  2. 职责分离——systemd 管崩溃,cron 管假死
  3. 尊重人为操作——手动 stop 不会被自动拉起
  4. 指数退避——避免连续失败时雪崩式重启
  5. 一键维护——维护开关 + 紧急停止命令

没有花哨的功能,但足够实用。OpenClaw 官方目前也没有内置方案(GitHub Issue #32917 还在讨论中),社区方案本质上也是类似的 cron + 脚本思路,只是多了配置回滚和告警通知等功能。