VPN 边车的终止开关:它退出时阻断流量
共享 VPN 容器的命名空间并不会在隧道失效时停住应用。要加上明确的出站策略,并且测试重启,而不只是断开。
共享 VPN 容器网络命名空间的应用容器没有自己的网络接口,所以人们很容易以为隧道失败时它无法向外发送任何数据。这个假设在真正重要的故障情形下是错的。如果 VPN 进程死亡而命名空间仍在,应用会保留自己的套接字,命名空间也会保留自己的路由,流量就会沿着仍然安装的那条路径继续走。真正的终止开关是一套明确的过滤策略,加上对运行时生命周期的测试,而不是一个启动依赖。
为什么共享命名空间不是终止开关
共享命名空间意味着应用使用 VPN 容器的接口、地址和路由表。只要共享命名空间完好,它就消除了应用通往网络的独立路径,但它对隧道消失时该命名空间会怎样做只字未提。
Compose 的依赖条件可以把应用的启动推迟到某个依赖健康为止。这只覆盖了时间上的一个瞬间,不覆盖之后的任何故障,所以它是 一种启动排序工具,而不是持续的出站防火墙。
容器退出时实际会发生什么
区分三种事件。容器内的 VPN 进程可能在容器继续运行时死亡。容器可能停止或被删除,这会销毁命名空间。容器可能被重建,这会构建新的命名空间和新的规则。
第一种情况下命名空间继续存在,默认路由可能仍指向物理接口,应用继续直接发送。第二种情况下依赖它的应用通常会完全失去连接,因为它的命名空间消失了。第三种情况下应用可能在隧道就绪之前就重新连接到新的命名空间。每种情况都需要不同的测试,而只覆盖有序断开的方案恰恰覆盖了最不有趣的那一种。
建立明确的出站策略
把策略写在共享命名空间内部,让它作用于应用的报文。形态是默认丢弃加上很窄的例外:loopback、隧道接口,以及隧道传输所需的具体端点地址和端口。
端点例外必须在隧道建立之前就位,否则 VPN 客户端无法到达它的服务器。这就是规则集明确允许外层传输、而不是笼统允许物理接口的原因。如果命名空间具备 IPv6 连通性,还要加上 IPv6 规则,因为只针对 IPv4 的策略会把另一族地址留在敞开状态。
DNS 也属于策略的一部分。如果应用只能通过隧道内部才能访问的解析器解析名称,那么隧道断开时丢弃规则会中断解析,而这正是终止开关应有的行为。请有意识地决定这是否就是你想要的。
测试生命周期,而不只是断开
在隧道健康时发送一个全新的无害请求,然后杀掉 VPN 进程,再发一个。容器停止后、重启后、重建后分别重复一次。全新的请求很重要,因为已有连接可能经受住一次阻止新连接的规则变更。
不要把健康检查或出口 IP 查询当作阻断机制。检查只是观察状态并报告状态;策略才控制流量。两者属于同一次部署,不能互相替代。
记录规则,让测试在更新后依然可用
把每种故障情形的预期结果连同实现它的规则一起写进部署文档。有了这份记录,镜像更新或运行时升级之后就能跑同一套测试,因为命名空间行为或默认路由可能在无人修改 compose 文件的情况下发生变化。
把 VPN 端点必需的外层路径与不受限制的应用出站分开,并把 DNS 和 IPv6 保持在意在覆盖的范围内。如果策略为了方便而允许整个物理接口,终止开关就没了。Docker 的网络文档描述了该策略所依托的共享命名空间安排,当运行时行为存疑时,它就是应当查阅的参考。