Kubernetes 中 VPN 客户端 IP:保留还是做 SNAT?
在 Kubernetes 中根据三个条件在保留真实 VPN 客户端地址和源 NAT 之间选择,然后验证回程路径与 pod 更替。
在隧道和 backend 之间的某处,客户端地址要么存活,要么被替换,两种结果都站得住脚。这个选择属于接收服务的一方,而不属于安装 CNI 的人。当按客户端的授权、配额或审计记录重要时,地址必须在每一跳都保留下来。当共享归因可以接受时,源 NAT 会消除一个你否则将永远维护的路由问题。
客户端地址究竟在哪里消失
远程客户端的数据包以隧道范围内的内部源,比如 10.90.0.42,到达 VPN pod。当 pod 把该数据包转发到 ClusterIP 或 pod IP 时,三种不同的机制会改写源。节点上的 CNI masquerade 规则覆盖 pod 发往集群 CIDR 之外的流量。目标节点上的 kube-proxy conntrack 条目覆盖到 Service 的这一跳本身。终止连接的代理根本不转发数据包,所以你最终看到的源属于该代理。
它们各自位于不同的层,因此单个 Service 字段几乎从来修不好整条路径。
VPN 公网 Service 上的 externalTrafficPolicy 只决定外层连接的去向:Local 把远端地址保留在接收它的节点上,而 Cluster 让 kube-proxy 把数据包交给另一个节点,并在那里做 masquerade。这个设置支配你的客户端所拨入的监听器。它对 VPN 进程随后路由进集群的解密数据包只字不提。
三个能终结争论的条件
每次这个话题出现,我都会按同样的顺序问同样三个问题,因为第一个通常就结束了讨论。请针对具体工作负载回答,而不是针对整个集群。同一家公司里的两个集群可能得出不同答案,而且都正确。
| Question | Preserve | SNAT |
|---|---|---|
| Does the backend enforce policy per client? | Yes, the address is the identity | Only if a trusted proxy supplies identity |
| Can the cluster route 10.90.0.0/24 back to a stable gateway? | Required | Not needed |
| Is the workload a forwarder or a proxy? | Forwarder | Proxy is fine |
如果 backend 对第一个问题答是、对第二个答否,那你要做的是网络工作,而不是改 manifest。如果第三行说代理,那这个地址本来就不可能存活,假装不是这样要花一周的抓包时间。
preserve 模式对网络的要求
保留源意味着目标节点必须通过一个活跃的 VPN 网关把流量送回 10.90.0.42。VPN 池不是集群 CIDR,所以没有任何 CNI 会默认为你路由它。你自己添加路由,然后确保它指向的网关端点能熬过一次重新调度。
ip route add 10.90.0.0/24 via 10.244.3.17 dev eth0
sysctl -w net.ipv4.conf.eth0.rp_filter=2
下一跳是 pod 地址,而 pod 地址会移动。把路由指向 Service VIP 或固定不动的节点地址,或者接受每次驱逐后你都要去追网关。反向路径过滤是另一个陷阱:严格模式会丢弃回程路径与进入接口不匹配的数据包,而这正是一个被保留的 VPN 流的形状。
宽松校验常常就是可用回程路径与 离开 backend 后就消失的响应 之间的差别。VPN 节点上的 masquerade 规则也必须排除隧道范围,否则被转发的数据包无论你建了什么路由,都会带着 pod 地址出去。最好在 CNI 自身的配置里表达这个排除,而不是手改 iptables,因为代理重启时会重写自己的链,你手写的规则恰好只能活到下次升级。
NetworkPolicy 是访问控制,不是路由。收紧策略永远不会恢复一个 masquerade 已经替换掉的地址。
SNAT 的代价,以及如何把它写下来
面向集群的源 NAT 是更便宜的设计,而且不是错误。backend 看到的是 VPN pod 或节点的地址,日志彼此一致,而且不需要存在回程路由。你失去的是在网络层区分两个客户端的能力,所以配额、封禁和审计线索必须来自别处。
通常那个别处就是应用:会话令牌、签名头,或者由你控制的代理写下的 PROXY 协议行。只有当代理位于路径中、且头无法从客户端侧设置时,这些才给接收方一个可信的身份。在 runbook 里明确哪一种适用,因为下一位工程师会假设日志里的地址就是客户端。它不是,一次事故就足以让这个假设变得昂贵。
只要写下来,这笔交换就仍然是诚实的。
区分可用路径与侥幸路径的检查
用新连接而不是缓存连接测试,并且两个方向都测。有些故障看起来时断时续,只是因为新流转走了被改写的路径。
在一次获授权的请求中,于三个点抓取 tuple:解封装之后、VPN pod 的出向接口上,以及 backend 上。如果源在第二次和第三次抓取之间变化,原因在 pod 下游。如果在第二次之前变化,那就是 pod 自己在转换。
然后删除 VPN pod,在它被重新调度时重复测试。在驱逐时断掉的保留路径是没做完的,固定在一个已不存在的地址上的 NAT 规则也一样。最后,从一个你预期会被拒绝的源发一个请求,并确认拒绝来自策略,而不是来自缺失的路由。