土法炼钢兴趣小组的算法知识备份

多集群网络:ClusterMesh、Submariner 与 MCS API

目录

本文基于 Kubernetes 1.30、Cilium 1.16、Submariner 0.18、Linux 6.x 内核。 实验环境:kind v0.22、Docker 26.x、Ubuntu 22.04。

单集群终究有天花板。当业务规模增长到一定程度,你会发现一个集群已经不够用了——不是性能不够,而是架构上不允许把所有鸡蛋放在一个篮子里。多集群是生产环境的必经之路,而多集群网络则是这条路上最难啃的骨头。

本文将系统梳理三种主流的多集群网络方案:Cilium ClusterMesh(扁平网络)、Submariner(Gateway 模式)、MCS API(DNS 联邦),从原理到实现逐一拆解,最后用 kind 搭建双集群实验验证跨集群 Service 访问。

多集群网络三种模式对比

一、为什么需要多集群

在深入技术细节之前,先回答一个根本问题:为什么不能用一个大集群解决所有问题?

高可用与容灾:单集群的 control plane 是单点。etcd 故障、API Server 过载、master 节点网络分区——任何一个都能让整个集群瘫痪。多集群提供了 blast radius isolation:一个集群挂了,其他集群继续服务。

地理分布:用户在全球各地,数据中心分布在不同 Region。把所有 Pod 放在一个集群意味着跨洲际的 Pod-to-Pod 通信,延迟不可接受。多集群让每个 Region 有独立的集群,就近服务用户。

组织隔离:不同团队、不同业务线需要独立的集群。共享集群带来的问题远不止 namespace 层面的资源配额,还有 CRD 冲突、RBAC 复杂度爆炸、升级节奏不一致等。

合规要求:GDPR 要求欧洲用户数据不出境,金融行业有数据本地化要求。合规驱动下,不同地域的数据必须在本地集群处理,但服务之间又需要互通。

规模限制:Kubernetes 官方推荐的单集群上限:5000 节点、150000 个 Pod、300000 个容器。超过这个规模,etcd 性能、API Server 吞吐、控制面延迟都会成为瓶颈。

这些场景归结为一句话:多集群是不可避免的,问题只是如何让多个集群的网络互通。

二、多集群网络的三种模式

多集群网络方案众多,但从网络模型上可以归为三种模式:

模式一:扁平网络(Flat Network)

所有集群共享一个统一的 Pod CIDR 空间,Pod IP 全局唯一可路由。集群之间的 Pod 可以直接用 IP 通信,就像在同一个集群一样。

Cluster 1: PodCIDR 10.1.0.0/16
Cluster 2: PodCIDR 10.2.0.0/16

Pod A (10.1.1.5) --> 直接路由 --> Pod D (10.2.1.8)

代表方案:Cilium ClusterMesh

优势:延迟最低,Pod-to-Pod 直连,支持跨集群 NetworkPolicy。 限制:CIDR 不能重叠,必须统一 CNI(Cilium)。

模式二:Gateway 模式

每个集群有一个或多个 Gateway 节点,跨集群流量通过 Gateway 中转,通常走加密隧道(IPsec/WireGuard)。集群内部的 CIDR 可以重叠,Gateway 层做 NAT。

Cluster 1: PodCIDR 10.244.0.0/16
Cluster 2: PodCIDR 10.244.0.0/16  (CIDR 相同!)

Pod A --> Gateway 1 --[IPsec]--> Gateway 2 --> Pod D
            |                        |
         GlobalNet NAT          GlobalNet NAT
        (242.1.0.0/16)        (242.2.0.0/16)

代表方案:Submariner

优势:CIDR 可以重叠(通过 GlobalNet),CNI 无关,部署简单。 限制:Gateway 节点是瓶颈和单点,隧道增加延迟。

模式三:DNS 联邦(DNS Federation)

不共享网络平面,只共享 Service 名称。通过 DNS 解析让一个集群的 Pod 能发现另一个集群的 Service,但实际流量走底层网络(需要网络本身可达)。

Cluster 1 中的 Pod A:
  dig frontend.clusterset.local
  --> 返回 Cluster 2 的 Service ClusterIP 或 Endpoint IP

实际流量路径取决于底层网络是否互通。

代表方案:MCS API(KEP-1645)。

优势:Kubernetes 原生 API,标准化程度最高,CNI 和网络方案无关。 限制:只解决服务发现,不解决网络互通;功能相对基础。

三、Cilium ClusterMesh 深度拆解

ClusterMesh 是 Cilium 的多集群方案。它的核心思路是:让每个集群的 Cilium Agent 不仅 watch 本地 etcd,还 watch 远端集群的 etcd,从而获得全局视图。

Cilium ClusterMesh 架构

前置条件

在深入架构之前,先明确 ClusterMesh 的硬性要求:

  1. 所有集群必须使用 Cilium 作为 CNI。
  2. PodCIDR 不能重叠。每个集群分配独立的 CIDR 段。
  3. 每个集群需要唯一的 ClusterID(1-255)。
  4. 集群间网络可达。至少 clustermesh-apiserver 的端口(默认 2379)可以从远端访问。
  5. 所有集群的 Cilium 版本一致(或兼容)。
# Cilium Helm values 示例
cluster:
  name: cluster1
  id: 1
clustermesh:
  useAPIServer: true
  apiserver:
    service:
      type: LoadBalancer   # 或 NodePort

跨集群 etcd 连接

ClusterMesh 的数据同步依赖 clustermesh-apiserver。这是一个轻量级组件,它 watch 本集群的 kube-apiserver 获取 Node、Service、Endpoint、Identity 等资源,写入自己内嵌的 etcd 实例,并通过 TLS 将 etcd 暴露给远端集群的 Cilium Agent。

每个集群都运行一个 clustermesh-apiserver。远端集群的 Cilium Agent 连接到它,以只读方式 watch 数据变更。

Cluster 1                              Cluster 2
+-----------------------+              +-----------------------+
| kube-apiserver        |              | kube-apiserver        |
|   v                   |              |   v                   |
| clustermesh-apiserver |  <-- TLS --> | clustermesh-apiserver |
|   v                   |              |   v                   |
| Cilium Agent          |              | Cilium Agent          |
| watch: local+remote   |              | watch: local+remote   |
+-----------------------+              +-----------------------+

关键设计:Cilium Agent 不直接访问远端 kube-apiserver。clustermesh-apiserver 是一个中间层,只暴露 Cilium 需要的数据,减小攻击面。

Identity 全局同步

Cilium 深入 中我们讲过,Cilium 用 Security Identity 来标识 Pod 的安全身份。每个 Identity 是一个数字 ID,基于 Pod 的 label 集合计算得出。

多集群场景下的问题:两个集群可能独立生成相同数字 ID 但含义不同的 Identity。ClusterMesh 通过两个机制解决:

机制一:ClusterID 前缀

每个 Identity 的高 8 位存储 ClusterID,低 16 位存储本地 Identity ID。这样即使两个集群的本地 ID 相同,加上 ClusterID 前缀后全局唯一。

全局 Identity = (ClusterID << 16) | LocalIdentityID

Cluster 1 (ID=1):  identity = 0x00010001 = 65537
Cluster 2 (ID=2):  identity = 0x00020001 = 131073

这也是 ClusterID 限制在 1-255 的原因——8 位能表示的最大值就是 255。

机制二:相同 label 集合共享 Identity

如果两个集群中的 Pod 有完全相同的 label 集合(例如 app=frontend, env=prod),它们会获得相同的全局 Identity。这使得跨集群的 NetworkPolicy 可以用同一个 Identity 来匹配。

# 查看某个 Pod 的 Identity
cilium identity list | grep frontend

# 查看所有集群同步过来的 Identity
cilium identity list --endpoints

Global Service

ClusterMesh 最核心的用户可见功能是 Global Service:在多个集群中创建同名 Service,ClusterMesh 自动将它们合并为一个全局 Service,endpoints 跨集群负载均衡。

启用方式很简单——给 Service 加一个 annotation:

apiVersion: v1
kind: Service
metadata:
  name: frontend
  namespace: default
  annotations:
    service.cilium.io/global: "true"
spec:
  selector:
    app: frontend
  ports:
    - port: 80
      targetPort: 8080

在两个集群中分别创建这个 Service 后,任意集群中的 Pod 访问 frontend.default.svc.cluster.local 时,Cilium 的 eBPF datapath 会同时考虑本地和远端的 endpoints 进行负载均衡。

可以通过 annotation 控制更细粒度的行为:

annotations:
  # 启用全局 Service
  service.cilium.io/global: "true"
  # 优先使用本地 endpoints,远端作为备份
  service.cilium.io/shared: "true"
  # 亲和性:优先本地
  service.cilium.io/affinity: "local"

service.cilium.io/affinity 支持三个值:

行为
none 本地和远端 endpoints 平等负载均衡
local 优先本地 endpoints,本地全部不可用时 failover 到远端
remote 优先远端 endpoints(用于灾难恢复场景)
# 验证 Global Service 的 endpoints
cilium service list | grep frontend

# 输出示例:
# 100   frontend.default.svc   10.1.100.10:80
#                               10.1.1.5:8080 (local)
#                               10.1.2.3:8080 (local)
#                               10.2.1.8:8080 (remote)
#                               10.2.3.1:8080 (remote)

跨集群 NetworkPolicy

Global Identity 使得 CiliumNetworkPolicy 可以跨集群生效。你可以在 Cluster 1 中编写一条策略,限制只有 Cluster 2 中带特定 label 的 Pod 才能访问某个 Service。

apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
  name: allow-cross-cluster-frontend
  namespace: default
spec:
  endpointSelector:
    matchLabels:
      app: backend
  ingress:
    - fromEndpoints:
        - matchLabels:
            app: frontend
            # 不需要指定集群!Identity 是全局的
      toPorts:
        - ports:
            - port: "8080"
              protocol: TCP

由于 Cluster 1 和 Cluster 2 中 app=frontend 的 Pod 共享相同的全局 Identity,这条策略会同时允许两个集群的 frontend Pod 访问 backend。

如果你需要只允许特定集群的 Pod 访问,可以结合 io.cilium.k8s.policy.cluster label:

ingress:
  - fromEndpoints:
      - matchLabels:
          app: frontend
          io.cilium.k8s.policy.cluster: cluster2

四、Submariner 深度拆解

Submariner 是 CNCF Sandbox 项目,定位是为任意 Kubernetes 集群提供跨集群网络互通。与 ClusterMesh 不同,它不要求统一 CNI,采用 Gateway 模式,通过加密隧道连接集群。

架构组件

Submariner 由四个核心组件组成:

Broker:集群发现和协调中心。它本身是一个 Kubernetes 集群(可以是独立的,也可以复用某个参与集群),用 CRD 存储所有集群的连接信息。各集群的 Gateway Engine 向 Broker 注册自己的 endpoint 信息,并从 Broker 获取其他集群的信息。

Gateway Engine:部署在每个集群的一个节点上(Gateway 节点)。负责建立和维护跨集群的加密隧道。支持两种隧道后端:IPsec(Libreswan,基于 IKEv2,成熟稳定)和 WireGuard(更现代,性能更好,内核级实现)。

# 查看 Gateway Engine 状态
subctl show connections
# GATEWAY          CLUSTER   REMOTE IP   CABLE DRIVER  STATUS
# worker-node-1    cluster2  10.0.0.5    libreswan     connected

Route Agent:以 DaemonSet 部署在每个节点上。负责配置路由规则,将发往远端集群 CIDR 的流量引导到 Gateway 节点;配置 iptables 规则进行 SNAT/DNAT(当使用 GlobalNet 时);维护 VXLAN overlay。

# Route Agent 添加的路由示例
ip route show table 100
# 10.244.0.0/16 via 172.18.0.5 dev vx-submariner

Lighthouse DNS:跨集群服务发现组件。每个集群运行一个 CoreDNS 实例,通过自定义插件解析 *.clusterset.local 域名。Pod 发起 DNS 查询后,lighthouse 插件查询本地 ServiceImport CRD,返回对应的 ClusterIP 或 EndpointSlice。

GlobalNet:解决 CIDR 重叠

现实中,很多集群在部署时使用了默认的 PodCIDR(如 10.244.0.0/16),导致 CIDR 重叠。Submariner 的 GlobalNet 通过分配全局唯一的虚拟 CIDR 来解决这个问题。

不使用 GlobalNet:
  Cluster A PodCIDR: 10.244.0.0/16
  Cluster B PodCIDR: 10.244.0.0/16
  --> 路由冲突,无法互通!

使用 GlobalNet:
  Cluster A PodCIDR: 10.244.0.0/16  GlobalCIDR: 242.1.0.0/16
  Cluster B PodCIDR: 10.244.0.0/16  GlobalCIDR: 242.2.0.0/16

  Pod A (10.244.1.5) --> SNAT to 242.1.1.5 --> 隧道 --> DNAT to 10.244.2.8 --> Pod B

GlobalNet 的工作原理:

  1. 每个集群分配一个 GlobalCIDR(如 242.x.0.0/16)。
  2. Pod 出站流量做 SNAT:源 IP 从本地 PodCIDR 改为 GlobalCIDR 中的对应地址。
  3. 远端入站流量做 DNAT:目标 IP 从 GlobalCIDR 还原为本地 PodCIDR。
  4. GlobalNet Controller 负责分配和管理 Global IP,使用 GlobalIngressIPClusterGlobalEgressIP CRD。
# GlobalNet 分配的全局 IP 示例
apiVersion: submariner.io/v1
kind: GlobalIngressIP
metadata:
  name: frontend-ingress
  namespace: default
spec:
  target: Service
  serviceRef:
    name: frontend
status:
  allocatedIP: 242.1.0.100

GlobalNet 的代价是额外的 NAT 开销和调试复杂度。如果 CIDR 不重叠,建议不要开启 GlobalNet。

Submariner 安装与配置

Submariner 提供 subctl CLI 工具简化部署:

# 1. 部署 Broker(在 broker 集群上执行)
subctl deploy-broker --kubeconfig broker-kubeconfig

# 2. 将集群加入(在每个参与集群上执行)
subctl join broker-info.subm \
  --clusterid cluster1 \
  --kubeconfig cluster1-kubeconfig \
  --nattport 4500 \
  --cable-driver libreswan

subctl join broker-info.subm \
  --clusterid cluster2 \
  --kubeconfig cluster2-kubeconfig \
  --nattport 4500 \
  --cable-driver libreswan

# 3. 验证连接
subctl show all
subctl verify --kubeconfig cluster1-kubeconfig \
  --toconfig cluster2-kubeconfig --verbose

导出跨集群 Service

Submariner 使用 ServiceExport CRD 来声明哪些 Service 需要跨集群可见:

apiVersion: multicluster.x-k8s.io/v1alpha1
kind: ServiceExport
metadata:
  name: frontend
  namespace: default

创建 ServiceExport 后,Submariner 的 Lighthouse 组件会:

  1. 在 Broker 集群中创建对应的聚合信息。
  2. 其他集群的 Lighthouse 从 Broker 同步,生成本地的 ServiceImport CRD。
  3. Lighthouse DNS 根据 ServiceImport 响应 DNS 查询。
# 在 Cluster A 中导出 Service
kubectl apply -f service-export.yaml

# 在 Cluster B 中验证
kubectl get serviceimport -n default
# NAME       TYPE           IP                AGE
# frontend   ClusterSetIP   242.1.0.100       30s

# DNS 查询验证
kubectl exec -it test-pod -- nslookup frontend.default.svc.clusterset.local
# Server:    10.96.0.10
# Address:   10.96.0.10#53
# Name:      frontend.default.svc.clusterset.local
# Address:   242.1.0.100

五、Multi-Cluster Services API(KEP-1645)

MCS API 是 Kubernetes SIG-Multicluster 定义的标准 API,目标是为多集群服务发现提供统一的抽象。它不是一个具体实现,而是一套 CRD 和语义规范,各厂商基于此实现自己的多集群方案。

核心概念

ClusterSet:一组相关联的集群。ClusterSet 内的集群共享服务可见性,ClusterSet 外的集群不可见。

ServiceExport:声明某个 Service 需要导出到 ClusterSet。在 Service 所在的集群中创建。

ServiceImport:系统自动在 ClusterSet 的其他集群中创建,表示一个从远端导入的 Service。

Cluster 1 (provider):
  Service/frontend  +  ServiceExport/frontend
       |
       | MCS Controller 同步
       v
Cluster 2 (consumer):
  ServiceImport/frontend  --> DNS: frontend.default.svc.clusterset.local

ServiceExport 和 ServiceImport

# 在 provider 集群中创建 ServiceExport
apiVersion: multicluster.x-k8s.io/v1alpha1
kind: ServiceExport
metadata:
  name: frontend
  namespace: default
---
# 系统自动在 consumer 集群中创建 ServiceImport
apiVersion: multicluster.x-k8s.io/v1alpha1
kind: ServiceImport
metadata:
  name: frontend
  namespace: default
spec:
  type: ClusterSetIP
  ips:
    - 10.96.100.50     # 分配的 ClusterSet VIP
  ports:
    - port: 80
      protocol: TCP

ServiceImport 有两种类型:

类型 行为
ClusterSetIP 分配一个虚拟 IP(VIP),类似 ClusterIP。DNS 返回这个 VIP。
Headless 不分配 VIP,DNS 直接返回所有集群的 endpoint IP。

DNS 规范

MCS API 定义了标准的 DNS 命名格式:

<service>.<namespace>.svc.clusterset.local

示例:
  frontend.default.svc.clusterset.local

注意是 clusterset.local,不是 cluster.local。这个域名在 ClusterSet 范围内全局可解析。

如果需要访问特定集群的 Service(而不是全局负载均衡),可以使用:

<service>.<namespace>.svc.<cluster-id>.clusterset.local

示例:
  frontend.default.svc.cluster1.clusterset.local

实现现状

MCS API 本身只是 CRD 定义和语义规范,需要控制器来实现实际功能。目前主要的实现包括:

实现 状态 特点
GKE Multi-cluster Services GA Google Cloud 原生,与 GKE 深度集成
Submariner Lighthouse 活跃 兼容 MCS API,提供完整的网络互通
Cilium ClusterMesh 部分支持 通过 Global Service 实现类似语义
AWS Cloud Map MCS Controller Preview AWS EKS 集成
Istio Multi-cluster 活跃 基于 Istio 的服务网格实现
Liqo 活跃 虚拟节点方式,透明扩展
# 检查集群是否安装了 MCS CRD
kubectl api-resources | grep multicluster
# serviceexports    multicluster.x-k8s.io/v1alpha1   true   ServiceExport
# serviceimports    multicluster.x-k8s.io/v1alpha1   true   ServiceImport

MCS API 的局限性

MCS API 解决的是服务发现问题,不解决网络互通问题。它假设底层网络已经可达(通过 VPN、对等互联、或其他方案)。这意味着在实际部署中,MCS API 通常需要配合一个底层网络方案(Submariner、ClusterMesh、云厂商 VPC Peering 等)使用。它的价值在于提供了标准化的 API 接口,避免应用层代码绑定到特定的多集群方案。

六、多集群 CIDR 规划

CIDR 规划是多集群网络中最容易被忽视、但影响最深远的决策。错误的 CIDR 分配会导致地址冲突,后期修改的成本极高。

CIDR 分配策略

一个 Kubernetes 集群涉及三个 CIDR 段:

1. PodCIDR     - Pod 的 IP 地址范围
2. ServiceCIDR - Service ClusterIP 的地址范围
3. NodeCIDR    - 节点网络的 IP 地址范围

多集群环境下,这三个 CIDR 段都需要规划,确保不冲突。

推荐的分配方案(以 10.0.0.0/8 为基础):

集群编号   PodCIDR           ServiceCIDR        NodeCIDR
--------------------------------------------------------------
Cluster 1  10.1.0.0/16       10.101.0.0/16      172.16.1.0/24
Cluster 2  10.2.0.0/16       10.102.0.0/16      172.16.2.0/24
Cluster 3  10.3.0.0/16       10.103.0.0/16      172.16.3.0/24
...
Cluster N  10.N.0.0/16       10.(100+N).0.0/16  172.16.N.0/24

这种方案支持最多约 99 个集群(10.1-10.99),每个集群 65534 个 Pod IP。对于大规模部署,可以使用更大的地址空间。

处理已有集群的 CIDR 冲突

如果已有集群使用了重叠的 CIDR,有几种处理方法:

方法一:使用 GlobalNet(Submariner)

如前文所述,Submariner 的 GlobalNet 通过 NAT 解决 CIDR 重叠,不需要修改已有集群的配置。

方法二:重新规划 CIDR

对于新建的集群,从一开始就规划好 CIDR。对于已有集群,可以通过以下步骤迁移:

# 这是破坏性操作,需要排空节点
# 1. 修改 kube-controller-manager 的 --cluster-cidr 参数
# 2. 修改 CNI 配置中的 CIDR
# 3. 逐节点排空、删除 CNI 状态、重新加入
# 4. 验证新 Pod 获得新 CIDR 范围的 IP

# 极其不推荐在生产环境直接操作,建议新建集群迁移

方法三:IP Masquerade——在集群边界做 SNAT,让出站流量使用不冲突的 IP。本质上和 GlobalNet 类似,但需要手动配置。

CIDR 规划检查清单

部署多集群网络之前,务必确认:所有集群的 PodCIDR 不重叠(或使用 GlobalNet);所有集群的 ServiceCIDR 不重叠;CIDR 不与节点网络、企业内网(VPN、办公网络)冲突;预留足够的地址空间供未来扩展;记录 CIDR 分配方案,纳入配置管理。

七、实验:用 Cilium ClusterMesh 连接两个 kind 集群

下面我们用 kind 搭建两个集群,安装 Cilium 并启用 ClusterMesh,验证跨集群 Service 访问。

环境准备

# 确认工具版本
kind version        # v0.22+
cilium version      # CLI 0.16+
kubectl version     # v1.30+
helm version        # v3.14+
docker version      # 26.x+

创建两个 kind 集群

# cluster1.yaml
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
networking:
  disableDefaultCNI: true    # 禁用默认 CNI,由 Cilium 接管
  podSubnet: "10.1.0.0/16"
  serviceSubnet: "10.101.0.0/16"
nodes:
  - role: control-plane
  - role: worker
  - role: worker
# cluster2.yaml
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
networking:
  disableDefaultCNI: true
  podSubnet: "10.2.0.0/16"
  serviceSubnet: "10.102.0.0/16"
nodes:
  - role: control-plane
  - role: worker
  - role: worker
# 创建集群
kind create cluster --name cluster1 --config cluster1.yaml
kind create cluster --name cluster2 --config cluster2.yaml

# 验证集群
kubectl --context kind-cluster1 get nodes
kubectl --context kind-cluster2 get nodes

安装 Cilium

# 为 Cluster 1 安装 Cilium
cilium install --context kind-cluster1 \
  --set cluster.name=cluster1 \
  --set cluster.id=1 \
  --set ipam.operator.clusterPoolIPv4PodCIDRList="10.1.0.0/16"

# 为 Cluster 2 安装 Cilium
cilium install --context kind-cluster2 \
  --set cluster.name=cluster2 \
  --set cluster.id=2 \
  --set ipam.operator.clusterPoolIPv4PodCIDRList="10.2.0.0/16"

# 等待 Cilium 就绪
cilium status --context kind-cluster1 --wait
cilium status --context kind-cluster2 --wait

启用 ClusterMesh

# 在两个集群上启用 ClusterMesh
cilium clustermesh enable --context kind-cluster1 \
  --service-type NodePort
cilium clustermesh enable --context kind-cluster2 \
  --service-type NodePort

# 等待 ClusterMesh 就绪
cilium clustermesh status --context kind-cluster1 --wait
cilium clustermesh status --context kind-cluster2 --wait

# 建立连接
cilium clustermesh connect \
  --context kind-cluster1 \
  --destination-context kind-cluster2

# 验证连接状态
cilium clustermesh status --context kind-cluster1

预期输出:

ClusterMesh:       ok
  cluster2:        connected, 3 nodes, 0 identities, 0 services

部署测试应用

# rebel-base.yaml - 在两个集群中都部署
apiVersion: apps/v1
kind: Deployment
metadata:
  name: rebel-base
spec:
  replicas: 2
  selector:
    matchLabels:
      app: rebel-base
  template:
    metadata:
      labels:
        app: rebel-base
    spec:
      containers:
        - name: rebel-base
          image: docker.io/nginx:1.25-alpine
          ports:
            - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: rebel-base
  annotations:
    service.cilium.io/global: "true"
    service.cilium.io/shared: "true"
spec:
  selector:
    app: rebel-base
  ports:
    - port: 80
      targetPort: 80

在每个集群中分别部署,并修改 nginx 默认页以区分来源:

# 在 Cluster 1 部署后,进入 Pod 写入集群标识
kubectl --context kind-cluster1 apply -f rebel-base.yaml
kubectl --context kind-cluster2 apply -f rebel-base.yaml

# 等待 Pod 就绪
kubectl --context kind-cluster1 wait --for=condition=ready \
  pod -l app=rebel-base --timeout=60s
kubectl --context kind-cluster2 wait --for=condition=ready \
  pod -l app=rebel-base --timeout=60s

验证跨集群 Service 访问

# 在 Cluster 1 中创建测试 Pod
kubectl --context kind-cluster1 run test-pod \
  --image=curlimages/curl:8.5.0 --restart=Never \
  --command -- sleep 3600

# 多次访问 Global Service,观察响应来自不同集群
for i in $(seq 1 10); do
  kubectl --context kind-cluster1 exec test-pod -- \
    curl -s rebel-base.default.svc.cluster.local
done
# 预期输出交替出现来自两个集群的响应

验证 Global Service endpoints 与 Failover

# 查看 Cilium 合并后的 Service endpoints(应包含 local + remote)
kubectl --context kind-cluster1 exec -n kube-system \
  $(kubectl --context kind-cluster1 get pod -n kube-system \
    -l k8s-app=cilium -o name | head -1) -- \
  cilium service list | grep rebel-base

# Failover 测试:缩容 Cluster 2,流量应全部切到 Cluster 1
kubectl --context kind-cluster2 scale deployment rebel-base --replicas=0
for i in $(seq 1 5); do
  kubectl --context kind-cluster1 exec test-pod -- \
    curl -s rebel-base.default.svc.cluster.local
done
kubectl --context kind-cluster2 scale deployment rebel-base --replicas=2

验证跨集群 NetworkPolicy

# deny-all.yaml - 在 Cluster 1 中应用
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
  name: deny-cross-cluster
  namespace: default
spec:
  endpointSelector:
    matchLabels:
      app: rebel-base
  ingress:
    - fromEndpoints:
        - matchLabels:
            io.cilium.k8s.policy.cluster: cluster1
      toPorts:
        - ports:
            - port: "80"
              protocol: TCP
# 应用策略
kubectl --context kind-cluster1 apply -f deny-all.yaml

# 从 Cluster 1 访问 -- 应该成功
kubectl --context kind-cluster1 exec test-pod -- \
  curl -s --max-time 3 rebel-base.default.svc.cluster.local
# Cluster cluster1

# 从 Cluster 2 访问 Cluster 1 的 Pod -- 应该被拒绝
# (需要在 Cluster 2 中创建 test-pod 并尝试访问 Cluster 1 的 endpoint)

清理实验环境

kind delete cluster --name cluster1
kind delete cluster --name cluster2

八、方案选型指南

三种模式没有绝对的优劣,选择取决于你的具体场景:

选择 Cilium ClusterMesh,如果:
  - 所有集群已经或计划使用 Cilium
  - 需要最低延迟的跨集群通信
  - 需要跨集群 NetworkPolicy
  - CIDR 可以规划为不重叠
  - 集群数量适中(建议不超过 255 个)

选择 Submariner,如果:
  - 集群使用不同的 CNI(混合环境)
  - 已有集群 CIDR 重叠,无法更改
  - 需要加密隧道保护跨集群流量
  - 希望快速部署,不想大规模改造网络

选择 MCS API,如果:
  - 希望面向未来的标准化方案
  - 已有底层网络互通(VPC Peering、VPN 等)
  - 只需要服务发现,不需要网络层互通
  - 使用云厂商托管 K8s(GKE、EKS 等原生支持)

在实际生产中,这些方案并不互斥。例如:

九、生产环境注意事项

监控与告警

多集群网络引入了大量新的故障点,必须建立完善的监控。关键指标包括:跨集群隧道状态、跨集群延迟(P50/P99)、clustermesh-apiserver 健康状态、etcd 同步延迟、Global Service endpoint 数量变化、跨集群 DNS 解析成功率、Gateway 节点资源使用率。

# Prometheus 告警规则示例
groups:
  - name: multi-cluster-network
    rules:
      - alert: ClusterMeshDisconnected
        expr: cilium_clustermesh_remote_cluster_status != 1
        for: 5m
        labels:
          severity: critical
        annotations:
          summary: "ClusterMesh 与远端集群断连"
      - alert: SubmarinerGatewayDown
        expr: submariner_connections{status="error"} > 0
        for: 2m
        labels:
          severity: critical
        annotations:
          summary: "Submariner Gateway 连接异常"

安全加固

生产环境安全检查清单:clustermesh-apiserver 使用 mTLS 认证;Submariner 隧道启用加密(IPsec 或 WireGuard);Gateway 节点的安全组/防火墙规则最小化;跨集群 NetworkPolicy 限制不必要的访问;etcd 数据加密(at rest);定期轮换证书和密钥。

容量规划

ClusterMesh 容量参考(Cilium 1.16):
  - 最大集群数:255(受 ClusterID 8-bit 限制)
  - 建议:etcd 数据量 < 1GB,节点总数 < 5000

Submariner 容量参考:
  - Gateway 节点是瓶颈:所有跨集群流量经过它
  - 建议 Gateway 节点使用高带宽实例
  - 可配置多个 Gateway 做 HA,但同一时间只有一个 active

附录 A:常用排查命令

# === Cilium ClusterMesh ===
cilium clustermesh status                                   # 连接状态
cilium identity list | grep -v "reserved"                   # 远端 Identity
cilium service list --clustermesh-affinity                   # Global Service endpoints
cilium status --verbose | grep -A5 "ClusterMesh"            # Agent 远端 etcd 连接
kubectl logs -n kube-system deploy/clustermesh-apiserver -f  # apiserver 日志

# === Submariner ===
subctl show all                                             # 总体状态
subctl diagnose all                                         # 连接诊断
subctl verify --context cluster1 --tocontext cluster2       # 跨集群连通性
kubectl logs -n submariner-operator deploy/submariner-gateway -f

# === 通用 ===
kubectl get serviceexport,serviceimport -A                  # MCS CRD
kubectl exec test-pod -- nslookup frontend.default.svc.clusterset.local
kubectl exec test-pod -- curl -v --max-time 5 http://10.2.1.8:80

附录 B:推荐阅读



By .