Please enable Javascript to view the contents

Envoy & Istio 性能指标与原理初探

 ·  ☕ 15 分钟

logo
(Tower Bridge)伦敦的塔桥的开口跨度

💂 关于封面:
相信很多人听过《London Bridge Is Falling Down》这个儿歌,有人也知道这个是英国传统的儿童游戏歌曲。但很多人和我一样,以为 “London Bridge” 就是封面中的塔桥。直到今天,我才知道,“London Bridge” 并不是 “Tower Bridge”。所以,封面不是 “London Bridge”。无论喜欢与否,一个时代的符号走到了终点,愿安宁。

📚 摘录说明:
本文摘自一本我在写作中的开源书《Istio & Envoy 内幕》Istio 与 Envoy 指标 一节。如果说你看到的转载图片不清,可回到原书。

Istio 与 Envoy 指标概述

指标监控,可能是 DevOps 监控最重要的一环。但同时也可能是最难的一环。你可以从网上找到各种系统和中间件的 Grafana 监控仪表盘,它们大都设计得很漂亮得体,让人感觉监控已经完美无缺。

但是,不知道你是否与我有同样的经历:在系统遇到问题时,手头有一大堆指标和监控仪表盘。

  • 却不知道哪个指标才是问题相关的。
  • 或者是,问题已经有个方向定位,却发现这个方向上,根本没有记录指标。

事情终究是要人来解决,再多的数据,如果:

  • 不去理解这些数据背后的意义
  • 不去主动分析自己的应用场景和部署环境需要什么数据,只是系统默认给什么就用什么

那么指标越多,越让人迷失在茫茫指标的海洋中。

作为一个混后端江湖多年的老程(老程序员),总有很多东西不懂,却难以启齿的。其中一个就是一些具体指标的意义。举个两个例子:

  1. 我之前定位一个 Linux 下的网络问题,用了一个叫 nstat 的工具,它输出了非常多的指标,但很多会发现,有些指标是死活找不到说明文档的。这也是开源软件一直以来的问题,变化快,文档跟不上,甚至错误或过时未更新。
  2. 我之前定位一个 Linux 下的 TCP 连接问题,用了一个叫 ss 的工具,它输出的神指标,也是搜索引擎也无能为力去解释。最后不得不看原码。还好,我把调查结果记录到 Blog 中:《可能是最完整的 TCP 连接健康指标工具 ss 的说明》,希望对后来人有一些参考作用吧。

故事说完了,回到本书的主角 Istio 与 Envoy 上。它们的指标说明文档比上面的老爷车开源软件好一些。起码基本每个指标都有一行文字说明,虽然文字一样非常短且模糊。

Istio 的 istio-proxy 的数据面指标是 基于 Envoy 的指标构架实现的。所以,后面我将先说 Envoy 的指标架构。

如果你和我一样,是个急性子。那么下图就是 Istio & Envoy 的指标地图了。它说明了指标产生在什么地方。后面内容会一步步推导出这个地图。

图:Envoy@Istio的指标
图:Envoy@Istio的指标。用 Draw.io 打开

Envoy 指标

Envoy 指标概述

Envoy 的主要目标之一是使网络易于理解。 Envoy 会根据其配置方式产生大量统计信息。一般来说,统计数据(指标)分为三类:

  • Downstream:Downstream 指标与外来的连接/请求有关。它们由 listenerHTTP connection manager(HCM)TCP proxy filter 等产生。
  • Upstream:Upstream 指标与外向的连接/请求有关。它们由 connection poolrouter filtertcp proxy filter等产生。
  • ServerServer 指标信息描述 Envoy 服务器实例的运作情况。服务器正常运行时间或分配的内存量等统计信息。

在最简单场景下,单个 Envoy Proxy 通常涉及 DownstreamUpstream 统计数据。这两种指标反映了取该 网络节点 的运行情况。来自整个网格的统计数据提供了每个 网络节点 和整体网络健康状况的非常详细的汇总信息。Envoy 的文档对这些指标有一些简单的说明。

Tag

Envoy 的指标还有两个子概念,支持在指标中使用: 标签(tags)/维度(dimensions) 。这里的 tags 对等于 Prometheus 指标的 label。意义上,可以理解为:分类维度。

Envoy 的 指标 由规范的字符串来标识。这些字符串的动态部分(子字符串)被提取成为 标签(tag)。可以通过指定 tag 提取规则(Tag Specifier configuration.) 来定制 tag 。

举个例子:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
### 1. 原始的 Envoy 指标 ###

$ kubectl exec fortio-server -c istio-proxy -- curl 'localhost:15000/stats'

# 返回:
cluster.outbound|8080||fortio-server-l2.mark.svc.cluster.local.external.upstream_rq_2xx: 300

# 其中:
# - `outbound|8080||fortio-server-l2.mark.svc.cluster.local` 部分是 upstream cluster 的名字。可以正则提取作为 tag。
# - `2xx` 部分是 HTTP Status Code 的分类。可以正则提取作为 tag。 下文将有这个提取规则的配置说明。

### 2. 给 Prometheus 的指标 ###
$ kubectl exec fortio-server -c istio-proxy -- curl 'localhost:15000/stats?format=prometheus' | grep 'outbound|8080||fortio-server-l2' | grep 'external.upstream_rq'

# 返回:
envoy_cluster_external_upstream_rq{response_code_class="2xx",cluster_name="outbound|8080||fortio-server-l2.mark.svc.cluster.local"} 300

指标数据类型

Envoy 发出三种类型的值作为统计信息:

  • 计数器(Counters):无符号整数,只会增加而不会减少。例如,总请求。
  • 仪表(Gauges):增加和减少的无符号整数。例如,当前活动的请求。
  • 直方图(Histograms):作为指标流的一部分的无符号整数,然后由收集器聚合以最终产生汇总的百分位值(percentile,即平常说的 P99/P50/Pxx)。例如,Upsteam 响应时间。

在 Envoy 的内部实现中,Counters 和 Gauges 被分批并定期刷新以提高性能。Histograms 在接收时写入。

指标释义

从指标的产出地点来划分,可以分为:

  • cluster manager : 面向 upstream 的 L3/L4/L7 层指标
  • http connection manager(HCM) : 面向 upstream & downstream 的 L7 层指标
  • listeners : 面向 downstream 的 L3/L4 层指标
  • server(全局)
  • watch dog

下面我只选择了部分关键的性能指标来简单说明。

cluster manager

Envoy 文档:cluster manager stats

上面文档已经说得比较详细了。我只补充一些在性能调优时需要关注的方面。那么,一般需要关注什么指标?

我们从著名的 Utilization Saturation and Errors (USE) 方法学来分析。

利用率(Utilization):

  • upstream_cx_total (Counter): 连接数
  • upstream_rq_active

饱和度(Saturation):

  • upstream_rq_time (Histogram): 响应时间
  • upstream_cx_connect_ms (Histogram)
  • upstream_cx_rx_bytes_buffered
  • upstream_cx_tx_bytes_buffered
  • upstream_rq_pending_total
  • upstream_rq_pending_active (Gauge)

错误(Error):

  • upstream_cx_connect_fail (Counter): 连接失败数
  • upstream_cx_connect_timeout (Counter): 连接超时数
  • upstream_cx_overflow (Counter): 集群连接断路器溢出的总次数
  • upstream_cx_pool_overflow
  • upstream_cx_destroy_local_with_active_rq
  • upstream_cx_destroy_remote_with_active_rq
  • upstream_rq_timeout
  • upstream_rq_retry
  • upstream_rq_rx_reset
  • upstream_rq_tx_reset

其它:

  • upstream_rq_total (Counter): TPS (吞吐)
  • upstream_cx_destroy_local (Counter): Envoy 主动断开的连接计数
  • upstream_cx_destroy_remote (Counter): Envoy 被动断开的连接计数
  • upstream_cx_length_ms (Histogram)

http connection manager(HCM)

Envoy 文档:http connection manager(HCM) stats

可以认为,这是面向 downstream & 部分 upstream 的 L7 层指标

利用率(Utilization):

  • downstream_cx_total
  • downstream_cx_active
  • downstream_cx_http1_active
  • downstream_rq_total
  • downstream_rq_http1_total
  • downstream_rq_active

饱和度(Saturation):

  • downstream_cx_rx_bytes_buffered
  • downstream_cx_tx_bytes_buffered
  • downstream_flow_control_paused_reading_total
  • downstream_flow_control_resumed_reading_total

错误(Error):

  • downstream_cx_destroy_local_active_rq
  • downstream_cx_destroy_remote_active_rq
  • downstream_rq_rx_reset
  • downstream_rq_tx_reset
  • downstream_rq_too_large
  • downstream_rq_max_duration_reached
  • downstream_rq_timeout
  • downstream_rq_overload_close
  • rs_too_large

其它:

  • downstream_cx_destroy_remote
  • downstream_cx_destroy_local
  • downstream_cx_length_ms

listeners

Envoy 文档:listener stats

可以认为,这是 downstream 的 L3/L4 层的指标。

利用率(Utilization):

  • downstream_cx_total
  • downstream_cx_active

饱和度(Saturation):

  • downstream_pre_cx_active

错误(Error):

  • downstream_cx_transport_socket_connect_timeout
  • downstream_cx_overflow
  • no_filter_chain_match
  • downstream_listener_filter_error
  • no_certificate

其它:

  • downstream_cx_length_ms

server

Envoy 基础信息指标

Envoy 文档:server stats

利用率(Utilization):

  • concurrency

错误(Error):

  • days_until_first_cert_expiring

watch dog

Envoy 文档: Watchdog

Envoy 还包括一个可配置的看门狗系统,它可以在 Envoy 没有响应时增加统计数据并选择性地终止服务器。 系统有两个独立的看门狗配置,一个用于主线程,一个用于工作线程; 因为不同的线程有不同的工作负载。 这些统计数据有助于从高层次上理解 Envoy 的事件循环是否因为它正在做太多工作、阻塞或没有被操作系统调度而没有响应。

饱和度(Saturation):

  • watchdog_mega_miss(Counter): mega 未命中数
  • watchdog_miss(Counter): 未命中数

如果你对 watchdog 机制的兴趣,可以参考:

https://github.com/envoyproxy/envoy/issues/11391
https://github.com/envoyproxy/envoy/issues/11388

Event loop

Envoy 文档: Event loop

Envoy 架构旨在通过在少量线程上运行事件循环来优化可扩展性和资源利用率。 “main” 线程负责控制面处理,每个 “worker” 线程分担数据面的一部分任务。 Envoy 公开了两个统计信息来监控所有这些线程事件循环的性能。

跑一轮循环的耗时:事件循环的每次迭代都会执行一些任务。任务数量会随着负载的变化而变化。但是,如果一个或多个线程具有异常长尾循环执行耗时,则可能存在性能问题。例如,负责可能在工作线程之间分配不均,或者插件中可能存在长时间阻塞操作阻碍了任务进度。

轮询延迟:在事件循环的每次迭代中,事件调度程序都会轮询 I/O 事件,并在某些 I/O 事件就绪 或 发生 超时 时 “唤醒” 线程,以先发生者为准。在 超时 的情况下,我们可以测量轮询后预期唤醒时间与实际唤醒时间的差值;这种差异称为 “轮询延迟”。看到一些小的 轮询延迟 是正常的,通常等于内核调度程序的 “时间片(time slice”)” 或 “量子(quantum)” ——这取决于运行 Envoy 的操作系统 —— 但如果这个数字大大高于其正常观察到的基线,它表示内核调度程序可能发生延迟。

可以通过将 enable_dispatcher_stats 设置为 true 来启用这些统计信息。

  • main 线程的事件调度器有一个以 server.dispatcher. 为根的统计树。
  • 每个 worker 线程的事件调度器都有一个以 listener_manager.worker_<id>.dispatcher. 为根的统计树。

每棵树都有以下统计信息:

Name Type Description
loop_duration_us Histogram 以微秒为单位的事件循环持续时间
poll_delay_us Histogram 以微秒为单位的轮询延迟

请注意,此处不包括任何辅助(非 main 与 worker)线程。

Watch Dog 和 Event loop 都是解决与监控事件处理延迟与时效的工具,这里有很多细节和故事,甚至可以说到 Linux Kernel。希望本书后面有时间,可以和大家一起学习和分析这些有趣的细节。

配置说明

如果你认真看过本书的前言中的 {ref}`index:本书不是什么` ,说好的不是“使用手册”,为何又讲起配置来了?好吧,我只能说,从了解使用方法入手,再学实现,比直接一来就源码的方法更好让人类入门。

本节参考:
[Envoy 文档](https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/metrics/v3/stats.proto)

config.bootstrap.v3.Bootstrap

Envoy 文档:config.bootstrap.v3.Bootstrap proto

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
{
  "node": {...},
  "static_resources": {...},
  "dynamic_resources": {...},
  "cluster_manager": {...},
  "stats_sinks": [],
  "stats_config": {...},
  "stats_flush_interval": {...},
  "stats_flush_on_admin": ...,
...
}
什么是 `stats sink`? 本书不作说明。Istio 默认没定制相关配置。以下只说关注的部分配置。
  • stats_config
    (config.metrics.v3.StatsConfig) 用于内部处理统计信息的配置。

  • stats_flush_interval
    (Duration) 刷新 stats sink 的时间间隔。。出于性能原因,Envoy 不会实时刷新 counter ,仅定期刷新 counter 和 gauge 。 如果未指定,则默认值为 5000 毫秒。 stats_flush_intervalstats_flush_on_admin 只能设置之一。 Duration 必须至少为 1 毫秒,最多为 5 分钟。

  • stats_flush_on_admin
    (bool) 仅当在 管理界面(admin interface) 上查询时才将统计信息刷新到 sink。 如果设置,则不会创建刷新计时器。 只能设置 stats_flush_on_adminstats_flush_interval 之一。

config.metrics.v3.StatsConfig

Envoy 文档:config-metrics-v3-statsconfig

1
2
3
4
5
6
{
  "stats_tags": [],
  "use_all_default_tags": {...},
  "stats_matcher": {...},
  "histogram_bucket_settings": []
}
  • stats_tags - 维度提取规则(对应 Prometheus 的 label 提取)
    (多个 config.metrics.v3.TagSpecifier ) 每个 指标名称字符串 都通过这些标签规则独立处理。 当一个标签匹配时,第一个捕获组不会立即从名称中删除,所以后面的 TagSpecifiers 也可以重复匹配同一部分。在完成所有标签匹配后,再剪裁 指标名称字符串 的匹配部分,并作为 stats sink 的指标名,例如 Prometheus的指标名。

  • use_all_default_tags
    (BoolValue) 使用 Envoy 中指定的所有默认标签正则表达式。 这些可以与 stats_tags 中指定的自定义标签结合使用。 它们将在自定义标签之前进行处理。Istio 默认为 false.

  • stats_matcher
    (config.metrics.v3.StatsMatcher) 指定 Envoy 要产出哪些指标。支持 包含/排除 规则指定。 如果未提供,则所有指标都将产出。 阻止某些指标集的统计可以提高一点 Envoy 运行性能。

config.metrics.v3.StatsMatcher

Envoy 文档:config-metrics-v3-statsmatcher

用于禁用/开启统计指标计算与产出的配置。

1
2
3
4
5
{
  "reject_all": ...,
  "exclusion_list": {...},
  "inclusion_list": {...}
}
本节参考了: https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/observability/statistics

下一节,将以 Istio 如何使用上面的配置为例,举例说明。

Istio 指标

Istio 自己的 Metrics

标准指标说明

参考:https://istio.io/latest/docs/reference/config/metrics/

Metrics

对于 HTTP、HTTP/2 和 GRPC 流量,Istio 默认生成以下指标:

  • Request Count (istio_requests_total): This is a COUNTER incremented for every request handled by an Istio proxy.
  • Request Duration (istio_request_duration_milliseconds): This is a DISTRIBUTION which measures the duration of requests.
  • Request Size (istio_request_bytes): This is a DISTRIBUTION which measures HTTP request body sizes.
  • Response Size (istio_response_bytes): This is a DISTRIBUTION which measures HTTP response body sizes.
  • gRPC Request Message Count (istio_request_messages_total): This is a COUNTER incremented for every gRPC message sent from a client.
  • gRPC Response Message Count (istio_response_messages_total): This is a COUNTER incremented for every gRPC message sent from a server.

对于 TCP 流量,Istio 生成以下指标:

  • Tcp Bytes Sent (istio_tcp_sent_bytes_total): This is a COUNTER which measures the size of total bytes sent during response in case of a TCP connection.
  • Tcp Bytes Received (istio_tcp_received_bytes_total): This is a COUNTER which measures the size of total bytes received during request in case of a TCP connection.
  • Tcp Connections Opened (istio_tcp_connections_opened_total): This is a COUNTER incremented for every opened connection.
  • Tcp Connections Closed (istio_tcp_connections_closed_total): This is a COUNTER incremented for every closed connection.
Prometheus 的 Labels
  • Reporter: This identifies the reporter of the request. It is set to destination if report is from a server Istio proxy and source if report is from a client Istio proxy or a gateway.

  • Source Workload: This identifies the name of source workload which controls the source, or “unknown” if the source information is missing.

  • Source Workload Namespace: This identifies the namespace of the source workload, or “unknown” if the source information is missing.

  • Source Principal: This identifies the peer principal of the traffic source. It is set when peer authentication is used.

  • Source App: This identifies the source application based on app label of the source workload, or “unknown” if the source information is missing.

  • Source Version: This identifies the version of the source workload, or “unknown” if the source information is missing.

  • Destination Workload: This identifies the name of destination workload, or “unknown” if the destination information is missing.

  • Destination Workload Namespace: This identifies the namespace of the destination workload, or “unknown” if the destination information is missing.

  • Destination Principal: This identifies the peer principal of the traffic destination. It is set when peer authentication is used.

  • Destination App: This identifies the destination application based on app label of the destination workload, or “unknown” if the destination information is missing.

  • Destination Version: This identifies the version of the destination workload, or “unknown” if the destination information is missing.

  • Destination Service: This identifies destination service host responsible for an incoming request. Ex: details.default.svc.cluster.local.

  • Destination Service Name: This identifies the destination service name. Ex: “details”.

  • Destination Service Namespace: This identifies the namespace of destination service.

  • Request Protocol: This identifies the protocol of the request. It is set to request or connection protocol.

  • Response Code: This identifies the response code of the request. This label is present only on HTTP metrics.

  • Connection Security Policy: This identifies the service authentication policy of the request. It is set to mutual_tls when Istio is used to make communication secure and report is from destination. It is set to unknown when report is from source since security policy cannot be properly populated.

  • Response Flags: Additional details about the response or connection from proxy. In case of Envoy, see %RESPONSE_FLAGS% in Envoy Access Log for more detail.

  • Canonical Service: A workload belongs to exactly one canonical service, whereas it can belong to multiple services. A canonical service has a name and a revision so it results in the following labels.

    1
    2
    3
    4
    
    source_canonical_service
    source_canonical_revision
    destination_canonical_service
    destination_canonical_revision
    
  • Destination Cluster: This identifies the cluster of the destination workload. This is set by: global.multiCluster.clusterName at cluster install time.

  • Source Cluster: This identifies the cluster of the source workload. This is set by: global.multiCluster.clusterName at cluster install time.

  • gRPC Response Status: This identifies the response status of the gRPC. This label is present only on gRPC metrics.

使用

istio-proxy 与应用的 Metrics 整合输出

图:istio-proxy 与应用的 Metrics 整合输出
图:istio-proxy 与应用的 Metrics 整合输出。用 Draw.io 打开

参考:https://istio.io/v1.14/docs/ops/integrations/prometheus/#option-1-metrics-merging

Istio 能够完全通过 prometheus.io annotations 来控制抓取。虽然 prometheus.io annotations 不是 Prometheus 的核心部分,但它们已成为配置抓取的事实标准。

此选项默认启用,但可以通过在 安装 期间传递 --set meshConfig.enablePrometheusMerge=false 来禁用。启用后,将向所有数据平面 pod 添加适当的 prometheus.io annotations 以设置抓取。如果这些注释已经存在,它们将被覆盖。使用此选项,Envoy sidecar 会将 Istio 的指标与应用程序指标合并。合并后的指标将从 /stats/prometheus:15020 中抓取。

此选项以明文形式公开所有指标。

定制:为 Metrics 增加维度

参考: https://istio.io/latest/docs/tasks/observability/metrics/customize-metrics/#custom-statistics-configuration

如,增加端口、与 HTTP HOST 头 维度。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
apiVersion: install.istio.io/v1alpha1
kind: IstioOperator
spec:
  values:
    telemetry:
      v2:
        prometheus:
          configOverride:
            inboundSidecar:
              metrics:
                - name: requests_total
                  dimensions:
                    destination_port: string(destination.port)
                    request_host: request.host
            outboundSidecar:
              metrics:
                - name: requests_total
                  dimensions:
                    destination_port: string(destination.port)
                    request_host: request.host
            gateway:
              metrics:
                - name: requests_total
                  dimensions:
                    destination_port: string(destination.port)
                    request_host: request.host
  1. 使用以下命令将以下 annotation 应用到所有注入的 pod,其中包含要提取到 Prometheus 时间序列 的维度列表:

仅当您的维度不在 [DefaultStatTags 列表] 中时才需要此步骤(https://github.com/istio/istio/blob/release-1.14/pkg/bootstrap/config.go)

1
2
3
4
5
6
7
apiVersion: apps/v1
kind: Deployment
spec:
  template: # pod template
    metadata:
      annotations:
        sidecar.istio.io/extraStatTags: destination_port,request_host

要在网格范围内启用额外 Tag ,您可以将 extraStatTags 添加到网格配置中:

1
2
3
4
5
meshConfig:
  defaultConfig:
    extraStatTags:
     - destination_port
     - request_host

参考 : https://istio.io/latest/docs/reference/config/proxy_extensions/stats/#MetricConfig

定制:加入 request / response 元信息维度

可以把 request 或 repsonse 里一些基础信息 加入到 指标的维度。如,URL Path,这在需要为相同服务分隔统计不同 REST API 的指标时,相当有用。

参考 : https://istio.io/latest/docs/tasks/observability/metrics/classify-metrics/

工作原理

istio stat filter 使用

Istio 在自己的定制版本 Envoy 中,加入了 stats-filter 插件,用于计算 Istio 自己想要的指标:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
$ k -n istio-system get envoyfilters.networking.istio.io stats-filter-1.14 -o yaml
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
  annotations:
  labels:
    install.operator.istio.io/owning-resource-namespace: istio-system
    istio.io/rev: default
    operator.istio.io/component: Pilot
    operator.istio.io/version: 1.14.3
  name: stats-filter-1.14
  namespace: istio-system
spec:
  configPatches:
  - applyTo: HTTP_FILTER
    match:
      context: SIDECAR_OUTBOUND
      listener:
        filterChain:
          filter:
            name: envoy.filters.network.http_connection_manager
            subFilter:
              name: envoy.filters.http.router
      proxy:
        proxyVersion: ^1\.14.*
    patch:
      operation: INSERT_BEFORE
      value:
        name: istio.stats
        typed_config:
          '@type': type.googleapis.com/udpa.type.v1.TypedStruct
          type_url: type.googleapis.com/envoy.extensions.filters.http.wasm.v3.Wasm
          value:
            config:
              configuration:
                '@type': type.googleapis.com/google.protobuf.StringValue
                value: |
                  {
                    "debug": "false",
                    "stat_prefix": "istio"
                  }                  
              root_id: stats_outbound
              vm_config:
                code:
                  local:
                    inline_string: envoy.wasm.stats
                runtime: envoy.wasm.runtime.null
                vm_id: stats_outbound
  - applyTo: HTTP_FILTER
    match:
      context: SIDECAR_INBOUND
      listener:
        filterChain:
          filter:
            name: envoy.filters.network.http_connection_manager
            subFilter:
              name: envoy.filters.http.router
      proxy:
        proxyVersion: ^1\.14.*
    patch:
      operation: INSERT_BEFORE
      value:
        name: istio.stats
        typed_config:
          '@type': type.googleapis.com/udpa.type.v1.TypedStruct
          type_url: type.googleapis.com/envoy.extensions.filters.http.wasm.v3.Wasm
          value:
            config:
              configuration:
                '@type': type.googleapis.com/google.protobuf.StringValue
                value: |
                  {
                    "debug": "false",
                    "stat_prefix": "istio",
                    "disable_host_header_fallback": true,
                    "metrics": [
                      {
                        "dimensions": {
                          "destination_cluster": "node.metadata['CLUSTER_ID']",
                          "source_cluster": "downstream_peer.cluster_id"
                        }
                      }
                    ]
                  }                  
              root_id: stats_inbound
              vm_config:
                code:
                  local:
                    inline_string: envoy.wasm.stats
                runtime: envoy.wasm.runtime.null
                vm_id: stats_inbound
  - applyTo: HTTP_FILTER
    match:
      context: GATEWAY
      listener:
        filterChain:
          filter:
            name: envoy.filters.network.http_connection_manager
            subFilter:
              name: envoy.filters.http.router
      proxy:
        proxyVersion: ^1\.14.*
    patch:
      operation: INSERT_BEFORE
      value:
        name: istio.stats
        typed_config:
          '@type': type.googleapis.com/udpa.type.v1.TypedStruct
          type_url: type.googleapis.com/envoy.extensions.filters.http.wasm.v3.Wasm
          value:
            config:
              configuration:
                '@type': type.googleapis.com/google.protobuf.StringValue
                value: |
                  {
                    "debug": "false",
                    "stat_prefix": "istio",
                    "disable_host_header_fallback": true
                  }                  
              root_id: stats_outbound
              vm_config:
                code:
                  local:
                    inline_string: envoy.wasm.stats
                runtime: envoy.wasm.runtime.null
                vm_id: stats_outbound
  priority: -1
istio stat Plugin 实现

https://github.com/istio/proxy/blob/release-1.14/extensions/stats/plugin.cc

内置的 Metric:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
const std::vector<MetricFactory>& PluginRootContext::defaultMetrics() {
  static const std::vector<MetricFactory> default_metrics = {
      // HTTP, HTTP/2, and GRPC metrics
      MetricFactory{"requests_total", MetricType::Counter,
                    [](::Wasm::Common::RequestInfo&) -> uint64_t { return 1; },
                    static_cast<uint32_t>(Protocol::HTTP) |
                        static_cast<uint32_t>(Protocol::GRPC),
                    count_standard_labels, /* recurrent */ false},
      MetricFactory{"request_duration_milliseconds", MetricType::Histogram,
                    [](::Wasm::Common::RequestInfo& request_info) -> uint64_t {
                      return request_info.duration /* in nanoseconds */ /
                             1000000;
                    },
                    static_cast<uint32_t>(Protocol::HTTP) |
                        static_cast<uint32_t>(Protocol::GRPC),
                    count_standard_labels, /* recurrent */ false},
      MetricFactory{"request_bytes", MetricType::Histogram,
                    [](::Wasm::Common::RequestInfo& request_info) -> uint64_t {
                      return request_info.request_size;
                    },
                    static_cast<uint32_t>(Protocol::HTTP) |
                        static_cast<uint32_t>(Protocol::GRPC),
                    count_standard_labels, /* recurrent */ false},
      MetricFactory{"response_bytes", MetricType::Histogram,
                    [](::Wasm::Common::RequestInfo& request_info) -> uint64_t {
                      return request_info.response_size;
                    },
                    static_cast<uint32_t>(Protocol::HTTP) |
                        static_cast<uint32_t>(Protocol::GRPC),
                    count_standard_labels, /* recurrent */ false},
...

https://github.com/istio/proxy/blob/release-1.14/extensions/stats/plugin.cc#L591

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
void PluginRootContext::report(::Wasm::Common::RequestInfo& request_info,
                               bool end_stream) {

...
  map(istio_dimensions_, outbound_, peer_node_info.get(), request_info);

  for (size_t i = 0; i < expressions_.size(); i++) {
    if (!evaluateExpression(expressions_[i].token,
                            &istio_dimensions_.at(count_standard_labels + i))) {
      LOG_TRACE(absl::StrCat("Failed to evaluate expression: <",
                             expressions_[i].expression, ">"));
      istio_dimensions_[count_standard_labels + i] = "unknown";
    }
  }

  auto stats_it = metrics_.find(istio_dimensions_);
  if (stats_it != metrics_.end()) {
    for (auto& stat : stats_it->second) {
      if (end_stream || stat.recurrent_) {
        stat.record(request_info);
      }
      LOG_DEBUG(
          absl::StrCat("metricKey cache hit ", ", stat=", stat.metric_id_));
    }
    cache_hits_accumulator_++;
    if (cache_hits_accumulator_ == 100) {
      incrementMetric(cache_hits_, cache_hits_accumulator_);
      cache_hits_accumulator_ = 0;
    }
    return;
  }
...
}                                  

关于 Istio 的指标原理,这是一个很好的参考文章:https://blog.christianposta.com/understanding-istio-telemetry-v2/

Envoy 内置的 Metrics

Istio 默认用 istio-agent 去整合 Envoy 的 metrics。
而 Istio 默认打开的 Envoy 内置 Metrics 很少:

见:https://istio.io/latest/docs/ops/configuration/telemetry/envoy-stats/

cluster_manager
listener_manager
server
cluster.xds-grpc

定制 Envoy 内置的 Metrics

参考:https://istio.io/latest/docs/ops/configuration/telemetry/envoy-stats/

如果要配置 Istio Proxy 以记录 其它 Envoy 原生的指标,您可以将 ProxyConfig.ProxyStatsMatcher 添加到网格配置中。 例如,要全局启用断路器、重试和上游连接的统计信息,您可以指定 stats matcher,如下所示:

代理需要重新启动以获取统计匹配器配置。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
apiVersion: install.istio.io/v1alpha1
kind: IstioOperator
spec:
  meshConfig:
    defaultConfig:
      proxyStatsMatcher:
        inclusionRegexps:
          - ".*circuit_breakers.*"
        inclusionPrefixes:
          - "upstream_rq_retry"
          - "upstream_cx"

您还可以使用 proxy.istio.io/config annotation 为个别代码指定配置。 例如,要配置与上面相同的统计信息,您可以将 annotation 添加到 gateway proxy 或 workload,如下所示:

1
2
3
4
5
6
7
8
9
metadata:
  annotations:
    proxy.istio.io/config: |-
      proxyStatsMatcher:
        inclusionRegexps:
        - ".*circuit_breakers.*"
        inclusionPrefixes:
        - "upstream_rq_retry"
        - "upstream_cx"      

原理

下面,看看 Istio 默认配置下,如何配置 Envoy。

1
istioctl proxy-config bootstrap fortio-server | yq eval -P  > envoy-config-bootstrap-default.yaml

输出:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
bootstrap:
...
  statsConfig:
    statsTags: # 从指标名中抓取 Tag(prometheus label)
      - tagName: cluster_name
        regex: ^cluster\.((.+?(\..+?\.svc\.cluster\.local)?)\.)
      - tagName: tcp_prefix
        regex: ^tcp\.((.*?)\.)\w+?$
      - tagName: response_code
        regex: (response_code=\.=(.+?);\.;)|_rq(_(\.d{3}))$
      - tagName: response_code_class
        regex: _rq(_(\dxx))$
      - tagName: http_conn_manager_listener_prefix
        regex: ^listener(?=\.).*?\.http\.(((?:[_.[:digit:]]*|[_\[\]aAbBcCdDeEfF[:digit:]]*))\.)
...
    useAllDefaultTags: false
    statsMatcher:
      inclusionList:
        patterns: # 选择要记录的指标
          - prefix: reporter=
          - prefix: cluster_manager
          - prefix: listener_manager
          - prefix: server
          - prefix: cluster.xds-grpc ## 只记录 xDS cluster. 即不记录用户自己服务的 cluster !!!
          - prefix: wasm
          - suffix: rbac.allowed
          - suffix: rbac.denied
          - suffix: shadow_allowed
          - suffix: shadow_denied
          - prefix: component

这时,如果修改 pod 的定义为:

1
2
3
4
5
6
7
8
    annotations:
      proxy.istio.io/config: |-
        proxyStatsMatcher:
          inclusionRegexps:
          - "cluster\\..*fortio.*" #proxy upstream(outbound)
          - "cluster\\..*inbound.*" #proxy upstream(inbound,这里一般就是指到同一 pod 中运行的应用了)
          - "http\\..*"
          - "listener\\..*"        

产生新的 Envoy 配置:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
 "stats_matcher": {
   "inclusion_list": {
     "patterns": [
       {
         "prefix": "reporter="
       },
       {
         "prefix": "cluster_manager"
       },
       {
         "prefix": "listener_manager"
       },
       {
         "prefix": "server"
       },
       {
         "prefix": "cluster.xds-grpc"
       },
 

       {
         "safe_regex": {
           "google_re2": {},
           "regex": "cluster\\..*fortio.*"
         }
       },
       {
         "safe_regex": {
           "google_re2": {},
           "regex": "cluster\\..*inbound.*"
         }
       },
       {
         "safe_regex": {
           "google_re2": {},
           "regex": "http\\..*"
         }
       },
       {
         "safe_regex": {
           "google_re2": {},
           "regex": "listener\\..*"
         }
       },

总结:Istio-Proxy 指标地图

要做好监控,首先要深入了解指标原理。而要了解指标原理,当然要知道指标是产生流程中的什么位置,什么组件。看完上面关于 Envoy 与 Istio 的指标说明后。可以大概得到以下结论:

图:Envoy@Istio的指标
图:Envoy@Istio的指标。用 Draw.io 打开

本节的实验环境说明见于: https://istio-insider.mygraphql.com/zh_CN/latest/appendix-lab-env/appendix-lab-env-base.html

分享

Mark Zhu
作者
Mark Zhu
An old developer