服务网格可观测与分层排障:从配置同步到日志、Hubble、指标和 Trace
一次订单接口故障中,Kiali 图上 checkout -> orders 的边完全消失,值班人员据此判断调用没有发生。应用日志却显示请求已经发出,orders 也处理了部分请求。真正的断点在 Prometheus:抓取证书轮换后失败,Kiali 失去了生成图所需的 Istio 指标;访问日志和 Trace 后端仍有数据。空图描述的是“当前查询没有足够的遥测样本”,不是“网络上没有流量”。
另一个现场里,Gateway API Route 显示 Accepted=True,istioctl proxy-status 也全部 SYNCED,请求却持续返回 503。源代理日志的 UPSTREAM_HOST 为空,实际 cluster 的 endpoint 列表也是空的:控制器已经接受最新声明,代理也收到了某一版配置,但 Service selector 没选中任何 Ready Pod。condition、配置同步、端点发现和真实请求分别回答不同问题;把任一绿色状态当成端到端证明,都会让排障停在错误层。
四类证据各自回答什么
服务网格故障至少同时存在 desired state、observed state、单请求执行和窗口趋势四个视角。YAML 是操作者想要的状态;controller condition 和 observedGeneration 说明控制器处理到哪一代;sidecar、ztunnel、waypoint 或节点 Envoy 的配置说明执行点实际拿到了什么;日志、flow 和 Trace 说明某次请求走了哪条路径;指标与拓扑则是采样、聚合后的时间窗口投影。
| 证据 | 能证明 | 不能单独证明 |
|---|---|---|
| condition / event | 对象是否被接受、引用是否解析、控制器处理到哪代 | 数据面已有正确配置、请求成功 |
| proxy/ztunnel observed config | listener、route、cluster、endpoint、policy、证书是否到执行点 | 应用健康、上游成功、每次请求都命中 |
| access log / Hubble flow | 某连接或请求到达哪一层、状态、drop/route/upstream | 控制面完全收敛、未记录流量不存在 |
| metrics | 请求率、错误率、延迟、连接、drop、资源与趋势 | 单次请求的完整因果 |
| Trace | 被采样请求跨 hop 的父子关系与耗时 | 未采样请求、所有网络丢包、策略全貌 |
| Kiali/topology | 遥测后端在给定时间窗计算出的服务关系 | 物理网络路径、Header 已完整传播 |
这张表决定排障顺序:先固定一个请求,再对照声明代际和执行点配置,然后读离故障最近的日志/flow,最后用指标判断影响面、用 Trace 判断跨 hop 耗时。先打开几十个 Dashboard 再猜原因,往往只是把不同采样窗口的现象堆在一起。
建立一个可关联的 mesh-lab
用 Istio sidecar 作为主实验路径。先选择与 Kubernetes 匹配的 受支持发行线,固定 Istio CLI、控制面、Envoy 镜像 digest、安装 values、Gateway API CRD 与观测后端版本。demo profile 只用于一次性实验;它的日志、Trace 和资源默认值不适合作为生产容量基线。
: "${CURL_IMAGE:?set CURL_IMAGE to an approved curl image digest}"
: "${HTTPBIN_IMAGE:?set HTTPBIN_IMAGE to an approved go-httpbin image digest}"
istioctl version --remote=false
istioctl x precheck
istioctl install --set profile=demo -y
kubectl create namespace mesh-observe-lab
kubectl label namespace mesh-observe-lab istio-injection=enabled
kubectl -n mesh-observe-lab create deployment client \
--image="${CURL_IMAGE}" -- sleep infinity
kubectl -n mesh-observe-lab create deployment orders-v1 \
--image="${HTTPBIN_IMAGE}" --port=8080
kubectl -n mesh-observe-lab label deployment orders-v1 app=orders version=v1 --overwrite
kubectl -n mesh-observe-lab expose deployment orders-v1 \
--name=orders-v1 --port=80 --target-port=8080
kubectl -n mesh-observe-lab rollout status deployment/client
kubectl -n mesh-observe-lab rollout status deployment/orders-v1
istioctl analyze -n mesh-observe-lab
istioctl proxy-status两个镜像变量都必须使用团队已扫描、目标仓库可拉取的 digest 引用,并记录实际 digest。go-httpbin 提供 /headers、/delay/<seconds> 与 /status/<code>,只用于隔离 namespace,调试端点不得暴露到公网。创建后先检查 Pod 的 istio-proxy、Service selector、EndpointSlice 和实际代理版本,再发基线请求:
REQ_ID="mesh-observe-001"
kubectl -n mesh-observe-lab exec deployment/client -c client -- \
curl -sv --max-time 5 -H "x-request-id: ${REQ_ID}" http://orders-v1/headers
kubectl -n mesh-observe-lab get endpointslice \
-l kubernetes.io/service-name=orders-v1 -o wide预期客户端在 /headers 响应中看到相同 request ID,源/目标代理能按该 ID 找到记录,EndpointSlice 至少包含一个 Ready 地址。只有在目标环境实际取得这些输出,才能把基线路径记为通过;命令示例本身不是证据。若镜像无法拉取、集群无出网或 Pod Security 拒绝注入,先保留 event 和 admission 结果,不要把环境失败解释成网格故障。
condition 与 observedGeneration 先解决“控制器看过了吗”
Kubernetes 对象的 metadata.generation 在 spec 变化时递增。支持状态的控制器通常把已处理代际写入 condition 的 observedGeneration;只有二者相等,才能说该 condition 针对当前 spec。旧的 Accepted=True 配上更小的 observed generation,只能说明旧版本曾被接受。status.conditions[].status、reason、message 和 lastTransitionTime 要与 generation 一起保存。
要执行下面的 condition 实验,先安装目标 Istio 版本要求的 Gateway API Standard CRD,并确认目标实现支持 GAMMA Service attachment。创建一个同 namespace 的无 selector 前端 Service orders 和指向 orders-v1 的 HTTPRoute;不同实现的 Mesh Profile 与 conformance 必须单独核对。若当前集群不支持该 profile,保留为状态读取练习,不要把无 condition 冒充控制器已接纳。
kubectl apply -f - <<'YAML'
apiVersion: v1
kind: Service
metadata:
name: orders
namespace: mesh-observe-lab
spec:
ports:
- name: http
port: 80
targetPort: 80
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: orders-route
namespace: mesh-observe-lab
spec:
parentRefs:
- group: ""
kind: Service
name: orders
port: 80
rules:
- backendRefs:
- name: orders-v1
port: 80
YAMLkubectl -n mesh-observe-lab get httproute orders-route -o jsonpath='{.metadata.generation}{"\n"}'
kubectl -n mesh-observe-lab get httproute orders-route \
-o jsonpath='{range .status.parents[*].conditions[*]}{.type}{"="}{.status}{" observed="}{.observedGeneration}{" reason="}{.reason}{"\n"}{end}'
kubectl -n mesh-observe-lab describe httproute orders-routeAccepted=True 说明 controller 接受 route,ResolvedRefs=True 说明 backend、Secret 或跨 namespace 引用已解析;实现提供 Programmed 时,它表示编程进度。不同资源和实现的 condition 集合并不完全相同,必须读目标版本规范与 controllerName。即使三者为真,也还要检查代理 route/cluster/endpoint 和真实请求,因为对象状态无法证明上游进程健康。
Istio 的分析与同步入口分三层:
istioctl analyze -n mesh-observe-lab
istioctl proxy-status
istioctl proxy-config listeners deployment/client.mesh-observe-lab
istioctl proxy-config routes deployment/client.mesh-observe-lab
istioctl proxy-config clusters deployment/client.mesh-observe-lab
istioctl proxy-config endpoints deployment/client.mesh-observe-labanalyze 发现静态配置冲突、错误引用和部分弃用;proxy-status 比较代理与 istiod 的 xDS 状态;proxy-config 读取执行点 observed config。SYNCED 的含义是该代理对相应 xDS 类型已确认控制面版本,不等于 route 一定符合业务意图,也不等于 endpoint 健康。应把代理 ID、xDS 类型、控制面 revision/config version 和请求时间一起记录,避免拿故障后的新配置解释故障前的请求。
ambient 路径不能原样照搬 sidecar 命令。ztunnel 是每节点 L4 数据面,使用 istioctl ztunnel-config workloads|services|certificates 与 ztunnel 日志;proxy-status 不展示 ztunnel。需要 HTTP route、L7 policy、HTTP metrics、access log 与 tracing 时,请求必须经过并绑定 waypoint,再读取 waypoint Envoy 的 xDS 与日志。只有 ztunnel 时看到的主要是 TCP 连接与字节,不会凭空出现 HTTP 状态码。
让代理访问日志成为单请求证据
Istio 可用 Telemetry API 对 namespace/workload 启用 Envoy access log。provider 名必须对应 mesh config 中已配置的扩展 provider;内置 envoy 输出到代理容器标准输出。生产启用前先评估 CPU、尾延迟、日志采集阻塞、字段敏感性和存储费用。
Telemetry API 配置按 root namespace、业务 namespace、workload 逐层继承和覆盖;删除 workload 级对象后,行为可能回到 namespace 或 mesh 默认值,并不等于关闭。两个带 selector 的 Telemetry 对象若同时选中同一 workload,官方说明其行为未定义,因此发布流水线应计算 selector 交集并拒绝冲突。每次变更都要保存资源 generation、选择到的 Pod 集合、provider 名和代理实际输出,避免把 YAML 中的意图当成已经生效的采集配置。
kubectl apply -f - <<'YAML'
apiVersion: telemetry.istio.io/v1
kind: Telemetry
metadata:
name: mesh-observe-access-log
namespace: mesh-observe-lab
spec:
accessLogging:
- providers:
- name: envoy
YAML
kubectl -n mesh-observe-lab exec deployment/client -c client -- \
curl -sS -H 'x-request-id: mesh-observe-log-001' http://orders/headers
kubectl -n mesh-observe-lab logs deployment/client -c istio-proxy --since=5m \
| grep mesh-observe-log-001
kubectl -n mesh-observe-lab logs deployment/orders-v1 -c istio-proxy --since=5m \
| grep mesh-observe-log-001有效日志格式至少应保留:request ID、源/目标 workload identity、authority、route、cluster、upstream host、下游与上游状态、RESPONSE_FLAGS、RESPONSE_CODE_DETAILS、UPSTREAM_TRANSPORT_FAILURE_REASON、总耗时、上游耗时和字节数。客户端状态码是代理本地生成还是上游返回,可由 upstream host、upstream status、response flags 与应用日志共同判断。
高流量环境可使用 Telemetry access-log filter 保留错误和关键路径,但过滤表达式必须覆盖“连接失败时根本没有 response code”的情况。只写 response.code >= 500 会漏掉 DNS、连接、TLS 等前置失败;Istio 官方示例使用 !has(response.code) || response.code >= 500 处理这类事件。正例发送一个上游 503,反例暂时指向不可连接 endpoint,二者都应产生日志;恢复 endpoint 后再发 200,按既定成功采样策略确认是否记录。过滤器上线前后要比较错误计数、日志字节率与查询结果,防止用降成本的过滤规则抹掉第一故障证据。
日志不是越多越好。Authorization、Cookie、JWT、完整 query、请求体、真实租户、内部域名/IP、证书和 config dump 都可能泄露敏感信息。使用字段 allowlist、Header 哈希/删除、分层保留期与基于事故编号的受控访问;Tap、代理 debug endpoint 和临时 debug log 是高权限入口,启用要有过期时间。Linkerd 默认关闭 HTTP access log,正是因为它会增加 CPU 和尾延迟;若打开,HTTP access log 写到 proxy stderr,debug log 在 stdout,采集管道需区分。
Hubble 看到的是 Cilium 管理端点的网络事实
在 Cilium 数据面中,Hubble server 位于每个 cilium-agent,观察 Cilium 管理 endpoint 的 flow、identity/label、DNS、drop reason 与部分 L7 元数据;Hubble Relay 聚合多节点,CLI/UI 再消费 Relay。查询单节点 Hubble API 与查询 Relay 的可见范围不同,未被 Cilium 管理的 Pod 也不会得到完整 Hubble 语义。
目标集群已采用 Cilium 时,可按当前 chart values 启用 Relay/UI 和所需 metrics。升级命令必须从现有 release 导出 values 后评审,不能盲目 --reuse-values:
cilium status --verbose
cilium config view
cilium hubble port-forward &
HUBBLE_PORT_FORWARD_PID=$!
sleep 2
hubble status
hubble observe --namespace mesh-observe-lab --last 20
hubble observe --namespace mesh-observe-lab --verdict DROPPED
kill "${HUBBLE_PORT_FORWARD_PID}"这些是只读查询与临时本机端口转发;若 Relay 尚未启用,应先导出现有 Helm values,在变更评审中固定 Cilium chart/image 版本后启用,不能让一次排障命令隐式改写集群级 CNI release。先发受控请求,再读取最近 20 条 namespace flow 和 drop 记录;结束后用 kill 清理本机端口转发。
预期 hubble status 显示服务可达、当前/最大 flow buffer 与 flows/s,基线请求出现源、目标、verdict 和协议元数据。buffer 达到 100% 只表示环形缓冲已经填满,不代表长期数据完整;事件限速、采样、Relay 断连和 exporter 背压都可能丢失观测。没有 flow 时先确认 endpoint 是否由 Cilium 管理、查询的是本地 API 还是 Relay、时间窗与过滤器是否正确,再判断网络上是否没有流量。
纯 L3/L4 请求通常只有连接/packet 语义。HTTP L7 policy、Gateway API/GAMMA 或显式 L7 visibility 会让流量进入 Envoy,并产生 HTTP 元数据或 Hubble L7 metrics。没有 HTTP 指标可能只是没有启用 L7 visibility,不能推出没有 HTTP 流量。Cilium agent/operator/Envoy 的 cilium_/envoy_ 指标描述组件状态,Hubble metrics 描述工作负载网络行为;二者需要分别采集和告警。
指标负责趋势,不要塞入单请求身份
Istio 的 istio_requests_total、请求时延直方图、TCP connection/bytes 与 source/destination workload、namespace、response code、reporter 等标签适合计算 RED 指标。sidecar 常有 source/destination reporter,ambient ztunnel 使用 source reporter,waypoint 使用 waypoint reporter;迁移后若仍按旧标签求和,可能重复计数或漏计。
request ID、用户 ID、原始 URL、完整 Host、订单号和动态错误文本不应成为 Prometheus 标签。假设 200 个源、200 个目标、20 个状态/route,再乘 10 个集群和多个 histogram bucket,时序规模已经很大;加入每请求标签会把指标系统变成昂贵且不可控的事件库。Telemetry API 可以删除不需要的维度或重定义受控标签,变更前后要比较 active series、scrape payload、ingestion rate、查询 P99 和保留成本。
最小告警组合应同时覆盖业务与观测系统自身:请求错误率/延迟、无健康上游、policy deny、xDS reject/stale、代理 CPU/内存/连接、Hubble lost events、Prometheus target down、remote-write backlog、collector export failure 和 Kiali 查询错误。只有业务告警时,采集断链会制造“系统突然完全健康”的假象;只有采集告警时,又无法判断实际用户影响。
服务级指标与单代理 Envoy stats 不能混用。Istio 为降低代理 CPU 和内存,默认只保留有限的 Envoy 统计;临时排查 circuit breaker、retry、upstream connection 或 timeout 时,先用 pilot-agent request GET stats 查看当前代理实际暴露的名称,再在 canary 中评估 ProxyStatsMatcher。扩大 stats matcher 会在每个相关代理创建更多统计项,命名还可能随生成配置变化。新增告警应先固定 Istio/Envoy 版本与 canary 样本,升级前比较名称和基数,不能把某个 Pod 的局部 counter 当成全服务 SLI。
Trace 能连成链,前提是应用继续传播上下文
代理可以为经过的 hop 生成 span,却无法替应用把 Trace Header 从一次入站请求复制到下一次出站请求。入口要创建或接受 Trace,上游应用要转发 W3C traceparent/tracestate;使用 B3 的链路还要按 provider 约定传播对应 B3 Header。业务异步队列、线程切换和后台任务也要显式携带上下文。
Istio 的 Telemetry API 可选择 tracing provider、设置采样比例与受控 tag;provider endpoint、TLS、认证和超时通常先在 mesh config extensionProviders 中配置。低采样环境查不到某次 Trace 很正常,先用 request ID 在 access log 和 metrics 中证明请求存在,再检查 sampling decision、代理 exporter、collector receiver、queue/retry 和后端索引。
Istio Trace 采样中的 randomSamplingPercentage 是百分比而不是 0 到 1 的比例,允许 0.0 到 100.0,精度为 0.01,默认值为 1%。正向联调可在隔离 workload 临时设为 100 并限制请求量,生产则按入口流量、每请求 span 数、平均 attribute 字节和保留期反推预算。head sampling 已经拒绝的请求不会因为下游 collector 使用 tail sampling 而重新出现;强制保留错误链路需要在入口采样决策、应用 SDK 与 collector 策略之间明确责任,不能只提高后端采样率。
HTTP 语义字段升级也要当作契约迁移。OpenTelemetry HTTP semantic conventions 当前包含稳定与迁移规则;旧 instrumentation 迁向稳定字段时可短期使用 http/dup 双写,但双写会增加 attribute、索引与查询成本。先让 Dashboard、告警和检索同时理解新旧字段,观察完整保留窗口后再停止旧字段;不要在一次升级中同时改 provider、采样率、字段名和 tenant,否则 Trace 消失时无法区分是哪一层造成。
正反实验使用同一条已经具备下游调用和 tracing instrumentation 的测试链。前面的 go-httpbin 只有单服务响应,不能调用 inventory,因此只能验证入站 Header,不能冒充跨服务 Trace 实验。另行准备 client -> orders -> inventory fixture 后,正例中 orders 应用读取受控 W3C 上下文,并在调用 inventory 时通过 tracing SDK 注入新的下游 traceparent:trace ID 保持一致,parent ID 应按新 client span 更新,不能“原样复制”整个 Header。预期入口、代理和两端应用 span 的父子关系合理。反例只关闭 orders 到 inventory 的上下文注入;两个局部 span/trace 仍可能存在,但链在应用边界断开。这个结果证明“代理 tracing 已启用”和“分布式链路完整”是两件事。
TRACE_ID=11111111111111111111111111111111
PARENT_ID=2222222222222222
kubectl -n mesh-observe-lab exec deployment/client -c client -- \
curl -sS \
-H "traceparent: 00-${TRACE_ID}-${PARENT_ID}-01" \
-H 'x-request-id: mesh-observe-trace-001' \
http://orders-v1/headers这个命令只证明测试服务收到了受控 traceparent,并为代理/collector 查询提供已知 trace ID。它不证明存在下游 span;跨服务正反例必须对 fixture 的真实下游调用执行,并同时读取应用 span、代理 span、collector 接收计数和后端查询结果。
Trace tag 同样受敏感信息和基数约束。不要把 Authorization、Cookie、原始 SQL、完整 URL、用户账号或请求体放进 span attribute。collector 到后端的 API Token、tenant header 和 TLS 私钥应来自 Secret,使用最小 RBAC 与短期凭证;Kiali/Jaeger/Tempo 查询权限应按 namespace/租户隔离,不能因为它们“只读”就向所有开发者开放全部拓扑。
Kiali 空图先检查数据链,不要先猜业务链
Kiali Graph 主要由 Prometheus 中的 Istio telemetry 计算。完整图、健康和流量功能依赖 Prometheus;Trace 与 Grafana 是可选外部服务。打开空图时按以下顺序检查:选择的 cluster/namespace 是否正确;时间范围是否覆盖请求;窗口内是否有足够请求计算 rate;工作负载是否真的经过 sidecar/waypoint;Prometheus target 与查询是否成功;指标 label 是否被 Telemetry API 删除或改名;Kiali 到 Prometheus 的认证/TLS/tenant 是否正确。
隔离实验可使用 Kiali Operator。先从 Kiali prerequisites选择与目标 Istio minor 配套的版本,固定 chart、operator image digest 和 CR;不要让无版本的 latest 漂移。下面的匿名认证只允许配合本地 port-forward 用于一次性 namespace,生产必须接入 OpenID、Token 或平台支持的认证,并限制 Kiali Service、Prometheus 与 tracing backend 的网络可达性。
: "${KIALI_CHART_VERSION:?set KIALI_CHART_VERSION to a version compatible with the installed Istio minor}"
helm repo add kiali https://kiali.org/helm-charts
helm repo update
helm upgrade --install kiali-operator kiali/kiali-operator \
--version "${KIALI_CHART_VERSION}" \
--namespace kiali-operator --create-namespace
kubectl apply -f - <<'YAML'
apiVersion: kiali.io/v1alpha1
kind: Kiali
metadata:
name: mesh-observe
namespace: kiali-operator
spec:
auth:
strategy: anonymous
deployment:
namespace: mesh-observe-lab
accessible_namespaces:
- mesh-observe-lab
YAML
kubectl -n mesh-observe-lab rollout status deployment/kiali
kubectl -n mesh-observe-lab port-forward service/kiali 20001:20001预期只在本机 http://localhost:20001 访问,并只能查询 mesh-observe-lab。Operator Ready、Kiali Pod Ready 和页面可打开仍不能证明图有数据;继续用受控请求、Prometheus 原始查询和 Kiali 日志验证。若 CR 所用字段在目标 Kiali 版本发生变化,以该版本 CRD schema 为准,先 kubectl explain kiali.spec 与 server-side dry-run,不要把未知字段静默丢弃。
低流量还有显示阈值:至少需要足够样本计算 rate,低于图阈值的边可能不显示。主动连续发送一小段带 request ID 的受控流量,再直接查询对应 istio_requests_total;Prometheus 有样本而图为空,才转向 Kiali query、版本兼容与图过滤器。Prometheus 本身无样本时,应检查 scrape、reporter、waypoint/sidecar 遥测,而不是重装 Kiali。
外部 HTTPS 若没有 L7 语义,可能显示为 TCP;未声明外部服务或 passthrough 可能表现为缺边或 PassthroughCluster。这些都是遥测投影。Kiali 连接 Tempo/Jaeger 时,internal_url 是服务端查询地址,external_url 是用户浏览器跳转地址;Tempo HTTP/gRPC 协议、tenant/org header、Grafana datasource UID 与 health URL 也要分别验证。
Kiali 还可能通过 Istio 8080 debug interface读取代理状态。如果安全策略关闭 ENABLE_DEBUG_ON_HTTP,应在 Kiali CR 中关闭相应 Istio API 能力并接受功能缺失;不要为了让 UI 变绿重新开放未经授权的 debug 端口。Kiali 与 Istio 的支持组合需要成对核对,不能各自安装“最新”就假定兼容。
七类故障先看第一证据
DNS:名字没有变成预期地址
现象可能是客户端超时、代理 DNS resolution failure、Hubble DNS error 或 cluster 长期无 endpoint。先在应用容器检查 /etc/resolv.conf、搜索域、A/AAAA、TTL,再看 CoreDNS、代理 DNS cache/cluster 和 Hubble DNS flow。若应用解析成功但代理按另一规则解析,检查 ServiceEntry resolution、DNS proxying 与缓存;若 DNS 成功后连接到错误地址,再查 DNS 重绑定/FQDN policy。修复后应看到正确答案、对应 endpoint/cluster 和成功请求,而不只是 nslookup 成功。
策略:执行点主动拒绝
典型证据是代理本地 403、RBAC access denied details、Cilium drop reason/policy verdict、Linkerd policy deny,且目标应用没有请求日志。先确认源/目标 identity、ServiceAccount、namespace、端口和协议,再读取执行点 observed policy;不要从 YAML 猜匹配。修复应修改最小 selector/principal/path,重新发允许身份与错误身份两组请求,证明 allow 成功且 deny 仍失败。
TLS:连接到了目标,但身份或加密参数不一致
证据常是代理 503/connection failure、UPSTREAM_TRANSPORT_FAILURE_REASON 中的证书校验、SAN、SNI、协议或 alert,应用没有完整 HTTP 请求。检查双方时钟、证书有效期、trust bundle、SNI、SAN、TLS mode、客户端证书和端口协议。不要用 -k、关闭验证或改成明文消除告警;恢复后要同时验证成功握手、正确对端身份和错误 CA/SAN 仍失败。
端点:route/cluster 存在,但没有可选上游
客户端常见 503,Envoy 可出现 no healthy upstream,proxy-config endpoints 为空或全部不健康。沿 Service selector、EndpointSlice readiness、端口名/targetPort、subset label、跨集群发现逐层检查。Route Accepted=True 在这里完全可能为真。修复后先确认 endpoint 进入 observed config,再发请求并保存实际 UPSTREAM_HOST。
上游:请求已经进入应用或真实依赖
代理日志有 upstream host、upstream service time 和上游 4xx/5xx,目标应用也有相同 request ID。此时应转向应用日志、线程池、数据库/下游依赖和业务错误,保留代理是否重试、reset 或超时的事实。不要把所有 503 都归因于 mesh:如果上游明确返回 503,代理只是转发;如果代理本地生成 503,上游可能从未收到请求。
数据面容量:代理或节点在到达上游前溢出
连接池、pending request、并发、文件描述符、Envoy worker、ztunnel/waypoint CPU、Hubble buffer 或 eBPF map 都可能耗尽。第一证据是 overflow/reset/drop、资源饱和和“应用未收到请求”的组合。修复不是无限加重试,而是降低到达率、扩容执行点、调整受证据支持的连接预算,并验证恢复后队列收敛与 P99,而非只看平均 CPU。
采集:业务成功,遥测没有抵达后端
访问日志仍有 200、应用处理成功,但 Prometheus target down、remote write backlog、collector exporter error、Relay 断连或 Kiali query 失败。沿 producer -> agent/exporter -> network -> collector -> storage -> query 逐跳检查队列、丢弃、认证、TLS、tenant、时间戳与保留期。修复后用已知 request ID/trace ID 和一条受控 metric 证明写入与查询闭环;“collector Ready”不能证明后端已接收。
把排障动作固化为团队工作流
事故开始先冻结自动重试和大范围配置变更,记录 UTC 时间窗、客户端现象、影响 namespace、一个脱敏 request ID、当前 generation 与发布版本。接着按“声明状态 -> 数据面配置 -> 单请求日志/flow -> endpoint/identity/TLS -> 指标影响面 -> Trace 耗时 -> 采集健康”推进。每一步写下假设、读取入口、观察结果和下一步,避免多人同时修改 route、policy、证书和采样率后无法归因。
常用只读入口应封装成受控脚本或事故模板:
kubectl get events -n mesh-observe-lab --sort-by=.lastTimestamp
istioctl analyze -n mesh-observe-lab
istioctl proxy-status
istioctl proxy-config routes deployment/client.mesh-observe-lab
istioctl proxy-config clusters deployment/client.mesh-observe-lab
istioctl proxy-config endpoints deployment/client.mesh-observe-lab
kubectl -n mesh-observe-lab logs deployment/client -c istio-proxy --since=10m
kubectl -n mesh-observe-lab logs deployment/orders-v1 -c istio-proxy --since=10m脚本输出进入受控目录前应脱敏并限制保留期。proxy-config、config dump、ztunnel certificate、Hubble flow、Tap 和 Trace 会暴露服务名、IP、身份、请求参数或证书元数据;读取权限至少区分业务开发者、平台值班和安全人员。生产 debug endpoint 不应直接通过公网或普通 Ingress 暴露,临时端口转发和 impersonation 也要进入审计。
接入项目时先统一上下文与故障字段
项目接入的第一项不是画 Dashboard,而是在 HTTP/gRPC client middleware 中统一生成或接受 request ID、传播 traceparent/tracestate 和约定的 B3 Header,并把它们写入应用结构化日志。应用日志至少包含 service、operation、request ID、trace ID、attempt、上游逻辑名、结果类别和耗时;不得记录凭证、请求体或完整个人数据。代理与应用使用同一 ID 后,才能判断 503 是哪一层生成、一次业务请求扩成了多少次上游尝试。
第二项是给每个 Service 建立最小观测契约:成功率与延迟 SLI 的指标口径、route/response code 标签白名单、日志采样、Trace 采样与强制采样入口、值班查询、数据 owner 和保留期。业务仓库只维护与服务语义相关的 instrumentation 和告警阈值,平台仓库维护 Telemetry、collector、scrape、Kiali/Hubble 权限和后端容量。双方通过服务目录中的稳定 service ID 对齐,避免 Deployment 改名后历史图表断裂。
CI 可先做静态 schema、禁止高基数标签与敏感 Header 检查,再在临时 namespace 发正向请求、业务 503、错误身份和无 endpoint 反例。流水线读取应用日志、两端代理日志、指标增量和可选 Trace,不要求每次 CI 都部署整套长期观测后端。进入预发布环境后再验证 Prometheus scrape、collector exporter、Kiali graph 与 Hubble Relay 的真实集成,并把未采样与采集故障设计成明确结果。
采集链反例应单独执行:先记录一段基线流量对应的代理日志、Prometheus 样本数、collector accepted/exported 数与后端 trace 查询;再阻断 collector 到后端而不影响业务流量。预期业务仍成功,代理或 collector 出现有界 queue/retry,export failure 增长,后端不再出现新 trace;恢复网络后,只有在队列未溢出且重试窗口未过期时才应补写。若恢复后历史数据缺口仍在,应保留 dropped/refused 计数而不是回填伪造。这个实验把“业务故障”和“观测故障”拆开,也给队列容量、重试上限和降级策略提供真实判据。
观测机制选型与容量成本
访问日志适合精确回答“这个请求在哪一跳发生了什么”,代价与请求量、字段长度和同步输出路径近似线性;高流量服务应采样成功日志、全量保留错误与安全事件,并验证采样不会丢失所需审计。Hubble 适合 Cilium 管理网络的 flow、identity、DNS 和 drop 取证,但长期保存仍需 exporter/后端,L7 语义取决于可见性与 Envoy 路径。
指标适合 SLO、告警和容量趋势,最危险的成本来自标签笛卡尔积、histogram bucket 与多 reporter 重复;Trace 适合跨 hop 时延和因果,成本来自采样、span 数、attribute 大小、collector queue 和索引。Kiali 提供面向 Istio 的聚合拓扑与诊断入口,不是日志、Trace、Prometheus 或网络 flow 的替代品。团队应按问题选择证据,而不是为了“全量可观测”把所有信号永久开到最高精度。
容量压测至少测正常峰值、错误风暴、控制面重连、配置批量推送、证书轮换和 collector/Prometheus 短时不可用。观察代理 CPU/内存、日志阻塞、指标 active series/scrape size、Hubble lost events、collector refused/dropped spans、后端写入延迟、Kiali 查询 P99 与存储增长。预算要按 namespace/租户和信号类型分配,并为审计日志与调试 Trace 设置不同保留策略。
清理实验、回滚遥测与长期维护
遥测回滚先降低采样或停用新增 provider,再确认代理不再发送、collector 队列排空、后端无持续写入,最后删除索引/tenant、Secret 和网络权限。直接删除 collector 会让代理持续重试并积压;直接删 Telemetry 对象也可能恢复到 mesh 级继承配置,而不是“关闭全部遥测”。回滚前必须读取配置层级和继承关系。
实验环境按资源所有权清理:删除 namespace 级 Telemetry、测试 Route/Policy、测试 namespace;只有本实验独占的 Kiali/Hubble 组件才能卸载。Hubble 若属于集群 CNI 基线,不应为了文章实验关闭;Kiali 若服务多个 namespace,也不能随测试 namespace 一起删除。
kubectl -n mesh-observe-lab delete telemetry --all
kubectl -n mesh-observe-lab delete httproute,authorizationpolicy --all --ignore-not-found
kubectl delete namespace mesh-observe-lab --wait=true --timeout=2m
kubectl get namespace mesh-observe-lab --ignore-not-found
# 仅当 Operator 与 CR 都由本实验独占:
kubectl -n kiali-operator delete kiali mesh-observe --ignore-not-found
helm uninstall kiali-operator -n kiali-operator
# 仅限确认由本实验独占的一次性 Istio 集群:
istioctl uninstall --purge -y长期治理要给日志、指标、Trace、Hubble、Kiali 和 config dump 分别登记 owner、用途、数据分类、采样/基数预算、SLO、保留期、访问角色、凭证轮换、版本兼容和退出条件。变更评审必须包含新增标签/字段的基数估算、最坏日志量、collector/backend 容量、敏感信息检查和故障降级方式。季度演练主动制造 DNS 失败、policy deny、错误 CA、无 endpoint、上游 503、Prometheus scrape 失败和 Trace Header 断链,确认值班人员能从第一证据分型,而不是依赖一张绿色拓扑图。
当服务归档或迁出网格时,先停止对应采集并确认没有活跃流量,再删除 Dashboard、告警、recording rule、Trace service mapping、Kiali 权限、Hubble exporter 过滤器和日志索引;撤销查询 Token、tenant header 与 Secret,核销存储和网络费用。保留法规要求的审计数据时,应转为只读归档并记录删除日期。可观测系统的完成标准不是“还能看到图”,而是故障时有足够且可关联的证据,平时成本、权限和敏感信息都在可控边界内。
