- API网关
- 云原生
- 微服务
【免费下载链接】kgateway
The Cloud-Native API Gateway and AI Gateway
kgateway(The Cloud-Native API Gateway and AI Gateway)的策略 CR 基于 Kubernetes Gateway API 的gwv1.PolicyStatus上报状态。但由于策略的挂载(attachment)机制涉及按优先级应用、合并以及被更高优先级策略覆盖等复杂语义,仅仅一个笼统的状态无法区分"策略已被接受"和"策略已实际生效"。本文围绕设计文档 design/11741-policy-status-and-merge-reporting.md,系统讲解 kgateway 如何通过Accepted与Attached两个自定义 Condition 分离上报策略的接受与挂载状态,并在下发的 Envoy 配置中以MergeOrigins元数据记录每个合并字段的来源策略,为策略合并结果提供可观测性。读完本文,你将掌握 kgateway 策略状态字段的完整语义、三种挂载状态的判定规则与优先级顺序,以及如何从 Kubernetes status 与 Envoy 配置中诊断"已接受但未生效"的策略问题。
背景:为什么需要区分"接受"与"挂载"
kgateway 的各类策略 CR(如 TrafficPolicy、ListenerPolicy、BackendConfigPolicy、DirectResponse 等)都使用gwv1.PolicyStatusAPI 作为状态载体。在 api/v1alpha1/shared/shared_types.go 中可以看到,PolicyStatus由Conditions(条件列表,最多 8 条)与Ancestors(祖先状态条目,最多 16 个)组成:
type PolicyStatus struct { // +optional // +listType=map // +listMapKey=type // +kubebuilder:validation:MaxItems=8 Conditions []metav1.Condition `json:"conditions,omitempty"` // +kubebuilder:validation:MaxItems=16 // +required Ancestors []PolicyAncestorStatus `json:"ancestors"` }PolicyAncestorStatus通过AncestorRef(对应 spec 中的 ParentRef)、ControllerName(写状态的控制器的域名/路径标识)与Conditions描述策略相对于某个祖先的状态。
设计文档指出,旧版状态 API 存在一个根本性局限:它无法区分"策略被接受"与"策略被挂载"。策略挂载是一个复杂过程:
- 多个策略可能以优先级顺序依次应用到同一目标资源;
- 支持合并的策略(如 TrafficPolicy)会与其他策略做合并,最终挂载到目标资源上的可能是多个策略合并后的结果;
- 优先级较低策略中的部分或全部字段,在最终挂载结果中可能被更高优先级策略覆盖。
这会导致一个令人困惑的场景:策略被接受(Accepted)了,但由于与其他策略冲突而没有被实际挂载。只有"Accepted"而没有"Attached"维度的状态,会让用户看到策略一切正常,实际上配置却完全没有生效。
设计目标与非目标
目标
- 使用自定义 Condition 分别上报策略的接受状态(acceptance)与挂载状态(attachment)。
- 使用
AttachedCondition 汇总挂载信息。 - 在下发的 Envoy 配置上增加
MergeOrigins元数据,作为诊断用途。
非目标
- 不在状态 API 中暴露细粒度或高基数(high cardinality)的数据。
- 不针对策略 CR 上无法解析的
targetRefs上报状态。
核心实现:策略状态条件类型与原因
在 api/v1alpha1/shared/policy_types.go 中定义了完整的条件类型与原因常量,这是整个状态报告体系的类型基础:
const ( // PolicyConditionAccepted 表示策略是否被系统接受或被拒绝,以及原因。 // 该条件为 True 的可能原因:* Valid // 该条件为 False 的可能原因:* Pending、* Invalid、* TargetNotFound PolicyConditionAccepted PolicyConditionType = "Accepted" // PolicyConditionAttached 表示策略是否已挂载到目标资源。 // 该条件为 True 的可能原因:* Attached、* Merged // 该条件为 False 的可能原因:* Pending、* Overridden、* TargetNotFound PolicyConditionAttached PolicyConditionType = "Attached" PolicyReasonValid PolicyConditionReason = "Valid" PolicyReasonInvalid PolicyConditionReason = "Invalid" PolicyReasonAttached PolicyConditionReason = "Attached" PolicyReasonMerged PolicyConditionReason = "Merged" PolicyReasonOverridden PolicyConditionReason = "Overridden" PolicyReasonPending PolicyConditionReason = "Pending" PolicyReasonPartiallyValid PolicyConditionReason = "PartiallyValid" PolicyReasonTargetNotFound PolicyConditionReason = "TargetNotFound" )两类条件的分工如下:
Accepted:策略是否被系统接受。True表示有效(Valid);False表示无效(Invalid)、待处理(Pending)或目标不存在(TargetNotFound)。Attached:策略是否真正挂载到目标资源。True的可能原因是Attached(完全挂载)或Merged(合并后挂载);False的原因则是Pending、Overridden(被更高优先级策略完全覆盖)或TargetNotFound。
注意TargetNotFound的语义:当策略的targetRefs指向不存在的资源时,由于缺失的目标没有 Gateway 祖先可供上报,条件会被上报到策略的合成StatusSummary祖先条目上(见下文"状态写入路径")。
挂载状态的判定:Attached / Merged / Overridden
设计文档给出了Attached条件的三态语义:
status: "True", reason: Attached:策略挂载到了全部目标,且未与其他策略合并;status: "True", reason: Merged:策略挂载到了全部目标,但在一个或多个目标上与其他策略发生了合并;status: "False", reason: Overridden:策略在一个或多个目标上被更高优先级策略覆盖。
由于一个策略可以挂载多个目标,且每个目标上的挂载状态可能不同,同一个策略可能同时处于多种状态。Attached条件在聚合时遵循严重程度优先级:Overridden>Merged>Attached,即策略同时存在多个关联状态时,以最严重者为准。
源码实现:PolicyAttachmentState 位掩码
在 pkg/pluginsdk/reporter/types.go 中,挂载状态被建模为一个可按位组合的枚举:
type PolicyAttachmentState int const ( // PolicyAttachmentStatePending 表示策略挂载处于待处理状态 PolicyAttachmentStatePending PolicyAttachmentState = iota // PolicyAttachmentStateAttached 表示整个策略被成功挂载 PolicyAttachmentStateAttached PolicyAttachmentState = 1 << iota // PolicyAttachmentStateMerged 表示策略与其他策略合并后被挂载 PolicyAttachmentStateMerged // PolicyAttachmentStateOverridden 表示策略与更高优先级策略冲突并被完全覆盖 PolicyAttachmentStateOverridden ) // Has 检查现有状态是否包含给定状态 func (a PolicyAttachmentState) Has(b PolicyAttachmentState) bool { return a&b != 0 }同一文件还定义了各状态对应的用户可见消息常量,这些消息会直接出现在 status 条件中:
PolicyAcceptedMsg = "Policy accepted" PolicyAttachedMsg = "Attached to all targets" PolicyMergedMsg = "Merged with other policies in target(s) and attached" PolicyOverriddenMsg = "Overridden due to conflict with higher priority policy in target(s)" PolicyTargetNotFoundMsg = "Policy is not attached to targets that could not be resolved"在 pkg/reports/policy.go 的addAttachmentCondition函数中,实现了"最严重状态优先"的聚合逻辑:依次检查Overridden、Merged、Attached,命中即设置对应条件,从而保证多个目标上不同状态的并集被收敛为一个最严重的Attached条件:
switch { case attachmentState.Has(reporter.PolicyAttachmentStateOverridden): // status: False, reason: Overridden meta.SetStatusCondition(&existing, metav1.Condition{ Type: string(shared.PolicyConditionAttached), Status: metav1.ConditionFalse, Reason: string(shared.PolicyReasonOverridden), Message: reporter.PolicyOverriddenMsg, }) case attachmentState.Has(reporter.PolicyAttachmentStateMerged): // status: True, reason: Merged ... case attachmentState.Has(reporter.PolicyAttachmentStateAttached): // status: True, reason: Attached ... }状态写入路径:从翻译到上报
接受状态与挂载状态的报告
在 pkg/kgateway/translator/irtranslator/policy.go 中,两个核心函数分别负责两类状态:
reportPolicyAcceptanceStatus:对每个关联 CR 的策略,若存在Errors则上报Accepted=False, reason=Invalid(消息为策略的错误格式化文本);否则上报Accepted=True, reason=Valid(消息为Policy accepted)。reportPolicyAttachmentStatus:对无错误的策略,根据mergeOrigins判断挂载状态:- 若
mergeOrigins.IsSet()为 false,说明没有发生合并,是直接挂载,上报PolicyAttachmentStateAttached; - 否则根据策略引用在
MergeOrigins中的出现次数,映射到Overridden(None)、Merged(Partial)、Attached(All)。
- 若
在 pkg/kgateway/translator/irtranslator/gateway.go 的监听器插件执行路径中可以看到两者的串联调用:
reportPolicyAcceptanceStatus(reporter, l.PolicyAncestorRef, pols...) policies, mergeOrigins := mergePolicies(pass, pols) // ...应用插件... out.Metadata = addMergeOriginsToFilterMetadata(gk, mergeOrigins, out.GetMetadata()) reportPolicyAttachmentStatus(reporter, l.PolicyAncestorRef, mergeOrigins, pols...)即在一次合并处理中:先上报接受状态 → 执行策略合并得到mergeOrigins→ 应用插件产出 Envoy 配置 → 把MergeOrigins写入过滤器元数据 → 再上报挂载状态。
状态汇总:BuildPolicyStatus
pkg/reports/policy.go 的BuildPolicyStatus负责把翻译过程中累积的 typed report 转为最终的gwv1.PolicyStatus:为每个祖先条目合并Accepted默认值(缺失时置为False/Pending)、调用addAttachmentCondition追加Attached条件、保留旧条件以维持lastTransitionTime连续性,并保留不由本报告器拥有的外来条件。
未解析 targetRefs 的补充上报
设计文档的非目标声明"不报告未解析 targetRefs 的状态",而仓库中的实现(pkg/kgateway/proxy_syncer/policy_target_status.go)将这类情况收敛为TargetNotFound条件:正向遍历每个策略的显式targetRefs,通过 krt(Kubernetes 运行时转换)驱动的 informer 集合解析目标;无法解析的引用上报到策略的合成StatusSummary祖先条目上(Accepted=False/TargetNotFound),而不是直接使用缺失引用本身作为祖先——后者会因引用不存在对象而浪费祖先配额。
合成祖先的定义位于 pkg/pluginsdk/reporter/types.go:
func PolicyStatusSummaryAncestorRef() gwv1.ParentReference { return gwv1.ParentReference{ Group: new(gwv1.Group(kgateway.GroupName)), Kind: new(gwv1.Kind(PolicyStatusSummaryAncestorName)), Name: PolicyStatusSummaryAncestorName, } }合并来源追踪:MergeOrigins
数据结构与操作语义
在 pkg/pluginsdk/ir/merge.go 中,MergeOrigins被定义为"策略字段名 → 贡献该字段的策略引用集合"的映射,并附带三个计数枚举:
// MergeOrigins maps policy field names to policy refs that contribute to it // during policy merging type MergeOrigins map[string]sets.Set[string] // MergeOriginsRefCount 用于追踪策略引用在 MergeOrigins 中的状态 type MergeOriginsRefCount int const ( // MergeOriginsRefCountNone 表示该引用未出现在任何字段的 MergeOrigins 中 MergeOriginsRefCountNone MergeOriginsRefCount = iota // MergeOriginsRefCountPartial 表示该引用出现在部分(非全部)字段中 MergeOriginsRefCountPartial // MergeOriginsRefCountAll 表示该引用出现在全部字段中 MergeOriginsRefCountAll )MergeOrigins提供的操作与合并策略一一对应:
SetOne(field, ref, mergeOrigins):浅合并(shallow merge)场景使用——字段被覆盖后,仅记录新的来源(或当 ref 为 nil 时继承来源的MergeOrigins)。Append(field, ref, mergeOrigins):深合并(deep merge)场景使用——字段值是多个策略拼接/并集的结果,因此把来源引用追加到已有集合中。GetRefCount(ref):统计某策略引用出现在多少个字段中,返回None/Partial/All,这正是reportPolicyAttachmentStatus判定Overridden/Merged/Attached的依据。ToProtoStruct():把MergeOrigins转换为 Envoystructpb.Struct,供写入过滤器元数据使用。
策略引用的唯一 ID 由AttachedPolicyRef.ID()生成(见 pkg/pluginsdk/ir/gw.go),其格式为Group + delimiter + Kind + delimiter + Namespace + delimiter + Name,并通过 intern 共享底层字符串以减少内存占用。
各字段的实际合并调用
以 pkg/kgateway/extensions2/plugins/trafficpolicy/merge.go 为例,TrafficPolicy 的合并函数(mergeExtProc、mergeExtAuth、mergeRustformation、mergeJwt、mergeCORS、mergeFaultInjection等约 25 个字段级合并器)在合并每个字段时同步维护MergeOrigins:
- 深合并路径(
AugmentedDeepMerge/OverridableDeepMerge)调用mergeOrigins.Append("extProc", p2Ref, p2MergeOrigins)等,把参与合并的第二个策略追加为该字段的来源; - 浅合并路径通过
defaultMerge内部的mergeOrigins.SetOne(...)记录字段被哪个策略设置; - 特殊场景(如 buffer 与 HTTP upgrade 互斥冲突解决)还会通过
delete(mergeOrigins, "buffer")移除被剔除字段的来源记录。
写入 Envoy 元数据
addMergeOriginsToFilterMetadata(见 pkg/kgateway/translator/irtranslator/policy.go)在合并结果非空时,将MergeOrigins以merge.<group>/<kind>为 key 写入 EnvoyMetadata.FilterMetadata:
const mergeMetadataKeyPrefix = "merge." func addMergeOriginsToFilterMetadata(gk schema.GroupKind, mergeOrigins ir.MergeOrigins, metadata *envoycorev3.Metadata) *envoycorev3.Metadata { if !mergeOrigins.IsSet() { return metadata } pb := mergeOrigins.ToProtoStruct() ... metadata.FilterMetadata[mergeMetadataKeyPrefix+gk.String()] = pb return metadata }这样,下发的 Envoy 监听器配置中就带上了每个合并字段的来源策略信息,供诊断工具分析"某个字段最终来自哪个策略"。设计文档明确表示:将来工具可以利用该元数据深入洞察策略合并结果。
状态示例解读
以下三个示例均取自设计文档,展示了三种典型场景下Gateway祖先条目的完整状态(controllerName为kgateway.dev/kgateway)。
场景一:策略成功挂载到全部目标
status: ancestors: - ancestorRef: group: gateway.networking.k8s.io kind: Gateway name: test namespace: default conditions: - lastTransitionTime: "2025-07-24T18:06:29Z" message: Policy accepted observedGeneration: 1 reason: Valid status: "True" type: Accepted - lastTransitionTime: "2025-07-24T18:06:29Z" message: Attached to all targets observedGeneration: 1 reason: Attached status: "True" type: Attached controllerName: kgateway.dev/kgatewayAccepted=True/Valid表示策略有效;Attached=True/Attached表示策略未经过合并、直接挂载到了所有目标。
场景二:策略与其他策略合并但部分挂载
status: ancestors: - ancestorRef: group: gateway.networking.k8s.io kind: Gateway name: test namespace: default conditions: - lastTransitionTime: "2025-07-24T18:06:29Z" message: Policy accepted observedGeneration: 1 reason: Valid status: "True" type: Accepted - lastTransitionTime: "2025-07-24T18:06:29Z" message: Merged with other policies in target(s) and attached observedGeneration: 1 reason: Merged status: "True" type: Attached controllerName: kgateway.dev/kgatewayAttached=True/Merged说明策略已生效,但其部分字段与更高优先级策略合并——消息Merged with other policies in target(s) and attached与 pkg/pluginsdk/reporter/types.go 中的PolicyMergedMsg完全一致。
场景三:策略被接受但未挂载
status: ancestors: - ancestorRef: group: gateway.networking.k8s.io kind: Gateway name: test namespace: default conditions: - lastTransitionTime: "2025-07-24T18:06:29Z" message: Policy accepted observedGeneration: 1 reason: Valid status: "True" type: Accepted - lastTransitionTime: "2025-07-24T18:06:29Z" message: Overridden due to conflict with higher priority policy in target(s) observedGeneration: 1 reason: Overridden status: "False" type: Attached controllerName: kgateway.dev/kgateway这是设计文档最想解决的场景:Accepted=True/Valid但Attached=False/Overridden。策略本身合法,却在目标上被更高优先级策略完全覆盖,用户一眼即可看出"策略被接受了但没有生效",而无需逐个比对策略优先级。
状态如何落到 Kubernetes 对象上
策略状态的最终写入由状态同步层完成。在 pkg/kgateway/proxy_syncer/status.go 中可以看到各来源的报告生成函数(如GenerateBackendPolicyReport为挂载到 Backend 上的策略生成Accepted/Attached条件),而 pkg/reports/policy.go 的MergePolicyReports负责跨贡献折叠报告:当ReportMap中某策略已有报告时,把src的祖先条目合并进去;同一祖先在两边都出现时以src的条目替换。
写入端的核心约束(见 pkg/reports/policy.go 的注释)是:其他控制器拥有的祖先条目被有意保留,祖先数量上限(cap)与"仅替换我方条目"逻辑放在写入路径(statussync.MergePolicyAncestorStatuses)执行,该路径才能权威地读取 live 对象——这保证了多控制器共存时状态不被误删,同时BuildPolicyStatus中的排序保证消费方(如 golden-output 翻译测试)拿到确定性输出。
测试与验证
设计文档的 Test Plan 要求通过单元测试、translator 测试与 e2e 测试验证策略 Status API。仓库中的对应测试覆盖包括:
- pkg/reports/policy_test.go:验证
BuildPolicyStatus生成的Attached条件(如Attached=True/Attached、消息Attached to all targets)。 - pkg/pluginsdk/ir/merge_test.go:覆盖
MergeOrigins的Get、SetOne、Append、IsSet、GetRefCount等全部操作方法,包括浅合并覆盖与深合并追加的来源记录行为。 - pkg/kgateway/proxy_syncer/policy_target_status_test.go 与 pkg/reports/observed_generation_test.go:验证
TargetNotFound上报、observedGeneration与状态纯度的行为。 - pkg/kgateway/extensions2/plugins/trafficpolicy/merge_test.go:验证具体字段合并时
MergeOrigins的记录是否符合预期。
总结
kgateway 通过将gwv1.PolicyStatus中的状态拆分为Accepted与Attached两个自定义 Condition,彻底解决了"策略被接受但未生效"难以诊断的问题:
Accepted反映策略本身的合法性与目标可解析性;Attached反映策略是否真正挂载到目标资源,并通过Attached、Merged、Overridden三种原因(按Overridden > Merged > Attached的严重程度聚合)描述合并场景下的最终生效状态;- 同时,
MergeOrigins作为元数据写入下发的 Envoy 配置,记录了每个合并字段的来源策略,为构建"策略合并结果洞察"类工具提供了数据基础。
这套设计让运维人员可以用kubectl get <policy> -o yaml直接读取状态条件,快速区分"配置写错了"(Accepted=False)与"配置被覆盖了"(Accepted=True但Attached=False/Overridden),显著提升了多策略叠加场景下的可观测性与排障效率。
- API网关
- 云原生
- 微服务
【免费下载链接】kgateway
The Cloud-Native API Gateway and AI Gateway
相关推荐
PDF Processing
PDF Processing Quick start Extract text with pdfplumber: code example Advanced f
人工智能AI 应用AI 技能/插件AI Agent金融科技ngxtop函数测试策略:基于行为与基于状态的测试
ngxtop函数测试策略:基于行为与基于状态的测试 引言 在软件开发中,测试是确保代码质量和可靠性的关键环节。对于ngxtop这样的实时Nginx服务器指标工具
运维可观测性CLIReselect与GraphQL:结合API数据的状态选择策略
Reselect与GraphQL:结合API数据的状态选择策略 你是否在React应用中遇到过这样的困境:GraphQL获取的数据需要复杂处理后才能在UI展示,
状态管理
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考