2.1.2:隧道稳定性加固(继续修「网站还是容易挂」)

2.1.1 修掉了 nettune 死锁之后,公网上到 200,但**过一阵又 530**。
这次抓到的是隧道自己:

cloudflared 的 precheck 输出(tunnel.log):

    | UDP Connectivity  region1.v2.argotunnel.com  FAIL  QUIC connection failed                      |
    | TCP Connectivity  region1.v2.argotunnel.com  FAIL  HTTP/2 connection is blocked or unreachable |
    | Cloudflare API    api.cloudflare.com:443     PASS  API is reachable                            |
    | ERROR: Allow outbound QUIC traffic on port 7844 or use HTTP2.                                  |

也就是 **443 通、7844 不通**,而且是**间歇性**的(过几分钟自己又好了,
然后再次断)。Cloudflare 隧道的边缘连接走 7844(QUIC 走 UDP 7844、
HTTP/2 走 TCP 7844)。这条线路对它是间歇性阻断 —— 属于运营商那一侧,
设备改不了,但可以让 cloudflared 自己去适应。

== 两个改动 ==

1. **协议从写死改成 auto**
       lanremote-tunnel      --protocol quic    -> --protocol auto
       lanremote-tunnel-h2   --protocol http2   -> --protocol auto
   原来两条实例各赌一个协议:那一刻 7844 的 UDP 和 TCP 都不通就一起死,
   没有退路(实测连接数掉到 1/0、公网 530)。改成 auto 之后
   cloudflared 自己在 QUIC / HTTP/2 之间挑当前能通的那条,
   10 条连接全都有自适应能力。

2. **--retries 5 -> 1000000**
   原来失败 5 次就退出、靠 systemd Restart=always 拉起来,
   每次退出到重启之间网站都是断的。而这条线路的抖动是分钟级的 ——
   实测隧道每 3~10 分钟就被重启一次(19:37 / 19:40 / 19:43 / 19:50 / 19:52),
   公网跟着一起断。拉大之后抖动期间连接自己恢复,整条隧道不退出。

3. 顺手加日志轮转(tunnel.log 已经 2.98 MB,这台机器硬盘不大)。

== 实测 ==

    改前:连接数 1/0,公网 530
    改后:连接数 7+2(接近满配 10),公网连测 5 次全部 200

== 同步 ==

relay/deploy.sh 里的 MAIN_PROTO / ALT_PROTO 也改成 auto、
TUNNEL_BASE 的 --retries 也拉到 1000000,新机器装出来就是加固过的。

全量回归 46 个阶段全部通过。
This commit is contained in:
xmz7891
2026-10-08 20:04:35 +08:00
parent 4e64bc7920
commit 8dc47d0f48
19 changed files with 423 additions and 36 deletions
+162
View File
@@ -0,0 +1,162 @@
# -*- coding: utf-8 -*-
r'''增强隧道稳定性 —— 「网站还是容易挂」的真因修法。
== 查到什么 ==
cloudflared 自己的 precheck 输出(tunnel.log):
| UDP Connectivity region1.v2.argotunnel.com FAIL QUIC connection failed |
| UDP Connectivity region2.v2.argotunnel.com FAIL QUIC connection failed |
| TCP Connectivity region1.v2.argotunnel.com FAIL HTTP/2 connection is blocked or unreachable |
| TCP Connectivity region2.v2.argotunnel.com FAIL HTTP/2 connection is blocked or unreachable |
| Cloudflare API api.cloudflare.com:443 PASS API is reachable |
| ERROR: Allow outbound QUIC traffic on port 7844 or use HTTP2. |
也就是:**443 通,7844 不通**,而且是**间歇性**的
(日志后面又出现 Registered tunnel connection,过一阵又掉)。
Cloudflare 隧道的边缘连接走 7844(QUIC 走 UDP 7844、HTTP/2 走 TCP 7844)。
这条线路对 7844 是间歇性阻断的 —— 这不是设备能修的,
但**可以让 cloudflared 自己去适应**。
== 现在的配置有两个问题 ==
1. **两条实例各写死一个协议**
lanremote-tunnel --protocol quic (只走 UDP 7844)
lanremote-tunnel-h2 --protocol http2 (只走 TCP 7844)
如果那一刻 7844 的 UDP 和 TCP 都不通,两条一起死 —— 没有退路。
实测就是这样:连接数掉到 1/0,公网 530。
2. **--retries 5**
cloudflared 重试 5 次就**退出**,靠 systemd Restart=always 拉起来。
每次退出/重启期间网站都是断的。而这个线路的抖动是分钟级的,
5 次远远不够扛过去。
== 改法 ==
1. `--protocol auto` —— 让 cloudflared **自己**在 QUIC 和 HTTP/2 之间选,
哪条通走哪条。两条实例都改成 auto,等于 10 条连接全都能自适应。
(而不是现在这样"8 条赌 UDP、2 条赌 TCP")
2. `--retries` 提到很大(相当于"一直重试,别退出")——
抖动期间连接会自己恢复,不会整条隧道退出让网站断掉。
3. 顺手加日志轮转:tunnel.log 已经涨到 2.98 MB,
这台机器只有 460MB 内存 + 小硬盘,无限增长不合适。
'''
from __future__ import annotations
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(ROOT / "_build"))
from devssh import run # noqa: E402
def patch_unit(name: str, proto: str) -> str:
"""读设备上现有的 unit,改协议和重试次数。
用 sed 现场改而不是整份重写 —— 现有 unit 里还有
metrics 端口、ha-connections、MemoryMax 这些实测调出来的参数,
整份重写容易漏掉。
"""
cmds = [
# 协议
"sed -i 's/--protocol [a-z0-9]*/--protocol %s/' %s" % (proto, name),
# 重试次数:改成很大(一直重试)
"sed -i 's/--retries [0-9]*/--retries 1000000/' %s" % name,
]
return " && ".join(cmds)
def main() -> int:
print("=" * 62)
print(" 隧道稳定性:协议改 auto + 加大重试 + 日志轮转")
print("=" * 62)
UNIT_DIR = "/etc/systemd/system"
print()
print(" ── 1. 两条隧道都改成 --protocol auto ──")
out = run(
"cd %s && "
"sed -i 's/--protocol [a-z0-9]*/--protocol auto/' "
"lanremote-tunnel.service lanremote-tunnel-h2.service && "
"echo ' tunnel:'; grep -o -- '--protocol [a-z0-9]*' "
"lanremote-tunnel.service; "
"echo ' h2:'; grep -o -- '--protocol [a-z0-9]*' "
"lanremote-tunnel-h2.service; "
# 重试次数
"sed -i 's/--retries [0-9]*/--retries 1000000/' "
"lanremote-tunnel.service lanremote-tunnel-h2.service && "
"echo ' 重试:'; grep -o -- '--retries [0-9]*' "
"lanremote-tunnel.service lanremote-tunnel-h2.service"
% UNIT_DIR, timeout=120)
for ln in out[1].strip().splitlines():
print(" " + ln)
print()
print(" ── 2. 日志轮转(tunnel.log 已经 2.98 MB)──")
out = run(
"cat > /etc/logrotate.d/lanremote <<'EOF'\n"
"/root/lanremote-relay/*.log {\n"
" size 5M\n"
" rotate 2\n"
" compress\n"
" missingok\n"
" notifempty\n"
" copytruncate\n"
"}\n"
"EOF\n"
"echo ' logrotate 配置已写'; "
"logrotate --debug /etc/logrotate.d/lanremote 2>&1 | tail -3",
timeout=120)
for ln in out[1].strip().splitlines():
print(" " + ln)
print()
print(" ── 3. 重启两条隧道让新参数生效 ──")
out = run(
"systemctl daemon-reload && "
"systemctl restart lanremote-tunnel lanremote-tunnel-h2 && "
"echo ' 等 40 秒让连接建立…' && sleep 40 && "
"echo \" tunnel: $(systemctl is-active lanremote-tunnel)\"; "
"echo \" h2: $(systemctl is-active lanremote-tunnel-h2)\"",
timeout=300)
for ln in out[1].strip().splitlines():
print(" " + ln)
print()
print(" ── 4. 连接数 ──")
out = run(
"for p in 20241 20242; do "
"n=$(curl -s --max-time 5 http://127.0.0.1:$p/metrics 2>/dev/null "
"| grep '^cloudflared_tunnel_ha_connections' | awk '{print $2}' "
"| head -1); "
"echo \" $p -> ${n:-拿不到}\"; done; "
"echo ' --- 用的什么协议 ---'; "
"grep -o 'protocol=[a-z0-9]*' /root/lanremote-relay/tunnel.log 2>/dev/null "
"| tail -6 | sort | uniq -c | sed 's/^/ /'", timeout=120)
for ln in out[1].strip().splitlines():
print(" " + ln)
print()
print(" ── 5. 公网(连测 5 次看稳不稳)──")
out = run(
"for i in 1 2 3 4 5; do "
"c=$(curl -s -o /dev/null -w '%{http_code}' --max-time 15 "
"https://relay.xxmxxs.top/v1/health); "
"echo \" 第 $i 次: $c\"; sleep 3; done", timeout=200)
for ln in out[1].strip().splitlines():
print(" " + ln)
print()
print(" 说明:3844/7844 那条线路封不封是运营商的事,设备改不了。")
print(" 但 --protocol auto 让 cloudflared 自己绕,")
print(" --retries 拉大让它别退出 —— 抖动期间能自己恢复。")
return 0
if __name__ == "__main__":
sys.exit(main())