Istio 可观测性之指标,指标提供了一种以聚合的方式监控和理解行为的方法

运维
指标提供了一种以聚合的方式监控和理解行为的方法。为了监控服务行为,Istio 为服务网格中所有出入网格,以及网格内部的服务流量都生成了指标,这些指标提供了关于行为的信息,例如总流量、错误率和请求响应时间。除了监控网格中服务的行为外,监控网格本身的行为也很重要。

Istio 为网格内所有的服务通信生成详细的遥测数据。这种遥测技术提供了服务行为的可观测性,使运维人员能够排查故障、维护和优化应用程序,而不会给开发人员带来其他额外的负担。通过 Istio,运维人员可以全面了解到受监控的服务如何与其他服务以及 Istio 组件进行交互。

Istio 生成以下类型的遥测数据,以提供对整个服务网格的可观测性:

  • Metrics(指标):Istio 基于 4 个监控的黄金标识(延迟、流量、错误、饱和)生成了一系列服务指标,Istio 还为网格控制平面提供了更详细的指标。除此以外还提供了一组默认的基于这些指标的网格监控仪表板。
  • Tracing(分布式追踪):Istio 为每个服务生成分布式追踪 span,运维人员可以理解网格内服务的依赖和调用流程。
  • Log(访问日志):当流量流入网格中的服务时,Istio 可以生成每个请求的完整记录,包括源和目标的元数据,该信息使运维人员能够将服务行为的审查控制到单个工作负载实例的级别。

接下来我们将分别来学习 Istio 的指标、分布式追踪和访问日志是如何工作的。

指标

指标提供了一种以聚合的方式监控和理解行为的方法。为了监控服务行为,Istio 为服务网格中所有出入网格,以及网格内部的服务流量都生成了指标,这些指标提供了关于行为的信息,例如总流量、错误率和请求响应时间。除了监控网格中服务的行为外,监控网格本身的行为也很重要。Istio 组件还可以导出自身内部行为的指标,以提供对网格控制平面的功能和健康情况的洞察能力。

指标类别

整体上 Istio 的指标可以分成 3 个级别:代理级别、服务级别、控制平面级别。

代理级别指标

Istio 指标收集从 Envoy Sidecar 代理开始,每个代理为通过它的所有流量(入站和出站)生成一组丰富的指标。代理还提供关于它本身管理功能的详细统计信息,包括配置信息和健康信息。

Envoy 生成的指标提供了资源(例如监听器和集群)粒度上的网格监控。因此,为了监控 Envoy 指标,需要了解网格服务和 Envoy 资源之间的连接。

Istio 允许运维人员在每个工作负载实例上选择生成和收集哪些 Envoy 指标。默认情况下,Istio 只支持 Envoy 生成的统计数据的一小部分,以避免依赖过多的后端服务,还可以减少与指标收集相关的 CPU 开销。但是运维人员可以在需要时轻松地扩展收集到的代理指标数据。这样我们可以有针对性地调试网络行为,同时降低了跨网格监控的总体成本。

服务级别指标

除了代理级别指标之外,Istio 还提供了一组用于监控服务通信的面向服务的指标。这些指标涵盖了四个基本的服务监控需求:延迟、流量、错误和饱和情况。而且 Istio 还自带了一组默认的仪表板,用于监控基于这些指标的服务行为。默认情况下,标准 Istio 指标会导出到 Prometheus。而且服务级别指标的使用完全是可选的,运维人员可以根据自身的需求来选择关闭指标的生成和收集。

控制平面指标

另外 Istio 控制平面还提供了一组自我监控指标。这些指标允许监控 Istio 自己的行为。

通过 Prometheus 查询指标

Istio 默认使用 Prometheus 来收集和存储指标。Prometheus 是一个开源的系统监控和警报工具包,它可以从多个源收集指标,并允许运维人员通过 PromQL 查询语言来查询收集到的指标。

首先要确保 Istio 的 prometheus 组件已经启用,如果没有启用可以通过以下命令启用:

kubectl apply -f samples/addons

上面的命令会安装 Kiali,包括 Prometheus、Grafana 以及 jaeger。当然这仅仅只能用于测试环境,在生产环境可以单独安装 Prometheus 进行有针对性的配置优化。

安装后可以通过以下命令查看 Prometheus 服务状态:

$ kubectl get svc prometheus -n istio-system
NAME         TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)    AGE
prometheus   ClusterIP   10.106.228.196   <none>        9090/TCP   25d
$ kubectl get pods -n istio-system -l app=prometheus
NAME                         READY   STATUS    RESTARTS       AGE
prometheus-5d5d6d6fc-2gtxm   2/2     Running   0              25d

然后我们还是以 Bookinfo 应用为例,首先在浏览器中访问 http://$GATEWAY_URL/productpage 应用,然后我们就可以打开 Prometheus UI 来查看指标了。在 Kubernetes 环境中,执行如下命令就可以打开 Prometheus UI:

istioctl dashboard prometheus
# 也可以创建 Ingress 或者 Gateway 来访问 Prometheus UI

打开后我们可以在页面中随便查询一个指标,比如我们查询 istio_requests_total 指标,如下所示:

查询指标

istio_requests_total 这是一个 COUNTER 类型的指标,用于记录 Istio 代理处理的总请求数。

当然然后可以根据自己需求来编写 promql 语句进行查询,比如查询 productpage 服务的总次数,可以用下面的语句:

istio_requests_total{destination_service="productpage.default.svc.cluster.local"}

查询 reviews 服务 v3 版本的总次数:

istio_requests_total{destination_service="reviews.default.svc.cluster.local", destination_versinotallow="v3"}

该查询返回所有请求 reviews 服务 v3 版本的当前总次数。

过去 5 分钟 productpage 服务所有实例的请求频次:

rate(istio_requests_total{destination_service=~"productpage.*", response_code="200"}[5m])

在 Graph 选项卡中,可以看到查询结果的图形化表示。

Graph

对于 PromQL 语句的使用可以参考官方文档 Prometheus Querying Basics,或者我们的 《Prometheus 入门到实战》课程,这并不是我们这里的重点,所以就不再详细介绍了。

虽然我们这里并没有做任何的配置,但是 Istio 默认已经为我们收集了一些指标,所以我们可以直接查询到这些指标了。

使用 Grafana 可视化指标

Prometheus 提供了一个基本的 UI 来查询指标,但是它并不是一个完整的监控系统,更多的时候我们可以使用 Grafana 来可视化指标。

首先同样要保证 Istio 的 grafana 组件已经启用,如果没有启用可以通过以下命令启用:

kubectl apply -f samples/addons

并且要保证 Prometheus 服务正在运行,服务安装后可以通过下面的命令来查看状态:

$ kubectl -n istio-system get svc grafana
NAME      TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)    AGE
grafana   ClusterIP   10.96.197.74   <none>        3000/TCP   25d
$ kubectl -n istio-system get pods -l app=grafana
NAME                       READY   STATUS    RESTARTS       AGE
grafana-5f9b8c6c5d-jv65v   1/1     Running   0              25d

然后我们可以通过以下命令来打开 Grafana UI:

istioctl dashboard grafana
# 也可以创建 Ingress 或者 Gateway 来访问 Grafana

然后我们就可以在浏览器中打开 Grafana UI 了,默认情况下 Grafana 已经配置了 Prometheus 数据源,所以我们可以直接使用 Prometheus 数据源来查询指标。

数据源

此外 Grafana 也已经内置了 Istio 的一些仪表盘,我们可以直接使用这些仪表盘来查看指标,比如我们可以打开 Istio Mesh Dashboard 仪表盘来查看网格的指标:

Dashboard

从图中可以看出现在有一些数据,但是并不是很多,这是因为我们现在还没产生一些流量请求,下面我们可以用下面的命令向 productpage 服务发送 100 个请求:

for i in $(seq 1 100); do curl -s -o /dev/null "http://$GATEWAY_URL/productpage"; done

然后我们再次查看 Istio Mesh Dashboard,它应该反映所产生的流量,如下所示:

Mesh Dashboard

当然除此之外我们也可以查看到 Service 或者 Workload 的指标,比如我们可以查看 productpage 工作负载的指标:

workload dashboard

这里给出了每一个工作负载,以及该工作负载的入站工作负载(将请求发送到该工作负载的工作负载)和出站服务(此工作负载向其发送请求的服务)的详细指标。

Istio Dashboard 主要包括三个主要部分:

  • 网格摘要视图:这部分提供网格的全局摘要视图,并显示网格中(HTTP/gRPC 和 TCP)的工作负载。
  • 单独的服务视图:这部分提供关于网格中每个单独的(HTTP/gRPC 和 TCP)服务的请求和响应指标。这部分也提供关于该服务的客户端和服务工作负载的指标。
  • 单独的工作负载视图:这部分提供关于网格中每个单独的(HTTP/gRPC 和 TCP)工作负载的请求和响应指标。这部分也提供关于该工作负载的入站工作负载和出站服务的指标。

指标采集原理

从上面的例子我们可以看出当我们安装了 Istio 的 Prometheus 插件后,Istio 就会自动收集一些指标,但是我们并没有做任何的配置,那么 Istio 是如何收集指标的呢?如果我们想使用我们自己的 Prometheus 来收集指标,那么我们应该如何配置呢?

首先我们需要去查看下 Istio 的 Prometheus 插件的配置,通过 cat samples/addons/prometheus.yaml 命令查看配置文件,如下所示:

# Source: prometheus/templates/service.yaml
apiVersion: v1
kind: Service
metadata:
  labels:
    component: "server"
    app: prometheus
    release: prometheus
    chart: prometheus-19.6.1
    heritage: Helm
  name: prometheus
  namespace: istio-system
spec:
  ports:
    - name: http
      port: 9090
      protocol: TCP
      targetPort: 9090
  selector:
    component: "server"
    app: prometheus
    release: prometheus
  sessionAffinity: None
  type: "ClusterIP"
---
# Source: prometheus/templates/deploy.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    component: "server"
    app: prometheus
    release: prometheus
    chart: prometheus-19.6.1
    heritage: Helm
  name: prometheus
  namespace: istio-system
spec:
  selector:
    matchLabels:
      component: "server"
      app: prometheus
      release: prometheus
  replicas: 1
  strategy:
    type: Recreate
    rollingUpdate: null
  template:
    metadata:
      labels:
        component: "server"
        app: prometheus
        release: prometheus
        chart: prometheus-19.6.1
        heritage: Helm
        sidecar.istio.io/inject: "false"
    spec:
      enableServiceLinks: true
      serviceAccountName: prometheus
      containers:
        - name: prometheus-server-configmap-reload
          image: "jimmidyson/configmap-reload:v0.8.0"
          imagePullPolicy: "IfNotPresent"
          args:
            - --volume-dir=/etc/config
            - --webhook-url=http://127.0.0.1:9090/-/reload
          resources: {}
          volumeMounts:
            - name: config-volume
              mountPath: /etc/config
              readOnly: true
        - name: prometheus-server
          image: "prom/prometheus:v2.41.0"
          imagePullPolicy: "IfNotPresent"
          args:
            - --storage.tsdb.retention.time=15d
            - --config.file=/etc/config/prometheus.yml
            - --storage.tsdb.path=/data
            - --web.console.libraries=/etc/prometheus/console_libraries
            - --web.console.templates=/etc/prometheus/consoles
            - --web.enable-lifecycle
          ports:
            - containerPort: 9090
          readinessProbe:
            httpGet:
              path: /-/ready
              port: 9090
              scheme: HTTP
            initialDelaySeconds: 0
            periodSeconds: 5
            timeoutSeconds: 4
            failureThreshold: 3
            successThreshold: 1
          livenessProbe:
            httpGet:
              path: /-/healthy
              port: 9090
              scheme: HTTP
            initialDelaySeconds: 30
            periodSeconds: 15
            timeoutSeconds: 10
            failureThreshold: 3
            successThreshold: 1
          resources: {}
          volumeMounts:
            - name: config-volume
              mountPath: /etc/config
            - name: storage-volume
              mountPath: /data
              subPath: ""
      dnsPolicy: ClusterFirst
      securityContext:
        fsGroup: 65534
        runAsGroup: 65534
        runAsNonRoot: true
        runAsUser: 65534
      terminationGracePeriodSeconds: 300
      volumes:
        - name: config-volume
          configMap:
            name: prometheus
        - name: storage-volume
          emptyDir: {}
# 省略了部分配置

从上面的资源清单中可以看出 Prometheus 服务的核心配置文件为 --config.file=/etc/config/prometheus.yml,而该配置文件是通过上面的 prometheus 这个 ConfigMap 以 volume 形式挂载到容器中的,所以我们重点是查看这个 ConfigMap 的配置,如下所示:

# Source: prometheus/templates/cm.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  labels:
    component: "server"
    app: prometheus
    release: prometheus
    chart: prometheus-19.6.1
    heritage: Helm
  name: prometheus
  namespace: istio-system
data:
  allow-snippet-annotations: "false"
  alerting_rules.yml: |
    {}
  alerts: |
    {}
  prometheus.yml: |
    global:
      evaluation_interval: 1m
      scrape_interval: 15s
      scrape_timeout: 10s
    rule_files:
    - /etc/config/recording_rules.yml
    - /etc/config/alerting_rules.yml
    - /etc/config/rules
    - /etc/config/alerts
    scrape_configs:
    - job_name: prometheus
      static_configs:
      - targets:
        - localhost:9090
    - bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
      job_name: kubernetes-apiservers
      kubernetes_sd_configs:
      - role: endpoints
      relabel_configs:
      - action: keep
        regex: default;kubernetes;https
        source_labels:
        - __meta_kubernetes_namespace
        - __meta_kubernetes_service_name
        - __meta_kubernetes_endpoint_port_name
      scheme: https
      tls_config:
        ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
        insecure_skip_verify: true
    - bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
      job_name: kubernetes-nodes
      kubernetes_sd_configs:
      - role: node
      relabel_configs:
      - action: labelmap
        regex: __meta_kubernetes_node_label_(.+)
      - replacement: kubernetes.default.svc:443
        target_label: __address__
      - regex: (.+)
        replacement: /api/v1/nodes/$1/proxy/metrics
        source_labels:
        - __meta_kubernetes_node_name
        target_label: __metrics_path__
      scheme: https
      tls_config:
        ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
        insecure_skip_verify: true
    - bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
      job_name: kubernetes-nodes-cadvisor
      kubernetes_sd_configs:
      - role: node
      relabel_configs:
      - action: labelmap
        regex: __meta_kubernetes_node_label_(.+)
      - replacement: kubernetes.default.svc:443
        target_label: __address__
      - regex: (.+)
        replacement: /api/v1/nodes/$1/proxy/metrics/cadvisor
        source_labels:
        - __meta_kubernetes_node_name
        target_label: __metrics_path__
      scheme: https
      tls_config:
        ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
        insecure_skip_verify: true
    - honor_labels: true
      job_name: kubernetes-service-endpoints
      kubernetes_sd_configs:
      - role: endpoints
      relabel_configs:
      - action: keep
        regex: true
        source_labels:
        - __meta_kubernetes_service_annotation_prometheus_io_scrape
      - action: drop
        regex: true
        source_labels:
        - __meta_kubernetes_service_annotation_prometheus_io_scrape_slow
      - action: replace
        regex: (https?)
        source_labels:
        - __meta_kubernetes_service_annotation_prometheus_io_scheme
        target_label: __scheme__
      - action: replace
        regex: (.+)
        source_labels:
        - __meta_kubernetes_service_annotation_prometheus_io_path
        target_label: __metrics_path__
      - action: replace
        regex: (.+?)(?::\d+)?;(\d+)
        replacement: $1:$2
        source_labels:
        - __address__
        - __meta_kubernetes_service_annotation_prometheus_io_port
        target_label: __address__
      - action: labelmap
        regex: __meta_kubernetes_service_annotation_prometheus_io_param_(.+)
        replacement: __param_$1
      - action: labelmap
        regex: __meta_kubernetes_service_label_(.+)
      - action: replace
        source_labels:
        - __meta_kubernetes_namespace
        target_label: namespace
      - action: replace
        source_labels:
        - __meta_kubernetes_service_name
        target_label: service
      - action: replace
        source_labels:
        - __meta_kubernetes_pod_node_name
        target_label: node
    - honor_labels: true
      job_name: kubernetes-service-endpoints-slow
      kubernetes_sd_configs:
      - role: endpoints
      relabel_configs:
      - action: keep
        regex: true
        source_labels:
        - __meta_kubernetes_service_annotation_prometheus_io_scrape_slow
      - action: replace
        regex: (https?)
        source_labels:
        - __meta_kubernetes_service_annotation_prometheus_io_scheme
        target_label: __scheme__
      - action: replace
        regex: (.+)
        source_labels:
        - __meta_kubernetes_service_annotation_prometheus_io_path
        target_label: __metrics_path__
      - action: replace
        regex: (.+?)(?::\d+)?;(\d+)
        replacement: $1:$2
        source_labels:
        - __address__
        - __meta_kubernetes_service_annotation_prometheus_io_port
        target_label: __address__
      - action: labelmap
        regex: __meta_kubernetes_service_annotation_prometheus_io_param_(.+)
        replacement: __param_$1
      - action: labelmap
        regex: __meta_kubernetes_service_label_(.+)
      - action: replace
        source_labels:
        - __meta_kubernetes_namespace
        target_label: namespace
      - action: replace
        source_labels:
        - __meta_kubernetes_service_name
        target_label: service
      - action: replace
        source_labels:
        - __meta_kubernetes_pod_node_name
        target_label: node
      scrape_interval: 5m
      scrape_timeout: 30s
    - honor_labels: true
      job_name: prometheus-pushgateway
      kubernetes_sd_configs:
      - role: service
      relabel_configs:
      - action: keep
        regex: pushgateway
        source_labels:
        - __meta_kubernetes_service_annotation_prometheus_io_probe
    - honor_labels: true
      job_name: kubernetes-services
      kubernetes_sd_configs:
      - role: service
      metrics_path: /probe
      params:
        module:
        - http_2xx
      relabel_configs:
      - action: keep
        regex: true
        source_labels:
        - __meta_kubernetes_service_annotation_prometheus_io_probe
      - source_labels:
        - __address__
        target_label: __param_target
      - replacement: blackbox
        target_label: __address__
      - source_labels:
        - __param_target
        target_label: instance
      - action: labelmap
        regex: __meta_kubernetes_service_label_(.+)
      - source_labels:
        - __meta_kubernetes_namespace
        target_label: namespace
      - source_labels:
        - __meta_kubernetes_service_name
        target_label: service
    - honor_labels: true
      job_name: kubernetes-pods
      kubernetes_sd_configs:
      - role: pod
      relabel_configs:
      - action: keep
        regex: true
        source_labels:
        - __meta_kubernetes_pod_annotation_prometheus_io_scrape
      - action: drop
        regex: true
        source_labels:
        - __meta_kubernetes_pod_annotation_prometheus_io_scrape_slow
      - action: replace
        regex: (https?)
        source_labels:
        - __meta_kubernetes_pod_annotation_prometheus_io_scheme
        target_label: __scheme__
      - action: replace
        regex: (.+)
        source_labels:
        - __meta_kubernetes_pod_annotation_prometheus_io_path
        target_label: __metrics_path__
      - action: replace
        regex: (\d+);(([A-Fa-f0-9]{1,4}::?){1,7}[A-Fa-f0-9]{1,4})
        replacement: '[$2]:$1'
        source_labels:
        - __meta_kubernetes_pod_annotation_prometheus_io_port
        - __meta_kubernetes_pod_ip
        target_label: __address__
      - action: replace
        regex: (\d+);((([0-9]+?)(\.|$)){4})
        replacement: $2:$1
        source_labels:
        - __meta_kubernetes_pod_annotation_prometheus_io_port
        - __meta_kubernetes_pod_ip
        target_label: __address__
      - action: labelmap
        regex: __meta_kubernetes_pod_annotation_prometheus_io_param_(.+)
        replacement: __param_$1
      - action: labelmap
        regex: __meta_kubernetes_pod_label_(.+)
      - action: replace
        source_labels:
        - __meta_kubernetes_namespace
        target_label: namespace
      - action: replace
        source_labels:
        - __meta_kubernetes_pod_name
        target_label: pod
      - action: drop
        regex: Pending|Succeeded|Failed|Completed
        source_labels:
        - __meta_kubernetes_pod_phase
    - honor_labels: true
      job_name: kubernetes-pods-slow
      kubernetes_sd_configs:
      - role: pod
      relabel_configs:
      - action: keep
        regex: true
        source_labels:
        - __meta_kubernetes_pod_annotation_prometheus_io_scrape_slow
      - action: replace
        regex: (https?)
        source_labels:
        - __meta_kubernetes_pod_annotation_prometheus_io_scheme
        target_label: __scheme__
      - action: replace
        regex: (.+)
        source_labels:
        - __meta_kubernetes_pod_annotation_prometheus_io_path
        target_label: __metrics_path__
      - action: replace
        regex: (\d+);(([A-Fa-f0-9]{1,4}::?){1,7}[A-Fa-f0-9]{1,4})
        replacement: '[$2]:$1'
        source_labels:
        - __meta_kubernetes_pod_annotation_prometheus_io_port
        - __meta_kubernetes_pod_ip
        target_label: __address__
      - action: replace
        regex: (\d+);((([0-9]+?)(\.|$)){4})
        replacement: $2:$1
        source_labels:
        - __meta_kubernetes_pod_annotation_prometheus_io_port
        - __meta_kubernetes_pod_ip
        target_label: __address__
      - action: labelmap
        regex: __meta_kubernetes_pod_annotation_prometheus_io_param_(.+)
        replacement: __param_$1
      - action: labelmap
        regex: __meta_kubernetes_pod_label_(.+)
      - action: replace
        source_labels:
        - __meta_kubernetes_namespace
        target_label: namespace
      - action: replace
        source_labels:
        - __meta_kubernetes_pod_name
        target_label: pod
      - action: drop
        regex: Pending|Succeeded|Failed|Completed
        source_labels:
        - __meta_kubernetes_pod_phase
      scrape_interval: 5m
      scrape_timeout: 30s
  recording_rules.yml: |
    {}
  rules: |
    {}
---

这个配置文件中描述了 6 个指标抓取任务的配置:

  • prometheus:抓取 Prometheus 服务自身的指标。
  • kubernetes-apiservers:抓取 Kubernetes API 服务器的指标。
  • kubernetes-nodes:抓取 Kubernetes 节点的指标。
  • kubernetes-nodes-cadvisor:抓取 Kubernetes 节点的 cadvisor 指标,主要包括容器的 CPU、内存、网络、磁盘等指标。
  • kubernetes-service-endpoints:抓取 Kubernetes 服务端点的指标。
  • kubernetes-pods:抓取 Kubernetes Pod 的指标。

prometheus 配置

这里我们可以重点关注下 kubernetes-pods 这个指标抓取任务的配置,因为我们大部分的指标数据都是通过 Pod 的 Envoy Sidecar 来提供的。

从配置上可以看到这是基于 pod 的服务发现方式:

  • 首先只会保留 __meta_kubernetes_pod_annotation_prometheus_io_scrape 这个源标签为 true 的指标数据,这个源标签表示的是如果 Pod 的 annotation 注解中有 prometheus.io/scrape 标签,且值为 true,则会保留该指标数据,否则会丢弃该指标数据。
  • 然后根据 prometheus.io/scheme 注解来配置协议为 http 或者 https。
  • 根据 prometheus.io/path 注解来配置抓取路径。
  • 根据 prometheus.io/port 注解来配置抓取端口。
  • 将 prometheus.io/param 注解的值映射为 Prometheus 的标签。
  • 然后还会将 pod 的标签通过 labelmap 映射为 Prometheus 的标签;最后还会将 pod 的 namespace 和 pod 的名称映射为 Prometheus 的标签。
  • 最后需要判断 Pod 的 phase 状态,只有当 Pod 的 phase 状态为 Running 时才会保留该指标数据,否则会丢弃该指标数据。

比如我们查询 istio_requests_total{app="productpage", destination_app="details"} 这个指标,如下所示:

测试

该查询语句的查询结果为:

istio_requests_total{
    app="details",
    connection_security_policy="mutual_tls",
    destination_app="details",
    destination_canonical_revisinotallow="v1",
    destination_canonical_service="details",
    destination_cluster="Kubernetes",
    destination_principal="spiffe://cluster.local/ns/default/sa/bookinfo-details",
    destination_service="details.default.svc.cluster.local",
    destination_service_name="details",
    destination_service_namespace="default",
    destination_versinotallow="v1",
    destination_workload="details-v1",
    destination_workload_namespace="default",
    instance="10.244.2.74:15020",
    job="kubernetes-pods",
    namespace="default",
    pod="details-v1-5f4d584748-9fflw",
    pod_template_hash="5f4d584748",
    reporter="destination",
    request_protocol="http",
    response_code="200",
    response_flags="-",
    security_istio_io_tlsMode="istio",
    service_istio_io_canonical_name="details",
    service_istio_io_canonical_revisinotallow="v1",
    source_app="productpage",
    source_canonical_revisinotallow="v1",
    source_canonical_service="productpage",
    source_cluster="Kubernetes",
    source_principal="spiffe://cluster.local/ns/default/sa/bookinfo-productpage",
    source_versinotallow="v1",
    source_workload="productpage-v1",
    source_workload_namespace="default",
    versinotallow="v1"
}  362

该查询表示的是从 productpage 服务到 details 服务的请求总次数,从查询结果可以看出该指标就是来源于 job="kubernetes-pods" 这个指标抓取任务,那说明这个指标数据是通过服务发现方式从 Pod 中抓取的。我们可以查看下 productpage Pod 的信息,如下所示:

$ kubectl get pods productpage-v1-564d4686f-l8kxr -oyaml
apiVersion: v1
kind: Pod
metadata:
  annotations:
    istio.io/rev: default
    kubectl.kubernetes.io/default-container: productpage
    kubectl.kubernetes.io/default-logs-container: productpage
    prometheus.io/path: /stats/prometheus
    prometheus.io/port: "15020"
    prometheus.io/scrape: "true"
    sidecar.istio.io/status: '{"initContainers":["istio-init"],"containers":["istio-proxy"],"volumes":["workload-socket","credential-socket","workload-certs","istio-envoy","istio-data","istio-podinfo","istio-token","istiod-ca-cert"],"imagePullSecrets":null,"revision":"default"}'
  labels:
    app: productpage
    pod-template-hash: 564d4686f
    security.istio.io/tlsMode: istio
    service.istio.io/canonical-name: productpage
    service.istio.io/canonical-revision: v1
    version: v1
  name: productpage-v1-564d4686f-l8kxr
  namespace: default
spec:
  containers:
  - image: docker.io/istio/examples-bookinfo-productpage-v1:1.18.0
    imagePullPolicy: IfNotPresent
# ......

我们从上面的资源清单中可以看到该 Pod 包含如下几个注解:

  • prometheus.io/path: /stats/prometheus
  • prometheus.io/port: "15020"
  • prometheus.io/scrape: "true"

这些注解就是用来配置 Prometheus 服务发现的,其中 prometheus.io/scrape: "true" 表示该 Pod 的指标数据是需要被抓取的,而 prometheus.io/path: /stats/prometheus 和 prometheus.io/port: "15020" 则是用来配置抓取路径和抓取端口的,当 Prometheus 发现这个 Pod 后根据配置就可以通过 <pod ip>:15020/stats/prometheus 这个路径来抓取该 Pod 的指标数据了,这个路径就是 Envoy Sidecar 提供的 /stats/prometheus 路径,而 15020 则是 Envoy Sidecar 的端口,这个端口是通过 istio-proxy 这个容器配置的静态监听器暴露出来的。

当然定义的标签也被映射为 Prometheus 的标签了,从结果来看除了 Pod 的这些标签之外,Envoy Sidecar 也会自己添加很多相关标签,主要是标明 destination 和 source 的信息,有了这些标签我们就可以很方便的对指标进行查询了。Envoy Sidecar 自行添加的一些主要标签如下所示:

  • reporter:标识请求指标的上报端,如果指标由服务端 Istio 代理上报,则设置为 destination,如果指标由客户端 Istio 代理或网关上报,则设置为 source。
  • source_workload:标识源工作负载的名称,如果缺少源信息,则标识为 unknown。
  • source_workload_namespace:标识源工作负载的命名空间,如果缺少源信息,则标识为 unknown。
  • source_principal:标识流量源的对等主体,当使用对等身份验证时设置。
  • source_app:根据源工作负载的 app 标签标识源应用程序,如果源信息丢失,则标识为 unknown。
  • source_version:标识源工作负载的版本,如果源信息丢失,则标识为 unknown。
  • destination_workload:标识目标工作负载的名称,如果目标信息丢失,则标识为 unknown。
  • destination_workload_namespace:标识目标工作负载的命名空间,如果目标信息丢失,则标识为 unknown。
  • destination_principal:标识流量目标的对等主体,使用对等身份验证时设置。
  • destination_app:它根据目标工作负载的 app 标签标识目标应用程序,如果目标信息丢失,则标识为 unknown。
  • destination_version:标识目标工作负载的版本,如果目标信息丢失,则标识为 unknown。
  • destination_service:标识负责传入请求的目标服务主机,例如:details.default.svc.cluster.local。
  • destination_service_name:标识目标服务名称,例如 details。
  • destination_service_namespace:标识目标服务的命名空间。
  • request_protocol:标识请求的协议,设置为请求或连接协议。
  • response_code:标识请求的响应代码,此标签仅出现在 HTTP 指标上。
  • connection_security_policy:标识请求的服务认证策略,当 Istio 使用安全策略来保证通信安全时,如果指标由服务端 Istio 代理上报,则将其设置为 mutual_tls。如果指标由客户端 Istio 代理上报,由于无法正确填充安全策略,因此将其设置为 unknown。
  • response_flags:有关来自代理的响应或连接的其他详细信息。
  • Canonical Service:工作负载属于一个 Canonical 服务,而 Canonical 服务却可以属于多个服务。Canonical 服务具有名称和修订版本,因此会产生以下标签:
  • source_canonical_service
  • source_canonical_revision
  • destination_canonical_service
  • destination_canonical_revision
  • destination_cluster:目标工作负载的集群名称,这是由集群安装时的 global.multiCluster.clusterName 设置的。
  • source_cluster:源工作负载的集群名称,这是由集群安装时的 global.multiCluster.clusterName 设置的。
  • grpc_response_status: 这标识了 gRPC 的响应状态,这个标签仅出现在 gRPC 指标上。

对于 Istio 来说包括 COUNTER 和 DISTRIBUTION 两种指标类型,这两种指标类型对应我们比较熟悉的计数器和直方图。

对于 HTTP,HTTP/2 和 GRPC 通信,Istio 生成以下指标:

  • 请求数 (istio_requests_total): 这都是一个 COUNTER 类型的指标,用于记录 Istio 代理处理的总请求数。
  • 请求时长 (istio_request_duration_milliseconds): 这是一个 DISTRIBUTION 类型的指标,用于测量请求的持续时间。
  • 请求体大小 (istio_request_bytes): 这是一个 DISTRIBUTION 类型的指标,用来测量 HTTP 请求主体大小。
  • 响应体大小 (istio_response_bytes): 这是一个 DISTRIBUTION 类型的指标,用来测量 HTTP 响应主体大小。
  • gRPC 请求消息数 (istio_request_messages_total): 这是一个 COUNTER 类型的指标,用于记录从客户端发送的 gRPC 消息总数。
  • gRPC 响应消息数 (istio_response_messages_total): 这是一个 COUNTER 类型的指标,用于记录从服务端发送的 gRPC 消息总数。

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

  • TCP 发送字节大小 (istio_tcp_sent_bytes_total): 这是一个 COUNTER 类型的指标,用于测量在 TCP 连接情况下响应期间发送的总字节数。
  • TCP 接收字节大小 (istio_tcp_received_bytes_total): 这是一个 COUNTER 类型的指标,用于测量在 TCP 连接情况下请求期间接收到的总字节数。
  • TCP 已打开连接数 (istio_tcp_connections_opened_total): 这是一个 COUNTER 类型的指标,用于记录 TCP 已打开的连接总数。
  • TCP 已关闭连接数 (istio_tcp_connections_closed_total): 这是一个 COUNTER 类型的指标,用于记录 TCP 已关闭的连接总数。

当我们了解了 Istio 指标数据采集的原理后,我们就可以根据自身的需求来定制了,比如在我们的监控系统采样的是 Prometheus Operator 方式,那么也应该知道该如何来配置采集这些指标数据了。

自定义指标

除了 Istio 自带的指标外,我们还可以自定义指标,要自定指标需要用到 Istio 提供的 Telemetry API,该 API 能够灵活地配置指标、访问日志和追踪数据。Telemetry API 现在已经成为 Istio 中的主流 API。

需要注意的是,Telemetry API 无法与 EnvoyFilter 一起使用。请查看此问题 issue。

从 Istio 版本 1.18 版本开始,Prometheus 的 EnvoyFilter 默认不会被安装, 而是通过 meshConfig.defaultProviders 来启用它,我们应该使用 Telemetry API 来进一步定制遥测流程,新的 Telemetry API 不但语义更加清晰,功能也一样没少。对于 Istio 1.18 之前的版本,应该使用以下的 IstioOperator 配置进行安装:

apiVersion: install.istio.io/v1alpha1
kind: IstioOperator
spec:
  values:
    telemetry:
      enabled: true
      v2:
        enabled: false

Telemetry 资源对象的定义如下所示:

$ kubectl explain Telemetry.spec
GROUP:      telemetry.istio.io
KIND:       Telemetry
VERSION:    v1alpha1

FIELD: spec <Object>

DESCRIPTION:
    Telemetry configuration for workloads. See more details at:
    https://istio.io/docs/reference/config/telemetry.html

FIELDS:
  accessLogging <[]Object>
    Optional.

  metrics       <[]Object>
    Optional.

  selector      <Object>
    Optional.

  tracing       <[]Object>
    Optional.

可以看到 Telemetry 资源对象包含了 accessLogging、metrics、selector 和 tracing 四个字段,其中 accessLogging 和 tracing 字段用于配置访问日志和追踪数据,而 metrics 字段用于配置指标数据,selector 字段用于配置哪些工作负载需要采集指标数据。

我们这里先来看下 metrics 字段的配置,该字段的定义如下所示:

$ kubectl explain Telemetry.spec.metrics
GROUP:      telemetry.istio.io
KIND:       Telemetry
VERSION:    v1alpha1

FIELD: metrics <[]Object>

DESCRIPTION:
    Optional.

FIELDS:
  overrides     <[]Object>
    Optional.

  providers     <[]Object>
    Optional.

  reportingInterval     <string>
    Optional.

可以看到 metrics 字段包含了 overrides、providers 和 reportingInterval 三个字段。

  • overrides 字段用于配置指标数据的采集方式。
  • providers 字段用于配置指标数据的提供者,这里一般配置为 prometheus。
  • reportingInterval 字段用于配置指标数据的上报间隔,可选的。目前仅支持 TCP 度量,但将来可能会将其用于长时间的 HTTP 流。默认持续时间为 5 秒。

删除标签

比如以前需要在 Istio 配置的 meshConfig 部分配置遥测,这种方式不是很方便。比如我们想从 Istio 指标中删除一些标签以减少基数,那么你的配置中可能有这样一个部分:

# istiooperator.yaml
telemetry:
  enabled: true
  v2:
    enabled: true
    prometheus:
      enabled: true
      configOverride:
        outboundSidecar:
          debug: false
          stat_prefix: istio
          metrics:
            - tags_to_remove:
                - destination_canonical_service
                  ...

现在我们可以通过 Telemetry API 来配置,如下所示:

apiVersion: telemetry.istio.io/v1alpha1
kind: Telemetry
metadata:
  name: remove-tags
  namespace: istio-system
spec:
  metrics:
    - providers:
        - name: prometheus # 指定指标数据的提供者
      overrides:
        - match: # 提供覆盖的范围,可用于选择个别指标,以及生成指标的工作负载模式(服务器和/或客户端)。如果未指定,则overrides 将应用于两种操作模式(客户端和服务器)的所有指标。
            metric: ALL_METRICS # Istio 标准指标之一
            mode: CLIENT_AND_SERVER # 控制选择的指标生成模式:客户端和/或服务端。
          tagOverrides: # 要覆盖的标签列表
            destination_canonical_service:
              operation: REMOVE
          # disabled: true  # 是否禁用指标

在上面的 Telemetry 资源对象中我们指定了一个 metrics 字段,表示用来自定义指标的,然后通过 providers.name 字段指定指标数据的提供者为 prometheus,然后最重要的是 overrides 字段,用于配置指标数据的采集方式。

其中 overrides.match.metric 字段用来指定要覆盖的 Istio 标准指标,支持指标如下所示:

名称

描述

ALL_METRICS

使用这个枚举表示应将覆盖应用于所有 Istio 默认指标。

REQUEST_COUNT

对应用程序的请求计数器,适用于 HTTP、HTTP/2 和 GRPC 流量。Prometheus 提供商将此指标导出为:istio_requests_total。Stackdriver 提供商将此指标导出为:istio.io/service/server/request_count(服务器模式)istio.io/service/client/request_count(客户端模式)

REQUEST_DURATION

请求持续时间的直方图,适用于 HTTP、HTTP/2 和 GRPC 流量。Prometheus 提供商将此指标导出为:istio_request_duration_milliseconds。Stackdriver 提供商将此指标导出为:istio.io/service/server/response_latencies(服务器模式)istio.io/service/client/roundtrip_latencies(客户端模式)

REQUEST_SIZE

请求体大小的直方图,适用于 HTTP、HTTP/2 和 GRPC 流量。Prometheus 提供商将此指标导出为:istio_request_bytes。Stackdriver 提供商将此指标导出为:istio.io/service/server/request_bytes(服务器模式)istio.io/service/client/request_bytes(客户端模式)

RESPONSE_SIZE

响应体大小的直方图,适用于 HTTP、HTTP/2 和 GRPC 流量。Prometheus 提供商将此指标导出为:istio_response_bytes。Stackdriver 提供商将此指标导出为:istio.io/service/server/response_bytes(服务器模式)istio.io/service/client/response_bytes(客户端模式)

TCP_OPENED_CONNECTIONS

工作负载生命周期中打开的 TCP 连接计数器。Prometheus 提供商将此指标导出为:istio_tcp_connections_opened_total。Stackdriver 提供商将此指标导出为:istio.io/service/server/connection_open_count(服务器模式)istio.io/service/client/connection_open_count(客户端模式)

TCP_CLOSED_CONNECTIONS

工作负载生命周期中关闭的 TCP 连接计数器。Prometheus 提供商将此指标导出为:istio_tcp_connections_closed_total。Stackdriver 提供商将此指标导出为:istio.io/service/server/connection_close_count(服务器模式)istio.io/service/client/connection_close_count(客户端模式)

TCP_SENT_BYTES

TCP 连接期间发送的响应字节计数器。Prometheus 提供商将此指标导出为:istio_tcp_sent_bytes_total。Stackdriver 提供商将此指标导出为:istio.io/service/server/sent_bytes_count(服务器模式)istio.io/service/client/sent_bytes_count(客户端模式)

TCP_RECEIVED_BYTES

TCP 连接期间接收的请求字节计数器。Prometheus 提供商将此指标导出为:istio_tcp_received_bytes_total。Stackdriver 提供商将此指标导出为:istio.io/service/server/received_bytes_count(服务器模式)istio.io/service/client/received_bytes_count(客户端模式)

GRPC_REQUEST_MESSAGES

每发送一个 gRPC 消息时递增的客户端计数器。Prometheus 提供商将此指标导出为:istio_request_messages_total

GRPC_RESPONSE_MESSAGES

每发送一个 gRPC 消息时递增的服务器计数器。Prometheus 提供商将此指标导出为:istio_response_messages_total

比如我们这里配置的指标为 ALL_METRICS 则表示要覆盖所有的 Istio 标准指标。

overrides.match.mode 则表示选择网络流量中底层负载的角色,如果负载是流量的目标(从负载的角度看,流量方向是入站),则将其视为作为 SERVER 运行。如果负载是网络流量的源头,则被视为处于 CLIENT 模式(流量从负载出站)。

名称

描述

CLIENT_AND_SERVER

选择适用于工作负载既是网络流量的源头,又是目标的场景。

CLIENT

选择适用于工作负载是网络流量的源头的场景。

SERVER

选择适用于工作负载是网络流量的目标的场景。

另外的 tagOverrides 字段表示要覆盖选定的指标中的标签名称和标签表达式的集合,该字段中的 key 是标签的名称,value 是对标签执行的操作,可以添加、删除标签,或覆盖其默认值。

字段

类型

描述

是否必需

operation

Operation

操作控制是否更新/添加一个标签,或者移除它。

value

string

当操作为 UPSERT 时才考虑值。值是基于属性的 CEL 表达式。例如:string(destination.port) 和 request.host。Istio 暴露所有标准的 Envoy 属性。此外,Istio 也将节点元数据作为属性暴露出来。更多信息请参见 自定义指标文档。

对应的操作 Operator 可以配置 UPSERT 和 REMOVE 两个操作:

名称

描述

UPSERT

使用提供的值表达式插入或更新标签。如果使用 UPSERT 操作,则必须指定 value 字段。

REMOVE

指定标签在生成时不应包含在指标中。

现在我们直接应用上面的这个资源对象,然后我们再去访问下 productpage 应用,再次验证下指标数据中是否包含我们移除的 destination_canonical_service 标签。

删除标签

从上面的结果可以看到,我们已经成功删除了 destination_canonical_service 标签,这样就可以减少指标数据的基数了,可以用同样的方法再去删除一些不需要的标签。

另外需要注意在 Telemetry 对象中我们还可以通过 selector 字段来配置哪些工作负载应用这个遥测策略,如果未设置,遥测策略将应用于与遥测策略相同的命名空间中的所有工作负载,当然如果是在 istio-system 命名空间中则会应用于所有命名空间中的工作负载。

添加指标

上面我们已经介绍了如何删除指标中的标签,那么我们也可以通过 Telemetry API 来添加指标中的标签,如下所示:

apiVersion: telemetry.istio.io/v1alpha1
kind: Telemetry
metadata:
  name: add-tags
spec:
  metrics:
    - overrides:
        - match:
            metric: REQUEST_COUNT
            mode: CLIENT
          tagOverrides:
            destination_x:
              operation: UPSERT
              value: "upstream_peer.labels['app'].value" # 必须加上双引号
        - match:
            metric: REQUEST_COUNT
          tagOverrides:
            destination_port:
              value: "string(destination.port)"
            request_host:
              value: "request.host"
      providers:
        - name: prometheus

在上面的这个资源对象中我们在 tagOverrides 中首先添加了如下的配置:

destination_x:
  operation: UPSERT
  value: "upstream_peer.labels['app'].value"

表示我们要添加一个名为 destination_x 的标签,然后通过 value 字段指定标签的值为 upstream_peer.labels['app'].value,这个值是一个 CEL 表达式(必须在 JSON 中用双引号引用字符串)。Istio 暴露了所有标准的 Envoy 属性,对于出站请求,对等方元数据作为上游对等方(upstream_peer)的属性可用;对于入站请求,对等方元数据作为下游对等方(downstream_peer)的属性可用,包含以下字段:

属性

类型

name

string

Pod 名

namespace

string

Pod 所在命名空间

labels

map

工作负载标签

owner

string

工作负载 owner

workload_name

string

工作负载名称

platform_metadata

map

平台元数据

istio_version

string

代理的版本标识

mesh_id

string

网格唯一 ID

app_containers

list<string>

应用容器的名称列表

cluster_id

string

工作负载所属的集群标识

例如,用于出站配置中的对等应用标签的表达式是 upstream_peer.labels['app'].value,所以上面我们最终添加的 destination_x 这个标签的值为上游对等方的 app 标签的值。

另外添加的两个标签 destination_port 和 request_host 的值分别为 string(destination.port) 和 request.host,这两个值就来源于暴露的 Envoy 属性。

另外这个资源对象我们指定的是 default 命名空间,则只会对 default 命名空间中的工作负载应用这个遥测策略。

同样应用这个资源对象后,再次访问 productpage 应用产生指标,现在我们可以看到指标中已经包含了我们添加的标签了。

添加标签

禁用指标

对于禁用指标则相对更简单了。比如我们通过以下配置禁用所有指标:

apiVersion: telemetry.istio.io/v1alpha1
kind: Telemetry
metadata:
  name: remove-all-metrics
  namespace: istio-system
spec:
  metrics:
    - providers:
        - name: prometheus
      overrides:
        - disabled: true
          match:
            mode: CLIENT_AND_SERVER
            metric: ALL_METRICS

通过以下配置禁用 REQUEST_COUNT 指标:

apiVersion: telemetry.istio.io/v1alpha1
kind: Telemetry
metadata:
  name: remove-request-count
  namespace: istio-system
spec:
  metrics:
    - providers:
        - name: prometheus
      overrides:
        - disabled: true
          match:
            mode: CLIENT_AND_SERVER
            metric: REQUEST_COUNT

通过以下配置禁用客户端的 REQUEST_COUNT 指标:

apiVersion: telemetry.istio.io/v1alpha1
kind: Telemetry
metadata:
  name: remove-client
  namespace: istio-system
spec:
  metrics:
    - providers:
        - name: prometheus
      overrides:
        - disabled: true
          match:
            mode: CLIENT
            metric: REQUEST_COUNT

通过以下配置禁用服务端的 REQUEST_COUNT 指标:

apiVersion: telemetry.istio.io/v1alpha1
kind: Telemetry
metadata:
  name: remove-server
  namespace: istio-system
spec:
  metrics:
    - providers:
        - name: prometheus
      overrides:
        - disabled: true
          match:
            mode: SERVER
            metric: REQUEST_COUNT

到这里我们就了解了如何通过 Telemetry API 来自定义指标了,这样我们就可以根据自身的需求来定制了。

责任编辑:姜华 来源: k8s技术圈
相关推荐

2022-09-27 21:32:14

Dapr指标与日志

2023-01-09 11:23:03

系统

2023-03-09 08:00:22

2022-08-30 08:22:14

可观测性监控软件

2023-12-05 07:21:17

IstioEnvoy

2023-05-18 22:44:09

2019-12-05 07:55:47

监控指标巡检指标数据库

2023-09-20 11:33:41

服务网格监控报警

2023-10-13 13:40:29

2020-06-29 10:35:26

监控系统架构技术

2023-08-21 09:37:57

MySQL工具MariaDB

2023-09-20 16:11:32

云原生分布式系统

2022-07-05 15:50:25

Kubernetes工具DevOps

2023-10-26 08:47:30

云原生数据采集

2024-03-27 14:43:07

.NET Core后端监控可观测性

2021-11-19 09:40:50

数据技术实践

2024-01-15 05:55:33

2021-05-04 18:28:23

Apache KafkSigNoz开源

2023-08-28 10:51:01

Raptor可观测性平台WOT
点赞
收藏

51CTO技术栈公众号