在一条公司 VPN 后面的容器里,DNS 能解析,登录页能打开,健康检查接口也有响应,然后一个 HTTPS 请求就卡到超时。重建镜像、改代理变量都没有用,因为出问题的那一段并不在镜像里。客户端用一条通道做认证,用另一条通道搬大数据,坏的只有第二条。

两条通道,一个让人困惑的现象

基于 TLS 的公司客户端通常先走一条普通 TLS 连接完成认证,再为会话协商第二条基于 UDP 的数据通道。这条腿能用时,吞吐和延迟都会变好。它被过滤、或者根本走不通时,就会出现大数据传输之前的步骤全部成功、传输本身却停住的情况,也就是上面那个现象。

容器并不改变这场协商,改变的是外层数据包所走的路径。主机对容器的流做地址转换,于是 UDP 数据报的源端口与客户端本地套接字不同,回包还要经过一条转换记录,而它的超时远短于一条已建立的 TCP 连接。如果隧道在主机上正常、在容器里失败,先对比这两条流,再怀疑应用。这个对比比反复重建镜像更能说明问题。

用实验决定,别靠直觉

最便宜的实验是关掉 UDP 数据通道再发一次请求。要用命令行客户端而不是桌面客户端,开关才会真正生效:openconnect vpn.example.net --user=you --interface=tun0 --no-dtls。如果原来卡住的请求现在能完成,问题就出在 DTLS 这条腿上,其余都是噪音。

在同一个容器里把两个方向都测一遍:带开关发一次大数据请求,不带开关再发一次。跟着开关变化的行为才算证据,页面最后能打开不算。

如果开关确实管用,先判断原因

三种情况会产生同样的现象,应对方式却不同。路径可能完全过滤 UDP,那这个开关就是该网络下正确的长期答案。路径可能允许 UDP 但丢弃大数据报,那指向 MTU 而不是过滤。也可能是路径丢包严重到 UDP 这条腿比 TCP 还差,那是容量问题,不是可达性问题。

在把它写成规避方案之前先区分清楚。过滤对任何包长都成立,MTU 问题只在超过某个阈值时出现,丢包则表现为重传和延迟抖动,而不是干净的卡死。每个测试都在不改客户端配置的前提下缩小原因范围。

保留规避方案之前先检查包大小

如果原因就是包大小,隧道接口会接受路径根本承载不了的包,只要负载变大,任何传输方式都会重演问题。对比两处接口:主机上的 ip link show tun0,再用 docker network inspect bridge 读出网络选项里记录的 MTU。隧道承载更小而容器网桥仍停在 1500 字节,是最常见的发现。

在隧道内用禁止分片的探测从已知可用的包长往上试,然后调低容器网络的 MTU,或者在隧道上钳制 TCP MSS。两种改法往往都能让 UDP 路径重新可用,也就不再需要那个开关,在原因并非过滤时,这是更好的结果。

想清楚你愿意拿什么去换

关掉数据通道后,隧道把负载放在一条 TCP 连接里。路径干净时只是慢一点;路径有丢包时,一个丢掉的报文段会拖住后面所有数据,外层的重传还要和内层的重传抢带宽。这种交换适合交互式开发和小规模 API 调用,不适合大数据传输或移动网络上的长会话。

无论选哪条路,都要记下隧道最终用了哪种传输方式、为什么。一个悄悄依赖几个月前某人加上的开关的容器,就是下一次故障;而接手排查的人不会知道这个开关是为了绕开过滤,还是 MTU 实验留下的残余。

相关文章