mirror of
https://gitee.com/xmz7891/ykxmz.git
synced 2026-10-18 21:50:44 +00:00
用户报 502 Bad Gateway,Cloudflare 的图显示
「Host: relay.xxmxxs.top - Error」,也就是源站/隧道这一跳挂了。
查 watchdog 日志,发现它在**每 50 秒重启一次隧道**:
[10:31:57] 只剩 1 条连接 -> 重启隧道
[10:32:22] 重启完
[10:32:24] 又只剩 1 条 -> 距上次才 27 秒,先不重启
[10:32:47] 距上次 50 秒 -> 又重启
... 循环不停
根因(我上一版的错):
隧道在**质量差的 4G 上重建 4 条连接要 30~60 秒**。
而我上一版把 MIN_RESTART_GAP 设成 45 秒,并且
"公网不通 + 连接数<=1"就立刻重启 ——
结果每次隧道还没把连接建齐,就被下一次重启打断,
**永远停在"只剩 1 条"的状态**。
等于我自己制造了不稳定。
修法:
1. 重启后给 GRACE=150 秒宽限期,这期间绝不重启,
让 cloudflared 专心把连接建齐
2. MIN_RESTART_GAP 从 45 提到 150(和宽限期一致)
3. "立刻重启"的判定加一条:距上次重启必须超过宽限期
实测(停掉 watchdog -> 手动重启隧道 -> 观察):
10 秒内建齐 4 条连接,公网立刻 200
宽限期生效后公网 20/20 = 100%,延迟 ~690ms
顺带一个教训:之前测出 80%、20% 的 000,
是我自己后台还跑着一个观察任务在并发打接口造成的干扰。
**测稳定性的时候,测量工具本身不能成为干扰源。**
169 lines
6.2 KiB
Bash
169 lines
6.2 KiB
Bash
#!/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 把连接建起来,又不会干等太久。
|
||
# 两次重启至少隔这么久。
|
||
# 150 秒不是拍脑袋:隧道在差的 4G 上重建 4 条连接要 30~60 秒,
|
||
# 原来设 45 秒会导致**隧道还没建齐就被下一次重启打断**,
|
||
# 永远停在只剩 1 条连接的状态(实测死循环,每 50 秒重启一次)。
|
||
# 给足宽限期,让它有机会稳定下来。
|
||
MIN_RESTART_GAP=150
|
||
GRACE=150
|
||
|
||
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
|
||
# 宽限期内不重启 —— 让上一次重启有机会把连接建齐。
|
||
# (不加这条会死循环:隧道还没建齐就被再次打断。)
|
||
LAST_R=$(cat "$LAST_RESTART_FILE" 2>/dev/null || echo 0)
|
||
SINCE=$(( $(date +%s) - LAST_R ))
|
||
if [ "$SINCE" -lt "$GRACE" ]; then
|
||
log "公网不通且只剩 ${CONNS} 条连接,但距上次重启才 ${SINCE} 秒(宽限 ${GRACE} 秒)—— 先让它建完"
|
||
else
|
||
restart_tunnel "公网不通且只剩 ${CONNS} 条隧道连接(确认故障,立即重启)"
|
||
fi
|
||
fi
|
||
|
||
if [ "$FAIL" -ge "$THRESHOLD" ] || [ "$ZERO" -ge "$THRESHOLD" ]; then
|
||
restart_tunnel "连续 $FAIL 次公网不通(含 $ZERO 次降级)"
|
||
fi
|
||
|
||
write_state "$ZERO" "$FAIL"
|