深色模式
监控自愈脚本
摘要:自愈脚本能在半夜自动处理「服务挂了」这类明确可恢复的故障,但也可能因为设计不当不断重启掩盖真实问题。本文给出带防抖动、护盾机制和完整留痕的自愈脚本写法,并明确哪些场景不该自愈。
适用环境
bash
which systemctl curl
systemctl is-active nginx || echo "示例服务"
mkdir -p /var/log/ops /var/lib/ops1
2
3
2
3
操作步骤
一、先明确:什么可以自愈,什么不能
| 可以自愈 | 不应该自愈 |
|---|---|
| 进程意外退出 | 配置错误导致反复启动失败 |
| 服务无响应但可重启恢复 | 数据不一致 / 数据库损坏 |
| 磁盘被日志打满(可清理) | 依赖的 downstream 不可用 |
| 临时端口占用 | 需要人工决策的容量问题 |
危险
自愈最大的风险是「掩盖问题」。一个每 5 分钟自动重启的服务,从监控上看一直在重启成功,实际却持续不可用。自愈必须配套告警——自愈动作发生时一定要通知人,而不是悄悄处理掉。
二、检测:用真实业务语义判断
bash
#!/usr/bin/env bash
# selfheal.sh - 服务自愈
set -euo pipefail
SERVICE="${SERVICE:-nginx}"
HEALTH_URL="${HEALTH_URL:-http://127.0.0.1/healthz}"
MAX_RESTART="${MAX_RESTART:-3}" # 时间窗口内最大重启次数
WINDOW_SEC="${WINDOW_SEC:-3600}" # 时间窗口
STATE_DIR="${STATE_DIR:-/var/lib/ops}"
LOG_FILE="${LOG_FILE:-/var/log/ops/selfheal.log}"
mkdir -p "$STATE_DIR" "$(dirname "$LOG_FILE")"
log() { printf '[%s] %s\n' "$(date '+%F %T')" "$*" | tee -a "$LOG_FILE" >&2; }
# 检测 1:进程是否 active
service_active() { systemctl is-active --quiet "$SERVICE"; }
# 检测 2:健康接口是否返回 200(更能代表「真的能服务」)
health_ok() {
local code
code="$(curl -s -o /dev/null -w '%{http_code}' --max-time 5 "$HEALTH_URL" || echo 000)"
[[ "$code" == "200" ]]
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
三、防抖动:连续多次才动作
单次检测失败就重启,容易因为网络抖动误动作。应连续 N 次失败再处理:
bash
check_n_times() {
local n="${1:-3}" interval="${2:-5}" i ok=0
for ((i=1; i<=n; i++)); do
if health_ok; then ok=$((ok+1)); fi
sleep "$interval"
done
# 全部失败才判定异常
(( ok == 0 ))
}1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
四、护盾:限制时间窗口内的重启次数
bash
counter_file="$STATE_DIR/${SERVICE}.restart.count"
restart_count_in_window() {
[[ -f "$counter_file" ]] || { echo 0; return; }
local last ts now
read -r ts last < "$counter_file"
now=$(date +%s)
if (( now - ts > WINDOW_SEC )); then echo 0; else echo "$last"; fi
}
bump_counter() {
local now cnt
now=$(date +%s)
cnt=$(( $(restart_count_in_window) + 1 ))
echo "$now $cnt" > "$counter_file"
}
if (( $(restart_count_in_window) >= MAX_RESTART )); then
log "ALERT $SERVICE 在 ${WINDOW_SEC}s 内已重启 ${MAX_RESTART} 次,停止自愈并告警(需人工介入)"
exit 2
fi1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
注意
没有次数上限的自愈等于「无限重启」。一旦根因是配置错误或依赖不可用,重启永远不会成功,反而持续消耗资源并掩盖故障。达到上限后必须停止动作、转为告警。
五、执行动作与留痕
bash
heal() {
log "WARN $SERVICE 健康检查失败,尝试重启(第 $(( $(restart_count_in_window) + 1 )) 次)"
systemctl restart "$SERVICE"
bump_counter
sleep 10
if health_ok; then
log "INFO $SERVICE 重启后恢复正常"
# 恢复后清零计数
rm -f "$counter_file"
return 0
fi
log "ERROR $SERVICE 重启后仍未恢复"
return 1
}
main() {
if service_active && health_ok; then
log "DEBUG $SERVICE 正常"
exit 0
fi
if check_n_times 3 5; then
heal || exit 1
exit 0
fi
}
main "$@"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
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
六、接入 systemd timer 或 cron
bash
# 每分钟检查一次
* * * * * SERVICE=nginx HEALTH_URL=http://127.0.0.1/healthz /usr/local/bin/selfheal.sh >>/dev/null 2>&1
# 或用 systemd timer(更推荐,有状态与依赖管理)
cat > /etc/systemd/system/selfheal.timer <<'EOF'
[Unit]
Description=Self heal check
[Timer]
OnUnitActiveSec=60s
Unit=selfheal.service
[Install]
WantedBy=timers.target
EOF1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
七、让 systemd 自己也能拉起(第一道防线)
ini
# /etc/systemd/system/your-app.service
[Service]
ExecStart=/usr/bin/your-app
Restart=on-failure
RestartSec=5s
StartLimitIntervalSec=300
StartLimitBurst=5 # 5 分钟内最多重启 5 次,超出后进入 failed1
2
3
4
5
6
7
2
3
4
5
6
7
bash
sudo systemctl daemon-reload
sudo systemctl enable --now your-app
# 查看重启次数与限制状态
systemctl show your-app -p NRestarts -p Result1
2
3
4
2
3
4
自愈脚本应作为「第二道防线」,处理 systemd 无法判断的应用层假死(进程在但不响应)。
验证
- [ ]
systemctl stop服务后,脚本能自动拉起 - [ ] 把
MAX_RESTART设为 1,连续触发两次后脚本退出码为 2 并输出人工介入告警 - [ ] 健康接口返回 500 时脚本判定失败,恢复正常后计数清零
- [ ]
/var/log/ops/selfheal.log中有完整时间线
bash
sudo systemctl stop nginx; sleep 70; systemctl is-active nginx
tail -5 /var/log/ops/selfheal.log1
2
2
常见坑
- 单次失败就重启:网络抖动导致误重启,反而造成真实中断。
- 没有上限:无限重启循环,故障被彻底掩盖。
- 自愈后不告警:团队永远不知道服务半夜挂过,根因无人跟进。
- 检测只看进程:进程在但线程池打满、连接泄漏导致的假死检测不到,必须查业务健康接口。
- 多台机器同时自愈:所有实例一起重启造成雪崩,应加随机延迟或分批。