土法炼钢 · 系统与基础设施

【Envoy Gateway】xDS Translator 与 Infra:推送不等于在服务

文章导航

分类入口
kubernetesnetwork
标签入口
#envoy-gateway#xds#ir#go-control-plane#delta-xds#infra-manager#envoyproxy#v1.9.0

目录

第 4 篇 把 Gateway API 对象编译成 XdsIR 与 InfraIR。值班里下一句常被说成「控制面已经推了」。这句话至少覆盖两件独立的事:xDS 资源表有没有进 go-control-plane 快照,以及 Kubernetes 里有没有可调度的 Envoy Pod。两件都完成,仍可能没有在服务——Envoy 可以 ACK 后继续 warming,也可以 NACK 后守着上一份 known-good。

本文只钉生产侧:XdsIR 如何变成 LDS/RDS/CDS/EDS/SDS,InfraIR 如何变成舰队,以及为什么「已推送」不能当成流量切新。Filter 链、HCM 与 warming 语义外链 envoy/09–11,不在本篇重写。

本文是「Envoy Gateway / Gateway API」系列第 5 篇(共 16 篇)。→ 系列目录

篇目 核心内容
第 4 篇 · Gateway API Translator 与 IR CR → XdsIR / InfraIR
第 5 篇 · xDS Translator 与 Infra IR → xDS;InfraIR → 舰队;推送 ≠ 在服务
第 6 篇 · HTTPRoute / GRPCRoute 匹配、权重、阴影路由

上一篇Gateway API Translator 与 IR · 下一篇HTTPRoute / GRPCRoute

版本锚定:Envoy Gateway v1.9.0(源码 tag v1.9.0,2026-08-14)。机制对齐官方 System Design、tag 下 internal/xdsinternal/infrastructure。数据面 Envoy v1.39.0 的资源树与 ACK 语义外链 envoy 系列。无集群则不粘贴伪造 egctl / config_dump


一、xDS Translator

官方 System Design 把 xDS Translator 写成:把 xDS IR 译成 xDS Server 消费的资源。tag v1.9.0 的实现入口是 internal/xds/translator.Translator.Translate。它吃一份 *ir.Xds,吐 *types.ResourceVersionTable。注释写明翻译是 best-effort:CRD 校验与 Gateway API Translator 应已挡住大部分错误;xDS 这一层只在少数路径失败(扩展 hook、EnvoyPatchPolicy JSON patch),不想因单点失败让整棵快照消失。

Translate 的顺序(删减注释后)是:

// envoyproxy/gateway tag v1.9.0
// internal/xds/translator/translator.go  Translator.Translate
if err := t.processHTTPReadyListenerXdsTranslation(tCtx, xdsIR.ReadyListener); err != nil { ... }
if err := t.processHTTPListenerXdsTranslation(tCtx, xdsIR.HTTP, ...); err != nil { ... }
if err := t.processTCPListenerXdsTranslation(tCtx, xdsIR.TCP, ...); err != nil { ... }
if err := t.processUDPListenerXdsTranslation(tCtx, xdsIR.UDP, ...); err != nil { ... }
if err := t.processMergedBackendClusters(tCtx, xdsIR); err != nil { ... }
if err := processSDSClusters(tCtx, xdsIR); err != nil { ... }
if err := t.patchGlobalResources(tCtx, xdsIR); err != nil { ... }
if err := processJSONPatches(tCtx, xdsIR.EnvoyPatchPolicies); err != nil {
    // JSONPatch 视为用户错误:打日志,不让整次翻译失败
}

envoy/09 的资源树对照:

xDS 类型 本层从哪来 不在本篇展开
LDS HTTP / TCP / UDP listener IR;另有 Ready listener Filter 链内核
RDS HTTP listener 上的 route IR HCM 如何挂 RDS
CDS / EDS backend cluster IR、合并集群、访问日志/tracing 附属 cluster 连接池 / outlier
SDS 证书与校验材料;另为外部 SDS unix URL 建 cluster 证书轮转时序

HTTP 路径上,一条已附着的 HTTPRoute 不会在本层再做一次 Gateway API 匹配计算——那是第 4 篇 IR 的职责。本层把 IR 里已经排好的 host / route / cluster 名字写成 Envoy proto。同文件后半把 HTTP listener 编成 LDS(内嵌或引用 RDS)、把 destination 编成 CDS/EDS;TCP/UDP 走 processTCPListenerXdsTranslation / processUDPListenerXdsTranslation,不经 HCM。TCP/UDP listener 若没有任何 route,翻译器会放一个名为 EmptyCluster 的静态空 cluster(常量 emptyClusterName),让 listener 仍能生成,而不是留下悬空引用。L4 路由「静默跳过」时,数据面可能表现为这类空 cluster 或根本没有对应 listener,而不是 HTTP 500。

Translate 末尾还会:为访问日志与 tracing 建附属 cluster、patchGlobalResources(客户端证书、OIDC HMAC、全局限流集群)、跑 EnvoyPatchPolicy JSON patch、调用 extension post-translation hook。这些步骤的失败分属「系统错误」和「用户 patch 错误」,门闩在 runner,不在 Translator 自己删除半棵树。

JSON patch(EnvoyPatchPolicy)故意不阻断其余资源:runner 把「系统级翻译错误」与「用户 patch 错误」分开,见第二节。这是排障时必须分开的两格——patch 没匹配到资源,快照仍可能更新;翻译器自己失败,快照应停在上一份。

flowchart LR
  xdsir["XdsIR"] --> xdstr["xDS Translator"]
  xdstr --> lds["LDS"]
  xdstr --> rds["RDS"]
  xdstr --> cds["CDS"]
  xdstr --> eds["EDS"]
  xdstr --> sds["SDS"]
  lds --> cache["Snapshot cache"]
  rds --> cache
  cds --> cache
  eds --> cache
  sds --> cache
  cache --> envoy["Envoy v1.39"]

图:XdsIR 经 Translator 进入按 IR key 切片的快照,再由 xDS 流交给 Envoy。节点为英文;正文解释每条边停在哪一层。


二、go-control-plane Delta xDS

System Design 把 xDS Server 写成基于 go-control-plane、实现 Delta xDS 的 gRPC 服务。tag 里对应包是 internal/xds/runnerinternal/xds/cacheinternal/xds/server 在 v1.9.0 只剩 GatewayNamespaceMode 的 kubejwt 认证,不要按旧目录名去找主循环。

Runner 启动时:

  1. cache.NewSnapshotCache(true, logger)true 打开 ADS 模式的 go-control-plane SnapshotCache
  2. serverv3.NewServer(ctx, r.cache, r.cache):cache 同时充当 snapshot 存储与 stream callbacks。
  3. registerServer 挂上 ADS、SDS、CDS、EDS、LDS、RDS、Runtime 七类 gRPC 服务。
  4. 订阅 XdsIR watchable;每次更新调用 Translator.Translate,成功则 GenerateNewSnapshot

internal/xds/cache/snapshotcache.go 的注释写明:按 Node ID 切片快照,是为了「do incremental xDS properly」。文件头注明实现派生自 Contour 的 snapshotter。NewSnapshotCache(ads=true) 把 go-control-plane 的 cache 置于 ADS 模式;同一结构体既实现 SotW 的 OnStreamRequest,也实现 Incremental 的 OnStreamDeltaRequest / OnDeltaStreamOpen。官方 System Design 把该服务器概括为 Delta xDS;源码同时接两条回调,排障时不要假设集群里绝对没有 SotW 流。

两条路径在 NACK 上的处理一致:ErrorDetail 非空时增加 xdsNACKTotal(按 node ID 与 type URL),并写错误日志——Envoy 继续服务上一份 known-good;重新拉起的代理会加载这份被拒配置并失败。这与 v1.9.0 Release Notes 新增 xdsNACKTotal 的口径一致。第一份 snapshot 尚未写出时,cache 对 DiscoveryRequest 直接返回,让 go-control-plane 先回空响应——「Envoy 已连上控制面」可以早于「LDS 里有内容」。

Runner 对翻译错误的门闩也写在源码里,不是口误:

v1.9.0 把 xDS gRPC maxReceiveMessageSize 默认从 4MiB 提到 32MiB(EnvoyGateway.xdsServer.maxReceiveMessageSize 可改)。发行说明写的失败模式是:大规模集群里 Envoy 重连时的 delta 请求超过旧上限,流以 “received message larger than max” 断开,代理停在 last known-good。这是控制面推送轴上的容量问题,不是 HTTPRoute YAML 写错。

GatewayNamespaceMode 下,同一 runner 改用 JWT interceptor 与每次握手重读磁盘证书(v1.9 安全更新:修复 xDS 认证绕过,并为 SotW 请求做校验)。认证失败的表象是代理连不上控制面,而不是「IR 空」。展开见第 10 篇。

协议层 ACK/NACK、nonce、SotW 与 Incremental 的报文差,以 envoy/10 为准。本篇只钉:EG 把 IR 编成 go-control-plane 快照;Delta 回调存在;NACK 有指标;快照更新不等于 Envoy 已切流量。


三、Infra Manager 与 EnvoyProxy

XdsIR 只描述「代理应加载什么配置」。谁把 Envoy 进程跑起来,是另一条管线。System Design 规定 Infra Manager 消费 Infra IR,对数据面做 CRUD(Deployment、Service 等),并可选地拉起辅助控制面(例如全局限流服务)。

tag v1.9.0 internal/infrastructure/manager.goManager 收成三个实现:

Provider v1.9.0 含义
Kubernetes internal/infrastructure/kubernetes 默认:用 kube API 管舰队
Host internal/infrastructure/host Custom provider 的本机进程
Remote internal/infrastructure/remote v1.9 新增 remote infrastructure provider

kubernetes.Infra.createOrUpdateResourceRender 依次保证 ServiceAccount、ConfigMap、Deployment、DaemonSet、Service、HPA、PDB。渲染器在 internal/infrastructure/kubernetes/proxyproxy.ResourceRender 实现该接口)。Gateway 对象触发这份 InfraIR,是第 4 篇已经钉过的翻译输出;本层不再读 HTTPRoute 来决定副本数。v1.9.0 修过:当配置了 HPA 时生成的 Deployment 省略 spec.replicas,避免 server-side apply 的 ForceOwnership 把副本数从 HPA 手里抢回来。这是 Infra 轴上的所有权问题,不是 xDS 翻译错误。

flowchart LR
  infroir["InfraIR"] --> mgr["Infra Manager"]
  proxycr["EnvoyProxy CR"] --> mgr
  mgr --> sa["ServiceAccount"]
  mgr --> cm["ConfigMap bootstrap"]
  mgr --> deploy["Deployment or DaemonSet"]
  mgr --> svc["Service"]
  deploy --> envoy["Envoy v1.39 dataplane"]
  svc --> envoy

图:InfraIR 与可选 EnvoyProxy 参数进入 Infra Manager,产物是可调度的数据面对象,与 xDS 快照并行,不互相替代。

EnvoyProxy 挂在 GatewayClass parametersRef(或更具体的 Gateway)上,用来改默认基础设施:Service 类型、副本、bootstrap 片段。System Design 写明未指定则用默认参数。v1.9 的 mergeBackends、GatewayNamespaceMode、remote infra 细节进第 10 篇;本篇只要求记住:没有舰队,xDS 没有消费者;有舰队而无对应 IR key 的快照,代理拿到的是空或旧配置。

两条管线失败时的表象不同:

失败点 典型表象 先查
Infra 未创建/未就绪 Gateway Programmed 不绿、没有 Envoy Service 地址 InfraIR、EnvoyProxy、调度
Infra 在、XdsIR 空 有 Pod、监听器与路由对不上 CR 第 4 篇翻译与 status
快照在、NACK xdsNACKTotal 涨;流量仍是旧图 patch 名、非法 proto、消息尺寸
ACK 后仍旧路由 证书/cluster warming、RDS 早于 CDS envoy/11

四、「控制面已推送」不等于「Envoy 在服务」

把这句话拆成四层,才能和本系列五轴对齐:

  1. IR 层:Gateway API Translator 是否为该 Gateway 产出了对应 Listener/Route。status 绿而 IR 空,是第 4、13 篇的题。
  2. xDS 层Translate 是否成功写入该 IR key 的 snapshot;Envoy 是否 ACK/NACK。NACK 时用户侧可以完全无感——发行说明与 cache 注释都写了「仍服务 last known-good」。
  3. 数据面 warming:ACK 只表示本批资源通过隔离校验并意图应用。Cluster/Listener 还要等 RDS/EDS/SDS 依赖齐。这是 Envoy 协议的规范锚点,不是 EG 特有 bug。见 envoy/11
  4. 入口之后:请求已经出 Envoy,失败在 CNI/identity。坐标换到 cilium/15,不要在本层改 HTTPRoute。

Runner 还有一处与「已推送」相关的时序:cache 在尚无 snapshot 时对 DiscoveryRequest 直接返回,让 go-control-plane 回空响应,等第一份 snapshot 再推。因此「控制器 Ready、Envoy 已连上」可以早于「第一份有内容的 LDS」。排障口令仍是先点名轴,而不是重装 Helm。

v1.9.0 把「部分翻译失败仍发半棵树」的风险收在系统级 err 上:有 err 就不 SetSnapshot。代价是一次扩展 hook 失败会让整个 Gateway 的配置冻结在旧快照——这是正确性选择,不是静默丢更新。用户可见信号应落在第 11 篇的 xdsNACKTotal 与第 13 篇的 IR/xDS 轴,而不是伪造一份 config_dump

生产联调清单(语义级,本篇未跑):

  1. 先确认 Infra:Envoy Deployment/Service 是否存在、Gateway 地址是否从 Service 或 externalIPs 回填(v1.9 修过无 LB 时 AddressNotAssigned 误报)。
  2. 再确认 xDS:有无 NACK 指标、控制面日志是否在报 type URL 级拒绝。
  3. 最后才问数据面:warming、证书、上游耗尽——方言在 envoy/15

第 13 篇会把这三步收进「未 Programmed / IR 空 / xDS NACK / Envoy 旧快照」坐标系。本篇的结论只需记住:Infra 与 xDS 是并行管线;任一成功都不能代替另一条;二者都成功仍不能代替 Envoy warming。 把「egctl 显示已同步」理解成「客户端已经走到新 backend」,会在证书旋转和 RDS 早到窗口里误判。


五、谱系、争论与开放问题

谱系

可编程反向代理 + 动态发现(Matt Klein / Lyft Envoy 工程传统)
  → Envoy xDS 协议(LDS/RDS/CDS/EDS/SDS;SotW 与 Incremental)
  → go-control-plane 作为控制面库
  → Envoy Gateway:Gateway API → IR → xDS Translator + Infra Manager(v1.9.0)
  → 对照:Istio istiod 更直接的 CRD→xDS(外链 istio-xds/)

Envoy 把「配置」定义成按类型、按名字引用的资源树,而不是一份不可部分更新的静态文件。Incremental/Delta 要解决的是 SotW 整表推送在大规模节点上的带宽与收敛问题;EG 用 go-control-plane 的 snapshot cache 承接这一协议,而不是自研一套 gRPC schema。

争论:IR 多一跳是否值得

A 侧(EG 官方设计):IR 把 Gateway API 与 Envoy 资源树解耦,同一套 InfraIR 可以换 provider(Kubernetes / host / v1.9 remote),xDS 翻译可以独立演进。

B 侧(Istio 一类路径):少一层中间表示,排障时 CR 与 xDS 名字更容易对账;多一跳意味着「Accepted 绿、IR 空、快照旧」三种失败可以同时成立。

本系列不站队。v1.9.0 的工程事实是:IR 与 xDS 已经分开,值班必须会点名轴。若平台只有会改 HTTPRoute 的人、没有人读 snapshot/NACK,IR 的解耦收益会被定位时间吃掉。

开放问题

  1. IR 与 xDS 对账是否应成为一等公民:今天要对一份 HTTPRoute 问「进了哪棵 RDS」,仍依赖实现细节与 dump 工具。可检验:能否在不伪造 config_dump 的前提下,用官方接口列出 IR key → snapshot 资源名。
  2. NACK 之后的用户可见性xdsNACKTotal 在 v1.9 才成为一等指标;Route status 仍可能保持 Accepted。status 能否承担「配置已生效」SLO,留到第 13、16 篇。
  3. remote infra 的责任面:控制面推 xDS、别人管 Pod 时,warming 失败工单应先找哪一侧?第 10 篇只划边界,本篇不写未核厂商适配。

六、小结

  1. xDS Translator(internal/xds/translator)把 XdsIR 编成 LDS/RDS/CDS/EDS/SDS;best-effort,系统级错误不发半棵树。
  2. xDS Server(internal/xds/runner + internal/xds/cache)用 go-control-plane;官方设计点名 Delta;NACK 记 xdsNACKTotal,代理守 known-good。
  3. Infra Manager(internal/infrastructure)把 InfraIR 变成舰队;与 xDS 并行。
  4. 「已推送」必须拆成 IR / snapshot / ACK / warming 四层;数据面内核外链 envoy 系列。

下一篇回到用户最常改的对象:HTTPRoute / GRPCRoute 如何进入 RDS,以及 v1.9 如何用 RouteRulesOverlap 标出同 listener 上的阴影规则。


七、参考资料

规范 / 官方文档(A)

源码(A)

核心论文 / 设计传统

实验 / 工具

站内对照


上一篇:Gateway API Translator 与 IR · 系列目录 · 下一篇:HTTPRoute / GRPCRoute

读完这篇,下一步读什么

优先读同系列或同问题的下一篇,把单篇消费变成主题集群。

2026-08-19 · kubernetes / network

Envoy Gateway / Gateway API:从 CR 附着到 xDS

补齐站内 Gateway API 产品教程与 Envoy 数据面消费侧之上的南北向控制面内核:附着与 status、Provider watch、IR 翻译、xDS 与 Infra、路由/TLS/L4/Policy 边界,并以排障与相对 Cilium Gateway / Istio / Ingress 的选型收束。


By .