Red Hat OpenShift Service Mesh Operator 部署、迁移与治理手册
发布窗口里,orders-v2 已经部署,控制台中的 Operator CSV 是 Succeeded,istiod Pod 也全是 Running,但 client 发出的请求仍然全部落到 orders-v1。有人认为路由规则没有生效,有人怀疑 Envoy 没拿到配置,还有人准备重装整个网格。真正危险的是,这三种现象可能同时存在:namespace 能被控制面发现,不代表工作负载已经注入代理;Pod 有代理,不代表代理与目标 revision 同步;代理同步也不代表请求命中了预期 Host、端口和 subset。
另一次故障更隐蔽:Kiali 拓扑图能画出调用边,Tempo 却没有新的 Trace。团队先扩了 Tempo 存储,后来才发现 ambient 工作负载只有 ztunnel,没有 waypoint,链路上根本没有生成 L7 span;而另一个 sidecar namespace 虽然有 Envoy,却没有把 OpenTelemetry provider 和 Telemetry 资源接通。OpenShift Service Mesh 不是把 Istio、Kiali、Tempo 装在一起就结束了,它是一组由不同 Operator、CR、权限和数据路径共同维持的能力。
先把“一个版本”拆成受支持组合
Red Hat OpenShift Service Mesh(OSSM)3.x 以 Istio 为核心,但生产支持对象不是一个孤立的“OSSM 版本”。Operator、OpenShift Container Platform(OCP)、Istio、Envoy、Istio CNI、Ztunnel 和 Kiali 必须落在同一行版本支持矩阵中。安装文档里的示例输出可能落后于 z-stream,决定组合时应优先查看矩阵和对应发行线的 release notes,再把实际 CSV、CR 和代理版本记录进变更单。
OSSM 3.x 的受支持安装入口是 Red Hat OpenShift Service Mesh 3 Operator。上游 Helm chart 或 istioctl install 即使能创建 Istio,也不能据此获得 Red Hat 对 OSSM 的支持。istioctl 在这里是诊断、验证和部分 waypoint/ztunnel 操作工具;客户端还要与控制面匹配或保持在官方允许的版本差内。GA、Technology Preview、Developer Preview 和 NA 也不是同义词:TP 不应成为关键生产链路基线,DP 不应进入生产,NA 则不能被包装成“社区方式也一样”。每次采用 ambient、多集群、VM 或某个 Envoy 扩展前,都要在功能支持矩阵逐项核对。
Gateway API 还多一层发行边界。OSSM 3.2 把 Gateway parentRef 的入口流量和 Service parentRef 的网格流量列为 GA,但 CRD 只在 OCP 4.19 及以后版本随平台提供并受支持;OCP 4.18 及以前版本不包含这组受支持 CRD,手工覆盖上游 bundle 也被产品矩阵标为 NA。因此,“API server 能接受 HTTPRoute”不能替代产品支持判断:先确认 OCP、OSSM、内置 CRD bundle 和目标 Route 特性,再决定是否进入生产。共享 OpenShift 集群不得用上游 standard-install.yaml 覆盖 Operator 或平台持有的 CRD。
开始操作前准备一个隔离的 OpenShift project mesh-lab,以及能够安装集群级 Operator、创建 CRD、管理 namespace 标签和读取 Pod 日志的账号。不要把日常开发者直接授予 cluster-admin;平台管理员负责 Operator、CRD、CNI 和集群级 RBAC,网格管理员负责 Istio/revision,应用团队只管理获准 namespace 内的 Route、策略与工作负载。命令中的对象全部是演示对象,不应替换成共享集群资源后直接执行删除操作。
先保存集群事实:
oc whoami
oc get clusterversion version -o jsonpath='{.status.desired.version}{"\n"}'
oc get network.config.openshift.io cluster -o jsonpath='{.status.networkType}{"\n"}'
oc get packagemanifest -n openshift-marketplace | grep -i service-mesh
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"}'第一条确认身份,中间两条决定 OCP 与网络插件是否在目标组合内,packagemanifest 让 OLM catalog 告诉你真实 package、channel 和 catalog source;最后一条记录集群实际提供的 Gateway API bundle 与 channel。若 CRD 不存在,先依据 OCP 支持边界判断,而不是直接手工安装;若 CRD 存在但注解为空,还要检查 managedFields 和 Operator ownership,不能把未知来源的 CRD 当成受支持组件。不要从博客复制固定 Subscription 名称;不同 catalog 状态下,臆造的 package 或 source 会让订阅长期停在解析失败。
通过 OLM 安装 Operator,再创建控制面
最稳妥的首次入口是 OpenShift Web Console 的 Operators → OperatorHub:搜索 Red Hat OpenShift Service Mesh 3,选择 stable-<minor> 固定 minor 线,安装到 openshift-operators 并监视所有 namespace。自动审批适合有完整预发布门禁的持续更新环境;手动审批让平台团队在读取 InstallPlan、兼容矩阵和 release notes 后再更新,代价是必须有人监控待审批计划。完整步骤以安装 Service Mesh为准。
安装后不要立刻部署业务,先验证 OLM 与 API:
oc get subscription,csv -n openshift-operators
oc get crd | grep -E 'sailoperator.io|istio.io'
oc api-resources | grep -E 'Istio|IstioCNI|IstioRevision|ZTunnel'预期证据是 Subscription 指向预定 channel、CSV 为 Succeeded,并且 Sail Operator 和 Istio CRD 已注册。若 CSV 为 InstallReady,通常是在等待手动批准 InstallPlan;若为 ResolutionFailed,先看 Subscription 的 package/channel/source 是否存在,而不是删除 CRD 重来。
sidecar 数据路径至少需要控制面和 CNI。先创建两个专用 project;Istio CR 放在 istio-system,IstioCNI CR 放在 istio-cni,而 spec.namespace 分别指定各自 operand 的部署位置。下面的版本占位符必须替换成目标支持矩阵同一行、且目标 Operator CR schema 接受的 v<major>.<minor>.<patch> 值:
oc get project istio-system >/dev/null 2>&1 || oc new-project istio-system
oc get project istio-cni >/dev/null 2>&1 || oc new-project istio-cniapiVersion: sailoperator.io/v1
kind: Istio
metadata:
name: default
namespace: istio-system
spec:
namespace: istio-system
version: <supported-istio-version-with-v-prefix>
updateStrategy:
type: InPlace
values:
meshConfig:
accessLogFile: /dev/stdout
---
apiVersion: sailoperator.io/v1
kind: IstioCNI
metadata:
name: default
namespace: istio-cni
spec:
namespace: istio-cni
version: <supported-istio-version-with-v-prefix>保存为 mesh-lab/platform/ossm.yaml,先让管理员检查渲染差异,再应用:
oc apply -f mesh-lab/platform/ossm.yaml
oc get istio,istiorevision -A -w
oc get istiocni -A
oc get pods -n istio-system -l app=istiodspec.version 决定 Operator 选择哪条受支持的 Istio 发行线;它不是镜像 tag 的随意输入框。spec.updateStrategy.type 决定控制面如何变更:InPlace 直接替换当前 revision,资源较省但影响面大;RevisionBased 并存新旧 revision,适合逐批迁移 sidecar。values 会进入 Operator 管理的 Istio Helm values,字段拼错可能被 schema 拒绝,也可能成为无效果配置,因此必须同时看 CR condition 和最终工作负载。meshConfig.accessLogFile 打开访问日志会明显增加 stdout、日志采集和索引成本,实验结束后应按生产采样与保留策略收敛。
只有当 Istio、IstioRevision 和 IstioCNI 的 condition 指向目标 generation、istiod 就绪,才进入业务纳管。可以继续执行:
oc get istio default -n istio-system -o yaml
oc get istiocni default -n istio-cni -o yaml
oc get istiorevision -A -o wide
istioctl version
istioctl analyze --all-namespacesstatus.conditions 中的 observedGeneration 应等于 metadata.generation。如果 generation 已增长而 observed generation 仍旧,说明控制器尚未处理新配置;此时旧 condition 不能作为新配置已生效的证据。
把 mesh-lab 接进真实请求路径
创建 mesh-lab 后,先部署无网格基线:client 发请求到 Service orders,后端 orders-v1 和 orders-v2 在响应中分别返回自己的版本。应用清单应把 HTTP 端口明确命名为 http,这样协议识别不会因为端口名含糊而退化为 TCP。先保存三类结果:Service DNS 请求、Service ClusterIP 请求和直接 Pod IP 请求。它们以后用于判断流量是否经过 Service frontend 和 Envoy 路由。
oc new-project mesh-lab
oc apply -f mesh-lab/apps/client.yaml
oc apply -f mesh-lab/apps/orders-v1.yaml
oc apply -f mesh-lab/apps/orders-v2.yaml
oc apply -f mesh-lab/apps/orders-service.yaml
oc exec deploy/client -n mesh-lab -- curl -sS http://orders:8080/version基线应返回 orders-v1 或 orders-v2 中的一个;若 DNS 或 Service 自身尚不可用,应先修复 Deployment、Service selector、EndpointSlice 和 NetworkPolicy,不能让网格掩盖原始 Kubernetes 故障。
再按实际 revision 或 revision tag 给 namespace 启用注入。默认注入入口可写成:
oc label namespace mesh-lab istio-injection=enabled --overwrite
oc rollout restart deployment -n mesh-lab
oc rollout status deployment/client -n mesh-lab
oc get pods -n mesh-lab -o jsonpath='{range .items[*]}{.metadata.name}{" containers="}{range .spec.containers[*]}{.name}{","}{end}{"\n"}{end}'标签只影响后续创建的 Pod,已有 Pod 不会凭空长出 sidecar,因此必须重建。使用 istio.io/rev=<revision-or-tag> 时,不要同时保留传统 injection 标签;重复 webhook 命中可能造成注入冲突。看到 istio-proxy 后继续检查:
istioctl proxy-status
istioctl proxy-config clusters deploy/client.mesh-lab
istioctl proxy-config endpoints deploy/client.mesh-lab | grep orders预期是 client 与 orders sidecar 都连接到目标控制面,CDS/LDS/EDS/RDS 处于 SYNCED,并能看到 orders 的两个端点。Pod 有两个容器而 proxy-status 缺席,常见原因是 sidecar 启动失败、证书签发失败、到 istiod 的网络不通或 revision 指错。
接着创建稳定 subset 与权重路由:
apiVersion: networking.istio.io/v1
kind: DestinationRule
metadata:
name: orders
namespace: mesh-lab
spec:
host: orders
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:
- route:
- destination: {host: orders, subset: v1, port: {number: 8080}}
weight: 90
- destination: {host: orders, subset: v2, port: {number: 8080}}
weight: 10字段之间有直接因果:DestinationRule.subsets[].labels 必须与 Pod label 相交,否则 Envoy 有 cluster 却没有 endpoint;VirtualService.hosts 必须与调用时看到的服务主机匹配;port.number 应对应 Service port;weight 只在匹配到该规则后分配请求,不保证短样本恰好是 9:1。应用后先运行 istioctl analyze -n mesh-lab,再发一组带 request ID 的请求并统计响应版本。正向证据不是一个 200,而是两个 subset 均有可解析端点、代理配置包含新规则、足够样本的比例接近配置且没有持续 503。
反向实验只改一个变量:把 v2 subset label 临时写成不存在的 version: v3。由于示例仍把 10% 权重分给 v2,预期不是所有请求失败,而是命中该分支的请求由代理返回 503;访问日志应显示对应 upstream cluster 没有健康端点,应用 orders-v2 则收不到这些请求。连续采样时若完全没有 503,先确认新 generation 和 EDS 已同步,不能用短样本宣称负例未触发。恢复 label 后,等 proxy-status 同步并重复请求;该分支恢复成功且 503 消失才算回滚完成。这个实验解释了为什么“Deployment Ready + Route Accepted”仍可能失败:控制面能生成合法配置,数据面却找不到满足 subset 的 EndpointSlice。
Istio、Kiali、Tempo 和 OpenTelemetry 各自保存什么
Istio 控制面把 Service、EndpointSlice、VirtualService、DestinationRule 和安全策略编译为 xDS;Envoy sidecar、waypoint 或 ztunnel 执行数据面行为。Kiali 是独立 Kiali Operator 管理的可视化与排障界面,它读取 OpenShift Monitoring/Thanos 指标并关联配置、日志与 Trace,但不保存网格流量本身。Tempo 是 Trace 存储与查询后端;Red Hat OpenShift distributed tracing data collection Operator 管理 OpenTelemetry Collector,负责接收、处理和转发遥测。它们的官方集成关系可从Observability 与 Service Mesh和分布式追踪集成核对。
因此,安装 Kiali 的正确判据不是“图能打开”,而是 Kiali CR 就绪、ServiceAccount 有读取目标 namespace 与监控数据的最小权限、Prometheus/Thanos 查询能返回 Envoy 指标,并且图中的边能用访问日志或请求 ID 回证。Community Kiali Operator 不等于 Red Hat 提供的 Kiali Operator;OSSMC 控制台插件也不是 Kiali Server,只是一个独立入口。
Trace 链路则至少经过:Envoy 或 waypoint 生成 span,meshConfig.extensionProviders 注册 OpenTelemetry provider,Telemetry CR 选择工作负载并设置采样,Collector 接收并导出,Tempo 存储,Kiali 再连接 Tempo 查询。演示时可以短暂使用高采样验证链路,但不能把 randomSamplingPercentage: 100 留在生产。采样率、Collector queue、批处理、Tempo 保留期和 Kiali 查询并发都要进入容量预算;否则一次流量峰值会同时推高代理 CPU、Collector 内存、对象存储和索引成本。
ambient 模式还要多问一层:ztunnel 只提供 L4 身份、mTLS、L4 授权和 TCP telemetry。没有 waypoint 时,不会凭空出现 HTTP 状态码、Header 路由和 L7 span。Kiali 只有 L4 边不一定是故障,可能正是当前数据路径;需要 L7 能力时创建并绑定 waypoint,再检查 istio.io/use-waypoint、waypoint proxy、HTTPRoute/策略和 reporter=waypoint 证据。
按证据层排障,而不是轮流重启组件
第一层看声明与代际。执行 oc get istio,istiorevision,istiocni -A -o yaml,确认 spec、generation、observedGeneration、reason 和 message。API server 接受 YAML 只说明 schema 与准入通过;condition 未观察到当前 generation 时,问题仍在 Operator reconcile、依赖或权限层。
第二层看控制面与数据面连接。istioctl proxy-status 显示某个 proxy 为 STALE 时,比较其连接的 revision、proxy version 和 xDS nonce;proxy-config listeners|routes|clusters|endpoints 分别回答端口是否接管、路由是否生成、cluster 是否存在、端点是否健康。不要只看 istiod Ready,因为旧代理可能仍连旧 revision。
第三层看 Kubernetes 原始对象。无端点时执行:
oc get service,endpointslice,pod -n mesh-lab -l app=orders --show-labels
oc describe pod -n mesh-lab -l app=ordersService selector、readiness、subset label 任一不相交,都会在 Envoy 层表现为无健康 upstream。先修 EndpointSlice,再等 EDS 同步。
第四层看真实请求责任方。给请求加入 x-request-id,同时保存 client 应用响应、client sidecar 访问日志、orders sidecar 访问日志和 orders 应用日志。代理本地 503 常带 UF、UH、NR 等 response flag;上游应用 503 则会在目标应用日志中出现。两者修复路径完全不同。
第五层才看观测集成。Kiali 空图先查指标查询和 RBAC,Trace 为空再查采样、extension provider、Telemetry selector、Collector receiver/exporter 与 Tempo tenant。把 Kiali、Collector 和 Tempo一起重启,只会丢掉故障发生时最有价值的状态。
6 到 3.x 是迁移,不是 CR 改名
OSSM 2.x 基于 Maistra,控制面常由 ServiceMeshControlPlane 表达,成员关系常由 ServiceMeshMemberRoll 管理;3.x 使用新的 Operator、Istio、IstioCNI、revision 与 discovery/injection 机制,Kiali 和 tracing add-on 也改为独立生命周期。官方迁移入口要求源环境先到 2.6.14,再进入 3.0;随后按 3.0、3.1、3.2 的相邻路径推进。完整约束应以从 Service Mesh 2 迁移到 3和目标 minor 的更新文档为准。
迁移前先导出 SMCP、MemberRoll、网关、NetworkPolicy、TLS、ServiceEntry、EnvoyFilter、Maistra 扩展字段、Kiali/Tempo 配置和 namespace 标签,并把每个工作负载的当前代理版本、revision、路由、mTLS 与授权反例保存为基线。ServiceMeshMemberRoll 到 discovery selector/label 的变化会改变控制面看见哪些 namespace;网关不再是简单 add-on,还要显式管理 Deployment、Service 与 OpenShift Route;DNS capture、TLS 与注入默认行为也可能变化。
3.x 内部升级有两个控制面。OLM channel/InstallPlan 更新 Operator,Istio.spec.version 与 updateStrategy 更新 Istio;批准前者不会自动完成后者,反过来也不成立。InPlace 每次只跨一个 minor,更新后仍要重启 sidecar workload 和 gateway 才能刷新 Envoy。RevisionBased 允许新旧控制面并存,通过 revision label/tag 加滚动重启分批迁移;旧 revision 只有在 IstioRevision 不再显示 in-use、所有 proxy 已迁移且正反请求通过后才能删除。
ambient 升级还包含 CNI、Ztunnel 和 waypoint。CNI 与 ztunnel 是节点级共享组件,不能把 revision canary 的工作负载粒度直接套过去;Red Hat 推荐 ambient 使用 InPlace,RevisionBased 存在限制并需要人工处理 waypoint、CNI 与 Ztunnel 的代际关系。数据面组件不得领先控制面,并应保持同 minor 或 n-1;升级时先记录控制面、IstioCNI、ZTunnel、waypoint 和 Gateway API 对象代际,再按故障域分批观察长连接与 L7 路由。只更新 istiod 会制造“控制面新、节点代理旧、路由对象又是另一版本”的半迁移状态。
容量、权限与成本会决定架构能否长期活下去
sidecar 的 CPU、内存和连接池随工作负载副本数增长;控制面则随 proxy 数、Service/Endpoint 数、配置可见范围和变更频率增长。全 mesh 可见会把大量无关 cluster/listener 推给每个 Envoy,扩大内存和配置风暴。使用 discovery selector 或受审查的配置作用域收窄对象时,必须配套负例,因为过度收窄会直接造成不可达。
容量测试不能只测平均 QPS。至少要观察证书集中轮换、批量 rollout、Endpoint 大幅变化、控制面短时断连和遥测后端变慢时的 p95/p99 延迟、代理内存、xDS push queue、配置收敛时间、503/重试次数以及 Collector dropped spans。阈值来自业务 SLO 和基线,不应照搬另一个集群的官方基准。重试还要与应用和入口网关合并计算,避免客户端三次、sidecar 三次、上游再重试形成乘法放大。
证书、代理 config dump、访问日志、Trace 和 Kiali 拓扑都可能暴露 ServiceAccount、namespace、服务名、Header、路径和集群拓扑。诊断包应按敏感资产设置访问控制、短保留期和脱敏,不能直接粘贴到公开工单。Operator catalog 凭据、镜像拉取 Secret、Tempo 对象存储凭据和 Collector exporter Secret 应由专用 ServiceAccount/Secret 管理,禁止写进 Istio.values、Git 或命令历史。离职、租户退出和集群下线时,要同时撤销 RBAC、Secret、外部存储访问和支持门户权限。
团队至少要明确四类 owner:平台 owner 管 OLM、CRD、CNI 与支持合同;网格 owner 管 Istio/revision、策略模板和升级;观测 owner 管 Kiali、Collector、Tempo 与数据保留;业务 owner 对路由、超时、重试、幂等和授权结果负责。mTLS 只证明传输与对等身份,不能替代业务授权;Kiali 有边也不能替代 SLO;Operator 自动更新更不能替代变更审批。
安全退出与回滚顺序
策略回滚优先恢复上一版声明并等待当前 generation 被观察,再验证 proxy sync 和正反请求。RevisionBased 升级若旧 revision 尚存,可把 namespace 或 revision tag 指回旧 revision并重启工作负载;移动 tag 只影响后续注入,运行中的 sidecar 不会自动降级。InPlace 回退还要核对 CR schema、Operator CSV、CNI/Ztunnel/waypoint 和 Envoy 兼容,不能只把 channel 改回去。
完全卸载前先让业务离开数据面。sidecar 模式移除 injection/revision 标签,重建所有 workload,并证明无 sidecar 时 Service DNS、授权替代方案和关键请求仍然正确。ambient 模式先解除 waypoint 绑定并删除不再使用的 waypoint,再移除 istio.io/dataplane-mode,确认 ztunnel 不再列出工作负载。随后才能按卸载 Service Mesh删除 Istio、CNI/Ztunnel 和 Operator 管理对象。
Kiali、OSSMC、OpenTelemetry Collector 与 Tempo 有自己的 CR、finalizer、RBAC、存储和 Operator,必须分别核销。先删除 Operator 可能让剩余 CR 无人处理 finalizer。删除 Istio CRD 会级联删除 VirtualService、DestinationRule、AuthorizationPolicy 等实例;Gateway API CRD 还可能由其他 controller 共享,因此 CRD 删除只能在导出、所有权确认和恢复演练之后进行。
最后审计 namespace 标签、mutating/validating webhook、ClusterRoleBinding、DaemonSet、CNI 节点文件或规则、Kiali/Tempo 存储、日志索引和云资源。退出成功的证据不是“控制面 Pod 消失”,而是业务请求恢复到明确的非网格路径、旧代理和节点接管规则消失、权限与凭据撤销、观测数据按保留策略处理,并且账单上不再有无人负责的资源。
