ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Istio可观测性实践:指标、日志、链路追踪与排障指南

Istio可观测性实践:指标、日志、链路追踪与排障指南 先说个我自己的经历。有一次线上某个接口突然变慢业务方跑来问怎么回事。我打开各个服务日志翻了一圈每个Pod看起来都正常数据库也没报警但用户就是反馈卡顿。那会儿我就意识到一件很现实的事微服务上了Kubernetes之后一个请求从网关进来要穿过七八个服务跨十几个Pod日志各自为政指标各查各的你根本拼不出一条完整的事件链。传统那种“看CPU、看内存、看磁盘”的监控思路在分布式环境里基本失灵了。后来我把Istio推进了业务集群核心目的之一就是希望用服务网格把“流量视角的观测能力”变成平台能力而不是让每个业务组去自己埋点、自己拼链路。这篇东西就是想把Istio在可观测性方面的实践思路、工具链、落地步骤以及我踩过的坑一次性整理出来。内容不堆理论尽量给能直接用的操作细节适合正在做云原生改造、或者K8s服务多到排查问题像大海捞针的运维和开发同学。刚接触服务网格的人最容易把它理解成一个“流量转发工具”这其实有点可惜。Istio真正值钱的地方不只是帮你做灰度发布、熔断限流而是它在转发流量的过程中顺手把“观测数据”这件事也给解决了。而且它解决得比你自己埋点要干净得多。1. 服务网格为什么成了云原生排查问题绕不开的一层1.1 传统监控在K8s和微服务场景下的失效原因先看一个最常见的场面服务没报错但用户觉得慢。你打开Zabbix或者Prometheus看了半天节点负载全是绿的。为什么因为传统监控的视角是“主机”而云原生场景下你的关注点是“工作负载”。比如K8s里一个Deployment有5个副本Pod随时可能被重新调度IP动不动就变。你按IP去追踪一个服务过一会儿它可能就不是那个Pod在跑了。更麻烦的是微服务之间调用关系复杂服务A调用BB调用CC还调了个外部接口。任何一个环节慢一点最终都会叠加到用户感知上。可如果你只有节点监控、Pod监控就只能看到“哪个PodCPU高了”根本看不到“是哪个调用导致这个Pod的CPU高了”。有人会说那我在代码里埋点不就行了埋点确实是一种解法但成本很高。老系统改造成本大第三方服务你改不了团队技术栈杂的话——Java一套、Go一套、Python一套——每个语言的SDK、上报方式都不一样维护起来很痛苦。这还没算上埋点本身对业务代码的侵入以及那种“谁都觉得自己负责的服务没问题”的数据孤岛效应。1.2 Istio给可观测性带来的核心变化Istio的思路是既然微服务之间所有的通信都要经过网络那我就在网络这一层做点事情。它给每个Pod注入一个Sidecar容器基于Envoy这个Sidecar会拦截进出Pod的所有流量。也就是说不管你的业务代码是Java还是Go不管你有没有埋点只要请求走了网络Sidecar就能看到它。用个不太好听的类比以前你要知道每户人家几点出门、几点回家你得去每户门口盯着还得说服每家装个摄像头。Istio的做法是直接在小区大门口装一个闸机系统每个进出的单元都在它眼皮底下数据格式统一、自动记录。对你来说业务代码完全不用动。在这个基础上Istio的控制面组件Istiod负责下发各种配置比如路由规则、流量策略、指标采集规则等。对于可观测性来说最重要的就是两件事一是Sidecar自动产生数据二是Istiod帮我们管理“数据该往哪发、以什么格式发、发哪些”。你在K8s里定义一个Telemetry资源全局的访问日志格式、链路追踪采样率、指标采集开关一下就统一了。1.3 哪些场景适合用Istio做可观测性不是所有项目都适合上Istio做技术选型时心里要有数。我把常见场景分成两类参考一下。适合不适合微服务数量多调用关系复杂单体应用没有拆分的必要团队技术栈杂统一埋点成本高服务总数很少手动排查也很快已经上了K8s想统一流量观测口径K8s版本很老节点资源紧张有灰度、熔断、安全策略等额外需求完全没有Istio经验且没有时间学习网格原理希望把“服务可观测性”沉淀为平台能力对Sidecar的性能损耗极其敏感且服务流量极高但规模很小一句话总结Istio最典型的应用场景就是“服务多到靠人肉翻日志已经不行了”的时候它用Sidecar帮你把网格内所有流量自动变成可查询、可统计、可追踪的数据。可观测性反而是它在业务上最快见效的部分。2. Istio可观测性三件套指标、日志、链路追踪的真实采集链路可观测性一般讲三件事Metrics指标、Logs日志、Tracing链路追踪。Istio在这三块分别是怎么做的理解了这个你才知道哪些数据是自动来的、哪些还需要业务配合不然调了半天没数据很容易怀疑人生。2.1 MetricsEnvoy指标是怎么变成Prometheus数据的Envoy本身就是个数据面代理每处理一个请求它都会在内部记录大量统计数据。经过Istio的整合之后这些指标会暴露成一个标准的Prometheus格式接口也就是Pod的15020端口那个路径。抓过来之后你就能看到类似这样的指标名istio_requests_total请求总数counteristio_request_duration_milliseconds请求耗时分布histogramistio_request_bytes、istio_response_bytes请求和响应大小关键是理解这些指标的标签维度。以istio_requests_total为例它至少会带着这些labelsource_workload/source_workload_namespace发起方destination_service/destination_workload目标方response_code返回码response_flags比如超时、熔断、连接重置等标识reporter是source还是destination生成的这里有个非常容易踩的坑同一个请求源端Envoy和目的端Envoy都会上报一次指标所以你在查QPS的时候如果不对reporter做过滤会出现“看起来请求量翻倍”的情况。我的做法是一般都按reporterdestination来查也就是说以服务端口径为准。你不需要关心Envoy是怎么采集这些数据的只要知道它在代理层自动完成就行。但有一个问题要注意类似istio_request_duration_milliseconds这种histogram指标本身桶很多再加上多维标签Prometheus的存储开销并不小。后面我会专门讲标签基数的问题这里先留个印象。2.2 Logs从默认明文到结构化JSONIstio默认会给Envoy开启访问日志输出到标准输出所以你在Pod的日志里其实能看到一行行类似Nginx格式的明文日志比如[2025-01-06T10:24:13.251Z] GET /product/list HTTP/1.1 200 - via_upstream - - 0 586 15 15 - curl/8.0 ... ... prod-product-v1-xxx ...默认的格式包含时间戳、请求方法、路径、状态码、响应大小、耗时、路由名、上游集群等字段。但默认明文格式有个问题就是第三方日志采集器比如Loki、Elasticsearch解析起来比较费劲正则写起来想骂人。所以我的建议是到后期直接把它改成JSON格式这样才能方便做结构化检索。Istio从1.7之后可以用Telemetry API来配置访问日志举个例子apiVersion: telemetry.istio.io/v1 kind: Telemetry metadata: name: access-log-json namespace: istio-system spec: accessLogging: - providers: - name: envoy - match: - mode: CLIENT_AND_SERVER disabled: false如果你想深度定制日志字段在Istio里还有一个更底层的办法就是用EnvoyFilter改Envoy的access_log配置把格式切成JSON挑出你想看的字段apiVersion: networking.istio.io/v1alpha3 kind: EnvoyFilter metadata: name: json-access-log namespace: istio-system spec: configPatches: - applyTo: NETWORK_FILTER match: context: ANY listener: filterChain: filter: name: envoy.filters.network.http_connection_manager patch: operation: MERGE value: typed_config: type: type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager access_log: - name: envoy.access_loggers.file typed_config: type: type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog path: /dev/stdout format: text_format_source: inline_string: {timestamp:%START_TIME%,method:%REQ(:METHOD)%,path:%REQ(X-ENVOY-ORIGINAL-PATH?:PATH)%,response_code:%RESPONSE_CODE%,duration:%DURATION%,upstream_cluster:%UPSTREAM_CLUSTER%,route_name:%ROUTE_NAME%}这里要提醒一下访问日志的量比指标大得多每一个请求都会打一行。如果某个服务的QPS很高同时又没有采集需求建议只对出问题的服务开或者只对部分采样开启不要全局无脑开。2.3 Tracing为什么在网格里做链路追踪最省力链路追踪的可观测性价值在于它能把一次完整的请求串起来。传统做法是每个服务都引入类似OpenTelemetry或者Jaeger的SDK在代码里手动创建Span。这套方案对新建项目还可行对老项目来说就是灾难。Istio通过Sidecar在代理层自动生成Span你不用改业务代码Envoy会自动为请求创建入口和出口的Span。同时Sidecar会负责传播trace相关 header比如x-request-id、traceparent、b3。如果你用的是比较新的Istio版本默认已经支持W3C的traceparent格式兼容OpenTelemetry的生态。但这个机制有一个前提请求必须经过Sidecar而且服务之间传递HTTP头时不能把它丢掉。如果你在代码里头对头做了一次完全重新构造或者走了异步消息队列那链路跟踪就会在这里断掉。在Istio里启用链路追踪一般是在meshConfig的defaultConfig里配置采样率和后端地址apiVersion: install.istio.io/v1alpha1 kind: IstioOperator metadata: name: istio spec: meshConfig: enableTracing: true defaultConfig: tracing: sampling: 10 zipkin: address: zipkin.istio-system:9411如果使用的是Telemetry API可以这样控制apiVersion: telemetry.istio.io/v1 kind: Telemetry metadata: name: mesh-default namespace: istio-system spec: tracing: - providers: - name: otel-tracing randomSamplingPercentage: 10.0这里我特意把采样率标了10%是因为很多人一开始图省事直接设100%结果生产环境直接打爆追踪后端。采样率的取舍后面专门讲。链路的形态大概是这样的客户端发请求到服务A服务A的Sidecar生成一个Span请求再调到服务B服务B的Sidecar生成了子Span以此类推。因为header一直在传递Jaeger收到所有Span之后就能按traceId把整条调用链串起来。请求里那个x-request-id其实就是你排障时在日志和Tracing之间互相跳转的钥匙。3. 落地一套开源可观测组合Prometheus、Grafana、Kiali、JaegerIstio自身不提供可视化大屏也不存历史数据它的职责是把数据“扒出来”。至于怎么存、怎么展示、怎么分析一般是用Prometheus Grafana Kiali Jaeger这套组合来完成的。这也是社区里最主流的开源方案不需要额外买商业产品。3.1 安装的时候就把组件带出来最简单的方式是用istioctl安装时带上相关的values。如果你只是想要一个能用的环境profiledemo自带了很多东西包括Prometheus、Grafana、Kiali、Jaeger不过demo profile对生产环境来说太全了里面很多组件你未必需要。我的建议是自己明确定义要装哪些组件istioctl install --set profiledefault \ --set meshConfig.enableTracingtrue \ --set meshConfig.defaultConfig.tracing.sampling10 \ --set meshConfig.defaultConfig.tracing.zipkin.addresszipkin.istio-system:9411Kiali、Prometheus、Grafana、Jaeger这些附属组件可以通过--set values.kiali.enabledtrue类似的参数一起装也可以单独用Helm部署。从生产可维护性的角度看更推荐单独部署这些组件毕竟Istio升级的时候你不想连带升级一堆监控组件。如果是全新环境我习惯的折腾顺序是先部署一个Jaeger确认链路数据能收到再部署Prometheus确认指标抓取正常然后部署Grafana导入官方仪表盘最后部署Kiali看拓扑和调用链顺序很重要Jaeger先起来istioctl安装时才能把Endpoint地址正确填进去。如果Jaeger地址变了你得重新配置Istio的meshConfig再滚动重启比较麻烦。3.2 指标采集和核心查询语句Prometheus一般通过Kubernetes的服务发现自动抓取。Istio的每个Pod因为有prometheus.io/scrapetrue这样的annotationPrometheus会自动发现并采集。你打开Prometheus的Targets页面应该能看到一堆来自命名空间下的Pod target。有了指标之后最关键的就是能写出高效的PromQL。下面几条是我日常排查用得最多的先把它们存成Grafana面板或者Recording Rule后面会省很多事。第一条看某个服务的QPSsum(irate(istio_requests_total{reporterdestination, destination_service_namecheckout-service}[1m])) by (destination_service_name)第二条看P95延迟histogram_quantile(0.95, sum(rate(istio_request_duration_milliseconds_bucket{reporterdestination, destination_service_namecheckout-service}[5m])) by (le))第三条看错误率sum(rate(istio_requests_total{reporterdestination, response_code~5..}[5m])) / sum(rate(istio_requests_total{reporterdestination}[5m]))这里我说一下为什么用reporterdestination打点数据源端和目的端各有一份如果只看源端你只能看到“你发起请求了”但服务端实际处理状态你未必清楚。比如网络层直接RESET源端看可能是503目的端看可能连接都没建立。统一用目的端口径数据冲突会少很多。需要特别小心的是istio_request_duration_milliseconds_bucket这个指标只有在开启PILOT_ENABLE_ANALYSIS之类功能后才会正常产生吗不是它是默认就有的。但如果你的Istio版本比较老可能会出现这个指标名不存在的情况需要先确认版本。版本问题我放到后面坑的部分细说。3.3 Kiali拓扑图不只是好看Kiali的价值在于它把Istio的数据可视化成一张服务调用拓扑图。启动Kiali之后你选一个命名空间就能看到应用之间的调用关系每条边上的数字代表QPS颜色代表健康状态。红色表示有错误黄色表示延迟高这些颜色是基于Prometheus中的数据算出来的。很多人觉得Kiali就是个监控大屏看看就完了。实际上它最大的用处是帮你发现“自己都不知道的依赖”。有一次我排查一个服务偶发抖动业务跟我说这个服务只依赖数据库结果打开Kiali一看它还在调用一个老旧的会员服务而且那个服务的错误率已经飙到20%。这种隐藏依赖靠人肉看代码得看半天拓扑图上一眼就出来了。Kiali和Jaeger的衔接也做得很好。在Kiali的Graph页面点一条边可以直接看这条边的请求趋势点一个节点可以看到对应的trace列表。配合起来从“发现异常”到“定位到具体请求”整个链路就通了。3.4 Grafana仪表盘和Jaeger的联动Grafana这边不需要做太多定制Istio官方提供了几个dashboardIstio Service Dashboard按服务维度看流量、延迟、错误Istio Workload Dashboard按工作负载维度看Istio Control Plane Dashboard看Istiod自身性能导入的时候注意数据源选对用Prometheus就可以。真正实用的玩法是在Grafana panel里发现某个服务P95延迟高直接通过Jaeger数据源按traceId搜这条请求看看慢在哪个Span。Grafana的Jaeger数据源支持直接输入traceId查询。你从Istio访问日志里捞一条traceId通常就是x-request-id的那个值然后贴到Jaeger里就能看到这条请求在整个网格内的完整路径。注意默认的访问日志不一定有traceId字段你要把REQ(HTTP-X-REQUEST-ID)加进日志格式里不然没法把日志和跟踪串起来。4. 从0到1落地时最容易翻车的四个真实场景Istio这套东西装起来很快生产上真正跑稳却有很多细节。下面四个问题都是我实际踩过、或者在社区里见到很多人踩的提前知道可以少走很多弯路。4.1 Sidecar资源配额给少了会引发超时雪崩Sidecar本质上是Envoy它替业务Pod处理所有流量也是要消耗CPU和内存的。默认情况下Istio给istio-proxy的requests很保守一般是100m/128Mi。如果业务流量稍微上来CPU直接被打到throttle请求处理不过来最终表现就是接口延迟高、超时增多。而且这个问题最坑的地方是业务的CPU使用率看起来不高但实际sidecar已经“饿得不行了”。我的建议是不要直接用默认配置在注入Sidecar的时候通过Pod注解改掉资源限制annotations: sidecar.istio.io/proxyCPU: 500m sidecar.istio.io/proxyCPULimit: 2 sidecar.istio.io/proxyMemory: 256Mi sidecar.istio.io/proxyMemoryLimit: 1Gi具体给多少得看业务的QPS和响应体大小。一般可以先给requests 500m / limits 2观察一段时间如果CPU使用率峰值只在20%左右可以往下调如果经常跑到limit就得再往上加。注意Envoy是单线程多worker模型给它太多核也未必能完全用上所以别无脑给8核。调完之后一定要看istio-proxy这个容器的监控指标比如envoy_server_live、envoy_http_downstream_rq_time别只看业务容器的指标否则永远发现不了问题。4.2 链路采样率不是越高越好很多人做链路追踪第一反应是“采样率拉满全都记录下来”。这个想法很美好但生产环境根本扛不住。假设你的网关QPS是5000全量采样意味着每秒产生5000条trace一条trace平均3到5个spanJaeger每秒要接收一两万个span。如果不做存储扩容ES或者ClickHouse很快就报警。实际上采样率达到10%就已经能覆盖绝大多数排障场景了。如果某个服务正在排查问题可以单独对那个服务临时调高采样率而不是全局拉满。你可以在Telemetry资源里针对某个工作负载设置更高的采样率apiVersion: telemetry.istio.io/v1 kind: Telemetry metadata: name: checkout-tracing namespace: prod spec: selector: matchLabels: app.kubernetes.io/name: checkout-service tracing: - providers: - name: otel-tracing randomSamplingPercentage: 100.0这个机制我特别常用。平时全局10%遇到线上问题就单独把出问题的服务调到100%问题定位完再调回来。精准、可控、不浪费资源。4.3 高基数标签把Prometheus内存打爆Istio指标本身带了很多维度的标签如果业务再通过EnvoyFilter加一些自定义标签高基数问题就会越来越猛。Prometheus最怕的不是数据量大而是标签基数爆炸。比如某个服务动态生成很多destination_service比如根据租户生成不同的service时间序列数量会指数级上升。一个服务如果只有几百个Pod理论上时间序列数量已经不少了每个请求还可能带不同的response_flags比如UAEX、RL、UT这些标签组合起来Prometheus内存很容易被打到几个GB甚至十几个GB查询速度直线下降。解决办法是裁剪不必要的指标。在meshConfig里通过proxyStatsMatcher来配置只保留哪些Envoy统计项apiVersion: install.istio.io/v1alpha1 kind: IstioOperator spec: meshConfig: defaultConfig: proxyStatsMatcher: inclusionRegexps: - .*http.* inclusionPrefixes: - cluster_manager inclusionSuffixes: - upstream_rq_time但更直接的办法是针对Istio生成的Telemetry指标做裁剪只保留你需要的指标和标签。反正我的经验是宁可一开始少采集发现不够再补也不要一下子全打开然后天天救Prometheus的内存。4.4 Istio升级导致指标名变化Istio版本迭代过程中指标名不是完全稳定的。比如老版本里有个istio_request_duration后来统一成了istio_request_duration_milliseconds再比如istio_tcp_connections_opened_total在不同版本也调整过。如果你在升级前没有同步改Prometheus里的查询语句和告警规则升级后仪表盘可能会突然一片空白。所以升级前我一般做三件事查一下官方Release Note里关于Telemetry、指标、EnvoyFilter的变更在测试环境升级后用Prometheus接口对比一下关键指标名是否还存在所有关键查询写进Grafana dashboard和Recording Rule升级后检查Recording Rule的出值是否正常Recording Rule是一个很推荐的做法它相当于把你的核心指标查询缓存起来平时告警和看板都查这个结果而不是每次现算。一旦指标名变了只需要改Recording Rule的定义不会影响一堆其他面板。5. 从“看到数据”到“定位问题”一次真实排障与SLO告警的串联可观测性真正值钱的地方在于它能帮你在故障发生时快速找到“证据链”。下面用我一次实际的排障过程把前面说的那些工具串起来。5.1 一次完整的排障证据链那天业务方反馈下单接口偶尔会变慢有时候要等三四秒但过一会又好了。业务日志里没发现明显异常数据库也正常。我第一步不是去翻业务日志而是打开Grafana先看下单服务相关的istio_request_duration_millisecondsP95。结果发现P95平时是300ms左右但每隔十几分钟会飙到3000ms。确认问题存在。第二步打开Kiali选择下单服务的命名空间看拓扑图。一眼就发现下单服务调用支付网关那一条边的颜色不对错误率很高。这就把排查范围从“所有服务”缩小到了“下单服务到支付网关”这一段。第三步在Kiali里点开支付网关相关的那条边看到对应trace列表挑了几条慢请求进入Jaeger。Jaeger的火焰图显示时间基本都花在一个连接外部支付网关的Span上而且每次都发生了connection timeout。业务代码里写了超时重试第一次超时后要等很久才重试所以整体就慢了。最终排查结果支付网关的证书过期了外部服务端不认这个连接导致连接一直在超时。换了新证书后问题消失。这个过程中我没有登录任何一个业务Pod去翻日志。Istio的指标、拓扑、追踪数据把整条证据链给串起来了效率完全不是人肉排查能比的。5.2 用SLI/SLO把观测数据变成告警有数据之后别只停留在“看”还要把数据转成告警和SLO。SLI就是“服务质量指标”比如“可用性”“延迟”SLO是“目标值”比如“99.9%的请求延迟在500ms以内”。用Istio指标做SLI特别方便因为请求数、错误数、延迟都在。我来写一个最基础的错误率告警规则groups: - name: istio-slo-alerts rules: - record: job:slo_errors_ratio:rate5m expr: | sum(rate(istio_requests_total{namespaceprod, reporterdestination, response_code~5..}[5m])) / sum(rate(istio_requests_total{namespaceprod, reporterdestination}[5m])) - alert: ServiceHighErrorRate expr: job:slo_errors_ratio:rate5m 0.01 for: 10m labels: severity: critical annotations: summary: 生产环境错误率超过1% description: 最近5分钟错误率 {{ $value | humanizePercentage }}超过1%阈值这里我用了for: 10m目的是过滤掉瞬时抖动。低流量服务尤其要注意某个服务平时一分钟就几个请求一个503就会把错误率拉到30%但这个并不意味着它真有故障。所以告警规则最好加时间窗口同时用“burn rate”这种多窗口思路来判断别看到瞬时值就报警。关于延迟类的SLI用histogram计算P95然后判断是否超过阈值同样可以写成告警。比如histogram_quantile(0.95, sum(rate(istio_request_duration_milliseconds_bucket{destination_service_namecheckout-service, reporterdestination}[5m])) by (le)) 500这种规则配好之后你的告警就不再是“CPU高”“内存高”这种离业务很远的东西了而是“用户的真实请求有多少比例是慢的”。5.3 观测面板的分层建议最后说下面板搭建。刚开始搭Istio监控的时候我建议按照“由粗到细”的层次来第一层集群总览。看整个网格的QPS、错误率、P95延迟掌握全局健康度。第二层服务维度。每个服务的QPS趋势、错误率、延迟分布定位哪个服务有问题。第三层工作负载维度。每个Deployment的Pod副本以及Envoy自身的CPU、内存、连接数。第四层单次请求维度。通过Jaeger看具体某条trace定位是哪个Span慢、哪个环节失败。这四层每一层都有对应的面板平时告警和人工排查都从第一层往下钻效率会高很多。我见过有些团队一上来就建了几百个panel真出事的时候根本不知道该看哪个这种属于给自己挖坑。另外Istio的可观测性不是万能的。它能看到流量经过网格的部分但如果你有业务逻辑在Pod内部、不经过网络比如本地缓存、定时任务、内部消息队列这些都是它在代理层看不到的。所以网格数据和业务日志、基础设施监控三者应该是互补关系而不是谁替代谁。我的经验是Istio把“服务间通信”这件事变成了可观测的但它替代不了业务日志里真正的业务语义。你可以把网格数据当成一张“城市交通图”它能告诉你哪条路堵了但要了解车里到底装了什么还是得看日志和业务指标。把这几个层次配合好排障效率才是真的提高。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进