Istio Ambient:用 ztunnel 与 Waypoint 分层治理东西向流量
mesh-lab 刚从 sidecar 切到 ambient,Pod 里少了 istio-proxy,请求延迟和内存看起来都下降了。值班同学用 client 调 orders 得到 200,又在 namespace 上看到 istio.io/dataplane-mode=ambient,便宣布迁移完成。几分钟后,一条本应拒绝 /admin 的七层授权规则被绕过,而 ztunnel 指标仍显示连接使用了 mTLS。
问题不在于 ambient 没有工作,而在于它只完成了安全四层覆盖:节点上的 ztunnel 已经用 HBONE 承载连接,却不会解析 HTTP path;目标 Service 没有绑定 waypoint,旧 sidecar 来源在混合迁移阶段又会绕过目标 waypoint。一次成功请求、一个 namespace 标签和一条 mTLS 指标分别证明了不同事实,却都不能证明七层执行点已经位于真实路径上。
先把“无 sidecar”翻译成真实网络路径
ambient 没有给每个应用 Pod 注入 Envoy,但数据面并没有消失。每个节点运行一个 ztunnel DaemonSet,Istio CNI 把已纳管 workload 的 TCP 流量交给本节点 ztunnel。HBONE 由 HTTP/2、HTTP CONNECT 和 mTLS 组合而成:HTTP/2 在一条安全连接上复用应用流,CONNECT 建立隧道,mTLS 以源、目标 workload 身份保护连接;共享 ztunnel 自身的 ServiceAccount 不能冒充业务身份。源 ztunnel 根据目标身份和地址建立隧道,目标 ztunnel 验证身份、执行四层策略,再把原始字节流交给目标 Pod。ztunnel 负责 mTLS、L4 authorization、TCP telemetry 和基础负载分发,不终止应用 HTTP,也不读取 method、path 或 header。
需要七层能力时,在目标侧部署 waypoint。waypoint 是由 istiod 通过 Gateway API 管理的 Envoy Deployment,承担 HTTPRoute、重试、超时、故障注入、JWT、HTTP 授权、HTTP 指标、访问日志与 Trace。它不是每 Pod sidecar,也不是所有流量必经的中央网关;它通常服务一个 namespace、Service 或 workload,并按原始目标类型决定是否被使用。
只需要服务身份、mTLS、端口级授权和 TCP 连接指标时,ztunnel-only 可以减少七层代理成本。只要需求包含 header/path 路由、HTTP 状态码、请求级授权、JWT、Trace 或 Envoy 七层扩展,就要为对应目标设计 waypoint。不能因为 ambient core 处于稳定阶段,就推导所有 Gateway API route、扩展、跨 namespace waypoint 和多集群组合都具有相同成熟度;应在 Feature Status 按单项能力核对。
安装前先检查节点与共享依赖
准备一个可重置的 Kubernetes 集群、kubectl、目标版本配套的 istioctl 和 cluster-admin 级安装身份。先在 Supported Releases 找到 Istio 与 Kubernetes 的受支持交集,再阅读 ambient 平台前提;不同云平台、主 CNI、Pod Security、健康检查与节点镜像会改变安装参数。
ambient 强制依赖 Istio CNI。它链在主 CNI 后面,默认 iptables 路径依赖节点的 netfilter、mark、conntrack、ipset 等内核能力;选择 nftables 后端时还要核对内核与 nft 版本。使用 Cilium 等主 CNI 时,需要按平台说明处理 CNI 配置所有权和排他模式。节点模块、CNI 目录、PriorityClass 或 ResourceQuota 不满足时,Pod 可能 Ready,流量却没有被正确接管。
istioctl version --remote=false
kubectl version
kubectl get nodes -o wide
kubectl auth can-i create daemonsets.apps -n istio-system
kubectl auth can-i create gatewayclasses.gateway.networking.k8s.io
kubectl get crd gateways.gateway.networking.k8s.io httproutes.gateway.networking.k8s.iowaypoint 依赖 Kubernetes Gateway API CRD。应安装目标 Istio release notes 指定的 CRD 版本,并把 CRD 版本与 Istio 版本一起锁定;旧 CRD 可能让 TLSRoute、ReferenceGrant 等对象对 istiod 不可见。Gateway API CRD 常被其他控制器共享,所有权不属于 Istio 单方,升级和删除必须由集群级 owner 统筹。
当前稳定安装说明使用 Gateway API v1.5.1 的 experimental bundle,因为 waypoint 依赖尚未全部进入 standard bundle。不要长期引用滚动的 main 清单;首次引入时核对 release tag,计算摘要并经代码评审固化到平台制品记录,再由共享资源 owner 执行 server-side apply:
export GW_API_VERSION=v1.5.1
curl -fL -o gateway-api-experimental.yaml \
"https://github.com/kubernetes-sigs/gateway-api/releases/download/${GW_API_VERSION}/experimental-install.yaml"
sha256sum gateway-api-experimental.yaml
kubectl apply --server-side -f gateway-api-experimental.yaml
kubectl get crd gateways.gateway.networking.k8s.io httproutes.gateway.networking.k8s.io版本锁定只保证输入可追踪,不证明升级安全。升级前应记录 CRD 的 status.storedVersions、现有 Gateway/Route 对象和其他 controller 的兼容矩阵;回退控制器前若新版本已经写入旧控制器不能理解的字段,恢复旧二进制并不会恢复旧对象语义。
安装身份会创建 CRD、ClusterRole、webhook、CNI DaemonSet 和节点级网络配置,权限远高于普通应用发布。把下载摘要、chart/CLI 版本、镜像 digest、最终 values 和操作审计保存到受控变更记录;应用团队只获得 namespace 内 workload、route 和获准 policy 的最小权限。远程 kubeconfig、ServiceAccount token、证书、ztunnel config dump 和访问日志都可能暴露拓扑与身份,不能贴进公共问题单。
用 profile=ambient 启动完整基础层
istioctl 会根据 ambient profile 安装 base、istiod、CNI 和 ztunnel,是隔离实验最直接的入口:
export REV=ambient-a
istioctl install -y \
--set profile=ambient \
--set revision="$REV"
kubectl get pods -n istio-system -o wide
kubectl get daemonset -n istio-system istio-cni-node ztunnel
istioctl analyze --all-namespaces预期 istiod 可用,每个可调度节点各有 CNI 与 ztunnel Pod。DaemonSet Ready 只证明进程调度完成,还要检查目标节点是否出现在 ztunnel config、CNI 是否完成流量规则、真实 workload 是否建立 HBONE。
生产使用 Helm 时按 base -> Gateway API CRD -> istiod(profile=ambient) -> cni(profile=ambient) -> ztunnel 安装,waypoint/gateway 独立创建。各 Helm release 要固定同一发行版本与 registry;istiod、CNI 等接受 deployment/platform profile 的 chart 还要使用一致的平台取值。但 revision 不能机械写给所有 chart:base/CRD 是集群共享层,Istio CNI 不能做 revision canary;istiod 使用 revision,ztunnel 只在官方 revisioned upgrade 流程中指向对应 revision。Helm 中的 profile 只加载当前 chart 的 values,不会自动安装其他 chart;遗漏 CNI 或 ztunnel 仍可能让控制面看起来健康。官方 ambient Helm 安装 给出了组件与卸载顺序,GitOps 渲染还要自行处理 field ownership、依赖顺序和 prune。
先做 ztunnel-only 接入
创建 mesh-lab,部署使用独立 ServiceAccount 的 client、orders-v1、orders-v2。两个 orders Deployment 都带 app: orders,并分别带 version: v1、version: v2;orders Service 的 8080 端口声明 appProtocol: http。先不加 ambient 标签,从 client 连续请求,保存无网格基线的 DNS、端点、响应版本与延迟。
kubectl create namespace mesh-lab
kubectl exec -n mesh-lab deploy/client -- \
sh -c 'for i in 1 2 3; do curl -fsS -H "x-request-id: plain-$i" http://orders:8080/version; done'
kubectl label namespace mesh-lab istio.io/dataplane-mode=ambient --overwrite按照 Add workloads to ambient 的标签方式纳管通常不需要重启应用,也不会增加容器。用 Pod spec、ztunnel 配置和真实请求共同证明接管:
kubectl get pods -n mesh-lab -o jsonpath='{range .items[*]}{.metadata.name}{" "}{.spec.containers[*].name}{"\n"}{end}'
istioctl ztunnel-config workloads
istioctl ztunnel-config services
istioctl ztunnel-config certificates
kubectl exec -n mesh-lab deploy/client -- \
curl -fsS -H 'x-request-id: ambient-l4' http://orders:8080/versionPod 容器列表不应出现 istio-proxy;ztunnel workload 输出应包含 mesh-lab 的地址、协议与身份,证书输出应显示对应 namespace/ServiceAccount 身份处于有效状态。ztunnel 日志或指标应能关联连接与 HBONE,而 orders 应用看到同一个 request ID。istioctl proxy-status 不显示 ztunnel,继续用它判断 ambient 接管会得到错误结论。
加一条四层授权,只允许 client ServiceAccount 访问 orders 的 8080 端口:
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
name: orders-l4
namespace: mesh-lab
spec:
selector:
matchLabels: {app: orders}
action: ALLOW
rules:
- from:
- source:
principals: ["cluster.local/ns/mesh-lab/sa/client"]
to:
- operation:
ports: ["8080"]正确身份访问 8080 应成功;使用另一个 ServiceAccount 的 Pod 应被 ztunnel 拒绝,orders 应用没有对应 request ID。策略中不要放 HTTP method/path,因为 ztunnel 看不到它们。四层策略、mTLS 和业务授权仍是三个层次:身份握手成功不代表调用者可以取消订单,端口允许也不代表 /admin 应被访问。
ambient workload 之间会通过 HBONE 使用 mTLS,但拥有 HBONE 配置不等于拒绝来自网格外的明文。用 PeerAuthentication 把 orders 设置为 STRICT,再从未纳管 namespace 发起直连反例。预期明文失败,纳管 client 仍成功;证据应包含源身份、目标身份、ztunnel 连接/拒绝日志和目标应用是否收到请求。DISABLE 不适用于 ambient workload 间通信,迁移前存在该模式就是阻塞项。
加 waypoint 才进入七层
按照 Configure waypoint proxies 为 namespace 创建目标侧 waypoint:
istioctl waypoint apply -n mesh-lab --name orders-waypoint --for service
kubectl get gateway -n mesh-lab orders-waypoint -o yaml
kubectl get pods -n mesh-lab -l gateway.networking.k8s.io/gateway-name=orders-waypoint
kubectl label service orders -n mesh-lab istio.io/use-waypoint=orders-waypoint --overwriteistioctl waypoint apply 创建 gatewayClassName: istio-waypoint 的 Gateway,istiod 再管理对应 Envoy Deployment。创建 Gateway 不会自动改变请求路径,istio.io/use-waypoint 才建立绑定。istio.io/waypoint-for 可为 service、workload、all 或 none;选择依据是请求原始目标。访问 Service 时,即使最终 Pod 绑定了 workload waypoint,也不会自动穿过它,必须给 Service 或 namespace 建立正确附着。HBONE 约定监听端口是 15008,但端口可达只证明网络路径开放,不能证明请求实际经过这个 waypoint。
用 Gateway API HTTPRoute 把带 header 的请求送到 v2,默认送到 v1。为了避免把 DestinationRule subset 语义硬搬到 waypoint,给两个版本创建 orders-v1、orders-v2 Service,并以 backendRefs 指向它们:
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: orders-route
namespace: mesh-lab
spec:
parentRefs:
- group: ""
kind: Service
name: orders
port: 8080
rules:
- matches:
- headers:
- name: x-orders-version
value: v2
backendRefs:
- name: orders-v2
port: 8080
timeouts:
request: 2s
- backendRefs:
- name: orders-v1
port: 8080
timeouts:
request: 2s先检查 status.parents[].conditions 中 Accepted、ResolvedRefs 和 observedGeneration,再检查 waypoint 实际路由,最后发请求:
kubectl get httproute -n mesh-lab orders-route -o yaml
istioctl proxy-config routes deploy/orders-waypoint -n mesh-lab
kubectl exec -n mesh-lab deploy/client -- \
curl -fsS -H 'x-orders-version: v2' -H 'x-request-id: waypoint-v2' http://orders:8080/version
kubectl exec -n mesh-lab deploy/client -- \
curl -fsS -H 'x-request-id: waypoint-v1' http://orders:8080/version正例应分别命中 v2 与 v1,waypoint access log 和 HTTP 指标应出现 route、状态码与延迟,目标应用也应看到 request ID。Accepted=True 只证明控制器接受声明,不证明 backend 有端点,更不证明请求经过 waypoint。
反例把 orders-v2 backendRef 临时改成 orders-missing。预期 ResolvedRefs=False,或 waypoint 中没有可用 cluster,请求在 waypoint 返回错误,两个 orders 应用都没有 request ID。恢复 backendRef 后,必须等 observed generation 与 waypoint 配置收敛,再验证请求恢复。这个实验把声明错误、配置未下发和上游无端点区分开来。
七层授权使用 targetRefs 附着到 Service,而不是把 sidecar selector 策略原样复制:
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
name: orders-http
namespace: mesh-lab
spec:
targetRefs:
- group: ""
kind: Service
name: orders
action: ALLOW
rules:
- from:
- source:
principals: ["cluster.local/ns/mesh-lab/sa/client"]
to:
- operation:
methods: ["GET"]
paths: ["/version"]正确 GET 应成功,错误身份、POST 和 /admin 应在 waypoint 被拒绝。保存 waypoint 命中策略、source principal、状态码、request ID 与应用缺失日志。RequestAuthentication、WasmPlugin 等七层资源迁移到 waypoint 时同样要核对 targetRefs 和目标版本成熟度;EnvoyFilter 不支持 waypoint,不能期待换一个标签继续工作。
targetRefs 还有多 revision 风险:旧于 1.22 的控制面不认识该字段,可能把策略误解为 namespace 级策略。跨越这条版本边界时,应给策略加 istio.io/rev 标签,把它固定到能够理解 targetRefs 的控制面,并确认 waypoint 实际连接该 revision;只有 CRD 接受对象并不代表每套 istiod 都按相同语义解释它。
waypoint 默认也不是无法绕过的安全边界。所有调用方都迁到 ambient 后,可以在目标 ztunnel 增加只允许 waypoint 身份进入 workload 的四层 DENY;迁移中的 sidecar 调用会绕过 waypoint,因此过早应用会把仍合法的调用一起拒绝:
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
name: deny-waypoint-bypass
namespace: mesh-lab
spec:
selector:
matchLabels: {app: orders}
action: DENY
rules:
- from:
- source:
notPrincipals:
- cluster.local/ns/mesh-lab/sa/orders-waypoint上线前先读取 waypoint Pod 的真实 serviceAccountName,不要从 Gateway 名猜身份。正例从 ambient client 经过 waypoint 成功;反例直接访问 Pod IP、从未纳管 Pod 或伪造 HTTP Header 都应在目标 ztunnel 前失败,应用没有 request ID。若必须边迁移边启用,需要把尚未迁移的 sidecar ServiceAccount 临时加入 notPrincipals 例外,并随 cohort 迁移逐项删除;例外清单没有 owner 和到期时间,就会永久保留旁路。
项目接入时区分四种目标
接入真实项目先为每条调用标记原始目标:Service、Pod IP、ServiceEntry 还是 ingress/gateway。waypoint 选择看原始目标,不只看最终 endpoint。Service 流量通常绑定 Service waypoint;直接访问 Pod IP 的 headless 或状态工作负载可能需要 workload waypoint;ingress 到目标 waypoint 默认可能绕过,需要按官方机制设置 istio.io/ingress-use-waypoint 并实测。
把依赖按 L4 与 L7 分组:数据库、消息队列和原始 TCP 可能只需 ztunnel;HTTP/gRPC 若需要 route、JWT、path policy、重试或 Trace,则要 waypoint。UDP 不会因为 namespace 纳管而获得这些能力。外部服务还要分别处理 DNS、SNI、TLS origination、ServiceEntry 与底层网络封口,不能把集中代理当成无法绕过的防火墙。
应用应传播 traceparent、tracestate 或项目约定的 B3 Header。ztunnel-only 只有连接打开/关闭和字节等 TCP 指标;HTTP 请求率、状态码、route、延迟与 Trace 依赖 waypoint。ambient 不支持 sidecar 的 metrics merging,Prometheus 必须分别 scrape 应用、ztunnel 和 waypoint。迁移仪表盘时,sidecar 的 reporter=source/destination 会变为 ztunnel 的 reporter=source 与 waypoint 的 reporter=waypoint,span 数和拓扑边也会变化。
迁移 sidecar 要处理不可原子化的空窗
sidecar 与 ambient 可以按 namespace 共存,但顺序决定是否出现无数据面窗口。先安装 ambient 组件,并重启既有 sidecar 以取得 ISTIO_META_ENABLE_HBONE=true;否则它们不能按迁移协议访问 ambient destination。随后创建 waypoint 但暂不绑定,准备 HTTPRoute、targetRefs 策略和版本化 Service。真正切换一个 namespace 时,顺序必须是:激活 waypoint;增加 istio.io/dataplane-mode=ambient;确认 workload 在 ztunnel 中显示 HBONE;移除注入标签;最后才重启 Pod 去掉 sidecar。sidecar 与 ambient 标签同时存在的短暂阶段由 sidecar 优先处理,不能反过来先重启再等待 ztunnel 接管。
kubectl label service orders -n mesh-lab istio.io/use-waypoint=orders-waypoint --overwrite
kubectl label namespace mesh-lab istio.io/dataplane-mode=ambient --overwrite
istioctl ztunnel-config workloads | grep mesh-lab
kubectl label namespace mesh-lab istio-injection- istio.io/rev-
kubectl rollout restart deployment -n mesh-lab
kubectl rollout status deployment -n mesh-lab
kubectl get pods -n mesh-lab -o jsonpath='{range .items[*]}{.metadata.name}{" "}{.spec.containers[*].name}{"\n"}{end}'Pod 已不再包含 istio-proxy 后,要立即删除被新资源替代的旧 selector L7 AuthorizationPolicy、VirtualService 和相应 subset 配置,再做业务验证。ztunnel 会丢弃它不理解的 HTTP 条件:只依赖 L7 条件的 ALLOW 可能变成没有规则而拒绝全部流量,DENY 则可能在去掉 L7 条件后匹配全部流量。等待“验证完成后再清理旧策略”反而会制造全拒绝事故。
当前 L7 policy 没有原子 handoff。旧 selector policy 从 sidecar 移除、新 targetRefs policy 由 waypoint 接管之间存在执行空窗;要求持续七层授权时,应计划维护窗口或在更底层保留可证明的等价拒绝。更隐蔽的是,迁移期间 sidecar source 访问 ambient destination 会绕过目标 waypoint,因此 waypoint 的 L7 policy、路由和遥测不会执行,直到 source 也迁入 ambient。正反实验必须覆盖 sidecar→sidecar、sidecar→ambient、ambient→sidecar、ambient→ambient 四个方向,不能把其中一个方向的 200/403 推广成迁移成功。
迁移前还要扫描阻塞项:VM workload、SPIRE certificate provider、PeerAuthentication DISABLE、primary-remote 多集群、EnvoyFilter、混用指向同一 workload 的 VirtualService 与 HTTPRoute,以及依赖 sidecar metrics merging 的采集。ambient multicluster 与跨 namespace waypoint 等能力应按目标版本和 feature stage 决策,不能从 sidecar 拓扑平移结论。
回退时先恢复旧 revision 的注入标签,重建 cohort,让 sidecar 重新出现在 Pod 并连接旧 istiod;验证身份、路由和遥测后,再移除 ambient 标签与 waypoint 绑定。整包 kubectl apply 旧备份可能覆盖同名的新策略,应逐项识别 selector 与 targetRefs 冲突。所谓可逆,指有明确对象和顺序可以回切,不代表状态可以无损瞬间倒带。
按 ztunnel、waypoint 和应用三层排障
当请求失败时,先确认 DNS 与 EndpointSlice,再确认 workload 是否被 ztunnel 识别:
kubectl get endpointslice -n mesh-lab
istioctl ztunnel-config workloads
istioctl ztunnel-config services
istioctl ztunnel-config certificates
kubectl logs -n istio-system ds/ztunnel --tail=100没有 workload 记录,优先查 namespace/Pod 标签、CNI、节点调度和排除 namespace;有 workload 但证书缺失,查 ServiceAccount、istiod、SDS、trust domain、时钟与证书签发。HBONE 连接失败要区分源 ztunnel、目标 ztunnel、节点网络和目标端口;目标应用无 request ID 说明故障仍在它之前。
若 L4 成功而 header/path 规则不生效,检查 Service 的 istio.io/use-waypoint、Gateway Programmed 状态、waypoint Pod、HTTPRoute conditions 与 waypoint 的 listener/route/cluster。创建了 waypoint 却未绑定、绑定在 Pod 但请求原始目标是 Service、sidecar source 绕过、ingress 未启用 waypoint,都会表现为“请求成功但 L7 规则没命中”。
唯一 request ID 要同时出现在 client、waypoint、目标应用和可用的代理日志中。访问日志回答单请求经过哪一层,metrics 回答一段时间内的连接、错误与资源趋势,Trace 回答跨 hop 上下文。Kiali 等拓扑只是 telemetry 投影;空图可能来自无流量、窗口过短、Prometheus 未 scrape、低采样或 reporter 变化,不能直接推断网络中断。
节点共享数据面的容量与升级代价
sidecar 把代理资源随 Pod 副本线性分散,ambient 把 L4 成本集中到每节点 ztunnel,并按使用范围增加 waypoint。ztunnel 的 CPU、RSS、连接数和加密吞吐是节点共享预算;一个 noisy workload 可以影响同节点其他 ambient workload。waypoint 则按目标服务聚合七层流量,需要为副本、HPA、连接池、PDB 和故障域单独建模。
容量实验要固定节点规格、Pod 密度、RPS、连接数、payload、协议、L4/L7 policy、waypoint 拓扑、访问日志和 Trace 采样。观察 p99、reset、ztunnel/waypoint CPU/RSS、连接与队列、istiod 配置收敛、Prometheus active series 和日志/Trace ingest。官方 性能与扩展性 提供的是特定版本与硬件下的复现入口,不是任意集群的资源申请值。
ambient 升级拆成 base/CRD、istiod、CNI、ztunnel、gateway/waypoint。控制面先升级,CNI 与 ztunnel 的版本差保持在官方允许窗口。Istio CNI 不能按 revision canary;它是节点级关键 DaemonSet,升级时会阻止新 Pod 在 agent 未就绪的节点启动,以避免流量保护空窗。ztunnel 也是节点共享代理,revision 不能把升级缩小到单 workload;原地重启可能中断节点上的 ambient 连接,长连接尤其敏感。
更小的影响面需要节点编排,但普通 helm upgrade 更新的是整个 DaemonSet,不能把它描述成天然的单 workload 或单节点 canary。生产可用 blue/green 节点池,或结合 cordon/drain、taint 和受控 DaemonSet rollout,把节点分批排空并验证 HBONE、L4/L7 policy、连接恢复与资源趋势后再推进;具体步骤必须服从集群提供商和主 CNI 的节点升级机制。waypoint/gateway 可以按 revision tag 分组切换,但手工 Helm 管理的 gateway 不会自动跟随 tag。升级事务还要包含 Gateway API CRD、平台 CNI 配置、webhook、策略对象和遥测查询,而不是只看 istiod Ready。
清理时先移出数据面
删除 waypoint 前,先撤掉七层 route/policy 或恢复应用侧等价能力,并证明关键请求的成功、拒绝、超时和观测仍符合预期。然后移除 Service/namespace 的 waypoint 绑定,删除 waypoint,再移除 ambient 标签:
kubectl label service orders -n mesh-lab istio.io/use-waypoint-
istioctl waypoint delete -n mesh-lab orders-waypoint
kubectl label namespace mesh-lab istio.io/dataplane-mode-
istioctl ztunnel-config workloads | grep mesh-lab || true最后一个命令不应再显示已退出的 workload。此时还要从未纳管路径验证应用自身 TLS、授权和 NetworkPolicy,确认没有把安全责任随标签一起删除。等待连接排空并清理测试 namespace 后,才按安装清单卸载 ambient 组件;Helm 顺序是手工 gateway 与托管 waypoint、ztunnel、CNI、istiod、base。Gateway API CRD 与 Istio CRD 不随普通 Helm 卸载自动删除,只能由共享资源 owner 在确认没有其他控制器和自定义资源依赖后另行处理。
istioctl uninstall --purge 会处理共享 cluster-scoped 资源,不适合多 revision 或共享 Gateway API 的普通清理。删除 Istio CRD 会级联删除所有 Istio 自定义资源;删除 Gateway API CRD 还会影响其他控制器。最终核销应覆盖 ambient/waypoint/revision 标签、Gateway/HTTPRoute、AuthorizationPolicy、webhook、ClusterRole、DaemonSet、节点 CNI 文件与规则、临时证书、访问日志、Trace 和镜像缓存。只有旧 ztunnel/waypoint 流量归零、替代告警正常、敏感数据按期限删除,ambient 才算真正退出,而不是“Pod 里本来就没有 sidecar”。
