最近遇到的一个问题:需要把老集群的部分服务迁移到新集群,要求新老集群里面的Pod可以互相通信调用。踩了几天的坑,磕磕绊绊总算是完成了。
公司的Kubernetes环境存在新老两套集群:
- 老集群:自己购买云ECS服务器,手动搭建的Kubernetes集群,使用Calico网络插件
- 新集群:直接使用云托管的Kubernetes服务,一键创建即用
为了完成最小改动代码迁移任务,两个集群的服务都注册到同一个Nacos注册中心,需要实现跨集群的服务调用。
2.1 老集群架构(自建K8s + Calico)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| ┌─────────────────────────────────────────────────────────────┐ │ 老集群(自建Kubernetes) │ ├─────────────────────────────────────────────────────────────┤ │ │ │ 物理层:云ECS实例 │ │ ┌────────────────────────────────────────────────────┐ │ │ │ Node网络:10.0.0.0/22(VPC默认子网) │ │ │ │ │ │ │ │ Node列表: │ │ │ │ • 10.0.0.3 (master01) │ │ │ │ • 10.0.0.19 (web02/docker-mgr) │ │ │ │ • 10.0.0.22 (web服务器03) │ │ │ │ • 其他节点... │ │ │ └────────────────────────────────────────────────────┘ │ │ ↓ │ │ Overlay层:Calico IPIP网络 │ │ ┌────────────────────────────────────────────────────┐ │ │ │ Pod网络:172.26.0.0/16(虚拟网络) │ │ │ │ │ │ │ │ 特点: │ │ │ │ • 使用IPIP隧道(tunl0)进行跨节点通信 │ │ │ │ • 每个Node分配一个/26子网(64个IP) │ │ │ │ • Pod IP对VPC不可见,只在集群内部路由 │ │ │ │ │ │ │ │ 示例Pod: │ │ │ │ • 172.26.139.114:9400 → 注册到Nacos │ │ │ │ • 172.26.95.0/26 → 分配给Node 10.0.0.19 │ │ │ │ • 172.26.241.64/26 → 分配给Node 10.0.0.3 │ │ │ └────────────────────────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────┘
|
关键点:
- Pod IP(172.26.x.x)是Overlay网络地址,不在VPC路由表中
- Pod之间通过IPIP隧道通信
- 服务注册到Nacos时使用Pod IP
2.2 新集群架构(云托管K8s)
云托管K8s在创建时会自动创建三个子网:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| ┌─────────────────────────────────────────────────────────────┐ │ 新集群(云托管K8s) │ ├─────────────────────────────────────────────────────────────┤ │ │ │ 自动创建的子网架构: │ │ │ │ ┌────────────────────────────────────────────────────┐ │ │ │ 1. Node子网:10.0.40.0/22 │ │ │ │ 路由表:k8s-axxxxxxxb-node-rt-0 │ │ │ │ 用途:K8s节点网络 │ │ │ │ Node示例:10.0.40.15 │ │ │ └────────────────────────────────────────────────────┘ │ │ ↓ │ │ ┌────────────────────────────────────────────────────┐ │ │ │ 2. Pod子网:10.0.32.0/21(重点) │ │ │ │ 路由表:k8s-axxxxxxxb-pod-rt-0 │ │ │ │ 特点: │ │ │ │ • Pod IP直接从VPC分配 │ │ │ │ • 对VPC可见,可直接路由 │ │ │ │ • 使用VPC-CNI插件 │ │ │ │ │ │ │ │ 示例Pod: │ │ │ │ • 10.0.32.21:9400 → 注册到Nacos │ │ │ └────────────────────────────────────────────────────┘ │ │ ↓ │ │ ┌────────────────────────────────────────────────────┐ │ │ │ 3. NAT子网:10.0.44.0/23 │ │ │ │ 路由表:k8s-axxxxxxxb-nat-rt-0 │ │ │ │ 用途:Pod访问公网的出口 │ │ │ └────────────────────────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────┘
|
关键点:
- Pod IP(10.0.32.x)是VPC内的真实IP
- 无需Overlay网络,性能更好
三、问题分析
3.1 初始状态
当服务部署到两个集群后,Nacos服务列表呈现如下状态:

3.2 通信测试结果
| 通信方向 |
结果 |
说明 |
| 新集群Node → 老集群Node |
通 |
都在VPC内,可直接通信 |
| 老集群Node → 新集群Node |
通 |
都在VPC内,可直接通信 |
| 老集群Pod → 新集群Pod |
通 |
10.0.32.x在VPC路由表中可识别 |
| 新集群Pod → 老集群Pod |
不通 |
172.26.x不在VPC路由表中 |
3.3 问题根源分析
为什么老集群Pod可以访问新集群Pod?
1 2 3 4 5 6 7 8 9 10 11 12 13
| 老集群Pod (172.26.139.114) 发起请求 ↓ 目标地址:10.0.32.21 ↓ 老集群Node查询本地路由表 ↓ 10.0.32.21 不在本地,走默认路由到 VPC 网关 ↓ VPC 识别 10.0.32.0/21 是合法子网(Pod子网) ↓ 路由到新集群Pod子网 ↓ 成功到达目标Pod
|
为什么新集群Pod无法访问老集群Pod?
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| 新集群Pod (10.0.32.21) 发起请求 ↓ 目标地址:172.26.139.114 ↓ 查询Pod子网路由表 (k8s-axxxxxxxb-pod-rt-0) ↓ 路由表内容: - local → local(VPC内部流量) - 0.0.0.0/0 → NAT网关(默认路由) - 没有 172.26.0.0/16 的路由! ↓ 匹配默认路由,流量发送到 NAT 网关 ↓ NAT网关用于公网访问,无法到达集群内部地址
|
四、解决方案
4.1 方案设计思路
核心思路:让新集群知道如何到达老集群的Pod网络(172.26.0.0/16)
需要解决两个问题:
- 路由问题:告诉新集群Pod子网,172.26.0.0/16的流量该发到哪里
- 信任问题:让老集群的Calico接受来自新集群的请求
4.2 步骤一:配置Pod子网路由表
在云控制台操作:
- 进入 VPC → 路由表
- 找到
k8s-axxxxxxxb-pod-rt-0
- 添加路由策略:
1 2 3 4 5
| 目的端:172.26.0.0/16 下一跳类型:云主机 下一跳:i-bddddddd5 (web服务器02 / 10.0.0.19) 路由类型:静态 备注:新集群pod通讯老集群pod
|
配置后的路由表:
1 2 3
| local → local # VPC内部流量 0.0.0.0/0 → NAT网关 # 公网流量 172.26.0.0/16 → 10.0.0.19(新增) # 老集群Pod流量
|
4.3 步骤二:配置中转节点NAT规则
为什么需要NAT?因为Calico会验证源IP,只信任本集群Node的IP。需要将新集群的请求伪装成老集群Node发出的。
在 10.0.0.19 节点上执行:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| echo 1 > /proc/sys/net/ipv4/ip_forward echo "net.ipv4.ip_forward = 1" >> /etc/sysctl.conf sysctl -p
iptables -t nat -A POSTROUTING -s 10.0.0.0/8 -d 172.26.0.0/16 -j MASQUERADE
cat > /etc/systemd/system/custom-nat.service << 'EOF' [Unit] Description=Custom NAT Rules for Cross-Cluster Communication After=network.target calico-node.service Before=kubelet.service
[Service] Type=oneshot ExecStart=/usr/sbin/iptables -t nat -A POSTROUTING -s 10.0.0.0/8 -d 172.26.0.0/16 -j MASQUERADE RemainAfterExit=yes
[Install] WantedBy=multi-user.target EOF
systemctl daemon-reload systemctl enable custom-nat.service systemctl start custom-nat.service
|
五、流量路径详解
5.1 配置后的完整通信流程
去程路径(新集群Pod → 老集群Pod)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| ┌─────────────────────────────────────────────────────────────┐ │ 新集群Pod → 老集群Pod 请求流程 │ ├─────────────────────────────────────────────────────────────┤ │ │ │ 1. 新集群Pod发起请求 │ │ ┌────────────────────────────────────────────────┐ │ │ │ Pod: 10.0.32.21 │ │ │ │ 目标: 172.26.139.114:9400 │ │ │ └──────────────────┬─────────────────────────────┘ │ │ ↓ │ │ 2. 查询Pod子网路由表 │ │ ┌────────────────────────────────────────────────┐ │ │ │ 匹配路由规则: │ │ │ │ 172.26.0.0/16 → 10.0.0.19 │ │ │ └──────────────────┬─────────────────────────────┘ │ │ ↓ │ │ 3. VPC路由到中转节点 (10.0.0.19) │ │ ┌────────────────────────────────────────────────┐ │ │ │ 数据包到达10.0.0.19的eth0网卡 │ │ │ │ 源IP: 10.0.32.21 │ │ │ │ 目标IP: 172.26.139.114 │ │ │ └──────────────────┬─────────────────────────────┘ │ │ ↓ │ │ 4. NAT地址伪装(关键步骤) │ │ ┌────────────────────────────────────────────────┐ │ │ │ iptables规则匹配并执行MASQUERADE │ │ │ │ 源IP: 10.0.32.21 → 10.0.0.19(伪装) │ │ │ │ 目标IP: 172.26.139.114(不变) │ │ │ │ NAT表记录连接信息用于回程 │ │ │ └──────────────────┬─────────────────────────────┘ │ │ ↓ │ │ 5. Calico路由转发 │ │ ┌────────────────────────────────────────────────┐ │ │ │ 10.0.0.19查询本地Calico路由表 │ │ │ │ 172.26.139.64/26 → 10.0.0.36 via tunl0 │ │ │ │ 通过IPIP隧道转发 │ │ │ └──────────────────┬─────────────────────────────┘ │ │ ↓ │ │ 6. IPIP隧道封装传输 │ │ ┌────────────────────────────────────────────────┐ │ │ │ 外层: 10.0.0.19 → 10.0.0.36 │ │ │ │ 内层: 10.0.0.19 → 172.26.139.114:9400 │ │ │ └──────────────────┬─────────────────────────────┘ │ │ ↓ │ │ 7. 到达目标Pod并处理请求 │ │ ┌────────────────────────────────────────────────┐ │ │ │ Pod: 172.26.139.114 │ │ │ │ 看到的源IP: 10.0.0.19(老集群Node IP) │ │ │ │ 处理业务请求,准备返回响应 │ │ │ └────────────────────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────┘
|
回程路径(老集群Pod → 新集群Pod 响应)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| ┌─────────────────────────────────────────────────────────────┐ │ 老集群Pod → 新集群Pod 响应流程 │ ├─────────────────────────────────────────────────────────────┤ │ │ │ 8. 老集群Pod发送响应 │ │ ┌────────────────────────────────────────────────┐ │ │ │ Pod: 172.26.139.114 │ │ │ │ 响应目标: 10.0.0.19(看到的源IP) │ │ │ │ 实际最终目标: 10.0.32.21(NAT会处理) │ │ │ └──────────────────┬─────────────────────────────┘ │ │ ↓ │ │ 9. 通过Calico路由返回 │ │ ┌────────────────────────────────────────────────┐ │ │ │ 172.26.139.114 → 10.0.0.36(本地Node) │ │ │ │ 10.0.0.36 查询路由: │ │ │ │ 目标10.0.0.19是本集群Node,直接路由 │ │ │ └──────────────────┬─────────────────────────────┘ │ │ ↓ │ │ 10. IPIP隧道封装返回 │ │ ┌────────────────────────────────────────────────┐ │ │ │ 外层: 10.0.0.36 → 10.0.0.19 │ │ │ │ 内层: 172.26.139.114 → 10.0.0.19 │ │ │ └──────────────────┬─────────────────────────────┘ │ │ ↓ │ │ 11. 中转节点NAT反向转换(关键步骤) │ │ ┌────────────────────────────────────────────────┐ │ │ │ 10.0.0.19收到响应包 │ │ │ │ 查询NAT连接跟踪表 │ │ │ │ 找到原始连接:10.0.32.21 ↔ 10.0.0.19 │ │ │ │ 执行反向NAT: │ │ │ │ 源IP: 172.26.139.114(保持不变) │ │ │ │ 目标IP: 10.0.0.19 → 10.0.32.21(还原) │ │ │ └──────────────────┬─────────────────────────────┘ │ │ ↓ │ │ 12. VPC路由转发回新集群 │ │ ┌────────────────────────────────────────────────┐ │ │ │ 10.0.0.19查询路由表 │ │ │ │ 目标10.0.32.21属于新集群Pod子网 │ │ │ │ 通过VPC路由转发 │ │ │ └──────────────────┬─────────────────────────────┘ │ │ ↓ │ │ 13. 响应到达新集群Pod │ │ ┌────────────────────────────────────────────────┐ │ │ │ Pod: 10.0.32.21 │ │ │ │ 收到响应:源IP显示为172.26.139.114 │ │ │ │ 完成一次完整的跨集群通信 │ │ │ └────────────────────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────┘
|
连接跟踪机制说明
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| NAT连接跟踪表(conntrack)工作原理:
初始请求建立连接: ┌──────────────────────────────────────┐ │ 原始连接: │ │ 10.0.32.21:45678 → 172.26.139.114:9400 │ │ │ │ NAT后连接: │ │ 10.0.0.19:52341 → 172.26.139.114:9400 │ │ │ └──────────────────────────────────────┘
响应包自动反向NAT: ┌──────────────────────────────────────┐ │ 收到响应: │ │ 172.26.139.114:9400 → 10.0.0.19:52341 │ │ │ │ 查表反向转换: │ │ 172.26.139.114:9400 → 10.0.32.21:45678 │ │ │ │ 转发到原始请求方 │ └──────────────────────────────────────┘
|
5.2 关键技术点解析
NAT规则详解
1
| iptables -t nat -A POSTROUTING -s 10.0.0.0/8 -d 172.26.0.0/16 -j MASQUERADE
|
参数说明:
-t nat:操作NAT表
-A POSTROUTING:在POSTROUTING链添加规则(数据包离开前的最后处理)
-s 10.0.0.0/8:源地址匹配(包含新集群的10.0.32.x)
-d 172.26.0.0/16:目标地址匹配(老集群Pod网络)
-j MASQUERADE:动态源地址伪装(自动使用出口网卡IP)
为什么选择10.0.0.19作为中转节点?
- 稳定性:该节点是老集群的一部分,Calico路由完整
- 信任关系:作为老集群Node,天然被Calico信任
- 网络位置:在VPC内,新集群可以直接访问
七、方案优缺点
优点
- 最小化改动:不需要修改应用代码,对业务透明
- 配置简单:只需配置路由表和NAT规则
缺点
- 单点依赖:依赖中转节点,存在单点故障风险
- 扩展性:流量都经过中转节点,可能成为瓶颈
改进建议
配置多个中转节点,这个需要子网路由表那边配置相同目的端,不同的下一跳云主机
