Istio:Sidecar 与 Ambient 数据面部署、迁移与治理
Istio 只有一个产品控制面,但可以采用两种数据面。Sidecar 模式让每个工作负载附近的 Envoy 承担流量接管、L4/L7 策略和遥测;Ambient 模式把安全四层能力放到每节点 ztunnel,再让目标侧 waypoint 按需处理 HTTP 路由、授权与可观测。把它们拆成两篇产品介绍,会重复安装、身份、策略、升级和退出边界,也容易把模式选择误读成两个独立工具。
判断迁移是否完成,不能只看 Pod 是否存在 istio-proxy、namespace 是否带 ambient 标签或控制面是否 Ready。必须同时核对真实请求进入哪个执行点、Envoy 或 ztunnel 装载了哪版配置、waypoint 是否覆盖目标、双方使用什么身份,以及回滚后旧路径是否恢复。本篇先建立共同控制面与证据链,再分别展开 Sidecar 和 Ambient,最后处理双轨迁移与长期治理。
Sidecar 模式的数据路径与控制面
sidecar 模式在每个应用 Pod 内运行一个 Envoy。应用仍连接 orders.mesh-lab.svc.cluster.local,但 Pod 网络命名空间中的重定向规则会把出站连接送进源 Envoy;目标节点收到流量后,目标 Pod 的规则再把连接送进目标 Envoy,最后才到 orders 容器。UDP 不在这条代理路径中;HTTP、gRPC、TLS 和原始 TCP 是否能获得七层语义,还取决于 Service 端口的协议声明和 Envoy 能否识别协议。
istiod 是控制面:它观察 Kubernetes Service、EndpointSlice、Istio 配置和安全策略,把它们转换为 xDS 资源。Envoy 是数据面:LDS 决定监听器,RDS 决定 HTTP 路由,CDS/EDS 决定上游 cluster 与 endpoint,SDS 交付证书和信任材料。控制面短暂失联时,Envoy 通常继续使用最后一次已接受的配置;但新端点、新策略和证书续期不会凭空出现。因此“旧流量还能跑”与“控制面健康”是两件事。
自动注入发生在 Pod 创建的准入阶段。namespace 标签只决定以后创建的 Pod 应由哪个 webhook 处理,既不会改写 Deployment 模板,也不会给已运行 Pod 热插入 Envoy。默认路径由 istio-init 在 Pod 网络命名空间建立重定向规则,因此发布身份必须能创建带 NET_ADMIN、NET_RAW capability 的 init container;Istio CNI 则把这份高权限集中到每节点一个 agent,由主 CNI 完成网卡配置后追加重定向。它降低了业务 Pod 的权限,却把故障域提升到了节点,绝不是 Calico、Cilium 等主 CNI 的替代品。
两条路径的取舍不是“有没有 sidecar”这么简单。每 Pod init 便于在普通集群快速验证,但会与 Pod Security、准入策略和最小权限发生冲突;节点级 CNI 更适合统一平台治理,却要求平台团队维护 DaemonSet、宿主机 CNI 目录、链式顺序和节点升级。使用 CNI 时,应用 init container 会早于 sidecar 启动,此时被重定向的网络可能没有可用代理;排除 IP/端口可以绕开启动依赖,也会让后续应用容器对同一目标永久绕过网格。任何排除项都必须进入出站资产清单,并用一个“未被 Envoy 记录但连接成功”的反向请求证明旁路范围没有被误扩大。注入标签优先级、手工注入和自定义模板可从 sidecar injection 继续核对,CNI 权限和竞态则应按 Istio CNI node agent 处理。
先固定支持组合,再安装隔离 revision
实验至少需要一个可丢弃的 Kubernetes 集群、集群管理员权限、kubectl、与目标版本配套的 istioctl,以及可以拉取目标镜像的网络。安装前在 Supported Releases 核对 Istio minor 与 Kubernetes 的受支持交集,再在 Feature Status 和目标 minor 的 upgrade notes 中核对要用的 API。tested 不能改写成 supported,滚动的 latest 也不能代替项目锁定版本。
从官方 release 下载与集群架构匹配的归档,校验发布摘要后,把其中 bin/istioctl 放入临时工具目录。先记录版本和集群事实:
istioctl version --remote=false
kubectl version
kubectl get nodes -o wide
kubectl auth can-i create customresourcedefinitions.apiextensions.k8s.io
kubectl auth can-i create mutatingwebhookconfigurations.admissionregistration.k8s.io这里需要 cluster-scoped CRD、webhook、ClusterRole 等高权限对象。个人账号不应长期持有集群管理员权限;更稳妥的做法是由受控发布身份安装控制面,应用团队只获得自己 namespace 内的 Deployment、路由和受批准策略写权限。安装清单、镜像 digest、最终 values 和执行审计应进入变更记录,不能只保存一条 shell 历史。
用不可变 revision 安装控制面,避免把实验工作负载接到已有默认控制面。default profile 是 sidecar 的生产起点;demo 会打开更重的日志与 tracing,适合展示功能,不适合容量基线。下面选择 CNI 路径;若实验明确选择每 Pod init,删除 components.cni 和 values.pilot.cni,同时接受业务 Pod 对网络 capability 的需求。
export REV=stable-a
cat > /tmp/mesh-lab-istio.yaml <<'EOF'
apiVersion: install.istio.io/v1alpha1
kind: IstioOperator
spec:
profile: default
components:
cni:
enabled: true
values:
pilot:
cni:
enabled: true
meshConfig:
accessLogFile: /dev/stdout
defaultConfig:
holdApplicationUntilProxyStarts: true
EOF
istioctl install -y --revision "$REV" -f /tmp/mesh-lab-istio.yaml
kubectl get pods -n istio-system
kubectl get daemonset -n istio-system istio-cni-node -o wide
istioctl analyze --all-namespacesrevision 把 istiod、注入 webhook 与将来要连接的数据面归入 stable-a。values.pilot.cni.enabled=true 告诉该 revision 的注入模板不要再创建特权 istio-init;漏掉它会出现“节点已有 CNI,Pod 仍带特权 init”的双重配置。accessLogFile 让实验可以直接取得代理访问证据,但会增加日志字节、采集 CPU 与敏感数据暴露面,生产环境应通过 Telemetry API 收窄作用域和字段。holdApplicationUntilProxyStarts 让应用容器等待代理就绪,可降低启动时绕过或连接失败窗口,也会把代理启动故障直接传导成 Pod 启动变慢。团队必须根据启动 SLO 决定是否使用。
CNI DaemonSet 的 Ready 也不是流量接管证据。选一个新建 Pod,确认其中没有 istio-init、存在 istio-validation,再从应用容器发请求并在 Envoy 访问日志中找到同一 request ID。反例是在隔离节点让 CNI agent 暂时不可用后创建测试 Pod:安全结果应是验证 init 阻止 Pod 正常启动,CNI repair 记录修复动作;若 Pod Ready 且请求没有经过 Envoy,说明出现了可绕过窗口。repair 的删除、打标签或原地修复权限会改变事故处置和攻击面,不能为了让 Pod 自动恢复就无条件授予宽泛的 Pod 更新、删除权限。
若采用 Helm,组件顺序至少是 istio/base 后接 istio/istiod,gateway 独立管理;各 chart 必须固定同一版本。Helm profile 只是 values 集合,不会像 istioctl install 一样替你选择并安装整套组件。GitOps 使用 helm template 或 istioctl manifest generate 时还要自行保证依赖顺序、field ownership、环境差异和 prune,因此渲染成功不等于发行路径已经验证。官方的 istioctl 安装说明 与 Helm 安装说明 应作为命令和 ownership 变化的入口。
把 mesh-lab 的两个版本接进来
先创建隔离 namespace,并只保留 revision 标签。普通 istio-injection=enabled 与 istio.io/rev 同时存在时,前者会优先,最容易造成“标签看似迁移、实际仍连旧控制面”。
kubectl create namespace mesh-lab
kubectl label namespace mesh-lab istio-injection- istio.io/rev=stable-a --overwrite在 mesh-lab 部署 client、orders-v1、orders-v2。三个 Deployment 都使用各自的 ServiceAccount;两个 orders Pod 带 app: orders 与不同 version 标签,Service selector 只选择 app: orders。应用可以是任意能返回自身版本并暴露 /healthz 的测试镜像,镜像必须由项目锁定 digest。Service 端口显式命名为 http 或设置 appProtocol: http:
apiVersion: v1
kind: Service
metadata:
name: orders
namespace: mesh-lab
spec:
selector:
app: orders
ports:
- name: http
appProtocol: http
port: 8080
targetPort: 8080appProtocol 优先于端口名。若端口被声明成 TCP,HTTP header 路由、方法级授权和七层指标不会按预期出现;若同一 Pod 端口被多个 Service 以不同协议声明,结果也可能不稳定。server-first 协议需要单独核对,不能依赖自动识别碰运气。
Deployment 创建后先检查哪个 webhook 会处理它,再逐层确认真实状态。check-inject 读取集群中已有的 Deployment;在对象创建前调用会因为找不到资源而失败:
istioctl experimental check-inject -n mesh-lab deploy/client
kubectl get pods -n mesh-lab
kubectl get pod -n mesh-lab -l app=client -o jsonpath='{.items[0].spec.containers[*].name}{"\n"}'
istioctl proxy-status
istioctl proxy-config listeners deploy/client -n mesh-lab
istioctl proxy-config clusters deploy/client -n mesh-lab | grep orders容器列表应同时出现应用与 istio-proxy;当前 CNI 路径不应出现 istio-init,但会有用于确认重定向已经建立的 istio-validation。使用非 CNI 路径时才应看到负责重定向的 istio-init。proxy-status 中对应代理应连接 stable-a 且 CDS/LDS/EDS/RDS 收敛。即使状态都是 SYNCED,仍要发真实请求:
kubectl exec -n mesh-lab deploy/client -c client -- \
sh -c 'for i in 1 2 3 4; do curl -fsS -H "x-request-id: sidecar-$i" http://orders:8080/version; done'预期请求在两个版本间按默认负载均衡分布,orders 应用日志能看到 request ID,源和目标 Envoy 访问日志也能关联同一请求。只有应用响应而代理没有记录,优先检查流量捕获、排除端口和实际调用地址;只有代理本地 503 而 orders 没日志,说明请求未到应用,不要先改业务代码。
用路由正反实验看懂配置字段
先定义 subset,再按 header 把实验请求定向到 v2:
apiVersion: networking.istio.io/v1
kind: DestinationRule
metadata:
name: orders
namespace: mesh-lab
spec:
host: orders.mesh-lab.svc.cluster.local
trafficPolicy:
connectionPool:
http:
http1MaxPendingRequests: 20
outlierDetection:
consecutive5xxErrors: 3
interval: 5s
baseEjectionTime: 15s
maxEjectionPercent: 50
subsets:
- name: v1
labels: {version: v1}
- name: v2
labels: {version: v2}
---
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: orders
namespace: mesh-lab
spec:
hosts: [orders]
http:
- match:
- headers:
x-orders-version: {exact: v2}
route:
- destination: {host: orders, subset: v2}
timeout: 2s
- route:
- destination: {host: orders, subset: v1}
timeout: 2sVirtualService 决定匹配、路由和请求总超时;DestinationRule 定义 subset 与目标侧连接池、负载均衡、异常摘除和 TLS。http1MaxPendingRequests 是容量保护,不是业务并发承诺;过小会在突发流量下由代理拒绝请求,过大则把延迟藏进队列。consecutive5xxErrors 与摘除时间只处理端点健康,不是应用熔断状态机,也不能代替客户端总 deadline。
正例请求应始终返回 v2,普通请求应返回 v1:
kubectl exec -n mesh-lab deploy/client -c client -- \
curl -fsS -H 'x-orders-version: v2' -H 'x-request-id: route-ok' http://orders:8080/version
kubectl exec -n mesh-lab deploy/client -c client -- \
curl -fsS -H 'x-request-id: route-default' http://orders:8080/version反例不要制造随机故障,而要只改变一个字段:把 v2 subset 的标签临时改成 version: missing,记录对象的 metadata.generation,再等待 proxy-status 与目标 Envoy 的 cluster/endpoint 配置收敛后请求。VirtualService、DestinationRule 并不提供可通用依赖的 status.observedGeneration,不能拿不存在的状态当发布闸门。预期代理返回 503,访问日志出现无健康上游相关的 response flag/details,orders 应用没有这次 request ID。随后恢复标签,确认 EDS 重新出现 v2 endpoint 且请求恢复。这个实验能区分“声明版本已变化但数据面尚未收敛”“路由已命中但 subset 无端点”和“路由根本没有下发”。
重试必须和总 deadline、幂等性一起设计。若代理、SDK 和入口各重试两次,最坏尝试数会相乘;对写请求,响应丢失并不表示上游没有完成写入。只有携带幂等键、服务端能返回既有结果且总 deadline 覆盖所有 attempt 与 backoff 时,才考虑代理重试。验证时保存每次上游 attempt 的 request ID、开始/取消时间和实际写入次数,不能用最终 200 掩盖放大。
mTLS 证明身份,不替业务做授权
Istio 默认身份通常映射为 spiffe://<trust-domain>/ns/<namespace>/sa/<service-account>。istio-agent 向 istiod 申请短期证书,再通过 SDS 交给 Envoy;私钥、证书链、trust bundle、URI SAN 和到期时间共同构成身份材料。访问配置转储或证书输出时要按敏感资产处理,避免把真实服务拓扑、证书链或请求 Header 写入公共工单。
Auto mTLS 会对已知 mesh endpoint 尽量使用 Istio mTLS,但不等于所有入站都拒绝明文。先用 PeerAuthentication 把 orders 切到 STRICT:
apiVersion: security.istio.io/v1
kind: PeerAuthentication
metadata:
name: orders-strict
namespace: mesh-lab
spec:
selector:
matchLabels: {app: orders}
mtls:
mode: STRICT随后加一条只允许 client ServiceAccount 的授权策略。Istio 授权顺序是 CUSTOM、DENY、ALLOW;目标上出现 ALLOW 后,未匹配请求会被拒绝:
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
name: orders-client-only
namespace: mesh-lab
spec:
selector:
matchLabels: {app: orders}
action: ALLOW
rules:
- from:
- source:
principals: ["cluster.local/ns/mesh-lab/sa/client"]
to:
- operation:
methods: ["GET"]
paths: ["/version"]正确 ServiceAccount 的 GET 应成功;换成另一个 ServiceAccount 的 Pod 应得到代理拒绝,POST 或错误 path 也应失败。再从未注入 Pod 直连 orders,STRICT 应阻止明文。证据至少包含源 principal、目标 workload、命中策略、代理拒绝码和目标应用是否收到请求。mTLS 成功只说明传输对端身份经过验证,不能推出调用者有权执行“取消订单”等业务动作;业务权限仍由应用鉴权和领域规则承担。
PeerAuthentication 管入站接受模式,DestinationRule.trafficPolicy.tls 管出站发起模式。随意把后者设为 SIMPLE、MUTUAL 或 DISABLE,可能制造 double TLS、明文或证书校验失败。排查握手问题时同时检查两端 TLS 模式、SDS 证书、URI SAN、系统时钟和 trust bundle,不能只看一张 mTLS 面板。
项目接入从依赖图开始
真实项目不要一次给整个集群打注入标签。先选一个无外部副作用、依赖清晰的 namespace,列出所有入站、出站、健康检查、数据库、消息队列、第三方 TLS 和 UDP 流量。为每条关键调用保存无网格基线:DNS 解析、端点、状态码、延迟分布、连接数和 request ID。然后按 cohort 重建 Pod,逐条比较代理前后的路径。
应用必须传播 traceparent、tracestate,兼容旧链路时再传播 B3 Header。Envoy 可以创建或上报 span,却不能替应用在业务线程和下游客户端之间传递上下文。访问日志用于单次请求到达与代理判责,指标用于窗口趋势,Trace 用于跨 hop 因果;三者互补。默认采样下查不到 Trace 可能只是未采样,应先用 request ID 和访问日志证明请求存在。
生产遥测要限制高基数。不要把用户 ID、完整 URL、订单号写成 Prometheus label;这会让 active series、内存和存储成本随业务数据基数膨胀。记录代理 CPU/RSS、每 Pod 启动时延、xDS push queue、配置收敛时间、日志字节与 Trace ingest,才能算出 sidecar 的真实单位成本。sidecar 每副本固定增加代理资源,小 Pod、多副本和短任务的相对开销尤其明显。
从现象回到责任层
排障先固定唯一 request ID,再按以下顺序推进,而不是一次收集所有日志:
kubectl get endpointslice -n mesh-lab 证明 Kubernetes 是否有目标端点。istioctl analyze -n mesh-lab 查 host、subset、端口和策略静态错误。istioctl proxy-status 查目标代理连接的 istiod 与 xDS 同步状态。
istioctl proxy-config routes|clusters|endpoints 查 Envoy 实际观察状态。源 Envoy 访问日志区分 NR、无健康上游、上游连接失败、超时和策略拒绝。目标 Envoy 与应用日志判断请求停在目标代理前还是应用内。
404 伴随 route not found 通常是 host、端口或协议识别问题;503 且没有 upstream host 常指向 subset/EDS/连接池;握手失败要查看 UPSTREAM_TRANSPORT_FAILURE_REASON;403 要关联 source principal 与授权策略。SYNCED 只表示代理确认了某版 xDS,不表示该配置符合业务意图。控制面断连实验则要分别观察既有连接、新连接、端点变化、新策略传播和证书续期,写清最后已知配置还能维持多久。
revision 升级不是移动一个标签
升级前保存 CRD、Istio CR、namespace 标签、webhook、安装 values、镜像 digest、gateway 和遥测配置,运行目标版本 istioctl x precheck --from-version=<old-minor>,并阅读源、目标 minor 的升级说明。官方推荐 canary revision 升级:先升级共享 base/CRD,再安装新 revision 的 istiod,最后按 workload cohort 重注入。
export NEW_REV=stable-b
istioctl install -y --revision "$NEW_REV" -f /tmp/mesh-lab-istio.yaml
istioctl tag set mesh-canary --revision "$NEW_REV"
istioctl tag list
kubectl label namespace mesh-lab istio-injection- istio.io/rev=mesh-canary --overwrite
kubectl rollout restart deployment -n mesh-lab
kubectl rollout status deployment -n mesh-lab
istioctl proxy-status安装新 istiod 不会改变运行中的 Envoy;移动 revision label 或 tag 也只改变以后创建 Pod 的注入目标。mesh-canary 是可移动指针,stable-b 是应保持不可变的控制面实例;只有重建 Pod 才会重新执行注入并把新 sidecar 接到新 revision。滚动重启后,应分别验证旧到旧、旧到新、新到旧、新到新的身份、路由、超时、遥测和长连接。通用 skew 约束是控制面可领先数据面一个 minor、数据面不能领先控制面;CNI 另有前后一个 minor 的兼容窗口,不能把两条规则混成“所有组件随便差一个版本”。revision 路径虽支持受控跨越两个 minor,仍要先证明中间 CRD、策略和扩展没有不可逆语义变化。
回滚首先把新建 Pod 的注入目标切回旧 revision,再滚动受影响 workload,并验证旧控制面仍能解释当前 CRD 和策略。若 gateway 曾原地升级,需要先用旧版本工具恢复 gateway;卸载新 revision 不会自动恢复它。revision tag 是可变指针,revision 实例应保持不可变,这样审计才能回答某个 Pod 究竟由哪套模板与控制面生成。
容量、所有权与长期治理
容量测试至少固定 RPS、连接数、payload、协议、sidecar worker、TLS、路由数量、策略数量、日志字段和 Trace 采样。观察 p50/p99、代理 CPU/RSS、连接与 pending queue、reset、istiod CPU/RSS、xDS push time 和 Prometheus active series。官方 Performance and Scalability 的数字适合复现实验方法,不能直接成为自己的容量承诺;硬件、拓扑、过滤器和遥测都会改变结果。
通过 Sidecar 资源缩小配置可见性可以降低每个 Envoy 的 listener、cluster 和内存,但错误的 egress.hosts 会直接让依赖不可达。每次收窄都要对配置数量、关键 endpoint 与正反请求做对照。配置风暴、证书轮换和 istiod 恢复比稳定平均负载更容易暴露长尾,因此压测应包含批量 rollout、EndpointSlice 抖动和控制面重启。
团队应给 namespace 注入标签、root namespace 策略、全局 Telemetry、revision tag、CA 和 CRD 分别指定 owner。应用团队可以维护自己的 VirtualService 与局部策略,平台团队维护跨 namespace 的信任、webhook 和升级事务;策略变更走评审与 dry-run,日志和 config dump 设置访问控制、脱敏、保留与删除期限。把所有权写清,才能避免一次全局 ALLOW、DENY 或 Telemetry 修改影响整个 mesh。
先撤数据面,再卸控制面
退出前先证明应用在无 sidecar 时仍有 DNS、TLS、授权、超时、重试和观测替代路径。移除 namespace 的 revision 标签不会改变旧 Pod,必须滚动重建,并检查实际 Pod spec 已无 istio-proxy 和重定向 init container。若 namespace 还保留 istio-injection=enabled,必须同时移除,否则默认 tag 的 webhook 仍会注入:
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}'对关键调用重新做成功、错误身份、超时和无端点反例,确认替代告警已经接管。先删除实验 namespace 和可变 tag,再确认两个 revision 都没有连接中的 sidecar 或 gateway,最后逐个定向卸载控制面:
kubectl delete namespace mesh-lab
istioctl tag remove mesh-canary
istioctl proxy-status
istioctl uninstall --revision stable-b -y
istioctl uninstall --revision stable-a -y
rm -f /tmp/mesh-lab-istio.yamlproxy-status 仍有旧 revision 连接时不得继续卸载;先找到对应 workload 或 gateway,完成重建、迁移或回滚。不要在多 revision 或共享集群直接执行 istioctl uninstall --purge;它会删除共享的 cluster-scoped Istio 资源。Helm 卸载也不会自动删除 CRD,而手工删除 *.istio.io CRD 会级联删除所有路由和策略实例。Gateway API CRD 还可能由其他控制器共享。最终审计应确认没有注入 Pod、旧 proxy 连接、revision 标签、tag webhook、临时 RBAC、测试证书和日志保留任务,再由共享资源 owner 决定是否删除 CRD 与 istio-system。
Ambient 模式的数据路径与执行边界
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”。
