Gateway API GAMMA Service 路由、状态证据与实现选型手册
HTTPRoute 已经成功创建,kubectl apply 没有报错,团队便认为 client 到 orders-v1 的请求会按新规则转向 orders-v2。上线后,一部分调用仍直达旧端点,另一部分开始返回 404。复盘时才发现:Route 的 parentRefs 指向的是 Gateway,不是 Service;另一个 Route 虽然指向 Service,却只附着到 8080 端口,而应用实际访问 8081。API server 接受对象只是把 YAML 存进 etcd,并没有证明 mesh controller 接纳了它,更没有证明请求经过 Service frontend。
第二个现场发生在跨 namespace 调用。client namespace 创建了 consumer route,平台团队看到它引用 mesh-lab 中的 Service,立即补了一份 ReferenceGrant;应用团队又把 consumer route 当成授权策略,撤掉原有网络与网格授权。结果路由确实改变了后端选择,却没有形成期望的租户隔离。GAMMA 的路由附着、对象引用和访问授权是三件事:能引用不等于能访问,能访问也不等于应该访问,Route 的 Accepted=True 更不等于数据面按预期执行。
GAMMA 没有创造一种新的 MeshRoute
Gateway API 最初主要围绕 GatewayClass、Gateway 和各类 Route 描述南北向入口。GAMMA 把同一组 xRoute 扩展到服务网格:HTTPRoute、GRPCRoute 等对象可以把 Kubernetes Service 写进 parentRefs,直接附着到 Service frontend。这里不需要为了东西向调用额外创建 GatewayClass 或 Gateway。官方的Service Mesh overview把 service mesh use cases 自 Gateway API v1.1.0 起列入 Standard Channel;当前 API 规范把 ClusterIP Service parentRef 列为 Mesh Profile 的 Core 支持,但 parentRef.port 和 consumer route 等能力仍要按 Extended feature 核对。Standard Channel 说明 API 字段的发布稳定性,不说明任意网格版本、任意 Route 类型、任意 filter 或 Extended feature 都已经实现。
理解 attachment 要先区分 Service 的两张“脸”。Service 作为 parentRef 时表示 frontend,也就是服务 DNS 名和 ClusterIP 所代表的调用入口;Service 作为 backendRef 时表示 backend,即由 EndpointSlice 提供的端点集合。请求如果直接发往 Pod IP,便绕开了 Service frontend,附着在该 Service 上的 Route 不应被当作必然生效。这个差异是后面正反实验的核心,也解释了为什么只看 Deployment 与 Route 对象不足以定位故障。官方在Service facets中对这两个角色有更完整的定义。
GAMMA 是规范与 conformance 测试,不是一个可以单独启动的数据面。真正执行 Route 的仍是 Istio、Cilium 或其他声明支持 Mesh Profile 的实现。开始前需要一个隔离 Kubernetes/OpenShift 集群、Gateway API Standard CRD、支持 Service parentRef 的 mesh controller,以及管理 CRD、namespace、Service 和 Route 的分级权限。先做发现,不要先装第二套 CRD:
oc api-resources --api-group=gateway.networking.k8s.io
oc get crd httproutes.gateway.networking.k8s.io -o yaml
oc get crd referencegrants.gateway.networking.k8s.io -o yaml
oc get crd httproutes.gateway.networking.k8s.io \
-o jsonpath='{.metadata.annotations.gateway\.networking\.k8s\.io/bundle-version}{" "}{.metadata.annotations.gateway\.networking\.k8s\.io/channel}{"\n"}'
oc get pods -A | grep -E 'istio|cilium|mesh'检查 CRD 的 served/stored versions、labels、annotations 和 managedFields,确认它由 OCP 平台、mesh Operator 还是团队自己维护。OpenShift 与商业 mesh 经常对 CRD bundle 有支持边界,手工覆盖 schema 可能让 Operator 和平台同时争夺 field ownership。目标实现的版本、模式和 Gateway API bundle 要与官方实现清单及其版本化 conformance report 对照,而不是只凭产品首页上的“支持 Gateway API”。
在 OpenShift Service Mesh 3.2 中还要先看 OCP 版本:Service parentRef 虽然是 GA,但受支持 Gateway API CRD 只由 OCP 4.19 及以后版本提供,OCP 4.18 及以前版本不包含也不支持这组 CRD,手工部署在产品矩阵中为 NA。这个分支不能用“上游 CRD 能 apply”绕过;应升级到受支持 OCP/OSSM 组合,或继续使用该发行线支持的 Istio API。只有一次性上游实验集群、且确认没有其他 owner 时,才从 Gateway API 官方 release 固定一个版本安装 Standard Channel CRD。命令形态如下:
kubectl kustomize "https://github.com/kubernetes-sigs/gateway-api/config/crd/standard?ref=<gateway-api-version>" > gateway-api-standard.yaml
kubectl diff -f gateway-api-standard.yaml
kubectl apply -f gateway-api-standard.yaml
kubectl wait --for=condition=Established crd/httproutes.gateway.networking.k8s.io先 kustomize 到文件再 diff,便于审查新增版本、conversion、schema 和 ownership;不要把浮动主分支直接 pipe 给共享集群。CRD Established 只说明 API 可用,下一步还必须确认 mesh controller 监视 Service parentRef,并找到与精确版本、profile 和模式相符的 conformance 证据。Gateway API 的版本与发布通道说明了 Standard 与 Experimental bundle 的差异;生产基线不应为了一个未批准特性整体切到 Experimental。
先建立没有 Route 的请求基线
实验使用两个 namespace:服务生产者位于 mesh-lab,消费方位于 client。mesh-lab 中有 orders-v1 和 orders-v2 两个 Deployment/Service,两个响应体分别返回自己的名字;orders-v1 是调用方使用的稳定服务名,后续 Route 会改变它的后端选择。client Deployment 只需要一个能发送 HTTP 请求的容器,应用配置固定为:
ORDERS_BASE_URL=http://orders-v1.mesh-lab.svc.cluster.local:8080先确认 Service 与 EndpointSlice:
oc get service,endpointslice,pod -n mesh-lab -o wide --show-labels
oc exec deploy/client -n client -- \
curl -sS -H 'x-request-id: gamma-baseline' \
http://orders-v1.mesh-lab.svc.cluster.local:8080/version没有 attached Route 时,mesh 应按自身默认行为把请求送到 orders-v1 的 endpoints,响应体应为 orders-v1。同时保存 orders-v1 的 ClusterIP 和一个 Pod IP,分别请求两者。此时两个入口都可能成功;后面 Route 生效后,ClusterIP/DNS 路径应改变,Pod IP 路径仍指向被直接访问的 Pod。若基线失败,先检查 DNS、Service selector、EndpointSlice、mesh 纳管、mTLS 和 AuthorizationPolicy,不能用 Route 修复一个尚未可用的服务。
Producer route 把服务生产者的默认路由附着到 Service
Route 与 parent Service 位于同一 namespace 时,它是 producer route,由服务生产者维护,原则上影响 mesh 内所有 namespace 对该 Service frontend 的调用。下面把 orders-v1 的默认流量切到 orders-v2:
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: orders-v1
namespace: mesh-lab
spec:
parentRefs:
- group: ""
kind: Service
name: orders-v1
port: 8080
rules:
- backendRefs:
- group: ""
kind: Service
name: orders-v2
port: 8080
weight: 1保存为项目中的 mesh-lab/routes/orders-v1-producer.yaml。group: "" 表示 Kubernetes core API group;kind: Service 明确告诉控制器这是 mesh attachment,而不是默认的 Gateway;name 是被附着的 Service;port 把作用域限制在 Service 的 8080 端口。省略 parentRef.port 时会附着到该 Service 的所有端口,多协议 Service 上很容易把原本只想治理 HTTP 的规则扩散到其他端口。
backendRefs 决定匹配后真正选择的后端。这里只给一个权重为 1 的 orders-v2,因此成功匹配后应全部转向 v2。若完全省略 backendRefs,规范语义是继续使用 parent Service 自己的 endpoints,适合只施加 match/filter 而不改变目标;这项行为仍要由目标实现的 Mesh Profile 与实际请求确认。示例显式使用 parentRef.port,而该字段在 API 规范中属于 Extended 支持,不能只凭 Service attachment 的 Core conformance 推定可用。
应用并立刻读取代际与 condition:
oc apply -f mesh-lab/routes/orders-v1-producer.yaml
oc get httproute orders-v1 -n mesh-lab \
-o jsonpath='{.metadata.generation}{"\n"}{range .status.parents[*]}{.controllerName}{" "}{range .conditions[*]}{.type}{"="}{.status}{" reason="}{.reason}{" observed="}{.observedGeneration}{"\n"}{end}{end}'
oc describe httproute orders-v1 -n mesh-lab至少寻找目标 parent 对应的 Accepted 与 ResolvedRefs。Accepted=True 表示 controller 接受该 Route 附着;ResolvedRefs=True 表示它能解析相关引用。两者的 observedGeneration 都应等于当前 metadata.generation。如果对象刚修改到 generation 3,而 condition 仍观察 generation 2,即便 condition 是 True,也只能证明旧配置成功。
状态收敛后重复 DNS/ClusterIP 请求,响应应变为 orders-v2;再直接请求 orders-v1 Pod IP,响应仍应为 orders-v1。这组对照证明 Route 绑定的是 Service frontend,而不是给每个 Pod 套上无条件重写。最后从 mesh proxy/config dump 或实现提供的路由检查命令确认数据面包含对应 attachment,避免把应用偶然访问 v2 当作路由证明。
用不匹配请求看见 attached Route 的拒绝语义
一个容易引发事故的变化是:没有 attached Route 时,请求按 Service 默认行为转发;一旦存在 attached Route,请求必须至少命中一个 Route。若都不匹配,数据面应拒绝请求,而不是静默回落到原 Service。
把 producer route 的 rule 临时改为只匹配 /canary:
rules:
- matches:
- path:
type: PathPrefix
value: /canary
backendRefs:
- name: orders-v2
port: 8080等待新 generation 被观察后,请求 /canary/version 应进入 orders-v2;请求 /version 应得到实现规定的未匹配拒绝,HTTP mesh conformance 测试通常以 404 验证这一点。与此同时,直接 Pod IP 的 /version 仍不受该 Service attachment 影响。若 /version 继续进入 v1,先确认调用确实使用 Service DNS/ClusterIP,再看 controller 是否接纳 path match;若 Route status 显示 unsupported feature,就不能把旧 condition 或另一实现的报告拿来辩解。
恢复无 match 的 producer route并再次观察 generation、condition、proxy config 和请求。反向实验的价值在于把“部分 match 会不会默认放行”变成可观察行为;上线前应使用业务真实路径集合做负例,防止只为 /canary 配路由却意外拒绝健康检查或其他 API。
Consumer route 给调用方 namespace 一个更具体的选择
Route 与 parent Service 位于不同 namespace 时,它是 consumer route。Route 所在 namespace 是消费方边界:client namespace 中的 consumer route只影响该 namespace 内工作负载对目标 Service 的调用,不会改变其他 namespace。它的粒度是 namespace,不是 ServiceAccount 或单个 Deployment;同一 namespace 的两个客户端若需要不同超时或后端,通常要拆 namespace 或使用经过批准的实现扩展。consumer route 在版本化 Mesh Profile 中作为 Extended feature 单独报告,因此以下实验只有在目标实现、精确版本和模式声明该 feature 后才成立,不能由 producer route 或 Mesh Core 通过外推。
下面让 client 调用 orders-v1 时转回 orders-v1,从而覆盖生产者默认指向 v2 的规则:
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: orders-v1
namespace: client
spec:
parentRefs:
- group: ""
kind: Service
name: orders-v1
namespace: mesh-lab
port: 8080
rules:
- backendRefs:
- group: ""
kind: Service
name: orders-v1
namespace: mesh-lab
port: 8080producer 与 consumer 同时存在时,不是先跑 consumer 再叠加 producer;对来自 client namespace 的请求,consumer route 优先。应用后,client 响应应为 orders-v1;另一个没有 consumer route 的调用方仍应受 producer route 影响并得到 orders-v2。删除 consumer route 后,client 应回落到 producer route并再次得到 v2。这三步共同证明 namespace 作用域和优先级。
同组的多个 Route 还可能 merge 或 conflict,结果由规范的匹配具体性、创建时间等规则与实现状态决定,不能把 YAML 文件顺序当优先级。制造两个竞争的 consumer routes 后,应查看各自 parent condition 的 reason/message,并用请求确认胜者;然后删除冲突对象,等待状态与数据面共同恢复。
普通 mesh 引用不靠 ReferenceGrant,例外扩权才需要它
Gateway-bound Route 的跨 namespace backend 引用通常需要被引用 namespace 用 ReferenceGrant 明确信任,但 GAMMA 的普通 mesh 路径不同:consumer Service parentRef 与 mesh backendRefs 可以跨 namespace,而不要求 ReferenceGrant,因为 Route 只是增强调用方原本可达的 Service,并未把 Service 暴露到更大范围。访问控制仍由 NetworkPolicy、mesh authorization 和业务鉴权承担,不能为了形式统一给每条 consumer route 机械补 grant。
只有某个 mesh 实现会让这次引用获得原本不存在的可达范围时,规范才要求用 ReferenceGrant 明示信任。此时应先从该实现的版本化文档和 ResolvedRefs 确认它采用这种扩权模型;若采用,则由 被引用对象所在 namespace 的 owner 创建,例如只允许 client namespace 的 HTTPRoute 引用 mesh-lab/orders-v1:
apiVersion: gateway.networking.k8s.io/v1
kind: ReferenceGrant
metadata:
name: orders-v1
namespace: mesh-lab
spec:
from:
- group: gateway.networking.k8s.io
kind: HTTPRoute
namespace: client
to:
- group: ""
kind: Service
name: orders-v1示例采用 Gateway API v1.6 Standard bundle 提供的 v1;较早的下游 bundle 可能只 serve v1beta1,必须先读取 CRD 的 spec.versions[].served,按集群真实版本生成清单,不能仅替换字符串后假定 schema 等价。from 按 API group、kind 和 namespace 标识引用方,不能把单个 Route 名写进去;to 可以用 name 收窄到特定 Service。省略 to.name 会授权该 namespace 内同类对象,影响面显著扩大。Grant 不应由消费方在生产者 namespace 随意创建,因此 RBAC 要把 ReferenceGrant 写权限交给资源 owner,而 Route 写权限留给应用团队。
这份例外 grant 只允许对象引用,不代表网络可达、mTLS 成功、mesh AuthorizationPolicy 放行或业务角色获准;它也不应出现在不扩权的常规 GAMMA 基线里。删除 NetworkPolicy/AuthorizationPolicy 后“Route 仍 Accepted”不能证明安全;正确反例是使用错误 ServiceAccount 发请求,预期仍被授权层拒绝,同时 Route status 保持已接纳。路由与授权证据应该分别保存。
Status 是控制器回执,真实请求是数据面回执
一个 Route 可以有多个 parent,每个 parent 由不同 controller 处理,因此不能只读取 conditions[0]。应按 status.parents[].parentRef 与 controllerName 找到目标实现,再读该 parent 下的 conditions。最少保存:
oc get httproute -A -o json | jq -r '
.items[] as $r |
$r.status.parents[]? as $p |
$p.conditions[]? |
[$r.metadata.namespace, $r.metadata.name,
($r.metadata.generation|tostring), $p.controllerName,
($p.parentRef.group // "gateway.networking.k8s.io"),
($p.parentRef.kind // "Gateway"),
($p.parentRef.namespace // $r.metadata.namespace),
$p.parentRef.name, (($p.parentRef.port // "-")|tostring),
.type, .status, .reason, (.observedGeneration|tostring)] | @tsv'Accepted=False 常见于 parent 不允许附着、端口/Route 类型不支持、冲突或 controller 不识别 Service attachment;ResolvedRefs=False 常见于 backend 不存在、端口错误、跨 namespace 引用未获准或 filter 引用了无效对象。condition 的 reason 与 message 是第一诊断线索,Event 和 controller 日志用于解释 reconcile 失败。
但 status 仍不是最终流量证据。一个完整判据至少包含当前 generation 已观察、Accepted/ResolvedRefs 为期望状态、proxy/data plane config 出现预期规则、Service DNS/ClusterIP 请求命中预期后端、Pod IP 对照不被改写、错误 path/身份得到预期拒绝。任何一项缺失,都可能把配置存储成功、控制器接纳成功或偶然响应误当成整链成功。
Conformance 必须精确到版本、Profile、Route 和特性
Gateway API 的版本化 conformance reports按实现版本和 Profile 展示结果。上游 release bundle 与报告归档并不总在同一天出现:即使官方已经发布更新的 Standard bundle,也不能把旧报告改名后当成新 bundle 结果。选型时至少记录 Gateway API bundle、实现名称与版本、数据面模式、Mesh Profile、HTTPRoute Core 以及实际用到的 Extended feature。consumer 正反实验额外依赖报告中的 Mesh Consumer Route;端口附着还要按该 bundle 的 API 支持级别、目标实现文档、status 与请求结果单独确认,不能拿 Gateway Profile 的同名能力替 Mesh Profile 背书。Core 通过也不意味着 timeout、header filter、重写、镜像流量或其他 Extended feature 全部通过,另一个 minor 的报告不能替当前版本背书。
例如,上游 Istio 某个版本通过 Mesh + HTTPRoute Core,只能证明报告所列版本和测试模式。OpenShift Service Mesh 内置的是 Red Hat 固定并支持的 downstream Istio 组合,不能把上游较新 Istio 的报告外推到 OSSM。Red Hat 将 Service parentRef 标为 GA,仍不等于它逐项声明通过同一 Gateway API bundle 的全部 Mesh Profile feature。Cilium、Linkerd 等实现也要读取自己的精确报告,conformant、partially conformant 和未报告不是措辞差异,而是上线风险差异。
规范也没有把 mTLS、授权、遥测、egress 和所有策略统一成一个完成的 mesh API。GAMMA 的强项是通用路由 attachment 与可测试契约;身份、安全和可观测仍由 mesh 实现与其他 Kubernetes API承担。正在提议的 Mesh/XMesh 等对象或 Experimental API 不能提前写进长期生产模板。
架构选型可以据此分三步。第一步确认核心诉求是否只是可移植的 Service 路由;若依赖大量实现专有 filter、subset 或策略,迁移收益会下降。第二步确认目标实现对精确 Profile 和特性的报告,并在同版本隔离集群跑组织自己的正反用例。第三步核对 downstream 支持合同、CRD owner 与升级节奏;规范稳定并不能消除发行商的版本滞后和支持边界。
项目接入要把 Route 当作共享 API 合同
推荐让服务生产者在 mesh-lab/routes/producer/ 维护 producer route,消费团队在自己的目录维护 consumer route,平台仓库只维护 CRD bundle、准入策略和基线 conformance。CI 在合并前运行 schema 校验、服务器端 dry-run 和策略检查:禁止省略跨 namespace owner,禁止未登记的 Experimental field,禁止把全端口 attachment 当默认模板,并要求变更说明中列出正向 path、未匹配 path、回落行为和清理对象。
GitOps 同步成功后,控制器 condition 仍可能滞后。部署流水线应轮询当前 generation,而不是固定睡眠:只有目标 parent 的 observedGeneration 追上、Accepted/ResolvedRefs 达到预期,才执行请求测试。失败时保存 Route YAML、condition、Event、controller 版本、proxy 配置摘要和 request ID;不要保存真实 Token、Cookie、客户路径或完整 config dump到公开构建日志。
应用侧只消费稳定 Service DNS,例如 http://orders-v1.mesh-lab.svc.cluster.local:8080,不应该知道 producer route 把请求送到哪个后端。consumer route 是 namespace 级平台配置,不应由应用运行时动态创建。需要按用户、租户或请求属性授权时,使用业务鉴权或 mesh authorization;需要按单工作负载差异化治理时,先评估 namespace 拆分和实现扩展的运维成本。
从现象反推 attachment 的故障层
Route 没有 status,先确认 CRD served version、controller 是否监视该 namespace,以及实现是否启用 Mesh Profile。只有 API 对象而没有任何 controllerName,通常不是规则写错,而是没有实现接管。
Accepted=False 时,查看 parentRef 的 group/kind/name/namespace/port。最常见的错误是漏写 kind: Service 后被当作 Gateway、引用不存在的 Service、端口不是 Service port,或实现不支持该 Route 类型。修复后必须看到新 generation 被观察。
ResolvedRefs=False 时,逐个检查 backend Service 与 port、跨 namespace 引用、ReferenceGrant 和实现支持的 filter。Service 存在但 EndpointSlice 为空不会总让 ResolvedRefs 变 False,因为对象引用合法、运行端点却不可用;这时 status 与请求会分叉,必须回到 oc get endpointslice 和 proxy endpoint 状态。
状态全 True 但请求没有改变,先确认请求目标是 Service DNS/ClusterIP,而不是 Pod IP;再看调用方是否被 mesh 纳管、consumer namespace 是否正确、Route 是否只附着到另一个端口,以及数据面是否同步当前配置。consumer route 优先级也可能让 producer route看起来“失效”。
请求返回 404,先判断是应用 404 还是 mesh 未匹配拒绝。用 request ID 对比 client proxy、destination proxy 和应用日志:若目标应用没有收到请求,而 Route 存在且 path 未匹配,优先检查 attached Route 的拒绝语义;若应用日志存在,则按业务路由排查。503 更常见于 backend 无端点、mTLS/连接失败或代理本地拒绝,不能和 404 混成“Route 不工作”。
不同 namespace 结果不一致时,列出 producer 与所有 consumer routes,按 parent 与 namespace 分组读取 status。不要只 kubectl get httproute -n mesh-lab,因为真正覆盖生产者默认行为的对象可能在 client namespace。
容量、权限和升级把规范问题变成平台问题
每个 attached Route 都会进入 controller reconcile、状态写入和数据面配置分发。Route、Service、Endpoint 和 namespace 数量增长时,要观察 controller queue、reconcile latency、API write rate、proxy 配置大小、xDS/等价分发延迟、代理内存和收敛期间的错误率。批量 GitOps 修改可能形成配置风暴;把几千个 Route 同时更新,比稳定态 QPS 更容易暴露控制面瓶颈。
producer route 的写权限属于服务 owner,consumer route 属于消费 namespace owner,ReferenceGrant 属于被引用资源 owner,CRD 与 controller 属于平台 owner。RBAC 和准入策略应阻止消费团队修改生产者 namespace 的 grant,也应阻止普通应用创建 cluster-scoped CRD。Route 可以包含域名、Header match 和内部服务拓扑,status/message、controller 日志和 config dump同样可能泄露这些信息,日志系统要脱敏并限制保留期。
成本不只来自代理 CPU。状态更新增加 API server/etcd 写入,访问日志和 Trace 会扩大遥测账单,双轨升级会同时运行新旧 controller/data plane,conformance 环境也需要持续维护。预算应覆盖峰值配置变更、失败重试、遥测后端和回滚窗口,而不是只按平均请求量估算。
升级顺序从 CRD owner 开始:先导出 CRD、Route 和 status.storedVersions,读取目标 bundle 的 release notes 与转换规则,再确认 mesh 实现支持该 bundle 和所用 features,然后在隔离环境运行 conformance 与项目正反实验,最后升级共享环境。迁移期间同时记录旧、新 served version 的服务器端 dry-run,以及 controller 对新 generation 写回的 status;只有对象能往返读取、目标 parent condition 收敛、数据面请求正确,才进入下一批。先升级 CRD、后发现 controller 不理解新字段,会产生 API 可写但数据面不执行的危险窗口;先升级 controller、却让旧 schema 拒绝新对象也同样失败。下游发行版必须跟随其 Operator 与支持矩阵,不能独立替换上游 CRD。
删除 Route 不等于立即恢复,删除 CRD 更不是日常清理
单次变更的回滚从最具体对象开始。删除 client 中的 consumer route后,等待它从 API 和数据面配置中消失,再证明请求回落到 producer route;删除 producer route后,等待 attachment 消失,再证明 Service 恢复默认 endpoints。若使用 ReferenceGrant,只有确认没有其他 Route引用时才删除。每一步都重新检查 status、proxy config 和请求,避免 API 删除完成而数据面仍保留旧配置。
实验对象可按下面顺序清理:
oc delete httproute orders-v1 -n client --ignore-not-found
oc delete httproute orders-v1 -n mesh-lab --ignore-not-found
oc delete referencegrant orders-v1 -n mesh-lab --ignore-not-found
oc delete namespace client mesh-lab
oc wait --for=delete namespace/client namespace/mesh-lab --timeout=5mnamespace 删除会清除示例工作负载、Service 和 namespace 内 Route,但不会删除 cluster-scoped Gateway API CRD,也不会卸载 mesh controller。共享集群中,这正是安全边界:CRD 可能被 Gateway、其他 mesh 和其他团队共同使用,不能因一次实验结束就删除。
只有在一次性集群、确认 CRD 由本次实验独占、所有实例已导出或删除、controller 已停止后,才按原安装清单反向删除 Standard CRD。删除 CRD 会级联删除所有对应 Route 和 ReferenceGrant,且 storedVersion/转换链也随之消失。最终核销还要检查 namespace finalizer、controller RBAC、webhook、日志/Trace 保留、临时 kubeconfig 与云资源。一个可治理的 GAMMA 平台,结束时不仅没有示例 Route,还能说明每个 CRD、controller、grant、状态记录和账单由谁持有、何时升级、怎样回滚。
