服务网格出口与外部服务:从 DNS、SNI 到强制出口和旁路审计
一次支付渠道切换后,应用访问 api.partner.example.com 偶发返回证书校验失败。出口网关日志里却只有成功请求,值班人员一度认定是外部厂商不稳定。最终抓到的失败连接根本没有经过网关:Pod 内缓存了旧 DNS 地址,sidecar 的排除网段又允许该地址直连;成功请求经网关使用正确 SNI,失败请求则把 IP 当成 TLS 目标。两个请求访问的是同一个业务域名,却走了不同路径、使用了不同验证输入。
另一次治理上线把未知外部目标改成默认拒绝,并部署了集中 egress gateway。路径核验时,正常 Pod 访问未声明域名已经失败,Dashboard 也显示所有外部流量经过网关。一个带 hostNetwork 的调试 Pod 却仍能直接访问公网,另一个工作负载通过被排除的 IP 段绕开 sidecar。集中出口只改变了愿意经过数据面的流量;没有 CNI、NetworkPolicy、路由或防火墙封住底层路径,“必须经网关”只是配置意图,不是网络事实。
先把一个外部请求拆成五个匹配输入
外部访问不是“域名白名单”一个开关。应用先得到 DNS 答案,再向某个 IP 和端口建连;HTTP 请求携带 Host 或 :authority,TLS ClientHello 携带 SNI,证书校验还要使用信任链与 SAN。代理、网关和底层网络看到的输入不同:L3/L4 策略通常只看到 IP/CIDR、端口与身份,L7 代理能看到 HTTP host/path,TLS passthrough 通常只能看到 SNI,TLS origination 则由代理成为上游 TLS 客户端。
应用 URL
-> DNS: api.partner.example.com -> 203.0.113.20
-> TCP: 203.0.113.20:443
-> TLS ClientHello: SNI=api.partner.example.com
-> HTTP: :authority=api.partner.example.com
-> 代理 route/cluster/endpoint
-> egress gateway/SNAT
-> 外部服务证书与响应这里至少有五个不可互换的判断:DNS 答案是否可信且仍在允许集合中;目标 IP 是否被数据面捕获;SNI 与声明 host 是否一致;证书 SAN 是否覆盖期望域名;最终源 IP 是否确实来自批准的出口。DNS 成功不能证明 TLS 正确,最终 200 也不能证明请求经过指定网关。通配域名会扩大 SNI/Host 匹配面,通配 CIDR 会扩大 IP 面;二者叠加时,DNS 重绑定可能把一个被允许的名字解析到不应访问的地址。
Istio sidecar 模式下,ALLOW_ANY 会让未知目标经过 Envoy 的 passthrough 路径;把地址排除在捕获范围之外,则连 Envoy 都不会经过。这两个现象都可能表现为“能访问公网”,但前者仍可产生部分代理遥测,后者完全失去 Istio 策略与日志。REGISTRY_ONLY 只让注册表中已知目标进入正常出站路径,也不是安全防火墙:能够绕过 sidecar 的进程仍可直连。
在隔离集群建立可撤销的出口实验
先从 Istio 受支持发行线选择与 Kubernetes 匹配的 minor 和安全 patch,固定 CLI、控制面、数据面镜像 digest、安装 profile 与 values。下面的 demo profile 会开启便于实验的组件和较高遥测开销,只适合一次性集群;共享或生产集群应使用评审后的 IstioOperator/Helm values,并先渲染 diff。
: "${CURL_IMAGE:?set CURL_IMAGE to an approved digest, for example registry/curl@sha256:...}"
istioctl version --remote=false
istioctl x precheck
istioctl install --set profile=demo \
--set meshConfig.outboundTrafficPolicy.mode=REGISTRY_ONLY -y
kubectl create namespace mesh-egress-lab
kubectl label namespace mesh-egress-lab istio-injection=enabled
kubectl -n mesh-egress-lab create deployment client \
--image="${CURL_IMAGE}" -- sleep infinity
kubectl -n mesh-egress-lab rollout status deployment/client
kubectl -n mesh-egress-lab get pod -l app=client \
-o jsonpath='{.items[0].spec.containers[*].name}{"\n"}'
istioctl proxy-status镜像 digest 必须替换为团队已扫描并能从目标仓库拉取的真实值。预期容器列表包含业务容器和 istio-proxy,proxy-status 中对应代理最终进入 SYNCED;这只证明注入和 xDS 同步。真正的基线请求还要保存唯一请求 ID、客户端返回、源 sidecar access log、上游是否收到请求以及代理实际 cluster。
REQ_ID="egress-baseline-001"
kubectl -n mesh-egress-lab exec deployment/client -c client -- \
curl -sv --max-time 10 -H "x-request-id: ${REQ_ID}" https://httpbin.org/headers在 REGISTRY_ONLY 且尚未声明 httpbin.org 时,预期请求失败,源代理中应出现无可用外部路由、BlackHoleCluster 或等价的本地拒绝证据,外部服务不应收到请求。若请求仍成功,先检查实际 mesh config、Pod 是否真的注入、目标 IP 是否落入排除范围,以及应用是否使用了未被捕获的 UDP/自定义网络路径。不要把一次失败直接解释成策略有效;DNS 失败、证书失败和网络超时也会让 curl 非零退出。
ServiceEntry 的字段改变了哪些运行对象
ServiceEntry 把平台服务注册表之外的目标加入 Istio 的内部服务模型。它不只是白名单:hosts 参与主机匹配,addresses 可提供 VIP/CIDR 匹配,ports 决定协议与端口语义,resolution 决定代理按 DNS、静态 endpoint 还是原始地址解析,endpoints 可以显式给出后端。它也能描述 VM 或平台未发现的网格服务,因此对象名不能被泛化成“公网域名规则”。字段定义应以目标版本的 ServiceEntry reference 为准。
kubectl apply -f - <<'YAML'
apiVersion: networking.istio.io/v1
kind: ServiceEntry
metadata:
name: httpbin-external
namespace: mesh-egress-lab
spec:
hosts:
- httpbin.org
location: MESH_EXTERNAL
ports:
- number: 443
name: https
protocol: HTTPS
resolution: DNS
YAMLkubectl -n mesh-egress-lab get serviceentry httpbin-external -o yaml
istioctl proxy-config clusters deployment/client.mesh-egress-lab | grep httpbin.org
kubectl -n mesh-egress-lab exec deployment/client -c client -- \
curl -sv --max-time 10 -H 'x-request-id: egress-allow-001' https://httpbin.org/headers预期 observed cluster 中出现 httpbin.org,请求经 sidecar 成功,上游看到相同 request ID。若声明对象存在而 cluster 不存在,检查 namespace 可见性、配置分析结果和 xDS 同步;若 cluster 存在但请求走 passthrough,检查应用实际 host、SNI、端口与协议识别。resolution: DNS 让代理参与目标解析,但不会自动限制所有 DNS 答案,也不会阻止应用通过裸 IP 建立另一条连接。
resolution: STATIC 配合 endpoints 适合固定后端;NONE 让代理使用原始目标地址,若 host 又是通配符,必须特别警惕任意 IP 被纳入。配置 addresses 可以避免多个纯 TCP 服务共享端口时无法区分,但地址与真实 DNS/IP 生命周期必须由 owner 更新。错误的 protocol 会让 HTTP 路由退化为 TCP,错误的端口则会产生 listener/cluster 不匹配;这些字段变化会改写每个相关代理的 listener、route、cluster 或 endpoint,配置规模与推送成本随作用域扩大。
外部目标还必须控制可见范围。默认导出会让一个 namespace 的声明进入更多代理的服务视图;团队应按目标版本核对 exportTo、配置根 namespace 与 Sidecar 作用域,只把 partner 目标分发给确实需要调用的工作负载。缩小分发范围既是权限边界,也是控制面容量手段:一万个外部 host 乘以数千个代理,会同时放大 xDS 生成、推送、Envoy cluster 数、DNS 刷新与内存。作用域改动后,既要在获准代理中证明 cluster 存在,也要在未获准代理中证明 cluster 不存在,不能只看对象创建成功。
DNS 目标应记录“名字代际”和“地址代际”。ServiceEntry 的 Git revision 只证明声明变了,DNS TTL、A/AAAA 集合、代理缓存与防火墙地址集合可能在不同时间收敛。轮换期间至少保存查询时间、resolver、TTL、答案集合、代理 observed endpoint 和真实上游地址;若供应商使用 CDN 或短 TTL,固定 CIDR 往往会造成误拒绝,而无限放宽 CIDR 又会失去出口约束。此时应在支持的实现中评估 DNS-aware/FQDN policy,或让受控代理解析并由防火墙只允许代理身份出网,而不是把动态域名伪装成静态地址清单。
把 TLS 发起点、SNI 和证书校验放在同一张证据表里
应用直接访问 https:// 时,TLS 通常由应用发起并穿过 sidecar;代理若只做 TLS passthrough,只能按连接和 SNI 观测,不能读取加密后的 HTTP path。TLS origination 则让应用向本地代理发送明文 HTTP,由 sidecar 或 egress gateway 对外建立 TLS。后者便于统一 CA、SAN、SNI 和客户端证书,却也让网格组件成为敏感密钥与明文边界。
下面的实验端口把应用侧 80 映射到外部 443,再由 DestinationRule 发起 TLS。应用 URL、ServiceEntry 端口、targetPort 和 TLS 规则必须成组修改:
kubectl apply -f - <<'YAML'
apiVersion: networking.istio.io/v1
kind: ServiceEntry
metadata:
name: httpbin-origination
namespace: mesh-egress-lab
spec:
hosts: [httpbin.org]
location: MESH_EXTERNAL
ports:
- number: 80
name: http-for-tls
protocol: HTTP
targetPort: 443
resolution: DNS
---
apiVersion: networking.istio.io/v1
kind: DestinationRule
metadata:
name: httpbin-origination
namespace: mesh-egress-lab
spec:
host: httpbin.org
trafficPolicy:
portLevelSettings:
- port:
number: 80
tls:
mode: SIMPLE
sni: httpbin.org
subjectAltNames:
- httpbin.org
YAML应用请求 http://httpbin.org:80/headers 时,预期应用到 sidecar 是明文,sidecar 到上游是 TLS,外部服务返回成功。应从代理 cluster 配置、上游 TLS 指标或受控抓包证明发起点,而不是只看响应。反向实验把 subjectAltNames 临时改成 wrong.example.com;预期代理因证书身份不匹配而拒绝上游连接,客户端收到代理本地 5xx,外部应用没有完整 HTTP 请求,access log 的上游传输失败原因指向证书校验。恢复正确 SAN 后再次请求,才构成回滚证据。
生产私有 CA 应通过受管 Secret/SDS 或产品支持的证书分发机制提供,禁止把 PEM、私钥、口令或真实合作方域名提交到 Git。读取 Secret 的 RBAC、网关 ServiceAccount、密钥轮换重叠期和日志脱敏都要单独审计。关闭证书验证、把 SAN 改成通配符或在应用里使用 -k 只能隐藏故障,不能作为修复。
集中出口必须同时证明“经过”与“绕不过”
Istio egress gateway 是独立 Envoy 工作负载,可集中执行路由、TLS origination、访问日志和出口身份。一个简化的 HTTP 路径包含三类对象:Gateway 声明网关监听,VirtualService 把 mesh 内请求先送到 egress gateway,再由 gateway 送往外部目标,DestinationRule 保护 sidecar 到网关的 mesh 内链路。目标版本的字段与 selector 应对照 Egress Gateways 示例。
前面的 ServiceEntry 就绪后,下面的 TLS passthrough 配置才会把 httpbin.org:443 放到真实的集中出口路径。先确认一次性实验安装确实提供了 istio-egressgateway;若平台使用自定义 gateway Deployment,应替换 Service 名、selector、端口和 ServiceAccount,而不是照搬默认值。
kubectl -n istio-system get deployment,service -l istio=egressgateway
kubectl apply -f - <<'YAML'
apiVersion: networking.istio.io/v1
kind: Gateway
metadata:
name: httpbin-egress
namespace: mesh-egress-lab
spec:
selector:
istio: egressgateway
servers:
- port:
number: 443
name: tls-httpbin
protocol: TLS
hosts: [httpbin.org]
tls:
mode: PASSTHROUGH
---
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: httpbin-via-egress
namespace: mesh-egress-lab
spec:
hosts: [httpbin.org]
gateways: [mesh, httpbin-egress]
tls:
- match:
- gateways: [mesh]
port: 443
sniHosts: [httpbin.org]
route:
- destination:
host: istio-egressgateway.istio-system.svc.cluster.local
subset: httpbin
port:
number: 443
- match:
- gateways: [httpbin-egress]
port: 443
sniHosts: [httpbin.org]
route:
- destination:
host: httpbin.org
port:
number: 443
---
apiVersion: networking.istio.io/v1
kind: DestinationRule
metadata:
name: httpbin-egress-gateway
namespace: mesh-egress-lab
spec:
host: istio-egressgateway.istio-system.svc.cluster.local
subsets:
- name: httpbin
trafficPolicy:
tls:
mode: ISTIO_MUTUAL
YAML
istioctl analyze -n mesh-egress-lab
istioctl proxy-config clusters deployment/client.mesh-egress-lab \
| grep 'istio-egressgateway.*443'这段配置不做 TLS origination:应用仍验证外部证书,网关按 SNI 转发加密流量。TLS origination 是另一种信任边界,不能与 passthrough 的证据混写。发请求后应在源代理 observed cluster、网关下游连接、网关上游连接和外部源 IP 四处取证;若要按 request ID 关联两级 HTTP access log,必须改用网关终止/发起 TLS 的 L7 设计并显式配置日志,passthrough 网关看不到加密后的 HTTP Header。
配置生效后,不要只从客户端发一个成功请求。对于 L7 网关,至少保存源 sidecar 与 egress gateway 两份同 request ID 日志、gateway 的上游 host/route、外部服务看到的源 IP,以及网关 Deployment 的实例数和负载;对于 TLS passthrough,只能按连接时间窗、SNI、源/目标地址、连接计数和外部源 IP 关联,不能声称网关读到了 request ID。删除或缩容仅由实验独占的 egress gateway,反例应产生“到 gateway 无端点/连接失败”,而不是悄悄直连外部目标。若失败时仍能访问,说明存在 passthrough、排除端口/IP、错误作用域或底层旁路。
强制路径还需要基础网络策略。下面的结构只展示责任分层:业务 namespace 默认拒绝外部 egress,只允许 DNS 和到 istio-system 中 egress gateway 的数据端口;gateway namespace 再由平台网络、云安全组或防火墙允许访问批准目标。集群 DNS 标签、gateway Pod 标签和实际 targetPort 必须从目标集群读取后替换。
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: client-egress-only-via-gateway
namespace: mesh-egress-lab
spec:
podSelector:
matchLabels:
app: client
policyTypes: [Egress]
egress:
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
podSelector:
matchLabels:
k8s-app: kube-dns
ports:
- { protocol: UDP, port: 53 }
- { protocol: TCP, port: 53 }
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: istio-system
podSelector:
matchLabels:
app: istiod
ports:
- { protocol: TCP, port: 15012 }
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: istio-system
podSelector:
matchLabels:
istio: egressgateway
ports:
- { protocol: TCP, port: 8443 }这里的 15012 是常见 istiod xDS 端口,8443 是默认 egress gateway Service 的 TLS targetPort;应用 NetworkPolicy 匹配的是转发后的 Pod 端口,不是 Service port。应用前必须用 kubectl -n istio-system get service istiod istio-egressgateway -o yaml 和实际 Pod 标签核对。标准 Kubernetes NetworkPolicy 只表达 IP/端口和 Pod/namespace selector,不提供通用 FQDN allowlist;不同 CNI 对 hostNetwork、节点流量、DNS-aware policy 和连接跟踪的实现也不同。网关到公网的最终域名/IP约束常需要 CNI FQDN policy、专用防火墙或代理 allowlist。路径验证必须包含未注入 Pod、裸 IP、curl --resolve、排除 IP 范围、hostNetwork/节点路径和新 Pod 冷启动,而不能只测守规矩的 sidecar Pod。
用正反实验形成可审计证据
正向请求应同时满足:声明对象存在;源代理 observed config 含目标;请求命中 egress gateway;SNI、SAN 与 host 一致;外部服务看到批准的出口源 IP;日志与指标可用同一 request ID、route 和 upstream 关联。DNS TTL 到期后重复实验,确认新答案仍在控制范围内;多 A/AAAA 记录要覆盖 IPv4、IPv6 和失败切换。
反向实验每次只改变一个变量,并记录第一证据:
| 变量 | 预期第一证据 | 容易误判为 |
|---|---|---|
| 未声明 host | 源代理无路由或进入明确拒绝 cluster,上游无请求 | DNS 故障 |
| 错误 SNI/SAN | gateway 或 sidecar 上游 TLS 校验失败 | 外部服务 5xx |
| gateway 无 endpoint | 源代理报告无健康上游,外部无请求 | 应用超时 |
| 裸 IP/排除端口直连 | 基础网络层 drop/deny,网关无日志 | 网格策略拒绝 |
| DNS 重绑定到非批准地址 | DNS/FQDN policy 或防火墙拒绝,新地址无成功连接 | 证书轮换故障 |
| 新 Pod 立即访问 | 策略尚未收敛时仍不得出现非批准源 IP | 偶发 SNAT 漂移 |
每组实验还要保存三种“没有发生”的证据:外部服务没有收到对应请求、批准出口没有该连接、底层 flow log 在预期执行点给出 deny/drop。只有客户端失败不能区分策略拒绝、DNS 失败和上游不可用;只有网关没有日志也不能证明旁路不存在。对于 TLS passthrough,关联键是时间窗、SNI、五元组、连接计数和外部源 IP;对于 TLS origination 的 L7 路径,才可以进一步使用 request ID、route、upstream status 与响应标志。
抓取证据时,外部域名、完整 URL query、Authorization/Cookie、客户端证书、内部 IP、拓扑和 partner 返回体都属于敏感信息。访问日志应采用字段 allowlist,仅保留时间、脱敏 request ID、源/目标身份、策略、route、上游状态、响应标志、延迟、字节和配置版本;不要记录请求体和凭证 Header。原始 config dump 能暴露集群名、域名、证书路径和端点,只能放在受控事故存储并设置保留期。
DNS、策略、TLS 与网关容量要分层排障
当客户端只报告 timeout 或 503,先确认 DNS:在应用容器和代理语义下分别读取解析结果、TTL、A/AAAA,检查 CoreDNS 日志与代理 DNS 缓存。然后确认流量是否被捕获:源 sidecar 是否有该 request ID,代理 listener/cluster 是否匹配,底层 flow log 是否显示直连。没有源代理日志时,不应继续在 egress gateway 中搜索。
流量进入代理后,用 istioctl proxy-status 看 xDS 同步,再用 istioctl proxy-config listeners|routes|clusters|endpoints 检查 observed 配置。对象已经 kubectl apply 但代理没有 cluster,属于配置分发或作用域问题;cluster 存在而无 endpoint,属于发现/DNS/endpoint 问题;到 gateway 成功而 gateway 上游 TLS 失败,才进入 SNI、CA、SAN、客户端证书与时钟检查;gateway 完成上游连接后收到真实 4xx/5xx,则应保留 upstream status 并转向外部依赖责任人。
集中网关本身是共享容量池。连接数、并发流、TLS handshake、DNS 缓存、CPU、内存、文件描述符、下游/上游重试和日志队列都可能形成瓶颈。容量测试应区分短连接、HTTP/2 多路复用、长连接、大响应、证书轮换和外部慢响应,观察 P50/P99、连接建立、pending、overflow、reset、网关实例间负载与扩容速度。平均 CPU 低不能排除单实例连接表或单热点域名耗尽。
容量预算不能只写“网关副本数”。至少估算 峰值新建连接率 × TLS CPU、并发连接 × 单连接内存、DNS 查询率、日志字节率和 NAT/SNAT 端口占用;再用 N-1 网关、单可用区故障和外部慢响应验证排队是否有界。重试必须计入源 sidecar、egress gateway 与 SDK 三层的乘法效应。若出口故障时业务允许降级,降级目标也必须预先声明并受相同数据分类与审计约束,不能在事故中临时切回无审计直连。
接进真实项目时把外部依赖当成契约
业务仓库不应自行提交一份拥有集群级权限的出口清单,也不应把合作方 Token 与网格对象放在同一个 values 文件。更稳妥的接入方式是把外部依赖拆成三层:业务仓库声明逻辑依赖、协议、端口、数据分类、超时和 owner;平台仓库把已审批依赖编译为 namespaced ServiceEntry/Route/Policy;凭证系统把短期 Token 或客户端证书注入指定 ServiceAccount。三个层次通过依赖 ID 关联,彼此不复制秘密。
项目模板应统一读取 HTTPS_PROXY/SDK endpoint 等可切换入口,保留总 deadline、幂等键和 request ID 传播,并提供“直连、网格出口、禁用依赖”的环境开关。开关不是让开发者绕过生产策略,而是让测试能明确比较路径。CI 在 manifest 层检查 host、SNI、端口、TLS mode、wildcard、owner 和失效日期,在隔离 namespace 发出允许 host、未声明 host、错误 SAN 与裸 IP 四组请求;只有 condition、observed config、网关日志和底层 deny 同时符合预期,才允许进入下一环境。
发布时先创建声明与证书,再等待代理配置收敛,然后小流量切向集中网关,最后收紧底层网络。回滚按反方向恢复,但每一步都要验证真实请求路径,不能只依赖 Git 回退成功。对 Node.js、JVM、Go 等连接池较长的客户端,还要主动排空旧连接或等待其 TTL;否则新配置已经生效,旧 socket 仍可能沿旧源 IP 持续访问,审计会出现看似随机的双路径。
依赖变更还要有清晰的职责分离。业务 owner 证明调用目的、数据最小化和失败降级;安全 owner 审核目标身份、凭证与日志字段;平台 owner 审核代理作用域、底层封口、出口容量和回滚顺序。紧急放行必须生成带到期时间的独立对象,不能直接扩大共享通配规则;到期控制器删除对象后,流水线再次发反向请求,证明旧目标已经不可达。这样才能让审批记录、Git 变更、控制器状态、数据面配置和真实流量形成同一条审计链。
不同实现的“出口”并不是同一个部件
Linkerd 的 EgressNetwork 是 namespaced 出站策略对象,可用 trafficPolicy: Allow|Deny 和 Gateway API Route 控制 sidecar 看见的外部网络;namespace-local 定义优先于全局定义。它不自动等于一个固定 SNAT 的集中出口节点。需要固定源 IP时,仍要配合底层网络或独立 egress/NAT 设施。
Cilium 的 CiliumEgressGatewayPolicy 选择源 Pod、目标 CIDR 和 gateway node,通过 eBPF 路由并 SNAT 为可预测 IP;它依赖 BPF masquerading 与 kube-proxy replacement。它解决的是路径与源 IP,不自动提供基于域名的 L7 allowlist,而且新 Pod 到策略生效存在延迟窗口。目标集群还要核对 Cluster Mesh、identity allocation 与 endpoint slice 等当前兼容限制。
Kuma 的传统 ExternalService、新实验性 MeshExternalService、MeshPassthrough 与 ZoneEgress 分担不同责任。关闭默认 passthrough、启用内置 DNS、声明目标并使用 ZoneEgress 才能形成收敛路径;实验性资源还要求 mTLS、HostnameGenerator 和对应 feature stage,不能直接成为稳定生产承诺。Consul terminating gateway 面向已注册的非 Connect 服务:它终止 Connect mTLS、执行 intentions 后转发,不是任意公网 URL 代理。
选型时先问需要哪种保证:只想获得外部请求可见性,sidecar 声明足够;要默认拒绝未知目标,需要 deny 基线与显式 route;要固定源 IP,需要 egress gateway/node/NAT;要统一 TLS origination,需要受管 CA、SNI、SAN 与密钥治理;要对抗旁路,必须把 CNI、节点、路由和防火墙纳入同一控制。产品名不能替代保证模型。
回滚、清理与长期治理
回滚集中出口时先恢复一条已验证的替代路径,再放宽底层网络,随后撤销指向 gateway 的路由;反过来先删 gateway 会把仍被封口的业务留在黑洞中。回滚默认拒绝时也要列出临时放行的 owner、目标、过期时间和审计范围,不能把 ALLOW_ANY 长期留作事故开关。
实验清理按依赖反序执行:删除测试 NetworkPolicy 前确认它只选择 mesh-egress-lab 的 client;删除 gateway 路由、TLS DestinationRule 与 ServiceEntry;删除 namespace;最后仅在一次性集群中按安装清单卸载 Istio。共享集群不得为了清理一篇实验删除公共 CRD、egress gateway 或控制面。
kubectl -n mesh-egress-lab delete networkpolicy client-egress-only-via-gateway --ignore-not-found
kubectl -n mesh-egress-lab delete virtualservice,gateway,destinationrule,serviceentry --all --ignore-not-found
kubectl delete namespace mesh-egress-lab --wait=true --timeout=2m
kubectl get namespace mesh-egress-lab --ignore-not-found
# 仅限确认由本实验独占的一次性集群:
istioctl uninstall --purge -y长期治理应给每个外部目标登记业务 owner、安全 owner、数据分类、合法用途、Host/SNI/CIDR、端口、TLS 模式、凭证来源、出口位置、容量预算、日志保留期和失效日期。变更流水线先做 schema/analyze 与 manifest diff,再在隔离 namespace 运行已声明成功、未声明拒绝、错误证书、gateway 故障和旁路直连五组测试;生产发布采用小批次并监控网关饱和、未知目标、DNS 答案漂移和非批准源 IP。
外部服务退订时先停止新调用并排空长连接,撤销客户端证书、API Token 和 Secret 读取权限,删除 route/声明/FQDN/CIDR 与防火墙规则,核销固定公网 IP、NAT、网关副本、日志索引和告警。最后从审计数据中证明没有残余调用;“配置文件已删除”不等于凭证、网络和成本都已经退出。
