Windows 容器与 VPN:本地网络为何中断
Windows 容器沿用主机的路由表,因此阻断本地子网的 VPN 会连同容器前缀一起带走。Linux 模式走的是另一条路。
在同一台笔记本上、连接同一个 VPN 客户端时,Linux 容器之间照常通信,Windows 容器却停了。变量不是镜像,而是数据包进入主机的路径。默认网络上的 Windows 容器借用主机的路由表和地址转换,因此任何改变主机路由的策略都会一并改变它们。在重装任何东西之前,先用一次路由查询确认这一点。
Windows 容器的数据包从哪里进入主机
默认 nat 网络上的容器从 Host Networking Service 的前缀中取得地址,通常位于 172.16.0.0/12 之内,并通过虚拟适配器到达主机。该前缀的路由归主机所有,主机把流量转换到当前生效的适配器上。这条路径中间没有独立的虚拟机。
正是这种设计让故障变成机械性的。容器发出数据包时,主机针对容器前缀做一次路由查询。哪条路由获胜,数据包就去哪里,容器对此没有任何发言权。
VPN 客户端改变了这条路径上的什么
企业客户端通常会安装一大批路由,并加一条过滤器,在隧道开启期间拒绝访问本地子网。其意图是防御性的:被攻破的本地网络不应触及受保护的机器。副作用是,主机通往容器前缀的路由不再是本地 NAT 网络。
于是查询命中了隧道,数据包被交给网关,而网关从未听说过那个容器地址。Ping 失败,应用超时,VPN 图标还亮着,容器看起来却坏了。连接状态下查看主机的路由表,那条竞争路由清晰可见。
| Container mode | Outbound path | Typical result behind the VPN |
|---|---|---|
| Windows, default nat | Host routes and translates container traffic | Container prefix follows the tunnel and fails |
| Windows, transparent | Container sits on the physical link | Depends on the local subnet being allowed |
| Linux engine on Docker Desktop | VM egress proxied by a host process | Often 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 工作负载跑在虚拟机代理之后,而 配有自己客户端的一台独立虚拟机 保留自己的策略。要有意识地选一种,并从容器内部测试它,因为用户的请求正是从那里发出的。