为什么 GKE 的 Pod 无法通过 VPN 访问本地主机
节点能访问本地网络,Pod 不能。GKE 的 masquerade 排除项让发往 10.x 的 Pod 地址不被转换。
从节点能 ping 通,从 Pod 却失败,本地一侧什么都收不到。隧道和路由都没问题;线上报文的源地址有问题。这两种情况的差别在于报文离开集群时声称自己是谁,而 GKE 对此有明确的做法。机制就是一份 masquerade 排除列表,读它只需要一条命令。
改任何东西之前先读生效的列表
GKE 自带一个代理,按目标网段决定 Pod 流量出集群时是否改写成节点地址。它出厂配置把三个 RFC1918 网段,10.0.0.0/8、172.16.0.0/12 和 192.168.0.0/16,列为豁免目标。因此从 Pod 发往本地 10.x 主机的流量离开节点时,仍带着 Pod 自己的地址。节点自身的流量不会被改写,这正是节点的 ping 能通的原因。
先读预期配置,再读实际安装的规则,因为两者可能不一致。kubectl -n kube-system get configmap ip-masq-agent -o yaml 显示告诉代理的配置,kubectl -n kube-system get pods -l k8s-app=ip-masq-agent 显示是否有东西在运行并应用它。在节点上,iptables-save | grep -i masq 显示内核里真正存在的规则。当 ConfigMap 说的是一回事、规则是另一回事时,嫌疑对象是代理而不是网络。
用抓包确认源地址
改列表之前先抓包。只对一个目标过滤能让输出保持可读:tcpdump -ni any host 10.20.30.40。
把它和远端报告的情况对照。在本地抓包看到源地址是 Pod 且没有回包,就把链条闭合了:排除列表解释了这种不对称,隧道本身没有故障。对同一个源地址的两次观察,足以停止对 CNI 的猜测。
把排除范围收窄到 Pod CIDR
官方支持的修复方式是收窄豁免,而不是取消豁免:只保留 Pod CIDR 豁免,去掉 10.x 空间里的其余部分。那份列表在代理的 ConfigMap 里,键名是 nonMasqueradeCIDRs,改动它就是一次编辑加一次滚动发布。把宽泛的条目换成集群实际的 Pod 网段,集群内流量仍需要的话保留其他私有网段,没有理由就不要动 resyncInterval。应用后,确认代理 Pod 已重启,确认内核规则已经变化。
这是整个集群范围的改动,影响面是真实的。生效后,原本保留 Pod 源地址的流量会以节点地址到达,所有依赖这些 Pod 地址的东西都会看到不同的发送方。受害的是日志、白名单和点名 Pod 地址的本地防火墙规则,而且它们会安静地失效。对外要把它宣布为行为变更,而不是修复。
有多个节点池的集群里,要检查代理是否被调度到受影响 Pod 真正运行的节点上。没有 DaemonSet 的节点池会保留旧规则,而 ConfigMap 里看不出这一点。
另一种做法及其代价
另一种做法保持 Pod 地址不变,让路径认识它们。把 Pod CIDR 加进 Cloud VPN 的流量选择器,本地一侧还需要为它配置回程路由,回包才知道往哪走。这样保住了某些安全模型想要的 Pod 身份,同时把远端必须接受的路由和防火墙范围都扩大了。
这笔交易是清晰度换范围。做地址转换后,本地一侧每个节点只看到一个地址,加白名单很容易,但也看不出是哪个 Pod 发的。不做转换则 Pod 地址端到端保留,归属更清楚,但路径上每个设备都要学会新网段。两种做法都不是 Docker 或 CNI 的缺陷,而是带运维账单的路由决策。选你们团队付得起的那一种,并把理由写下来。
验证与回滚
再抓一次同样的包。改完之后节点上抓包仍然显示 Pod 地址,就说明规则根本没变,ConfigMap 说什么都没用。线上的报文才是事实,其余都是推断。
然后检查反方向,因为这类改动有一半只验证了出向。确认回包到达发起请求的节点或 Pod,确认应用真正完成,而不只是收到了 SYN。抓包只有请求没有回包,是远端的路由问题,不是这一侧的 masquerade 问题。
回滚就是用同样的方式应用上一版 ConfigMap,并同样确认代理重启、规则变化。把某个已知请求改动前后的源地址留在变更记录里,因为那一行才是变更确实生效的证据。GKE 的 IP masquerade 代理文档写明了默认值和 ConfigMap 字段。如果发布后源地址没变,说明那个节点上代理没在运行,改多少 YAML 都没用。