Istio 为网格内所有的服务通信生成详细的遥测数据。这种遥测技术提供了服务行为的可观测性,使运维人员能够排查故障、维护和优化应用程序,而不会给开发人员带来其他额外的负担。通过 Istio,运维人员可以全面了解到受监控的服务如何与其他服务以及 Istio 组件进行交互。
Istio 生成以下类型的遥测数据,以提供对整个服务网格的可观测性:
接下来我们将分别来学习 Istio 的指标、分布式追踪和访问日志是如何工作的。
指标提供了一种以聚合的方式监控和理解行为的方法。为了监控服务行为,Istio 为服务网格中所有出入网格,以及网格内部的服务流量都生成了指标,这些指标提供了关于行为的信息,例如总流量、错误率和请求响应时间。除了监控网格中服务的行为外,监控网格本身的行为也很重要。Istio 组件还可以导出自身内部行为的指标,以提供对网格控制平面的功能和健康情况的洞察能力。
整体上 Istio 的指标可以分成 3 个级别:代理级别、服务级别、控制平面级别。
代理级别指标
Istio 指标收集从 Envoy Sidecar 代理开始,每个代理为通过它的所有流量(入站和出站)生成一组丰富的指标。代理还提供关于它本身管理功能的详细统计信息,包括配置信息和健康信息。
Envoy 生成的指标提供了资源(例如监听器和集群)粒度上的网格监控。因此,为了监控 Envoy 指标,需要了解网格服务和 Envoy 资源之间的连接。
Istio 允许运维人员在每个工作负载实例上选择生成和收集哪些 Envoy 指标。默认情况下,Istio 只支持 Envoy 生成的统计数据的一小部分,以避免依赖过多的后端服务,还可以减少与指标收集相关的 CPU 开销。但是运维人员可以在需要时轻松地扩展收集到的代理指标数据。这样我们可以有针对性地调试网络行为,同时降低了跨网格监控的总体成本。
服务级别指标
除了代理级别指标之外,Istio 还提供了一组用于监控服务通信的面向服务的指标。这些指标涵盖了四个基本的服务监控需求:延迟、流量、错误和饱和情况。而且 Istio 还自带了一组默认的仪表板,用于监控基于这些指标的服务行为。默认情况下,标准 Istio 指标会导出到 Prometheus。而且服务级别指标的使用完全是可选的,运维人员可以根据自身的需求来选择关闭指标的生成和收集。
控制平面指标
另外 Istio 控制平面还提供了一组自我监控指标。这些指标允许监控 Istio 自己的行为。
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_version="v3"}
该查询返回所有请求 reviews
服务 v3 版本的当前总次数。
过去 5 分钟 productpage
服务所有实例的请求频次:
rate(istio_requests_total{destination_service=~"productpage.*", response_code="200"}[5m])
在 Graph
选项卡中,可以看到查询结果的图形化表示。
Graph
对于 PromQL 语句的使用可以参考官方文档 Prometheus Querying Basics,或者我们的 《Prometheus 入门到实战》课程,这并不是我们这里的重点,所以就不再详细介绍了。
虽然我们这里并没有做任何的配置,但是 Istio 默认已经为我们收集了一些指标,所以我们可以直接查询到这些指标了。
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 主要包括三个主要部分:
从上面的例子我们可以看出当我们安装了 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 或者 httpsprometheus.io/path
注解来配置抓取路径prometheus.io/port
注解来配置抓取端口;prometheus.io/param
注解的值映射为 Prometheus 的标签;labelmap
映射为 Prometheus 的标签;最后还会将 pod 的 namespace 和 pod 的名称映射为 Prometheus 的标签。Running
时才会保留该指标数据,否则会丢弃该指标数据。比如我们查询 istio_requests_total{app="productpage", destination_app="details"}
这个指标,如下所示:
测试
该查询语句的查询结果为:
istio_requests_total{
app="details",
connection_security_policy="mutual_tls",
destination_app="details",
destination_canonical_revision="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_version="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_revision="v1",
source_app="productpage",
source_canonical_revision="v1",
source_canonical_service="productpage",
source_cluster="Kubernetes",
source_principal="spiffe://cluster.local/ns/default/sa/bookinfo-productpage",
source_version="v1",
source_workload="productpage-v1",
source_workload_namespace="default",
version="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
服务,而 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 响应主体大小。istio_request_messages_total
): 这是一个 COUNTER
类型的指标,用于记录从客户端发送的 gRPC 消息总数。istio_response_messages_total
): 这是一个 COUNTER
类型的指标,用于记录从服务端发送的 gRPC 消息总数。对于 TCP 流量,Istio 生成以下指标:
istio_tcp_sent_bytes_total
): 这是一个 COUNTER
类型的指标,用于测量在 TCP 连接情况下响应期间发送的总字节数。istio_tcp_received_bytes_total
): 这是一个 COUNTER
类型的指标,用于测量在 TCP 连接情况下请求期间接收到的总字节数。istio_tcp_connections_opened_total
): 这是一个 COUNTER
类型的指标,用于记录 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 来自定义指标了,这样我们就可以根据自身的需求来定制了。