无法访问企业 VPN 背后任何资源的容器,通常与隧道处在不同的网络命名空间中。宿主机客户端的路由、DNS 服务器和防火墙标记都属于宿主机的网络栈;桥接网络的容器有自己的网络栈。仅这一点就解释了平台团队收到的大部分报告,也解释了为什么「VPN 坏了」和「Docker 绕过了 VPN」可能同时都是错的。同一个症状背后藏着两种完全不同的配置,所以在改动任何设置之前,先确认你属于哪一种。

弄清隧道属于谁的网络命名空间

在 macOS 或 Windows 的 Docker Desktop 上,容器运行在 Linux 虚拟机中。安装在桌面上的 VPN 客户端在桌面操作系统中建立隧道,而虚拟机永远看不到它。任何容器配置都无法修复这一点,因为容器所需的接口在其网络命名空间中根本不存在。

在 Linux 宿主机上,隧道位于宿主机的命名空间中,桥接容器通常经由宿主机的路由表并在出方向应用源 NAT 来访问它。当这条路径失败时,原因往往不是缺少路由,而是转发策略、反向路径过滤或重叠的子网。继续阅读之前,请先确认你处在哪个世界:

docker info --format '{{.OperatingSystem}}'
docker run --rm --net=host alpine ip route | head -5
docker run --rm alpine ip route | head -5

这两张路由表的差异就是问题的全貌。如果宿主机视图中包含隧道接口,而桥接视图中没有,你看到的是命名空间隔离,其余一切都只是选择如何跨越它。

宿主机网络是测试手段,不是设计

用宿主机网络运行容器,Docker 将其记录为共享宿主机的网络命名空间,会让容器直接继承隧道接口及其路由。如果同一个请求随后成功,路由理论得到证实,故障就是命名空间隔离,而不是凭据、DNS 或访问规则。

docker run --rm --net=host curl -sS -o /dev/null -w '%{http_code}\n' https://api.internal/health

把它当作诊断手段。宿主机网络放弃了端口发布以及你希望从容器获得的大部分隔离,而在 Docker Desktop 上「host」指的是虚拟机而不是桌面,所以那里的成败可能误导你,如果你忘了当时在访问哪台宿主机。

tun 设备及其附带权限

容器内的 VPN 客户端需要隧道设备以及配置它的权限。常见的捷径是 --privileged,它授予所有设备并放松整个容器的安全策略。这远远超出本任务所需,在评审中很难站得住脚。

sudo modprobe tun
docker run --rm -it --device /dev/net/tun --cap-add NET_ADMIN myvpn-client

更窄的形式只映射单个设备节点,并且只增加网络管理能力。请先在宿主机上加载模块,使该节点存在。如果容器启动后客户端报告无法打开隧道设备,先检查映射,再怀疑配置;因为这个错误通常是字面意思。

这种设计也意味着客户端按容器分别运行。当每个工作负载需要自己的隧道时,这是正确答案;当你只是想让宿主机的隧道可见时,这是错误答案,而平台团队多半以为你在做前者。

桥接 NAT 改变了 VPN 看到的源地址

在默认桥接下,容器的出站流量会被转换到宿主机地址。因此在 VPN 之后,企业侧看到的是宿主机的隧道地址而不是容器的地址,按源地址的访问规则会表现得像宿主机发出了请求,两个容器对 VPN 服务器来说无法区分。

macvlan 或 ipvlan 改为给容器分配 LAN 地址,这有助于 LAN 发现,并让容器看起来像网段上的对等节点。它也会完全绕过宿主机隧道,因此这类容器无法访问仅限 VPN 的前缀,除非从该 LAN 也能访问 VPN。

两种形态都合理;它们回答不同的需求。如果需求是「这个容器必须访问仅限 VPN 的服务」,你就要在经由宿主机路由和在容器内终止隧道之间做选择。请在部署之前写下这个选择,而不是在第一次请求失败之后。无论选哪种,都要记录远端将看到的源地址,因为它的访问规则和日志显示的正是这个地址。一个说不清该地址的设计还不算完成。

DNS 跟随守护进程,而不是隧道

容器在启动时获得解析器配置,复制自守护进程或镜像。VPN 在连接后推送的地址对那份副本来说来得太晚,而且它们被推送到宿主机的解析器,而不是容器内部。

为该服务显式设置解析器,或将其指向可通过隧道访问的内部解析器。把私有名称解析为公网地址的容器通常使用的是公网解析器,而不是路由损坏,修复应放在 compose 文件或守护进程配置中。

同样的道理适用于出口地址问题。如果每个容器都必须以不同的 VPN 地址出现,单个宿主机隧道无法做到,因为 NAT 把它们合并成一个源。可行的形态是在每个容器的命名空间中运行客户端,或者使用拥有隧道并共享其网络栈的边车,两者都会改变应用的寻址方式。

关闭工单前的检查清单:确认拥有隧道的命名空间,用宿主机网络证明路径,只映射所需的 tun 设备,记住 NAT 之后的源地址,显式设置解析器,并把每个容器的出口地址当作架构决策而不是开关。

相关文章