在同一台笔记本上、连接同一个 VPN 客户端时,Linux 容器之间照常通信,Windows 容器却停了。变量不是镜像,而是数据包进入主机的路径。默认网络上的 Windows 容器借用主机的路由表和地址转换,因此任何改变主机路由的策略都会一并改变它们。在重装任何东西之前,先用一次路由查询确认这一点。

Windows 容器的数据包从哪里进入主机

默认 nat 网络上的容器从 Host Networking Service 的前缀中取得地址,通常位于 172.16.0.0/12 之内,并通过虚拟适配器到达主机。该前缀的路由归主机所有,主机把流量转换到当前生效的适配器上。这条路径中间没有独立的虚拟机。

正是这种设计让故障变成机械性的。容器发出数据包时,主机针对容器前缀做一次路由查询。哪条路由获胜,数据包就去哪里,容器对此没有任何发言权。

VPN 客户端改变了这条路径上的什么

企业客户端通常会安装一大批路由,并加一条过滤器,在隧道开启期间拒绝访问本地子网。其意图是防御性的:被攻破的本地网络不应触及受保护的机器。副作用是,主机通往容器前缀的路由不再是本地 NAT 网络。

于是查询命中了隧道,数据包被交给网关,而网关从未听说过那个容器地址。Ping 失败,应用超时,VPN 图标还亮着,容器看起来却坏了。连接状态下查看主机的路由表,那条竞争路由清晰可见。

Container modeOutbound pathTypical result behind the VPN
Windows, default natHost routes and translates container trafficContainer prefix follows the tunnel and fails
Windows, transparentContainer sits on the physical linkDepends on the local subnet being allowed
Linux engine on Docker DesktopVM egress proxied by a host processOften keeps working

Linux 容器为何表现不同

Linux 引擎把容器跑在一个轻量虚拟机里,从该虚拟机发出的连接由主机侧的代理代表容器打开。套接字由主机发起,因此关于容器前缀的 VPN 规则不会以同样的方式作用在它身上,工作负载得以继续访问网络。

这一差异也解释了 Linux 配方为何不能照搬。需要独立隧道的 Linux 容器会获得 TUN 设备和 NET_ADMIN 能力。Windows 容器不把任何接口暴露为设备,因此那种做法在那一侧没有对应物。

先证明路由,再选方案

证据要从主机收集,而不是从容器收集,因为执行查询的是主机:

docker network inspect nat --format "{{range .IPAM.Config}}{{.Subnet}}{{end}}"
route print -4
Get-NetRoute -AddressFamily IPv4 | Where-Object DestinationPrefix -like "172.16.*"

在 VPN 连接之前跑一次路由输出,连接之后再跑一次。如果第二次输出里容器前缀指向隧道适配器,诊断就完成了,重启容器不会有帮助。

第一个选项是策略决定:向网络所有者询问,能否为这台设备放行本地 LAN 访问。一些企业客户端把它做成设置项,但这会削弱一条刻意的边界。要把它作为有负责人和复核日期的书面例外来申请。一个没人回头检查的单设备开关,正是防御性策略悄悄消失的方式。

若策略必须保留,就搬动工作负载,而不是和路由硬碰硬。带明确子网和网关的透明网络把容器放到本地链路上,Linux 工作负载跑在虚拟机代理之后,而 配有自己客户端的一台独立虚拟机 保留自己的策略。要有意识地选一种,并从容器内部测试它,因为用户的请求正是从那里发出的。

相关文章