#!/bin/bash # 1) 给两个隧道实例显式钉死 --metrics 端口 # 2) 验证钉死之后端口↔实例的对应关系不再翻转 # # 问题复盘: # 两个服务的 ExecStart 都没写 --metrics,于是都用 cloudflared 的默认值 20241。 # 正常情况下先起的那个拿到 20241、后起的自动退到 20242, # 看起来"主=20241 副=20242"是稳的 —— 但这是**巧合**,靠的是启动顺序。 # # 实测暴露:跑 A/B 时我把主实例 stop 掉,20241 空出来, # 接着重启副实例,副实例就绑上了 20241。 # 于是"20241 是谁"变成了不确定 —— # watchdog 和所有按端口读指标的脚本全会读错实例。 # # 修法:显式指定,不让它自己选。 set -u MAIN=/etc/systemd/system/lanremote-tunnel.service H2=/etc/systemd/system/lanremote-tunnel-h2.service STAMP=$(date +%s) cp -a "$MAIN" "$MAIN.bak.metrics.$STAMP" cp -a "$H2" "$H2.bak.metrics.$STAMP" pin_metrics() { local f="$1" port="$2" name="$3" python3 - "$f" "$port" <<'PYEOF' import re, sys from pathlib import Path path, port = Path(sys.argv[1]), sys.argv[2] text = path.read_text(encoding="utf-8") # 已有 --metrics 就先去掉 text = re.sub(r"\s*--metrics\s+\S+", "", text) # 在 ExecStart 的 cloudflared tunnel 后面插进去 text = re.sub(r"(ExecStart=/usr/local/bin/cloudflared tunnel)", r"\1 \\\n --metrics 127.0.0.1:" + port, text, count=1) path.write_text(text, encoding="utf-8") PYEOF echo " $name -> 127.0.0.1:$port" } echo "════════ 1. 钉死端口 ════════" pin_metrics "$MAIN" 20241 "lanremote-tunnel(quic)" pin_metrics "$H2" 20242 "lanremote-tunnel-h2(http2)" echo echo " 改后的 ExecStart:" for f in "$MAIN" "$H2"; do echo " ── $(basename "$f") ──" grep -A9 'ExecStart=' "$f" | sed 's/^/ /' done echo echo "════════ 2. 重启并验证 ════════" systemctl daemon-reload systemctl restart lanremote-tunnel lanremote-tunnel-h2 sleep 20 check_map() { for p in $(pgrep cloudflared); do cg=$(cat /proc/$p/cgroup 2>/dev/null | head -1) svc=$(basename "$cg" 2>/dev/null | sed 's/\.service$//') port=$(ss -tlnp 2>/dev/null | grep "pid=$p" | grep -oE '127\.0\.0\.1:2024[0-9]' | head -1) proto=$(tr '\0' ' ' < /proc/$p/cmdline 2>/dev/null | grep -oE '\-\-protocol [a-z0-9]+') echo " $svc -> ${port:-(没绑)} $proto" done } echo " 重启后的对应:" check_map echo echo "════════ 3. 关键验证:停掉主实例,副实例还会不会抢 20241 ════════" systemctl stop lanremote-tunnel sleep 8 systemctl restart lanremote-tunnel-h2 sleep 12 echo " 主实例已停、副实例刚重启后的对应:" check_map echo " (期望:h2 仍然在 20242,不去抢 20241)" echo echo " 恢复主实例:" systemctl start lanremote-tunnel sleep 15 check_map echo echo "════════ 4. 端口各报什么 ════════" for port in 20241 20242; do body=$(curl -s --max-time 4 "http://127.0.0.1:$port/metrics" 2>/dev/null) if [ -z "$body" ]; then echo " 端口 $port: 无响应" continue fi echo " 端口 $port: ha_connections=$(echo "$body" | grep '^cloudflared_tunnel_ha_connections' | awk '{print $2}') quic指标=$(echo "$body" | grep -ci quic)" done echo echo " 备份: $MAIN.bak.metrics.$STAMP $H2.bak.metrics.$STAMP"