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

【Envoy Gateway】Policy attachment:五类策略编译到哪一层,mergeType 挂错父资源会发生什么

文章导航

分类入口
kubernetesnetwork
标签入口
#envoy-gateway#gateway-api#securitypolicy#backendtrafficpolicy#mergetype#lua#envoypatchpolicy#gep-713#v1.9.0

目录

平台在 Gateway 上写了一条「全局限流 + JWT」基线,应用团队又在 HTTPRoute 上贴 mergeType: StrategicMerge 的覆盖策略,升级到 Envoy Gateway v1.9.0 之后对象被 admission 拒绝;另一条 Lua EnvoyExtensionPolicy apply 成功,流量却没有任何脚本副作用。这两种「写了不生效」不是同一层失败:前者是 Policy Attachment 的父资源不合法,后者是 扩展 API 默认关闭。再往下,还有一类更隐蔽:策略已被翻译,但 EnvoyPatchPolicy 仍按旧 xDS 资源名打补丁。

本文只钉三件事:五类 Envoy Gateway 策略各自进入哪一层 IR/xDS;mergeType 在 v1.9 的合法目标;Lua 关闭与 Patch 键失效如何表现为「策略写了不生效」。不写每个字段的百科。BackendTLSPolicy 是 Gateway API 核心策略,落点在 第 7 篇,本文不展开。

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

篇目 核心内容
第 8 篇 · L4 路由 TCPRoute/UDPRoute 升 v1;未装 v1.6 CRDs 则静默跳过
第 9 篇 · Policy attachment 五类策略的编译层、mergeType 仅 xRoute、Lua 默认关
第 10 篇 · EnvoyProxy 与基础设施 数据面生命周期、GatewayNamespaceMode、remote infra

上一篇L4 路由 · 下一篇EnvoyProxy 与基础设施

版本锚定:Envoy Gateway v1.9.0(源码 tag v1.9.0,发布 2026-08-14)。机制对齐 gateway.envoyproxy.io/v1.9/ Concepts / Gateway API Extensions / API 类型,以及 v1.9.0 Release Notes。Policy Attachment 模式钉 Gateway API GEP-713。无真实集群则不粘贴伪造 kubectl status / egctl / config_dump实验台账:未跑。


一、SecurityPolicy

SecurityPolicy 按 GEP-713 的 Policy Attachment 挂到 Gateway API 对象上,不改 HTTPRoute spec。v1.9 Concepts 把它归为 Security 类,目标是 Gateway 与 Route;任务文档补充:同命名空间内可挂 GatewayListenerSetHTTPRouteGRPCRouteTCPRoute。挂 ListenerSet 只覆盖该 ListenerSet 里的监听器,不影响父 Gateway 自有监听器;sectionName 可再收窄到单个监听器或具名 route rule。

TCPRoute 上的能力是收窄的:官方只保证基于客户端 IP 的授权。JWT、API Key、Basic Auth、OIDC 不适用于 TCPRoute 目标——对象能挂上,不等于整份安全功能表都编译。

编译落点在 XdsIR 的 HTTP 安全过滤器,再经 xDS Translator 变成 HCM 链上的 jwt_authn / oauth2 / ext_authz / cors / csrf / rbac / api_key_auth / basic_auth。这是请求路径上的安全轴,不是 InfraIR、也不是 cluster 熔断。未设置 mergeType 时,官方规则是 最具体者获胜(route rule > route > ListenerSet listener > ListenerSet > Gateway listener > Gateway,且 ListenerSet 路径与 Gateway 自有 listener 是兄弟作用域,不互相覆盖)。同级多条策略按 creationTimestamp 再按 namespaced name 排序。

v1.9.0 在这条轴上钉了两处新能力,都不要当成「默认全开」:

JWT 在 v1.9 还有一处 xDS 名破坏面:生成的 listener 里,JWT provider / requirement 名称改为 内容派生、可跨路由去重的稳定名。以前按路由派生的 jwt_authn.providersrequirementMap 键,会让 EnvoyPatchPolicy 与 extension server 对不上。

策略是否「附上了」要看 Policy 自己的 status,以及目标对象上是否出现 GEP-713 建议的 PolicyAffected 类条件。GEP 把 Discoverability 写成:应用开发者最好只看自己的 HTTPRoute 就能知道生效策略从哪来;实现做不到时,排障跳数就会超过一次 kubectl get。Envoy Gateway 用 status.ancestors 回报附着结果,并给被盖住的父策略打 Overridden=True。v1.9 还把每个策略的 status.ancestors 在添加过程中就截到 CRD 上限,避免「很多策略打同一目标」时翻译延迟按平方涨——这是性能修复,也说明 fan-out status 仍是 713 点名的那个问题。

mergeType 的限制单独放第六节。这里先记住:基线可以挂 Gateway,合并声明只能写在 xRoute 上。把「Gateway 上的 SecurityPolicy Accepted」读成「所有子路由都强制 JWT」是错的:未合并时最具体的路由策略会整份替换基线。


二、BackendTrafficPolicy

BackendTrafficPolicy 管的是 Envoy 到后端 的连接与弹性:熔断、重试、超时、健康检查、负载均衡、限流。Concepts 把它标为 Traffic Handling,目标 Gateway 与 Route,并写明 Most specific configuration wins。v1.9 文档列出的目标包括 GatewayListenerSetHTTPRouteGRPCRouteTCPRouteUDPRouteTLSRoute。同样必须与策略同命名空间。

编译落点跨 RDS 路由动作与 CDS cluster:限流、重试、超时更靠近 route;熔断、主动健康检查、LB 更靠近 cluster。不要把它和 ClientTrafficPolicy 搞反——后者改的是 listener 上的下游连接。

v1.9.0 把 仅 shared 的全局限流规则 从生成 xDS 的 route.rateLimits 挪到 typedPerFilterConfig。对 EnvoyPatchPolicy / extension server 这是破坏性变更:继续按 route.rateLimits 匹配 shared-only 规则,升级后会静默打空。这不是「限流 CR 没 Accepted」,而是 补丁打在上一版布局上

mergeType 合法目标比 SecurityPolicy 更宽:HTTPRoute、GRPCRoute、TCPRoute、UDPRoute、TLSRoute。挂 Gateway / Gateway listener / ListenerSet / ListenerSet listener 会被拒。官方合并示例是平台在 Gateway 写 shared 全局滥用上限、应用在 HTTPRoute 上 StrategicMerge 一条更紧的非 shared 规则——合并后两条都生效。JSONMerge 走 RFC 7396,数组整段替换;StrategicMerge 对复杂结构做战略合并。未设 mergeType 时不合并,只留最具体那条。


三、ClientTrafficPolicy

ClientTrafficPolicy下游客户端到 Envoy listener 的行为:TLS 终止与 mTLS、连接超时、客户端 IP 探测、路径规范化、HTTP/1/2/3 旋钮。v1.9 合法目标只有 GatewayListenerSet(均可带 sectionName)。不能挂 HTTPRoute。 把下游超时写在 Route 上的 ClientTrafficPolicy 不会进入翻译输入集。

编译落点在 XdsIR 的 listener / 下游连接选项,对应 LDS。优先级是:带 sectionName 的 listener 级 > ListenerSet 整份 > Gateway 整份。被更具体策略盖住的祖先会报 Overridden=True。挂 ListenerSet 时,策略 status 的 ancestorRef 指向 ListenerSet 本身,而不是父 Gateway。

v1.9.0 收紧了 spec.clientIPDetection:必须且只能设 xForwardedForcustomHeaderdirectSourceIP 之一。以前能过的空对象 clientIPDetection: {} 现在被 CEL 拒绝。directSourceIP 把下游 TCP 源地址当作客户端 IP,用来在 NLB / 外部流量策略为 Local 这类 L4 透明拓扑里解锁 SecurityPolicy 的 clientIPGeoLocations。这是 listener 身份轴,不是后端限流轴。

本策略 没有 mergeType。下游连接参数不走「子路由合并父 Gateway」那条继承模型;要分层就靠 section 特异性,不要套用 SecurityPolicy 的合并直觉。


四、EnvoyExtensionPolicy(Lua 默认关)

EnvoyExtensionPolicy 把 Wasm、ext_proc、Lua、dynamic module 挂进 HTTP 过滤器链。Concepts 目标为 Gateway、Route、Backend;v1.9 起 ListenerSet 也可作 targetRef。Lua 任务文档写明:同时挂 Gateway 与 HTTPRoute 时,路由侧优先。v1.9 API 也为该策略增加了 mergeType,且 godoc 写明 目前只能设在 xRoute 目标上

Lua 在 v1.9.0 默认关闭。要开,必须在 EnvoyGateway 配置里设 extensionApis.enableLua: truedisableLua 已废弃,将在后续版本删除;两个字段互斥。官方 Lua 任务把默认关闭写成安全默认:脚本在 Envoy 进程内执行,没有强沙箱。没开 enableLua 时,一条看起来 Accepted 的 Lua 策略 不会把过滤器编进 listener。这是扩展 API 门,不是 IR 翻译 bug。

v1.9.0 还改了 Lua 的 xDS 布局:源码从每条路由的 LuaPerRoute 覆盖,挪到 listener 级 Lua filter,避免内存随路由数线性涨。过滤器名字与配置布局都变了。继续匹配旧的 envoy.filters.http.lua/ 键,Patch 会打空。作为补偿,v1.9 增加 filterContext:同一份共享脚本可通过 request_handle:filterContext() 按路由取键值,而不必再靠 per-route 源码覆盖。

Wasm / ext_proc / dynamic module 仍走同一 CRD,但 enableLua 门拴住。不要把「EEP 能挂」理解成「Lua 已启用」。


五、EnvoyPatchPolicy

EnvoyPatchPolicy 用 RFC 6902 JSON Patch 改 已经生成的 xDS,不是再走一遍 Gateway API Translator。默认关闭,需 extensionApis.enableEnvoyPatchPolicy: true。官方任务把它标为不稳定:Envoy API 字段会删,Envoy Gateway 也可能改资源 name。合法目标默认是同命名空间的 GatewaymergeGateways 打开时改为挂 GatewayClass。多条 Patch 按 priority 升序应用(int32.min 最高)。

这是五类策略里唯一主要停在 xDS Translator 之后 的。前面四类进入 XdsIR 再变成 LDS/RDS/CDS/EDS/SDS;Patch 假定你已经知道那些资源的 type URL 与名字。v1.9.0 让这个假设更脆,至少这些键会失效:

旧匹配面 v1.9.0 之后
路由派生的 JWT provider / requirement 名 改为内容派生的稳定名
shared-only 全局限流的 route.rateLimits 改为 typedPerFilterConfig
每路由 LuaPerRoute / 旧 Lua filter 名 listener 级 Lua filter
DNS cluster 顶层 dns_refresh_rate / respect_dns_ttl / dns_lookup_family envoy.cluster.dnsDnsCluster)typed config
每资源 WellKnownCACertificates: System 的 SDS secret 名 共享 system_ca_certificates(见第 7 篇)
Unix socket SDS cluster 的路径派生名 带 hash 后缀以防碰撞

官方从 v1.5 起引入 xDS Name Scheme V2(listener 从 ns/gw/listener 迁到 tcp-80 这类名字),v1.9 仍由 XDSNameSchemeV2 runtime flag 控制,默认关,计划 v1.10 默认开。Patch 若按旧 listener 名编写,即使策略 Programmed=True,也可能补丁打在不存在的资源上。Accepted 与「补丁命中了你以为的那棵树」不是同一句话。

官方安全警告把 Patch 标成高 CIA 风险:可注入任意配置,并提到凭据泄露面(CVE-2026-22771)。本文不复述利用步骤,只把结论钉住:Patch 是逃逸舱,不是日常 Policy Attachment。


六、v1.9 mergeType 仅 xRoute

未设 mergeType 时,五类策略里带层级的那些都走 最具体者获胜,不合并。mergeType 是显式退出这条默认规则:子策略与「路由附着层级上最近的父策略」做 StrategicMergeJSONMerge。父策略仍可挂 Gateway / ListenerSet——被拒绝的是 在父资源上声明 mergeType 字段

v1.9.0 admission 把这件事写成破坏性变更:

策略 mergeType 允许的目标 被拒的目标
SecurityPolicy HTTPRoute、GRPCRoute、TCPRoute Gateway、Gateway listener、ListenerSet、ListenerSet listener
BackendTrafficPolicy HTTPRoute、GRPCRoute、TCPRoute、UDPRoute、TLSRoute 同上
EnvoyExtensionPolicy API:仅 xRoute 按 godoc,不能设在父资源上
ClientTrafficPolicy 无此字段
EnvoyPatchPolicy 无此字段(用 priority

已有对象若在父资源上带了 mergeType,Release Notes 要求:CRD 升级后、更新这些对象之前,先删掉该字段。CEL 卡的是 create/update,不是「控制器运行时忽略」。表象是 kubectl 被拒,不是流量层 500。

合并时,路由策略找最近父策略:直连 Gateway 则先 listener 级再 Gateway 级;经 ListenerSet 则先 ListenerSet listener、再 ListenerSet、再父 Gateway。不会与兄弟作用域的 Gateway listener 策略合并。同一功能两边都配(例如两边都写 CORS)时,子策略该功能胜出。Secret / backend 引用按 当初声明该字段的策略的命名空间 解析,不改挂到路由之后就跑到路由命名空间去找。

flowchart TD
  cr["xPolicy CR"] --> attach{"targetRef kind"}
  attach -->|"Gateway / ListenerSet"| ir["XdsIR most-specific-wins"]
  attach -->|"xRoute without mergeType"| ir
  attach -->|"xRoute with mergeType"| merge["Merge with closest parent"]
  merge --> ir
  attach -->|"mergeType on parent"| reject["Admission reject"]
  ir --> xds["xDS Translator LDS RDS CDS"]
  xds --> patch{"EnvoyPatchPolicy"}
  patch -->|"JSON Patch"| envoy["Envoy dataplane"]
  patch -->|"miss old keys"| miss["Patch no-op"]

图:策略从 targetRef 进入 XdsIR 或被 admission 拒绝;Patch 发生在 xDS 生成之后。节点为英文;正文解释合法组合。

下表是 targetRef 合法组合(口径:v1.9 Concepts / 各策略任务文档 / Release Notes;「—」表示该文档未把该 kind 列为目标,不表示控制器会接受)。mergeType 列只描述「字段是否允许出现」,不表示父资源不能作为合并的

策略 Gateway Gateway listener ListenerSet HTTPRoute GRPCRoute TCPRoute UDPRoute TLSRoute GatewayClass Backend mergeType
SecurityPolicy Y Y Y Y Y Y* 仅 HTTP/GRPC/TCP Route
BackendTrafficPolicy Y Y Y Y Y Y Y Y 仅五种 xRoute
ClientTrafficPolicy Y Y Y
EnvoyExtensionPolicy Y 文档未单列 Y Y 含于 Route Y API:仅 xRoute
EnvoyPatchPolicy Y 仅 mergeGateways

* TCPRoute 上 SecurityPolicy 仅 IP 授权。所有策略默认与目标同命名空间。

落到本系列五轴,「策略写了不生效」至少要先点名层,再改 YAML:

表象 先怀疑的轴 v1.9 可核对锚点
kubectl 创建/更新被拒 附着 / admission mergeType 挂在 Gateway / ListenerSet;空的 clientIPDetection: {};不合规的 apiKeyAuth.extractFrom
对象存在,Lua 无副作用 扩展 API 门 未设 extensionApis.enableLua
对象 Accepted,流量仍无 JWT/限流 IR:最具体者获胜把父策略盖掉;或 TCPRoute 挂了不适用的 JWT 父策略 Overridden=True;TCPRoute 仅 IP 授权
策略与 Patch 都 Programmed,行为仍是旧的 xDS 布局变了 JWT 稳定名、typedPerFilterConfig、listener 级 Lua、V2 资源名未启用
安全过滤器在、上游仍 503 Envoy 数据面 / 后端 不在本篇;见 envoy 排障与第 13 篇

第 4 篇 把 Policy 折叠进同一次 Translate:Security / Backend / Client / EEP 进 XdsIR,再交给 xDS Translator;它们 进 InfraIR,不会让 Infra Manager 多建一个 Deployment。EnvoyPatchPolicy 发生在 XdsIR→xDS 之后,所以 IR dump 对不上 Patch 后的树——这是排障时必须换轴的原因。


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

Ingress 注解(无类型、不可移植)
  → Gateway API 核心资源保持可移植(角色分离)
  → GEP-713 Policy Attachment(元资源;Memorandum)
  → Direct / Inherited 曾拆成 GEP-2648 / GEP-2649,现已并回 713
  → Envoy Gateway 实现:最具体者获胜 + 可选 mergeType
  → v1.9:mergeType 只允许写在 xRoute 上

GEP-713 把 Policy 定义成「不改目标 spec、用独立 CR 增强行为」的元资源,并点名两个尚未被 API 消灭的工程问题:Discoverability(开发者看自己的 HTTPRoute 无法一眼看出生效策略从哪来)和 Fanout status(一条父策略要回写许多子孙 status)。GEP 自身的状态是 Memorandum:它是模式,不是一个新的核心 kind。Direct / Inherited 的独立 GEP 已标 obsolete,请以 713 正文为准,不要再把 2648/2649 当现行规范。

合并语义上,Envoy Gateway 暴露两条已标准化的补丁代数:JSONMerge 是 RFC 7396(数组替换);StrategicMerge 是 Kubernetes 战略合并。这不是 GEP-713 规定的唯一合并函数——713 只要求实现声明 \(f(\mathrm{spec},\mathrm{spec})\to\mathrm{spec}\)。v1.9 把「谁可以声明 merge」从父资源上拿掉,等于用 admission 收窄继承声明的位置,避免平台对象自己说「请把我和下级合并」造成的双向歧义。

争论:最具体者获胜(可预测、排障跳数少)vs 继承合并(平台基线 + 租户叠加)。713 把发现性问题写成开放工程;Envoy Gateway v1.9 选择「默认不合并、合并声明只能写在子路由」。反例是:平台无法再在 Gateway 策略上放 mergeType 表达「强制向下合并」——基线必须作为 被合并的父对象 存在,合并意图只能由路由策略发出。多租户里这更接近「应用选择加入」,而不是「平台强制覆盖」。

713 还把「一次 kubectl 跳数」写成可检验的发现性指标:从开发者拥有的 Route 出发,要几次才能知道哪条策略生效、内容是什么。Inherited 策略把有效目标(policy scope)扩到整棵子树,跳数天然变长;Direct 策略跳数短,但平台基线要复制到每个 Route。Envoy Gateway 的默认(最具体者获胜、可选子侧 merge)是在这两极之间切一刀。v1.9 禁止在父资源上写 mergeType,等于拒绝「父对象声明继承代数」这条路,只保留「子对象请求与最近父合并」。

开放问题:在 mergeType 不能出现在 Gateway / ListenerSet 之后,平台级强制策略(「所有路由必须叠加上这条 JWT」)如何既可发现、又可机械证明没有路由用最具体者获胜把基线盖掉?713 的 Discoverability 段落仍适用;本系列第 13 篇只把「策略写了不生效」收束到 admission / Lua 门 / Patch 旧键,不承诺 status 足以当 SLO。另一问:Patch 作为逃逸舱与类型安全 Policy 长期并存时,xDS 名稳定性是否应成为发行保证,而不是每条 Release Notes 里的破坏清单?


八、小结

  1. 五类策略不是同一层:Client 进 LDS;Security / EEP 进 HCM 过滤器;Backend 跨 RDS 与 CDS;Patch 打在生成后的 xDS 上。
  2. 未设 mergeType 则最具体者获胜;v1.9 起 mergeType 只能出现在 xRoute 目标,挂父资源会被拒。
  3. Lua EEP 默认关,需 enableLuadisableLua 已废弃。
  4. JWT 稳定名、shared 限流 typedPerFilterConfig、listener 级 Lua,都会让旧 Patch 键失效。

下一篇离开策略编译,问数据面 Pod 的生命周期归谁。


九、参考资料

规范 / 官方文档(A)

源码(A)

核心论文 / 模式文

实验 / 工具

站内对照


上一篇:L4 路由 · 系列目录 · 下一篇:EnvoyProxy 与基础设施

读完这篇,下一步读什么

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


By .