Envoy
Router 拿到的终局只是一个 cluster 名,这个名字来自 RDS
下发的
RouteConfiguration。生产上,写出这份路由表的
CRD 有两种:Istio 原生的 VirtualService,或
Kubernetes Gateway API 的 HTTPRoute(在 mesh
模式下即 GAMMA)。本文不讲这两种 CRD 的字段语法——那是 Gateway
API 深度拆解
的范围——只回答一个问题:两条输入路径最终都要喂给同一个
RdsGenerator,它们是怎么汇合的,汇合时会不会打架。
本文是「Istio / 网格控制面内核」系列第 6 篇。→ 系列目录
上接 第 5 篇:Service/Endpoint → CDS/EDS;下接 第 7 篇:DestinationRule。
版本锚定:Istio 1.30.3,源码路径对应
pilot/pkg/xds/rds.go、pilot/pkg/config/kube/gateway/(函数签名以 master 分支验证,1.30.x 语义一致)。Gateway API 资源模型与 GAMMA 语义见 k8s-network/18,本文不重复。
一、RDS 生成器:只做全量,按名字取路由
RdsGenerator.Generate(pilot/pkg/xds/rds.go)逻辑很短:先判断
rdsNeedsPush,需要时调用
ConfigGenerator.BuildHTTPRoutes(proxy, req, w.ResourceNames)。两处细节值得钉住:
rdsNeedsPush源码注释直白写着// RDS only handles full push——req.Full为假直接跳过。这与 第 5 篇 里 EDS 支持的部分推送是明确的不对称:端点抖动可以走部分推送省资源,路由表变化没有这条捷径。skippedRdsConfigs列出了一批确定不会影响 RDS 的 Kind:WorkloadEntry、WorkloadGroup、AuthorizationPolicy、RequestAuthentication、PeerAuthentication、Secret、WasmPlugin、Telemetry、ProxyConfig。换句话说,改一条AuthorizationPolicy不会触发 RDS 重算——它影响的是 filter chain 与 RBAC(第 8 篇),不是路由表本身。
BuildHTTPRoutes 按
w.ResourceNames(代理订阅的 route config
名,通常对应监听端口)逐个查内部路由模型,拼出
RouteConfiguration。这个「内部路由模型」是本文重点——它不是
VirtualService
本身,而是两条输入路径共同产出的中间表示。
二、路径一:VirtualService 原生编译
VirtualService 里的
hosts、http[].match/route/rewrite、gateways
字段被解析成
config.Config{GroupVersionKind: gvk.VirtualService, ...},存进
istiod 的配置存储(config store),供
BuildHTTPRoutes 按 host 查询。几个直接影响 RDS
的字段语义:
gateways未显式指定时默认值是mesh——即只对 sidecar 生效,不挂到任何 ingress gateway 上。- 同一
gateways: [mesh]绑定下,多个 VirtualService 定义同一个 host 会被istioctl analyze判为IST0109 ConflictingMeshGatewayVirtualServiceHosts:官方文档原文——「Istio supports merging of virtual services that are attached to the ingress gateways」,但没有对 mesh gateway 做同样的合并承诺,同 host 冲突直接报错。 - 挂在 ingress gateway 上的多个 VirtualService
允许合并成同一份路由表,但
http[]是一个线性列表,跨资源合并后的匹配顺序是未定义的——这不是本文的推测,是 Istio 维护者在 issue #7674 里的原话:因为路由匹配被绑定成一个线性 list,「we can’t infer a safe ordering…this is a major reason why we moved away from merging and revised the API」。
生产上这个「未定义顺序」不是理论问题:issue
#58453(截至本文写作仍开放)报告的现象是——两个
VirtualService 分别声明 / 和
/api/v1/specific,如果泛匹配 /
先被处理,所有请求都会被它吃掉,即便更具体的路径路由本该优先。路由对不对,取决于资源创建顺序,而不只取决于配置内容本身——这正是「RDS
已生成」不等于「行为符合直觉」的一类典型坑。
三、路径二:Gateway API HTTPRoute 编译成同一种内部模型
pilot/pkg/config/kube/gateway/controller.go
里的 Controller 用 krt(Istio
内部的声明式收集器框架)watch
HTTPRoute、GRPCRoute
等资源,HTTPRouteCollection(route_collections.go)对每条
Route 调用 conversion.go 的
convertHTTPRoute,把一条
HTTPRouteRule 转成
istio.HTTPRoute(与原生 VirtualService 里的
http[] 元素同一个 Go
类型),最终包进:
cfg := config.Config{
Meta: config.Meta{
GroupVersionKind: gvk.VirtualService,
Name: name,
Namespace: obj.Namespace,
},
Spec: &istio.VirtualService{
Hosts: []string{h},
Gateways: []string{parent.InternalName},
Http: routes,
},
}这是本篇最关键的一点:HTTPRoute 不是绕开
VirtualService 走另一条通道去 RDS,而是被 Gateway API
控制器编译成一个
GroupVersionKind: VirtualService
的内部对象,再和原生 VirtualService
一起存进同一个配置存储,被同一个 RdsGenerator /
BuildHTTPRoutes 处理。RDS
生成器完全不知道某条内部 VirtualService
「原本」是手写的还是从 HTTPRoute 转换出来的。
GAMMA:mesh 模式下 parentRef 指向 Service
Gateway API 支持把 parentRefs 指向一个
kind: Service 而不是 Gateway——这是
GAMMA(Gateway API for Mesh Management and
Administration)定义的 mesh
路由模式。conversion.go 里
parentTypes 会区分
r.IsMesh(),computeRoute 对 mesh
分支单独求值。效果上,这类 HTTPRoute 编译出的内部
VirtualService 等价于原生 VirtualService 省略
gateways 字段(默认绑到
mesh)——同样只影响 sidecar,不挂到任何
ingress gateway。
flowchart TD
vs["VirtualService CRD"] --> store["Config Store<br/>kind: VirtualService"]
hr1["HTTPRoute<br/>parentRef: Gateway"] --> conv["gateway controller<br/>convertHTTPRoute"]
hr2["HTTPRoute<br/>parentRef: Service (GAMMA)"] --> conv
conv --> store
store --> rds["RdsGenerator.BuildHTTPRoutes"]
rds --> rc["RouteConfiguration"]
四、双轨权威:谁的合并规则说了算
两条路径殊途同归到同一个内部模型,但上游的匹配优先级算法完全不同:
| 维度 | VirtualService | HTTPRoute(Gateway API 规范) |
|---|---|---|
| 单资源内顺序 | http[] 线性列表,第一条匹配的规则生效 |
同样是列表,但资源间还有确定性 tie-break |
| 跨资源合并(mesh) | 不支持,同 host
直接判冲突(IST0109) |
mesh 模式下多条 HTTPRoute 可按下方规则确定性合并 |
| 跨资源合并(ingress/gateway) | 允许合并,但顺序未定义(#7674) | 规范强制的确定性优先级 |
| 优先级判定依据 | 依赖创建顺序 / 编写顺序,官方未提供裁决算法 | Exact 路径 > 最长 Prefix > Method > header 数量
> query 参数数量 > 创建时间 >
namespace/name 字典序(Gateway API
规范原文) |
HTTPRoute 的确定性算法不是 Istio 发明的,是 Gateway API 规范(HTTPRouteRule 一节)强制要求所有实现遵守的行为——这也是为什么 #58453 里 Istio 维护者直接建议「切到 HTTPRoute,顺序问题就解决了」,而不是给 VirtualService 补一个等价算法:VirtualService 早期把匹配顺序设计成裸列表,事后加一套跨资源仲裁算法的改造成本高于迁移到新 API(同 issue 中维护者对 #22997 的评论)。
这不只是「两个 CRD 长得像」的表面重复,而是两套关于「谁能确定性地决定路由优先级」的设计在同一个内部模型里共存。Istio 官方文档对此有明确立场:「Istio supports the Kubernetes Gateway API and intends to make it the default API for traffic management in the future」——这句话钉在 Kubernetes Gateway API 任务文档的开篇,不是营销辞令,而是关于未来权威归属的官方声明。
五、运维层面的冲突边界
由于两条路径写入的是同一个 kind 的配置对象,下面这些组合在生产上是真实风险点,不是理论假设:
- 同一 host 同时被原生 VirtualService 与 mesh 模式
HTTPRoute 覆盖:两者编译出的内部 VirtualService
会被当成两个独立资源处理,命中前一节「mesh 网关下同 host
冲突判定」——但冲突检测(
istioctl analyze)目前只覆盖 VirtualService 视角,看不到「这条内部对象其实来自哪个 HTTPRoute」,排障时容易忽略源头。 - HTTPRoute 用了 VirtualService 不支持的字段(或反过来):Gateway API 规范明确「不覆盖 Istio 100% 的功能集」,混用时功能对齐需要逐字段核对,不能假设「反正都编译成 VirtualService,字段能力就一样」。
- 迁移期双写:把某个 host 从 VirtualService 迁移到 HTTPRoute 时,若忘记删旧资源,等于制造了上面第一类冲突——这是 GAMMA 落地里最常见的运维事故模式,本质原因是两条编译路径共享目标模型,但没有共享的「谁是权威来源」标记。
六、谱系、争论与开放问题
谱系:Istio 的
VirtualService 诞生于 2017 年前后的 Istio
0.x/1.0,是在 Kubernetes Ingress
表达力不足的背景下设计的专有 CRD;Gateway API 由 SIG-Network
主导,吸收了包括 Istio 在内的多家 Ingress
控制器经验,试图做「厂商中立」的标准化后继(k8s-network/18
有完整脉络,这里不重复)。GAMMA 是 Gateway API
工作组内专门解决「网格内路由」场景的扩展方向,parentRef
指向 Service 是其核心机制。
争论:路由匹配的合并顺序该由「配置书写顺序」隐式决定,还是由「协议规定的确定性算法」显式决定——这不是抽象的 API 设计品味问题,#7674 与 #58453 是两个跨越数年的真实工程分歧,前者导致 Istio 在 0.6 之后主动放弃了更激进的自动合并,后者是仍未关闭的生产诉求。Istio 官方选择的解法不是修补 VirtualService 的合并算法,而是把权威地位向 HTTPRoute 转移——这本身也是一种立场,而非纯技术必然。
开放问题:在 Istio 明确「计划让 Gateway
API 成为默认」但尚未给出 VirtualService
弃用时间表的当下,两套 API
长期并存期间的冲突检测应该做到什么粒度——目前的
istioctl analyze 规则集是围绕 VirtualService
视角设计的,能否/是否需要感知「这条 VirtualService 由哪个
HTTPRoute 编译而来」并给出跨 API
的冲突诊断,社区没有给出明确路线图。
七、参考资料
源码(A)
istio/istio:pilot/pkg/xds/rds.go(RdsGenerator.Generate、rdsNeedsPush、skippedRdsConfigs)。istio/istio:pilot/pkg/config/kube/gateway/controller.go、route_collections.go、conversion.go(HTTPRouteCollection、convertHTTPRoute、parentTypes)。
规范 / 官方文档(A)
- Kubernetes Gateway API 规范,HTTPRoute / HTTPRouteRule(匹配优先级算法:Exact > 最长 Prefix > Method > header 数 > query 参数数 > 创建时间 > 字典序)。
- Istio 1.30 文档,Kubernetes Gateway API(mesh
traffic 的
parentRef用法;「intends to make it the default API」表述)。 - Istio 1.30
文档,ConflictingMeshGatewayVirtualServiceHosts(
IST0109)。
源码问题追踪(B,工程判断佐证)
istio/istioissue #7674:VirtualService 放弃跨资源自动合并的设计取舍讨论。istio/istioissue #22997:mesh gateway 场景下 VirtualService 合并缺失的维护者回复。istio/istioissue #58453:多 VirtualService 同 host 路由优先级不可预测的现网报告(截至写作时仍开放)。
站内
- Gateway API 深度拆解(CRD 语法与 GAMMA 全景,本文不重复)
- Envoy / HTTP filter 与 Router
- 第 5 篇:Service/Endpoint → CDS/EDS · 第 7 篇:DestinationRule
- 系列目录
实验台账
- 本篇不复现 issue 中的具体集群实验;结论直接引用官方规范条款与公开 issue 讨论,未在本机验证路由优先级的具体表现。
八、小结
RdsGenerator只支持全量推送;skippedRdsConfigs明确了哪些 Kind 的变化根本不会碰 RDS。- VirtualService 与 HTTPRoute(含 GAMMA mesh
模式)最终编译成同一种内部
VirtualService表示,喂给同一个生成器——HTTPRoute 不是另一条独立通道。 - 两者的合并权威模型不同:VirtualService 跨资源合并顺序未定义(mesh 网关下直接冲突报错),HTTPRoute 有规范强制的确定性优先级算法。
- 双写同一 host 是真实的生产风险,因为两条路径写入的是同一个 kind、同一个配置存储,而现有分析工具不区分「谁是原始作者」。
- Cluster 名字对了、路由表也生成了,不代表 subset 一定命中——下一篇 DestinationRule 讲这一层怎么再收窄一次。
→ 系列目录 · 上一篇:Service/Endpoint → CDS/EDS · 下一篇:DestinationRule
同主题继续阅读
把当前热点继续串成多页阅读,而不是停在单篇消费。
Istio / 网格控制面内核:从 CRD 到 xDS
补齐 Envoy 数据面 xDS 消费侧与 Mesh 选型叙事之间的控制面内核:istiod 推送图、CRD/HTTPRoute 翻译、Ambient 多客户端、NACK/warming 责任与排障对齐。
【Istio 控制面】选型收束与开放问题:CRD、Gateway API 与 eBPF L4 的排除树
用机制排除树收束 Istio CRD 翻译、Gateway API/GAMMA 与 eBPF L4 的选型边界,回收系列阅读路径,并列出推送 SLO、Ambient 成熟度、GAMMA 双轨等开放问题;不做延迟排行榜。
【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 推送热路径上。