前几篇把单集群内”watch → push”的机制钉死了。多集群把同一套机制的输入源从一个 K8s API Server 变成多个,输出端还要多一步”这个端点对当前代理是否直接可达”的判断。本文只回答控制面层面的两个机制问题:istiod 如何获得跨集群的服务发现输入;同一个逻辑 Service 的端点分布在不同网络时,EDS 推给某个代理的地址列表如何被重写。
不做 Istio vs 其他网格的多集群方案评选,不编造跨集群延迟数字——那需要具体拓扑下的实测,本篇不具备。
本文是「Istio 控制面内核」系列第 12 篇(共 16 篇)。→ 系列目录
版本锚定:Istio 1.30.3;官方多集群安装文档同期版本路径(
istio.io/latest/docs/setup/install/multicluster/,Istio 1.30 系列)。
一、输入端:一个 istiod 如何 watch 多个 API Server
单集群时,istiod 的服务发现输入只有本集群 K8s API Server。多集群的 Primary-Remote 部署模型把输入端扩成多个:
- 生成一份远程访问凭证(
istioctl create-remote-secret),本质是把远程集群的 API Server 访问信息打包成一个 Kubernetes Secret。 - 把这个 Secret 应用到 primary 集群的 istiod
命名空间。istiod 读到这个 Secret 后,会像 watch 本地 API
Server 一样,对远程集群的 API Server 建立 watch
连接,抓取该集群的 Service / Endpoint(对应
topology.istio.io/controlPlaneClusters标注的远程集群关系)。 - 同名同命名空间的 Service 在不同集群上的端点会被合并进同一个逻辑 Service——对使用方(VirtualService、DestinationRule)来说,跨集群与否是透明的;对 istiod 内部来说,这个 Service 的 endpoint 列表现在混合了本地 Pod IP 与远程集群 Pod IP。
官方 Deployment Models 文档把这个约束写得很直接:多 primary 集群场景下,每个 primary 集群的配置来源仍是各自的 API Server,配置本身不跨集群同步——“两个集群上各自 apply 一份 VirtualService”这件事,Istio 不会替你保证一致,需要 CI/CD 或 GitOps 工具在配置分发层解决。这是多集群机制里第一个容易被低估的责任边界:服务发现是自动跨集群聚合的,配置分发不是。
flowchart TD
subgraph c1 ["cluster1 (primary)"]
api1["K8s API Server"]
istiod["istiod"]
end
subgraph c2 ["cluster2 (remote)"]
api2["K8s API Server"]
end
secret["remote secret<br/>(istioctl create-remote-secret)"]
api1 --> istiod
secret -.grants access.-> istiod
istiod -->|watch| api2
istiod --> merged["Merged Service:<br/>endpoints from cluster1 + cluster2"]
二、输出端:Split-Horizon EDS 按网络重写端点
合并出来的端点列表不能原样推给所有代理——如果两个集群之间没有
Pod 网络直连(不同 network),把远程集群的 Pod
IP 直接塞进本地代理的 EDS 响应,代理连不上。Istio 的解法是
Split-Horizon
EDS:对每个代理,按它所在的网络重写端点列表。
源码落点是
pilot/pkg/xds/endpoints/ep_filters.go 的
EndpointsByNetworkFilter:
// pilot/pkg/xds/endpoints/ep_filters.go(Istio 1.30.3,节选)
// EndpointsByNetworkFilter is a network filter function to support Split Horizon EDS - filter the endpoints based on the network
// of the connected sidecar. The filter will filter out all endpoints which are not present within the
// sidecar network and add a gateway endpoint to remote networks that have endpoints
// (if gateway exists and its IP is an IP and not a dns name).
func (b *EndpointBuilder) EndpointsByNetworkFilter(endpoints []*LocalityEndpoints) []*LocalityEndpoints {
if !b.gateways().IsMultiNetworkEnabled() && (!features.EnableAmbientMultiNetwork || isSidecarProxy(b.proxy)) {
// Multi-network is not configured (this is the case by default). Just access all endpoints directly.
return endpoints
}
...
gateways := b.selectNetworkGateways(epNetwork, epCluster)
...
}行为总结:
- 未配置多网络时(默认),这个过滤器直接跳过,端点原样下发——单网络多集群(Pod 网络本身跨集群直连)不需要东西向网关介入 EDS 层。
- 配置了多网络后,对属于其他网络的端点,不会把
Pod IP 原样发给代理;而是用
selectNetworkGateways找到目标网络对应的东西向网关地址,替换成网关的 IP:Port,并按网关数量重新分摊负载均衡权重(scaleEndpointLBWeight/splitWeightAmongGateways)。 - 代理侧看到的仍然是一个普通的 EDS
LbEndpoint列表,区别只是”部分端点其实是网关地址”——这一层重写对 Envoy 完全透明,Envoy 不知道、也不需要知道对端是真实 Pod 还是网关。
这就是”东西向网关”在控制面视角下的真实含义:它不是一个业务概念,而是 Split-Horizon EDS 用来替换跨网络端点的一个转发点。源码注释里还明确了一个历史遗留的边界情况:如果配置了多网络但没有部署东西向网关,Istio 仍会按同网络处理远程端点下发 EDS,实践中大概率因为 Pod 不可达而失败——这是一个已知的、故意不改的历史行为,因为改动可能破坏已有部署。
sequenceDiagram
participant Pilot as istiod (EndpointBuilder)
participant Sidecar as Sidecar on network1
Note over Pilot: Merged endpoints: network1 pods + network2 pods
Pilot->>Pilot: EndpointsByNetworkFilter(endpoints)
Pilot->>Pilot: network2 endpoints -> selectNetworkGateways(network2)
Pilot->>Sidecar: EDS response: network1 pod IPs (as-is) + network2 gateway IP (rewritten)
三、网络与地域标签:控制面如何知道谁在哪个网络
Split-Horizon EDS 依赖两个输入才能工作:代理属于哪个网络、目标端点属于哪个网络。Istio 用统一的资源标签解决:
topology.istio.io/network:可以打在系统命名空间(给整个集群设默认网络)、Pod(覆盖默认值,通常由注入 webhook 完成)、网关 Service(声明”这个网关服务负责哪个网络的东西向流量”)上。- 官方多集群排障文档明确:网络判定错误的典型症状是”客户端与服务端网络无法确定”——如果
global.network没配对,或注入 webhook 配置不对,istiod 可能把跨网络的代理误判成同网络,直接下发不可达的 Pod IP。
Locality(地域)负载均衡是独立的另一套机制,不是
Split-Horizon EDS 的一部分:它按
region/zone(以及可选的
subzone)标签给端点分优先级,让流量优先打到同
locality 的实例;多集群场景下,如果不同集群被打上不同
locality,流量会自然倾向本集群,除非显式关闭 locality
负载均衡或本地实例不健康触发故障转移。这一层只影响权重排序,不影响”这个端点是否可达”——可达性判断始终是
Split-Horizon EDS 的职责。
四、失败模式:控制面视角
多集群把单集群里”istiod 挂了”的故障域,变成了”哪个集群的哪个依赖挂了”的组合问题:
| 故障点 | 控制面表现 | 影响范围 |
|---|---|---|
| 远程集群 API Server 不可达 | istiod 对该远程集群的 watch 连接失败/重试;远程集群的端点从合并结果中消失 | 该远程集群的实例不再出现在任何代理的 EDS 里,不是进程崩溃,是端点列表变短 |
| remote secret 过期/被吊销 | 同上,鉴权失败导致 watch 无法建立 | 与 API Server 不可达表现类似,需要查 istiod 日志区分网络问题还是鉴权问题 |
| 东西向网关不可用 | Split-Horizon EDS 仍会把远程网络端点重写为该网关地址,因为 EDS 层不做网关健康探测 | 客户端拿到”看似合法”的网关端点,实际连接失败或超时,需要靠 Envoy 侧的健康检查/outlier 摘除 |
| 多 primary 配置漂移 | 控制面无检测机制 | 两个集群上同名 VirtualService 内容不一致,行为取决于流量落在哪个集群的数据面,且不会有任何”配置冲突”告警 |
后两行是本篇要强调的机制边界:Split-Horizon EDS 只负责”地址怎么写”,不负责”地址是否健康”;多 primary 的配置一致性,Istio 完全不管,责任在运维流程。
五、争论与开放问题
| 争论 | A | B |
|---|---|---|
| Primary-Remote vs 多 Primary | 单一配置源,运维简单,但 primary 集群是单点 | 多 primary 高可用,但配置一致性完全靠外部流程兜底 |
| 关闭东西向网关、走扁平网络直连 | 少一跳、少一个故障域 | 放弃了 Split-Horizon EDS 的隔离收益,网络必须真正互通且信任模型要延伸到跨集群 |
| Split-Horizon EDS 不做网关健康探测 | 保持 EDS 层职责单一,健康判断留给既有的 outlier/健康检查机制复用 | 网关本身的可用性只能靠数据面事后摘除,没有控制面前置探测 |
开放问题:多集群 EDS 合并 + Split-Horizon
重写叠加在一起后,第 10 篇讨论的
debounce/端点抖动问题会不会被放大——例如远程集群一次大规模滚动发布,是否会被本地
istiod 当作一次需要重算 push context 的 Full
事件、进而影响到与该远程集群完全无关的本地代理?这需要具体拓扑下的实测(多集群
+ 端点 churn 注入),本篇不给未跑过的结论。
六、参考资料
规范 / 官方文档(A)
- Istio, Install
Primary-Remote,
istio.io/latest/docs/setup/install/multicluster/primary-remote/(Istio 1.30 文档路径)。 - Istio, Install Primary-Remote on different
networks,
istio.io/latest/docs/setup/install/multicluster/primary-remote_multi-network/。 - Istio, Deployment
Models,
istio.io/latest/docs/ops/deployment/deployment-models/(多 primary 配置责任边界)。 - Istio, Troubleshooting Multicluster(诊断工具文档,网络标签排障)。
源码(A)
istio/istiotag 1.30.3:pilot/pkg/xds/endpoints/ep_filters.go(EndpointsByNetworkFilter、selectNetworkGateways)。
站内对照
实验台账
- 本篇为机制推导 + 官方文档/源码路径;无多集群环境实测数字,不写跨集群延迟或吞吐结论。
七、小结
- 服务发现跨集群自动聚合,配置分发不会:remote secret 让 istiod 跨集群 watch 端点,但多 primary 的配置一致性完全是运维流程的责任。
- Split-Horizon EDS 是一个地址重写层:同网络端点原样下发,跨网络端点被替换成东西向网关地址,Envoy 对此无感知。
- 网络标签决定 EDS 重写是否触发;Locality 标签只影响优先级排序——两者是独立的机制,不要混为一谈。
- 东西向网关的健康状态不在 EDS 层把关,网关不可用要靠数据面自身的健康检查/outlier 兜底。
→ 系列目录 · 上一篇:NACK / Warming 责任 · 下一篇:Ambient:ztunnel / waypoint
同主题继续阅读
把当前热点继续串成多页阅读,而不是停在单篇消费。
【Istio 控制面】控制面全景:从 CRD 到 xDS 的翻译与推送内核
定位 Istio 控制面内核相对 Envoy 消费侧、Service Mesh 税文与 Gateway API 资源模型的缺口;给出五条坐标系、16 篇地图与本系列明确不写的范围,钉住 istiod 1.30.3 为主线。
【Istio 控制面】istiod 进程模型:discovery、config、CA 与注入的边界
拆解 pilot-discovery 二进制内部的组件化启动顺序:CA 为何先于 Discovery 初始化、注入 Webhook 与配置校验为何是独立 HTTPS 服务、以及哪些组件真正位于 xDS 推送热路径上。
【Istio 控制面】Service / Endpoint / Workload → CDS/EDS:istiod 如何拼出一个 Cluster
拆解 istiod 如何把 K8s Service、EndpointSlice、WorkloadEntry 聚合进内部服务模型,再经 CdsGenerator/EdsGenerator 分别生成 Cluster 与 ClusterLoadAssignment;钉住命名契约与部分推送边界,subset 留给 DestinationRule 篇。
【Istio 控制面】VirtualService / mesh HTTPRoute → RDS:两条编译路径,一份路由表
拆解 istiod 的 RdsGenerator 如何把 VirtualService 与 Gateway API HTTPRoute(含 GAMMA mesh 模式)都编译成同一份内部路由模型,说明为何双轨输入会在同一 host 上产生冲突,以及两套 API 的合并顺序为何一个确定、一个未定义。