附着轴已经回答「规范上这条 Route
该不该生效」。下一步误区是:etcd
里有对象,Translator 就看得到。 Envoy Gateway
并不在每次 API 事件里单独翻译那一个 HTTPRoute。Kubernetes
provider 用 informer watch 一组类型,把属于本
controllerName 的 GatewayClass 收成一份
resource.Resources 快照,再整份交给
Translator。漏 watch、CRD 不存在、watch 被收窄到错误
namespace、status 写回在 cache 滞后时被丢掉,表象都是「YAML
在、流量不在」,但失败发生在翻译之前。
本文钉 Provider 轴:哪些对象进入翻译输入集,status
如何写回,漏 watch
长什么样。静态启动配置(EnvoyGateway
API)只决定 provider 类型、watch 范围与
controllerName,不代替 Gateway API 对象。
本篇在系列中的位置
篇目 核心内容 第 2 篇 · 角色与附着 parentRefs、ReferenceGrant、Accepted / Programmed第 3 篇 · Provider 与 watch 输入集、Status Manager、静态配置边界 第 4 篇 · Translator 与 IR 快照如何变成 XdsIR / InfraIR 系列目录 关键问题、阅读路径、全部价值点
版本锚定:Envoy Gateway v1.9.0(tag v1.9.0)。路径以该 tag 的
internal/provider/为准:不存在internal/status包;写回在internal/provider/kubernetes/status_updater.go。官方 System Design 仍写「file provider 在 Issue #37 路线图」——与 tag 源码冲突,正文以 tag 为准。无集群则不伪造 informer 日志或egctl。
一、Kubernetes provider
官方 System Design 把 Provider 定义成 Envoy
Gateway
用来建立运行时配置、做服务发现、持久化的基础设施组件。动态配置来自集群对象;静态配置来自启动时的配置文件(即
EnvoyGateway API)。组件之间的进程内通信由
Watching Components 设计描述。
tag v1.9.0 的 Provider 接口只有
Start 与
Type(internal/provider/resource_provider.go)。internal/provider/runner/runner.go
按 EnvoyGateway.Provider.Type 分支:
ProviderTypeKubernetes("Kubernetes"):默认生产路径。ProviderTypeCustom("Custom"):再按Custom.Resource.Type选择File或再走 Kubernetes。
Kubernetes
provider(internal/provider/kubernetes/kubernetes.go)用
controller-runtime Manager
搭脚手架:Scheme、health probe :8081、可选
leader election(Lease ID
5b9825d2.gateway.envoyproxy.io)、cache。就绪检查
cacheReadyCheck 要求 informer cache
已经同步,避免多副本在资源抖动时对外提供不一致的 xDS。当选
leader 后关闭
svrCfg.Elected,让需要领导权的任务继续——status
写回的 UpdateHandler.NeedLeaderElection() 返回
true,非 leader 不写 status。
这是 Burns 等人描述的 Kubernetes
控制器回路的实例:期望状态在 API
对象上,调和循环把实际状态推过去。Envoy Gateway
的特殊点是:一次 Reconcile 不对应某一个 HTTPRoute 的
NamespacedName。 Reconcile
注释写明任何资源事件都 enqueue 同一
request(gateway controller
name),以便多次更新合并成一次全量收集。
File provider:tag 有,System Design 仍写路线图
System Design 的 Provider 节仍写:截至 v0.2 只实现了 Kubernetes;file provider 在 Issue #37 路线图上。该句是历史叙述。
tag v1.9.0 存在完整包
internal/provider/file/(file.go、store.go、status.go),runner
在 ResourceProviderTypeFile 时调用
file.New。发行说明还修过 File provider 使用的
standalone offline 控制器索引。因此:不能把 file
provider 写成 v1.9.0
未实现;也不能把它写成默认生产路径。本系列主线是
Kubernetes provider。Custom/File 用于离线/standalone
输入,不把集群 informer 当权威源;其 status 写回语义与
Kubernetes UpdateHandler
不同,本篇不展开文件格式。
v1.9.0 另有 remote infrastructure
provider(InfrastructureProviderTypeRemote),管的是数据面舰队谁来
CRUD,不是 Resource Watcher 的输入源。责任面见第 10
篇。
二、Resource Watcher 输入集
System Design 称 Resource Watcher 以 provider
相关机制(informer、cache)监视动态配置,输出给
Translator。tag 上对应
gatewayAPIReconciler.watchResources +
Reconcile 里按 GatewayClass 填充的
resource.Resources。
2.1 监视哪些 kind
watchResources(internal/provider/kubernetes/controller.go)为每个
kind 注册 source.Kind watch,handler 一律
enqueueClass。部分 kind 先 discovery
再决定是否 watch,避免集群缺少该 CRD 时控制器启动即
crash-loop。v1.9.0 发行说明把这写成针对 GKE 托管 Gateway API
addon(缺 ListenerSet / GRPCRoute / TLSRoute)以及 OpenShift
托管集(缺 BackendTLSPolicy)的修复。
启动时按 CRD 是否存在而可选的 watch 包括:EnvoyProxy、ListenerSet、GRPCRoute、TLSRoute、UDPRoute、TCPRoute、ServiceImport、BackendTLSPolicy 等。HTTPRoute、Gateway、GatewayClass、Service、EndpointSlice、ReferenceGrant 走常规路径(仍受 namespace 选择器约束)。
这与第 8 篇的 L4 版本门是两层不同的静默:
| 层 | 条件 | 表象 |
|---|---|---|
| Watch 层 | 集群没有 TCPRoute/UDPRoute
CRD |
控制器跳过 watch 并打日志;对象根本进不了 informer |
| API 版本层 | CRD 在,但 EG v1.9 只调和
gateway.networking.k8s.io/v1 |
未升到 Gateway API v1.6 时 TCP/UDP 静默跳过(Release Notes / Helm 升级说明) |
值班若只看见「TCPRoute YAML 还在」,必须先问 CRD 的 served version,再问 provider 有没有 watch 该 kind。控制器不会把「我跳过了 v1alpha2」写成一条 Route 条件——对象可能根本不在输入集里。
所有 watch 的 handler 都 enqueueClass:不把
HTTPRoute 的 NamespacedName 放进 queue,而是 enqueue
本控制器名。结果是任意相关对象变更都触发同一条全量
Reconcile。managedGatewayClasses 先列出
controllerName 匹配的 GatewayClass;每个 class
单独 NewResources()。parametersRef
处理失败(且非 transient)时,该 GatewayClass 被标
Accepted=False /
InvalidParameters,status 立刻 store——不必等到
Translator。transient 错误(timeout、429、apiserver
不可用等)则返回 error 让 workqueue
重试,避免把暂时读失败写成永久非法参数。
2.2 收集成
resource.Resources
Reconcile 对每个本控制器管理的 GatewayClass
NewResources(),然后填充 System Design
所说的三类东西:Gateway API 资源、EG
扩展资源、被引用的核心对象。tag 上
internal/gatewayapi/resource/resource.go
的结构体字段就是这份输入集的权威清单,包括:
- GatewayClass、Gateways、ListenerSets
- HTTPRoute / GRPCRoute / TLSRoute / TCPRoute / UDPRoute
- ReferenceGrants、Namespaces、Services、ServiceImports、EndpointSlices、Secrets、ConfigMaps
- EnvoyPatchPolicy、ClientTrafficPolicy、BackendTrafficPolicy、SecurityPolicy、BackendTLSPolicy、EnvoyExtensionPolicy、HTTPRouteFilter、Backend
- ClusterTrustBundles、extension ref filters / extension server policies
EnvoyProxyForGatewayClass、EnvoyProxiesForGateways,以及来自静态配置的EnvoyProxyDefaultSpec
跨 ns 的 Secret / Service / ConfigMap 只有在
findReferenceGrant 成功(或同
ns)后才进入树。策略跨 ns targetRef 另走
processPolicyTargetReferenceGrants:先粗过滤可能相关的
Grant,细匹配留在 Translator。漏掉 Grant,对象可以在 etcd
里,却不在 Resources.ReferenceGrants
里——Translator 只能报 RefNotPermitted
或根本看不到后端。
Namespace 范围由静态配置
provider.kubernetes.watch
收窄。WatchesNamespaces() 为真时,cache
DefaultNamespaces 只覆盖所列 ns;v1.9.0
修复要求 namespace-scoped watch 始终包含 controller
namespace,否则控制器读不到自己的基础设施对象。把应用
ns 写进 watch 列表却忘掉
envoy-gateway-system,表象是数据面
Deployment/Secret 像「丢了」,其实是 cache 里没有。
另一种收窄是
watch.Type = NamespaceSelector:byNamespaceSelectorEnabled
要求 selector 至少有 matchLabels 或 matchExpressions,然后对
Gateway、Route、EnvoyProxy 等叠加
hasMatchingNamespaceLabels
predicate。名单模式漏的是「没写进列表的
ns」;选择器模式漏的是「namespace
标签不匹配」。两种都不会在被排除的 HTTPRoute 上写「我不
watch 你」——对象直接不在输入集,status
保持空或过期。平台若用 selector 做多租户隔离,必须把「租户
ns 的标签契约」写进上线检查单,否则新 ns 里的 Route
会像「控制器坏了」。
2.3 刻意缩小的 cache
为降低内存与同步抖动,Kubernetes provider
对若干只读类型关闭
deepcopy(Secret、ConfigMap、Service、EndpointSlice、Node、ReferenceGrant),Pod
cache 限制为 Envoy 数据面标签。这不是漏 watch:拓扑注入
webhook 仍能看到代理 Pod。发行说明把 EndpointSlice
字段索引默认打开(EndpointSliceIndex),并警告大规模
EndpointSlice 会抬高控制器内存——可禁用该 runtime
flag。另一条修复禁止「安装了 HTTPRouteFilter CRD
之后,集群里任意 Secret 变更都触发全量
reconcile」,否则证书轮转控制器会造成持续风暴。
漏 watch 的值班口令:先问「该 kind 的 informer 在不在、namespace 在不在清单里、CRD 在不在」,再问 Translator。 把缺 CRD 当成翻译 bug,会在第 4 篇空转。
| 漏 watch / 输入集残缺 | 典型静态原因 | 表象(语义,非实测输出) |
|---|---|---|
| 未装 TCPRoute/UDPRoute CRD | discovery 跳过 watch | L4 YAML 仍在 etcd,无 Route parent status、无 L4 监听 |
| watch.namespaces 未含应用 ns | DefaultNamespaces 收窄 |
该 ns 的 HTTPRoute 从不 enqueue 进本控制器的全量收集 |
| watch.namespaces 未含 controller ns | v1.9.0 已修;旧配置仍会踩 | 读不到自身 Deployment/Secret,舰队像消失 |
| 跨 ns Secret 无 Grant | 不进 Resources.Secrets |
listener TLS 失败或 RefNotPermitted |
enableBackend=false |
backendAPIDisabled() |
Backend CR 即使存在也不作为路由后端 |
| HTTPRouteFilter CRD 已装、Secret 风暴 | v1.9.0 前的索引过宽 | 控制器持续全量 reconcile,status 滞后 |
backendAPIDisabled 在 Backend CRD
不存在或静态 extensionApis.enableBackend
未打开时为真。这又是一层「对象在 etcd、不在输入集」:与缺
Grant 不同,这里是功能门,不是握手失败。
sequenceDiagram
participant API as APIServer
participant Watch as ResourceWatcher
participant Rec as Reconcile
participant Snap as ResourcesSnapshot
participant Bus as WatchableBus
participant Tr as Translator
API->>Watch: Gateway Route Secret events
Watch->>Rec: enqueueClass
Rec->>Snap: collect per GatewayClass
Snap->>Bus: ProviderResources
Bus->>Tr: Translate input set
图:watch 事件不直接翻译单对象;合并进按 GatewayClass 划分的快照后再交给 Translator。节点与参与者为英文;正文解释顺序。
三、Status Manager
Translator Design:Translator 计算条件并经消息总线发布;Status Manager 订阅这些消息,用已配置的 provider 写回。tag 上这条链拆成三段,没有单名为 Status Manager 的包:
- 计算:
gatewayapi.Translator在Translate里改对象的 Status(先StatusDeepCopy以免与 watchable coalesce 的reflect.DeepEqual竞态;该竞态在 v1.9.0 以 panic 形式修过)。 - 发布:
internal/gatewayapi/runner订阅 provider 快照,调用Translate,把 IR 写入XdsIR/InfraIR,把 status 写入ProviderResources上各*Statusesmap;多 GatewayClass 共享的 Route/Policy 先聚合 parent/ancestor 再 store。 - 写回:Kubernetes provider 的
gatewayAPIReconciler.subscribeToResources订阅这些 map;UpdateHandler(status_updater.go)在独立 goroutine 里Status().Update。通道缓冲 1000;leader 才跑。
UpdateHandler.apply 在 cache
Get 返回 NotFound 时,会再用 uncached
apiReader
确认对象是否真的不存在。v1.9.0 发行说明(issue
#9536):informer cache 落后 API Server
时,旧逻辑会把新建对象的 status
更新静默丢掉,直到控制器重启。高 churn 下「Route 一直没有
status」优先怀疑本段,而不是怀疑 Translator 没算。
写回前 isStatusEqual 忽略
LastTransitionTime,避免无意义的 status
抖动。支持的 kind 包括 GatewayClass、Gateway、五类
Route、各类 EG Policy、BackendTLSPolicy、Backend、以及
extension 的 Unstructured。
Gateway 的写回比 Route 多一跳。订阅到 Translator 算出的
GatewayStatus
之后,updateStatusForGateway 会再
Get 一次 Gateway,拉 Envoy Service 与
Deployment/DaemonSet:若尚未被翻译期显式拒绝,就从 listener
条件汇总 Accepted,并用活的基础设施对象填
status.addresses 与 Programmed。NodePort 且
externalTrafficPolicy: Local 时,v1.9.0
只把有就绪 Envoy
端点的节点写入地址,避免把没有代理 Pod 的节点 IP
标成已编程。remote infra 则走「远端基础设施可用」的
Programmed 文案,不再数副本。
这意味着 Provider 轴同时负责两件事:把对象送进翻译输入集,以及把翻译看不到的活基础设施折进 Gateway Programmed。只读 Translator 结果会漏掉「IR 已有、副本为零」。
File provider 有自己的
internal/provider/file/status.go,不走
Kubernetes API。主线排障不要把两种写回混为一谈。
与附着轴的分工:第 2 篇解释条件含义;本篇解释条件如何从 Translate 结果落到 etcd。status 空可能是:对象不在 scope(实现按规范不写)、写回被 NotFound 丢掉(已修,但仍受 cache 健康影响)、或非 leader 副本。不要用「没有 status」单独证明 YAML 非法。
四、与静态启动配置(EnvoyGateway API)的边界
System Design 把配置分成两截:
| 种类 | 载体 | 何时生效 | 例子 |
|---|---|---|---|
| 静态 | 启动配置文件 / Helm
config.envoyGateway |
进程启动(v1.9.0 另有 hot-reload 路径,校验前先 apply defaults) | provider.type、watch namespace
列表、gateway.controllerName、extensionApis.enableLua
/ enableBackend、runtime flags |
| 动态 | Gateway API + EG CR | reconcile 循环 | Gateway、HTTPRoute、SecurityPolicy、EnvoyProxy CR |
边界规则:静态配置决定「看哪些对象、叫什么控制器、哪些扩展
API 打开」;动态对象决定「译出什么 IR」。把 Lua
策略写在集群里却不在静态配置里
enableLua,对象可以进输入集,翻译仍按关闭处理——失败更像第
9 篇的功能门,但也是本篇的静态/动态交界。
v1.9.0 修过 EnvoyGateway 配置 hot-reload:reload 路径与启动路径一样,先 apply defaults 再校验,避免半初始化结构把合法配置判死。静态文件改了 watch 范围或 controllerName,等于换了一份输入集定义;正在调和的动态对象不会自动「解释」旧范围。换 watch 列表属于控制面重启/reload 事件,应在变更单里当输入集变更,而不是当 HTTPRoute 变更。
EnvoyProxy CR 是动态的,可通过 GatewayClass
parametersRef 或 Gateway 级引用进入
Resources;EnvoyProxyDefaultSpec
则从静态 EnvoyGateway 拷进每份快照,作为未指定
CR 时的最低优先级默认。三者冲突时的合并在 Translator 的
attachEnvoyProxy,本篇只要求:查「数据面副本数为什么不是我写的」时,先分清值来自静态默认、GatewayClass
级 CR,还是 Gateway 级 CR。
v1.9.0 把 xDS gRPC maxReceiveMessageSize
默认从 4MiB 提到 32MiB,字段在 EnvoyGateway
API(静态)。超大集群重连时请求超过旧上限,流断掉、代理停在
known-good——这是静态配置与 xDS 轴的交界,展开在第 5、12
篇。不要在动态 HTTPRoute 上找这个旋钮。
开放争论:watch 完整性 vs 缺 CRD 时仍能启动。 A 侧要求标准通道 kind 必须存在,缺则失败,避免静默丢功能;B 侧(v1.9.0 选择)让 ListenerSet/GRPCRoute/TLSRoute/TCPRoute/UDPRoute/BackendTLSPolicy 的 watch 以 discovery 为准,托管发行才能跑起来。代价正是第 8 篇的静默跳过。平台若用 GKE/OpenShift 托管 CRD,必须把「本集群实际 served 的 kind 集合」写进上线检查单,不能假设 Helm chart 的 experimental 通道清单。
五、小结
- 一次 Reconcile 收集整个 GatewayClass 的输入集,不是按单个 Route 翻译。
- 输入集以
resource.Resources字段为准;跨 ns 引用还要 Grant 才能进树。 - 缺 CRD 则跳过 watch;TCP/UDP 另有「只调和 v1」的静默层。
- Status Manager 在 tag 上是 runner 发布 +
UpdateHandler写回;cache NotFound 曾导致 status 永久空。 - 静态
EnvoyGatewayAPI 不替代动态对象;file provider 在 v1.9.0 已实现为 Custom/File,但不是本系列默认路径。System Design 的「路线图」句已过时。
下一篇从这份快照进入 Translator:对象已在输入集,仍可能在 IR 里消失。
六、参考资料
规范 / 官方文档(A)
- Envoy Gateway System Design — Provider、Resource Watcher、静态 vs 动态配置。注意其中 file provider「路线图」表述与 v1.9.0 源码不一致。
- Gateway API Translator Design — Status Manager 订阅并经 provider 写回。
- Envoy Gateway v1.9.0 Release Notes — 条件化 watch、controller namespace 必 watch、status 写回 NotFound、Secret 全量 reconcile 风暴、File provider 索引修复。
- Envoy Gateway Helm 安装说明 @
/v1.9/install/install-helm/— TCP/UDP 升 v1 与静默跳过。 api/v1alpha1/shared_types.go、envoygateway_types.go@ tag v1.9.0 —ProviderTypeKubernetes/ProviderTypeCustom;ResourceProviderTypeFile。
源码(A)
internal/provider/resource_provider.go、internal/provider/runner/runner.gointernal/provider/kubernetes/kubernetes.go、controller.go(Reconcile、watchResources、findReferenceGrant)internal/provider/kubernetes/status_updater.go(UpdateHandler)internal/provider/kubernetes/status.go(updateStatusFromSubscriptions、updateStatusForGateway)internal/provider/file/(Custom/File 实现存在)internal/gatewayapi/resource/resource.go(Resources输入集)internal/gatewayapi/runner/runner.go(Translate 后发布 IR 与 status)
核心论文 / 奠基 work
- Burns et al., Borg, Omega, and Kubernetes, ACM Queue 2016 —— 调和循环与「期望状态在 API 上」。
- Gateway API 排障文档的 scope 规则 —— 解释为何不在输入集的对象不会获得本实现写的 status。
实验 / 工具
- 本篇无 informer /
kubectl输出。实验台账:未跑。有集群时的语义检查:CRD discovery、watch namespace 是否含 controller ns、Route status 的observedGeneration,不伪造日志。
站内对照
- 系列目录
- 上一篇:角色与附着
- 下一篇:Translator 与 IR
- Istio 控制面 watch→model(对照:istiod 不走 EG 这套 IR)
← 上一篇:角色与附着 · 系列目录 · 下一篇:Translator 与 IR
读完这篇,下一步读什么
优先读同系列或同问题的下一篇,把单篇消费变成主题集群。
【Envoy Gateway】南北向全景:缺口、五轴坐标系与 16 篇路线
相对 Gateway API 产品教程、Cilium 南北向边界、Envoy 消费侧与 istiod 翻译,钉清 Envoy Gateway v1.9.0 控制面内核缺口;定义附着/status、IR、xDS、Envoy 数据面、入口之后东西向五条轴,给出 16 篇阅读路线。
【Envoy Gateway】角色与附着:GatewayClass / Gateway / Route 各保证什么
钉清 Gateway API v1.6.1 角色分离在 Envoy Gateway v1.9.0 里的失败语义:parentRefs 与跨 namespace、ReferenceGrant 握手、Accepted 相对 Programmed / ResolvedRefs,以及 YAML 已 apply 仍无流量时该先查哪条条件。
【Envoy Gateway】Gateway API Translator 与 IR:对象如何变成 XdsIR / InfraIR
钉清 Envoy Gateway v1.9.0 为何用中间表示解耦 Gateway API 与 Envoy 资源树:XdsIR 与 InfraIR 的分工、Translate 的顺序与依赖,以及 status 为何必须在同一次翻译里计算。
【Envoy Gateway】xDS Translator 与 Infra:推送不等于在服务
把 XdsIR 钉到 LDS/RDS/CDS/EDS/SDS,把 InfraIR 钉到 Envoy 舰队;说明 go-control-plane Delta xDS 只保证快照送达,不能代替 Envoy warming 与 ACK。