Files
ykxmz/relay/watchdog.sh
T
xmz7891 bcb089eded 看门狗:连接数掉到 1 就立刻重启,不等攒够 3 次
排查公网间歇 530 时发现的:
  watchdog 日志显示「公网不通(HTTP 530,ha_connections=1)」
  —— 出问题那段时间隧道**只剩 1 条连接**。

原来 watchdog 只把 CONNS == 0 算"零连接",
CONNS == 1 不当回事。但实测**只剩一条连接时跨机房回源就会失败**
(请求落 SEA,隧道只剩 LAX 一条)。

改法:
  * CONNS <= 1 都计入降级计数
  * 公网不通 **且** 连接数已在危险区 = 确认的故障(不是短抖动)
    -> 立刻重启,不等 3 次

每次检查间隔 20 秒,等 3 次就是白断 40 秒。现在能省下来。

实测:改完公网 15/15 = 100%,连接数 4 条。

顺带说明 watchdog 是怎么跑的:
  systemd timer(lanremote-watchdog.timer),每 20 秒调一次
  watchdog.service。之前查"服务是 inactive"一度以为它没在跑,
  其实是 timer 触发型的 —— oneshot 服务跑完就是 inactive,正常。
2026-10-07 10:00:50 +08:00

155 lines
5.4 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/bin/bash
# 看门狗第三版:两个信号都查。
#
# 为什么还要改:
# 第二版只看 cloudflared 的 ha_connections 指标。
# 但"僵死连接"会把指标维持在 1 —— 计数还在,实际早断了。
# 这种时候看门狗以为一切正常,一直不管。
# (今早就遇到一次:浏览器 1033,指标却说有 1 条连接)
#
# 这一版:
# 信号 A:ha_connections == 0 -> 记一次"零连接"
# 信号 B:公网 /v1/health 打不通 -> 记一次"公网不通"
# 任一信号连续 2 次 -> 重启隧道
# 另外:隧道日志超过 10 分钟没新行 + 公网不通 -> 判定进程卡死,重启
set -u
RELAY_DIR=/root/lanremote-relay
URL_FILE="$RELAY_DIR/public_url.txt"
STATE_FILE="$RELAY_DIR/watchdog.state"
TUNNEL_LOG="$RELAY_DIR/tunnel.log"
METRICS=http://127.0.0.1:20241/metrics
LOG_TAG=lanremote-watchdog
# 连续几次公网不通才重启。
# 设 3(约 60 秒)而不是 2:短抖动让 cloudflared 自己重连就好,
# 频繁重启反而让它一直停在"正在重连"的状态。
THRESHOLD=3
STALE_MINUTES=10 # 日志多久没动静算卡死
# 两次重启之间至少隔这么久(秒)。
# 实测隧道重建只要 30~40 秒,原来设 120 秒太保守 ——
# 真断的时候会干等(日志里出现过"距上次重启才 118 秒,先不重启",
# 结果多断了 2 分钟)。
# 设 45 秒:既够 cloudflared 把连接建起来,又不会干等太久。
MIN_RESTART_GAP=45
log() {
logger -t "$LOG_TAG" "$1"
echo "[$(date '+%m-%d %H:%M:%S')] $1"
}
read_state() {
local key="$1" default="$2" value=""
if [ -f "$STATE_FILE" ]; then
value=$(grep "^$key=" "$STATE_FILE" 2>/dev/null | cut -d= -f2)
fi
[ -n "$value" ] && echo "$value" || echo "$default"
}
write_state() { printf 'zero=%s\nfail=%s\n' "$1" "$2" > "$STATE_FILE"; }
LAST_RESTART_FILE="$RELAY_DIR/watchdog.last_restart"
restart_tunnel() {
local why="$1"
local now last gap
now=$(date +%s)
last=0
[ -f "$LAST_RESTART_FILE" ] && last=$(cat "$LAST_RESTART_FILE" 2>/dev/null || echo 0)
gap=$((now - last))
# 连接数归零 = 真的断了,不是抖动 —— 这种不受间隔限制
local conns_now
conns_now=$(timeout 5 curl -s --max-time 4 "$METRICS" 2>/dev/null \
| grep '^cloudflared_tunnel_ha_connections' | awk '{print $2}')
if [ "${conns_now:-1}" != "0" ] && [ "$gap" -lt "$MIN_RESTART_GAP" ]; then
# 刚重启过还没到间隔:这次先不重启,
# 让 cloudflared 自己把连接建起来(它本来就在重试)。
log "$why —— 但距上次重启才 ${gap} 秒(下限 ${MIN_RESTART_GAP} 秒),先不重启"
write_state 0 0
exit 0
fi
log "$why —— 重启隧道(距上次 ${gap} 秒)"
echo "$now" > "$LAST_RESTART_FILE"
systemctl restart lanremote-tunnel
write_state 0 0
sleep 25
log "重启后地址: $(cat "$URL_FILE" 2>/dev/null || echo '(还没拿到)')"
exit 0
}
ZERO=$(read_state zero 0)
FAIL=$(read_state fail 0)
# ---------- 信号 A:cloudflared 自报的连接数 ----------
CONNS=$(timeout 5 curl -s --max-time 4 "$METRICS" 2>/dev/null \
| grep '^cloudflared_tunnel_ha_connections' | awk '{print $2}')
[ -z "$CONNS" ] && CONNS=""
# ---------- 信号 B:公网真的通吗 ----------
URL=$(cat "$URL_FILE" 2>/dev/null | tr -d '\r\n')
CODE="000"
if [ -n "$URL" ]; then
CODE=$(timeout 20 curl -4 -s -o /dev/null -w '%{http_code}' \
-H 'User-Agent: LanRemote/1.4.0' "$URL/v1/health" 2>/dev/null)
CODE=${CODE:-000}
fi
PUBLIC_OK=0
[ "$CODE" = "200" ] && PUBLIC_OK=1
# ---------- 信号 C:日志僵死了吗 ----------
LOG_STALE=0
if [ -f "$TUNNEL_LOG" ]; then
LAST=$(stat -c %Y "$TUNNEL_LOG" 2>/dev/null || echo 0)
NOW=$(date +%s)
if [ "$LAST" -gt 0 ] && [ $((NOW - LAST)) -gt $((STALE_MINUTES * 60)) ]; then
LOG_STALE=1
fi
fi
# ---------- 判断 ----------
if [ "$PUBLIC_OK" = "1" ]; then
# 公网通就是真的通,计数器清零
if [ "$ZERO" != "0" ] || [ "$FAIL" != "0" ]; then
log "公网恢复(HTTP 200),计数清零(之前 zero=$ZERO fail=$FAIL)"
fi
write_state 0 0
exit 0
fi
# 公网不通 —— 看是哪种原因
DETAIL="HTTP $CODE"
[ -n "$CONNS" ] && DETAIL="$DETAIL,ha_connections=$CONNS"
if [ "$LOG_STALE" = "1" ] && [ "$FAIL" -ge 1 ]; then
restart_tunnel "日志 ${STALE_MINUTES} 分钟没动静加上公网不通(进程卡死)"
fi
FAIL=$((FAIL + 1))
# 连接数 <= 1 也算"降级"。
#
# 原来是 == 0 才算,但实测**只剩 1 条连接时跨机房回源就会失败**
# (用户撞上的 530 就是这种:请求落 SEA,隧道只剩 LAX 一条)。
# 所以 1 和 0 一样危险,都计入降级计数。
DEGRADED=0
if [ -n "$CONNS" ] && [ "$CONNS" -le 1 ] 2>/dev/null; then
ZERO=$((ZERO + 1))
DEGRADED=1
fi
log "公网不通($DETAIL),连续第 $FAIL 次(降级 $ZERO 次)"
# 公网不通 + 连接数已经降到危险区 = **确认的故障**,不是短抖动。
# 这种情况立刻重启,不等攒够 3 次 —— 每次检查间隔 20 秒,
# 等 3 次就是白断 40 秒。
if [ "$DEGRADED" = "1" ]; then
restart_tunnel "公网不通且只剩 ${CONNS} 条隧道连接(确认故障,立即重启)"
fi
if [ "$FAIL" -ge "$THRESHOLD" ] || [ "$ZERO" -ge "$THRESHOLD" ]; then
restart_tunnel "连续 $FAIL 次公网不通(含 $ZERO 次降级)"
fi
write_state "$ZERO" "$FAIL"