Skip to content

7、Istio可观测性

更新于:2023年12月7日08:

Istio可观测性

image-20231129072302901

前言

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

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

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

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

1、指标

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

指标类别

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

1、代理级别指标

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

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

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

2、服务级别指标

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

3、控制平面指标

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

通过 Prometheus 查询指标

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

  • 首先要确保 Istio 的 prometheus组件已经启用,如果没有启用可以通过以下命令启用:
bash
[root@master1 ~]#cd istio-1.19.3/[root@master1 istio-1.19.3]#ls samples/addons/extrasgrafana.yamljaeger.yamlkiali.yamlloki.yamlprometheus.yamlREADME.md#部署kubectlapply-fsamples/addons

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

  • 安装后可以通过以下命令查看 Prometheus 服务状态:
bash
$kubectlgetsvcprometheus-nistio-systemNAMETYPECLUSTER-IPEXTERNAL-IPPORT(S) AGEprometheusClusterIP10.106.228.196<none>9090/TCP25d$kubectlgetpods-nistio-system-lapp=prometheusNAMEREADYSTATUSRESTARTSAGEprometheus-5d5d6d6fc-2gtxm2/2Running025d
  • 首先在浏览器中访问 http:# 也可以创建 Ingress 或者 Gateway 来访问 Prometheus UI[root@master1 istio-1.19.3]#istioctl dashboard prometheushttp:Failedtoopenbrowser;openhttp:C[root@master1istio-1.19.3]#istioctldashboardprometheus--address0.0.0.0http:Failedtoopenbrowser;openhttp:NAMETYPECLUSTER-IPEXTERNAL-IPPORT(S) AGEgrafanaClusterIP10.96.197.74<none>3000/TCP25d$kubectl-nistio-systemgetpods-lapp=grafanaNAMEREADYSTATUSRESTARTSAGEgrafana-5f9b8c6c5d-jv65v1/1Running025d
  • 然后我们可以通过以下命令来打开 Grafana UI:
bash
istioctldashboardgrafana# 也可以创建 Ingress 或者 Gateway 来访问 Grafanaistioctldashboardgrafana--address0.0.0.0

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

img

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

img

从图中可以看出现在有一些数据,但是并不是很多,这是因为我们现在还没产生一些流量请求。

  • 下面我们可以用下面的命令向 productpage 服务发送 100 个请求:
bash
fori in$(seq1100);docurl-s-o/dev/null"http:apiVersion:v1kind:Servicemetadata:labels:component:"server"app:prometheusrelease:prometheuschart:prometheus-19.6.1heritage:Helmname:prometheusnamespace:istio-systemspec:ports:- name:httpport:9090protocol:TCPtargetPort:9090selector:component:"server"app:prometheusrelease:prometheussessionAffinity:Nonetype:"ClusterIP"---# Source:prometheus/templates/deploy.yamlapiVersion:apps/v1kind:Deploymentmetadata:labels:component:"server"app:prometheusrelease:prometheuschart:prometheus-19.6.1heritage:Helmname:prometheusnamespace:istio-systemspec:selector:matchLabels:component:"server"app:prometheusrelease:prometheusreplicas:1strategy:type:RecreaterollingUpdate:nulltemplate:metadata:labels:component:"server"app:prometheusrelease:prometheuschart:prometheus-19.6.1heritage:Helmsidecar.istio.io/inject:"false"spec:enableServiceLinks:trueserviceAccountName:prometheuscontainers:- name:prometheus-server-configmap-reloadimage:"jimmidyson/configmap-reload:v0.8.0"imagePullPolicy:"IfNotPresent"args:- --volume-dir=/etc/config- --webhook-url=http:resources:{}volumeMounts:- name:config-volumemountPath:/etc/configreadOnly:true- name:prometheus-serverimage:"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-lifecycleports:- containerPort:9090readinessProbe:httpGet:path:/-/readyport:9090scheme:HTTPinitialDelaySeconds:0periodSeconds:5timeoutSeconds:4failureThreshold:3successThreshold:1livenessProbe:httpGet:path:/-/healthyport:9090scheme:HTTPinitialDelaySeconds:30periodSeconds:15timeoutSeconds:10failureThreshold:3successThreshold:1resources:{}volumeMounts:- name:config-volumemountPath:/etc/config- name:storage-volumemountPath:/datasubPath:""dnsPolicy:ClusterFirstsecurityContext:fsGroup:65534runAsGroup:65534runAsNonRoot:truerunAsUser:65534terminationGracePeriodSeconds:300volumes:- name:config-volumeconfigMap:name:prometheus- name:storage-volumeemptyDir:{} ##临时的# 省略了部分配置

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

  • 所以我们重点是查看这个 ConfigMap 的配置,如下所示:
yaml
# Source:prometheus/templates/cm.yamlapiVersion:v1kind:ConfigMapmetadata:labels:component:"server"app:prometheusrelease:prometheuschart:prometheus-19.6.1heritage:Helmname:prometheusnamespace:istio-systemdata:allow-snippet-annotations:"false"alerting_rules.yml:|{}alerts:|{}prometheus.yml:|global:evaluation_interval:1mscrape_interval:15sscrape_timeout:10srule_files:- /etc/config/recording_rules.yml- /etc/config/alerting_rules.yml- /etc/config/rules- /etc/config/alertsscrape_configs:- job_name:prometheusstatic_configs:- targets:- localhost:9090- bearer_token_file:/var/run/secrets/kubernetes.io/serviceaccount/tokenjob_name:kubernetes-apiserverskubernetes_sd_configs:- role:endpointsrelabel_configs:- action:keepregex:default;kubernetes;httpssource_labels:- __meta_kubernetes_namespace- __meta_kubernetes_service_name- __meta_kubernetes_endpoint_port_namescheme:httpstls_config:ca_file:/var/run/secrets/kubernetes.io/serviceaccount/ca.crtinsecure_skip_verify:true- bearer_token_file:/var/run/secrets/kubernetes.io/serviceaccount/tokenjob_name:kubernetes-nodeskubernetes_sd_configs:- role:noderelabel_configs:- action:labelmapregex:__meta_kubernetes_node_label_(.+)- replacement:kubernetes.default.svc:443target_label:__address__- regex:(.+)replacement:/api/v1/nodes/$1/proxy/metricssource_labels:- __meta_kubernetes_node_nametarget_label:__metrics_path__scheme:httpstls_config:ca_file:/var/run/secrets/kubernetes.io/serviceaccount/ca.crtinsecure_skip_verify:true- bearer_token_file:/var/run/secrets/kubernetes.io/serviceaccount/tokenjob_name:kubernetes-nodes-cadvisorkubernetes_sd_configs:- role:noderelabel_configs:- action:labelmapregex:__meta_kubernetes_node_label_(.+)- replacement:kubernetes.default.svc:443target_label:__address__- regex:(.+)replacement:/api/v1/nodes/$1/proxy/metrics/cadvisorsource_labels:- __meta_kubernetes_node_nametarget_label:__metrics_path__scheme:httpstls_config:ca_file:/var/run/secrets/kubernetes.io/serviceaccount/ca.crtinsecure_skip_verify:true- honor_labels:truejob_name:kubernetes-service-endpointskubernetes_sd_configs:- role:endpointsrelabel_configs:- action:keepregex:truesource_labels:- __meta_kubernetes_service_annotation_prometheus_io_scrape- action:dropregex:truesource_labels:- __meta_kubernetes_service_annotation_prometheus_io_scrape_slow- action:replaceregex:(https?)source_labels:- __meta_kubernetes_service_annotation_prometheus_io_schemetarget_label:__scheme__- action:replaceregex:(.+)source_labels:- __meta_kubernetes_service_annotation_prometheus_io_pathtarget_label:__metrics_path__- action:replaceregex:(.+?)(?::\d+)?;(\d+)replacement:$1:$2source_labels:- __address__- __meta_kubernetes_service_annotation_prometheus_io_porttarget_label:__address__- action:labelmapregex:__meta_kubernetes_service_annotation_prometheus_io_param_(.+)replacement:__param_$1- action:labelmapregex:__meta_kubernetes_service_label_(.+)- action:replacesource_labels:- __meta_kubernetes_namespacetarget_label:namespace- action:replacesource_labels:- __meta_kubernetes_service_nametarget_label:service- action:replacesource_labels:- __meta_kubernetes_pod_node_nametarget_label:node- honor_labels:truejob_name:kubernetes-service-endpoints-slowkubernetes_sd_configs:- role:endpointsrelabel_configs:- action:keepregex:truesource_labels:- __meta_kubernetes_service_annotation_prometheus_io_scrape_slow- action:replaceregex:(https?)source_labels:- __meta_kubernetes_service_annotation_prometheus_io_schemetarget_label:__scheme__- action:replaceregex:(.+)source_labels:- __meta_kubernetes_service_annotation_prometheus_io_pathtarget_label:__metrics_path__- action:replaceregex:(.+?)(?::\d+)?;(\d+)replacement:$1:$2source_labels:- __address__- __meta_kubernetes_service_annotation_prometheus_io_porttarget_label:__address__- action:labelmapregex:__meta_kubernetes_service_annotation_prometheus_io_param_(.+)replacement:__param_$1- action:labelmapregex:__meta_kubernetes_service_label_(.+)- action:replacesource_labels:- __meta_kubernetes_namespacetarget_label:namespace- action:replacesource_labels:- __meta_kubernetes_service_nametarget_label:service- action:replacesource_labels:- __meta_kubernetes_pod_node_nametarget_label:nodescrape_interval:5mscrape_timeout:30s- honor_labels:truejob_name:prometheus-pushgatewaykubernetes_sd_configs:- role:servicerelabel_configs:- action:keepregex:pushgatewaysource_labels:- __meta_kubernetes_service_annotation_prometheus_io_probe- honor_labels:truejob_name:kubernetes-serviceskubernetes_sd_configs:- role:servicemetrics_path:/probeparams:module:- http_2xxrelabel_configs:- action:keepregex:truesource_labels:- __meta_kubernetes_service_annotation_prometheus_io_probe- source_labels:- __address__target_label:__param_target- replacement:blackboxtarget_label:__address__- source_labels:- __param_targettarget_label:instance- action:labelmapregex:__meta_kubernetes_service_label_(.+)- source_labels:- __meta_kubernetes_namespacetarget_label:namespace- source_labels:- __meta_kubernetes_service_nametarget_label:service- honor_labels:truejob_name:kubernetes-podskubernetes_sd_configs:- role:podrelabel_configs:- action:keepregex:truesource_labels:- __meta_kubernetes_pod_annotation_prometheus_io_scrape- action:dropregex:truesource_labels:- __meta_kubernetes_pod_annotation_prometheus_io_scrape_slow- action:replaceregex:(https?)source_labels:- __meta_kubernetes_pod_annotation_prometheus_io_schemetarget_label:__scheme__- action:replaceregex:(.+)source_labels:- __meta_kubernetes_pod_annotation_prometheus_io_pathtarget_label:__metrics_path__- action:replaceregex:(\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_iptarget_label:__address__- action:replaceregex:(\d+);((([0-9]+?)(\.|$)){4})replacement:$2:$1source_labels:- __meta_kubernetes_pod_annotation_prometheus_io_port- __meta_kubernetes_pod_iptarget_label:__address__- action:labelmapregex:__meta_kubernetes_pod_annotation_prometheus_io_param_(.+)replacement:__param_$1- action:labelmapregex:__meta_kubernetes_pod_label_(.+)- action:replacesource_labels:- __meta_kubernetes_namespacetarget_label:namespace- action:replacesource_labels:- __meta_kubernetes_pod_nametarget_label:pod- action:dropregex:Pending|Succeeded|Failed|Completedsource_labels:- __meta_kubernetes_pod_phase- honor_labels:truejob_name:kubernetes-pods-slowkubernetes_sd_configs:- role:podrelabel_configs:- action:keepregex:truesource_labels:- __meta_kubernetes_pod_annotation_prometheus_io_scrape_slow- action:replaceregex:(https?)source_labels:- __meta_kubernetes_pod_annotation_prometheus_io_schemetarget_label:__scheme__- action:replaceregex:(.+)source_labels:- __meta_kubernetes_pod_annotation_prometheus_io_pathtarget_label:__metrics_path__- action:replaceregex:(\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_iptarget_label:__address__- action:replaceregex:(\d+);((([0-9]+?)(\.|$)){4})replacement:$2:$1source_labels:- __meta_kubernetes_pod_annotation_prometheus_io_port- __meta_kubernetes_pod_iptarget_label:__address__- action:labelmapregex:__meta_kubernetes_pod_annotation_prometheus_io_param_(.+)replacement:__param_$1- action:labelmapregex:__meta_kubernetes_pod_label_(.+)- action:replacesource_labels:- __meta_kubernetes_namespacetarget_label:namespace- action:replacesource_labels:- __meta_kubernetes_pod_nametarget_label:pod- action:dropregex:Pending|Succeeded|Failed|Completedsource_labels:- __meta_kubernetes_pod_phasescrape_interval:5mscrape_timeout:30srecording_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 的指标。

img

这里我们可以重点关注下 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"}这个指标,如下所示:

img

  • 该查询语句的查询结果为:
bash
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: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:source_version="v1",source_workload="productpage-v1",source_workload_namespace="default",version="v1"} 362

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

  • 我们可以查看下 productpagePod 的信息,如下所示:
bash
$kubectlgetpodsproductpage-v1-564d4686f-l8kxr-oyamlapiVersion:v1kind:Podmetadata:annotations:istio.io/rev:defaultkubectl.kubernetes.io/default-container:productpagekubectl.kubernetes.io/default-logs-container:productpageprometheus.io/path:/stats/prometheusprometheus.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:productpagepod-template-hash:564d4686fsecurity.istio.io/tlsMode:istioservice.istio.io/canonical-name:productpageservice.istio.io/canonical-revision:v1version:v1name:productpage-v1-564d4686f-l8kxrnamespace:defaultspec:containers:-image:docker.io/istio/examples-bookinfo-productpage-v1:1.18.0imagePullPolicy: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/prometheusprometheus.io/port:"15020"则是用来配置抓取路径和抓取端口的,当 Prometheus 发现这个 Pod 后根据配置就可以通过 <pod ip>:15020/stats/prometheus这个路径来抓取该 Pod 的指标数据了,这个路径就是 Envoy Sidecar 提供的 /stats/prometheus路径,而 15020则是 Envoy Sidecar 的端口,这个端口是通过 istio-proxy这个容器配置的静态监听器暴露出来的。

当然定义的标签也被映射为 Prometheus 的标签了,从结果来看除了 Pod 的这些标签之外,Envoy Sidecar 也会自己添加很多相关标签,主要是标明 destinationsource的信息,有了这些标签我们就可以很方便的对指标进行查询了。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 来说包括 COUNTERDISTRIBUTION两种指标类型,这两种指标类型对应我们比较熟悉的计数器和直方图。

对于 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配置进行安装:

yaml
apiVersion:install.istio.io/v1alpha1kind:IstioOperatorspec:values:telemetry:enabled:truev2:enabled:false

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

yaml
$ kubectl explain Telemetry.specGROUP:telemetry.istio.ioKIND:TelemetryVERSION:v1alpha1FIELD:spec <Object>DESCRIPTION:Telemetry configuration for workloads. See more details at:https:FIELDS:accessLogging <[]Object>Optional.metrics <[]Object>Optional.selector <Object>Optional.tracing <[]Object>Optional.

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

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

yaml
$ kubectl explain Telemetry.spec.metricsGROUP:telemetry.istio.ioKIND:TelemetryVERSION:v1alpha1FIELD:metrics <[]Object>DESCRIPTION:Optional.FIELDS:overrides <[]Object>Optional.providers <[]Object>Optional.reportingInterval <string>Optional.

可以看到 metrics字段包含了 overridesprovidersreportingInterval三个字段。

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

1.删除标签

🚩 实战:自定义指标-删除标签-2023.12.4(测试成功)

实验环境:

bash
k8sv1.27.6(containerd:istiov1.19.3(--setprofile=demo)

实验软件:

链接:https:telemetry:enabled:truev2:enabled:trueprometheus:enabled:trueconfigOverride:outboundSidecar:debug:falsestat_prefix:istiometrics:- tags_to_remove:- destination_canonical_service...

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

在上面的 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_BYTESTCP 连接期间发送的响应字节计数器。Prometheus 提供商将此指标导出为:istio_tcp_sent_bytes_total。Stackdriver 提供商将此指标导出为:istio.io/service/server/sent_bytes_count(服务器模式)istio.io/service/client/sent_bytes_count(客户端模式)
TCP_RECEIVED_BYTESTCP 连接期间接收的请求字节计数器。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 是对标签执行的操作,可以添加、删除标签,或覆盖其默认值。

字段类型描述是否必需
operationOperation操作控制是否更新/添加一个标签,或者移除它。
valuestring当操作为 UPSERT时才考虑值。值是基于属性的 CEL 表达式。例如:string(destination.port)request.host。Istio 暴露所有标准的 Envoy 属性。此外,Istio 也将节点元数据作为属性暴露出来。更多信息请参见 自定义指标文档

对应的操作 Operator可以配置 UPSERTREMOVE两个操作:

名称描述
UPSERT使用提供的值表达式插入或更新标签。如果使用 UPSERT操作,则必须指定 value字段。
REMOVE指定标签在生成时不应包含在指标中。

看下当前指标:(是含有这个destination_canonical_service标签的)

部署资源,再次验证:

img

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

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

测试结束。😘

2.添加指标

🚩 实战:自定义指标-删除标签-2023.12.4

实验环境:

bash
k8sv1.27.6(containerd:istiov1.19.3(--setprofile=demo)

实验软件:

链接:https:apiVersion:telemetry.istio.io/v1alpha1kind:Telemetrymetadata:name:add-tagsspec:metrics:- overrides:- match:metric:REQUEST_COUNTmode:CLIENTtagOverrides:destination_x:operation:UPSERTvalue:"upstream_peer.labels['app'].value"# 必须加上双引号- match:metric:REQUEST_COUNTtagOverrides:destination_port:value:"string(destination.port)"request_host:value:"request.host"providers:- name:prometheus

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

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

属性类型
namestringPod 名
namespacestringPod 所在命名空间
labelsmap工作负载标签
ownerstring工作负载 owner
workload_namestring工作负载名称
platform_metadatamap平台元数据
istio_versionstring代理的版本标识
mesh_idstring网格唯一 ID
app_containerslist<string>应用容器的名称列表
cluster_idstring工作负载所属的集群标识

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

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

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

  • 同样应用这个资源对象后,再次访问 productpage 应用产生指标,现在我们可以看到指标中已经包含了我们添加的标签了。
bash
[root@master1 istio]#kubectl apply -f add-tags.yaml telemetry.telemetry.istio.io/add-tagscreated[root@master1 istio]#kubectl get telemetries.telemetry.istio.io NAMEAGEadd-tags20s#istioctl dashboard prometheus --address 0.0.0.0

img

测试结束。😘

  • 奇怪:我这里没有现象……(prometheus都已经删除重建过了的…… 先搁置吧。)

3.禁用指标

对于禁用指标则相对更简单了。

比如我们通过以下配置禁用所有指标:

yaml
apiVersion:telemetry.istio.io/v1alpha1kind:Telemetrymetadata:name:remove-all-metricsnamespace:istio-systemspec:metrics:- providers:- name:prometheusoverrides:- disabled:truematch:mode:CLIENT_AND_SERVERmetric:ALL_METRICS

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

yaml
apiVersion:telemetry.istio.io/v1alpha1kind:Telemetrymetadata:name:remove-request-countnamespace:istio-systemspec:metrics:- providers:- name:prometheusoverrides:- disabled:truematch:mode:CLIENT_AND_SERVERmetric:REQUEST_COUNT

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

yaml
apiVersion:telemetry.istio.io/v1alpha1kind:Telemetrymetadata:name:remove-clientnamespace:istio-systemspec:metrics:- providers:- name:prometheusoverrides:- disabled:truematch:mode:CLIENTmetric:REQUEST_COUNT

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

yaml
apiVersion:telemetry.istio.io/v1alpha1kind:Telemetrymetadata:name:remove-servernamespace:istio-systemspec:metrics:- providers:- name:prometheusoverrides:- disabled:truematch:mode:SERVERmetric:REQUEST_COUNT

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

2、分布式追踪

分布式追踪可以让用户对跨多个分布式服务网格的请求进行追踪分析,可以通过可视化的方式更加深入地了解请求的延迟,序列化和并行度。Istio 利用 Envoy 的分布式追踪功能提供了开箱即用的追踪集成,Istio 提供了安装各种追踪后端服务的选项,并且通过配置代理来自动发送追踪 Span到分布式追踪系统服务,比如 Zipkin、Jaeger、Lightstep、Skywalking等后端服务。

img

一次完整的链路是由多个 span 组成的,每个 span 代表了一次请求的一部分,每个 span 都有一个唯一的 ID,这个 ID 用来标识这个 span,同时还有一个父 span 的 ID,用来标识这个 span 的父 span,这样就可以将多个 span 组成一个链路了。将不同的 span 关联到一起的方式是通过将父 span 的 ID 传递给子 span,这样就可以将多个 span 关联起来了,也就是上下文传递

上下文传递

尽管 Istio 代理能够自动发送 Span,但需要一些附加信息才能将这些 Span 加到同一个调用链,所以当代理发送 Span 信息的时候,应用程序需要附加适当的 HTTP 请求头信息,这样才能够把多个 Span 加到同一个调用链。

要做到这一点,每个应用程序必须从每个传入的请求中收集请求头(Header),并将这些请求头转发到传入请求所触发的所有传出请求。 具体选择转发哪些请求头取决于所配置的跟踪后端。

img

虽然 Istio 代理能够自动发送 span 信息,但它们需要一些提示来将整个跟踪关联起来。应用程序需要传播适当的 HTTP 头,以便当代理发送 span 信息时,span 能够正确地关联到单个跟踪中。为了实现这一点,应用程序需要从传入的请求中收集和传播头信息到所有的外发请求。要传播的头信息的选择取决于所使用的跟踪配置。

首先所有应用程序必须转发以下请求头:

  • x-request-id:这是 Envoy 专用的请求头,用于对日志和追踪进行一致的采样。

对于 Zipkin、Jaeger、Stackdriver 和 OpenCensus Agent,应转发 B3请求头格式:

  • x-b3-traceid
  • x-b3-spanid
  • x-b3-parentspanid
  • x-b3-sampled
  • x-b3-flags

这些是 Zipkin、Jaeger、OpenCensus 和许多其他工具支持的请求头。

B3是一个跟踪上下文传播的格式,它起源于 Zipkin项目,但后来被其他许多分布式跟踪工具所采用。B3的名字来源于 BigBrotherBird,它的主要目的是为了在服务之间传播跟踪上下文。它是由一组特定的 HTTP 头部组成的,可以传递跟踪信息,如 trace ID、span ID、采样决策等。

对于 Datadog,应转发以下请求头,对于许多语言和框架而言,这些转发由 Datadog 客户端库自动处理。

  • x-datadog-trace-id
  • x-datadog-parent-id
  • x-datadog-sampling-priority

对于 Lightstep,应转发 OpenTracing span 上下文请求头:

  • x-ot-span-context

对于 Stackdriver 和 OpenCensus Agent,可以使用以下任一请求头来替代 B3多请求头格式。

  • grpc-trace-bin:标准的 gRPC 追踪头。
  • traceparent:追踪所用的 W3C追踪上下文标准,受所有 OpenCensus、OpenTelemetry 和 Jaeger 客户端库所支持。
  • x-cloud-trace-context:由 Google Cloud 产品 API 使用。

W3C Trace Context规范为追踪上下文传播数据的交换定义了一种普遍认同的格式 - 称为追踪上下文Trace Context是一个在分布式追踪中用于跨服务和跨进程传递 trace 数据的标准。它定义了如何在 HTTP headers 中编码 trace 数据,以便在不同的服务间传递这些数据。具体来说,Trace Context 包含两个部分:traceparenttracestate

  • traceparent以便携、固定长度的格式描述了传入请求在其追踪链路中的位置。它的设计重点是快速解析,每个跟踪工具都必须正确设置 traceparent,即使它仅依赖于 tracestate中的供应商特定信息。
  • tracestate通过一组 name/value键值对表示来扩展带有供应商特定数据的 traceparent。将信息存储在 tracestate中是可选的。

使用 W3C跟踪上下文上下文传播接收 HTTP 请求可能如下所示:

http
GET/my-service HTTP/1.1Host:myhost.comtraceparent:00–0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331–01tracestate:abc=00f067aa0ba902b7,xyz=99f067aa0ba902b7

traceparent 字段

Traceparent HTTP Header 字段标识跟踪系统中的传入请求,这是一个必要的部分,它由 4 个部分组成,所有字段都使用 16 进制编码:

  • version:系统适配的追踪上下文版本,当前版本是 00
  • trace-id:一个全局唯一的 ID,用于标识整个 trace,它是一个 32 个字符的十六进制字符串。
  • parent-id:一个服务内唯一的 ID,用于标识调用链中的当前操作(通常是一个函数或者请求处理器)。它是一个 16 个字符的十六进制字符串。
  • trace-flags:用于控制 trace 的行为,例如是否需要被采样等。

img

比如我们有一个前端应用中的一个接口中添加了 Trace Context,它的 traceparent就是这样的:

img

比如我们这里一次 HTTP 请求中通过 Header 传递的 Traceparent值为 00-a237a2ca46023ce3e1d214ad2866c9c0-d00a29e113663fed-01,对应到 Jaeger UI 中 trace id 为 a237a2ca46023ce3e1d214ad2866c9c0,parent id(也就是父级的 span id)为 d00a29e113663fed,trace flags 为 01

img

tracestate 字段

tracestate这是一个可选的部分,用于跨多个服务传递额外的 trace 信息。它是一个键值对列表,列表中的每一项都由一个服务添加。服务可以在 tracestate中添加一些自定义的数据,例如服务的版本、部署环境等。

Jaeger

接下来我们来看下如何在 Istio 中集成 Jaeger,Jaeger 是一个开源的分布式追踪系统,它由 Uber 开源,用于监视和故障排除复杂的分布式系统。同样我们这里还是以 Bookinfo 为例进行说明。

  • 首先要安装 Jaeger,我们这里只是演示,可以直接使用下面的方式进行安装:
bash
$kubectlapply-fsamples/addons/jaeger.yaml$kubectlgetpods-nistio-system-lapp=jaegerNAMEREADYSTATUSRESTARTSAGEjaeger-db6bdfcb4-qpmmv1/1Running20(5h40m ago) 29d$kubectlgetsvc-nistio-systemNAMETYPECLUSTER-IPEXTERNAL-IPPORT(S) AGEjaeger-collectorClusterIP10.107.60.208<none>14268/TCP,14250/TCP,9411/TCP,4317/TCP,4318/TCP29dtracingClusterIP10.98.235.44<none>80/TCP,16685/TCP29dzipkinClusterIP10.98.118.194<none>9411/TCP29d# ......

如果是要在生产环境中使用 Jaeger,则需要参考官方文档进行部署。

  • 安装 Jaeger 完毕后,需要指定 Istio Envoy Proxy 代理向 Deployment 发送流量,可以使用 --set meshConfig.defaultConfig.tracing.zipkin.address=jaeger-collector:9411进行配置,此外默认的采样率为 1%,可以通过 --set meshConfig.defaultConfig.tracing.sampling=100来修改采样率。
bash
istioctlinstall--setprofile=demo--setmeshConfig.defaultConfig.tracing.zipkin.address=jaeger-collector:9411--setmeshConfig.defaultConfig.tracing.sampling=100-y
  • 现在我们就可以通过下面的命令来查看 Jaeger UI 了:
bash
istioctldashboardjaeger--address0.0.0.0[root@master1 ~]#kubectl get istiooperators.install.istio.io -nistio-systemNAMEREVISIONSTATUSAGEinstalled-state26d[root@master1 ~]#kubectl get istiooperators.install.istio.io -nistio-system -oyaml……meshConfig:accessLogFile:/dev/stdoutdefaultConfig:proxyMetadata:{}tracing:sampling:100zipkin:address:jaeger-collector:9411
bash
http:fori in$(seq1100);docurl-s-o/dev/null"http:GROUP:telemetry.istio.ioKIND:TelemetryVERSION:v1alpha1FIELD:tracing <[]Object>DESCRIPTION:Optional.FIELDS:customTags <map[string]Object>Optional.disableSpanReporting <boolean>Controls span reporting.match <Object>Allows tailoring of behavior to specific conditions.providers <[]Object>Optional.randomSamplingPercentage <number><no description>useRequestIdForTraceSampling <boolean><no description>

从上面可以看出关于链路追踪相关配置的主要字段有:

  • providers:用于 span 报告的提供者名称,如果没有指定提供者,则将使用默认的跟踪提供者。注意:目前只能指定一个提供者。
  • randomSamplingPercentage:如果之前没有进行过采样决策,则控制选择流量进行跟踪的速率。如果已经进行了先前的采样决策,那么将使用该决策。然而,如果没有进行过采样决策(例如:请求中未包含 x-b3-sampled跟踪标头),则将以指定的百分比生成遥测数据。
  • disableSpanReporting:控制 span 上报。如果设置为 true,则不会上报受影响的工作负载的 span。这不会影响上下文传播或跟踪采样行为。
  • customTags:配置生成的 span 的其他自定义标签。
  • match:允许根据特定条件进行行为定制。

对于 Tracing 来说,要想使用 Telemetry API,必须先在 MeshConfig中配置相应的 Provider,然后在 TelemetryAPI 的配置中使用命名引用这个 Provider。

  • 比如我们可以添加一个名为 mytrace的 Trace Provider,对应的配置如下所示:
yaml
# mytrace.yamlapiVersion:install.istio.io/v1alpha1kind:IstioOperatorspec:profile:demo# 使用 demo profile,不能忽略meshConfig:enableTracing:truedefaultConfig:tracing:{} # 禁用全局的 tracing 配置extensionProviders:- name:mytracezipkin:# add zipkin providerservice:zipkin.istio-system.svc.cluster.localport:9411

上面的配置对象中我们这 meshConfig中首先将全局的 tracing配置禁用掉,然后在 extensionProviders下面添加了一个名为 mytrace的 Trace Provider,对应的后端服务为 zipkin.istio-system.svc.cluster.local,端口为 9411,由于前面我们安装的 Jaeger 服务暴露了 9411 端口,并创建了一个名为 zipkin的 Service 对象,所以这里我们可以直接使用 zipkin.istio-system.svc.cluster.local来访问 Jaeger 服务,因为 Jaeger 本身也是兼容 Zipkin 协议的。

  • 然后我们可以通过 istioctl命令来安装这个配置对象:
bash
istioctlinstall-fmytrace.yaml-y
  • 配置生效后我们就可以来使用 Telemetry API 来配置链路追踪了,比如我们可以通过以下配置来启用我们创建的 mytrace这个链路追踪 Provider:
yaml
#mesh-default.yamlapiVersion:telemetry.istio.io/v1alpha1kind:Telemetrymetadata:name:mesh-defaultnamespace:istio-systemspec:tracing:- providers:- name:mytrace

上面的对象我们指定的命名空间为 istio-system,表示会对整个服务网格中的工作负载应用这个配置。

  • 应用上面对象后,再次访问下 productpage 应用,然后在 Jaeger UI 中就可以看到追踪数据了。
bash
kubectlapply-fmesh-default.yaml

img

我们一共请求了 3 次 /productpage接口, Jaeger UI 中就显示了 3 个链路,证明默认情况下通过 Telemetry 方式启用的链路追踪采样率是 100%。

  • 如果现在我们要将采样率设置为 50%,则可以创建一个如下所示的资源对象:
yaml
apiVersion:telemetry.istio.io/v1alpha1kind:Telemetrymetadata:name:mesh-defaultnamespace:istio-systemspec:tracing:- providers:- name:mytracerandomSamplingPercentage:50

同样应用上面的资源对象后,这次我们一共访问 6 次 productpage 应用,最后在 Jaeger UI 只能看到 3 个链路数据了,证明采样率已经生效了。

除了配置采样率之外,我们还可以基于文本、环境变量和客户端请求等向 spans 添加自定义标签。

添加自定义标签的数量没有限制,但标签名称必须唯一。

  • 比如我们可以通过以下配置向 spans 添加自定义标签:
yaml
apiVersion:telemetry.istio.io/v1alpha1kind:Telemetrymetadata:name:mesh-defaultnamespace:istio-systemspec:tracing:- providers:- name:mytracerandomSamplingPercentage:50customTags:"provider":literal:value:"zipkin""cluster_id":environment:name:ISTIO_META_CLUSTER_IDdefaultValue:Kubernetes"group":header:name:x-groupdefaultValue:"unknown"

上面的对象中我们在 customTags下面添加了一个名为 provider的标签,它的值为 zipkin,这个标签的值是一个 Literal类型的值,Literal表示一个静态的值,会被添加到每个 span 中。另外添加的 cluster_id这个标签的值是通过环境变量 ISTIO_META_CLUSTER_ID来获取的,最后添加的 group这个标签的值是通过请求头 x-group来获取的,如果请求头中没有 x-group这个请求头,则使用默认值 unknown

img

注意基于环境变量添加自定义标签,需要在根 Istio 系统命名空间中的 istio-sidecar-injector的 ConfigMap 中进行定义。

  • 如果不通过 Telemetry API 来配置链路追踪,那么每次我们都需要去更新 Istio 的安装配置才能生效,比如要实现上面的配置,我们需要 MeshConfig的 全局配置来实现:
yaml
# mytrace.yamlapiVersion:install.istio.io/v1alpha1kind:IstioOperatorspec:profile:demo# 使用 demo profile,不能忽略meshConfig:enableTracing:truedefaultConfig:tracing:sampling:50zipkin:address:zipkin.istio-system.svc.cluster.local:9411customTags:"provider":literal:value:"zipkin""cluster_id":environment:name:ISTIO_META_CLUSTER_IDdefaultValue:Kubernetes"group":header:name:x-groupdefaultValue:"unknown"
  • 此外如果要对指定的工作负载应用链路追踪配置,以前的方式可以通过 Deployment 的注解来定义调用链的配置信息,比如对于 Productpage 应用,我们可以通过以下方式来配置:
yaml
apiVersion:apps/v1kind:Deploymentmetadata:name:productpage-v1labels:app:productpagespec:selector:matchLabels:app:productpageversion:v1template:metadata:labels:app:productpageversion:v1annotations:proxy.istio.io/config:|tracing:sampling:50zipkin:address:zipkin.istio-system.svc.cluster.local:9411customTags:"provider":literal:value:"zipkin""cluster_id":environment:name:ISTIO_META_CLUSTER_IDdefaultValue:Kubernetes"group":header:name:x-groupdefaultValue:"unknown"# ......

这种方式虽然可以达到目的,但显然不够灵活,而且需要修改 Deployment 的配置,不够优雅,侵入性太强,而通过 Telemetry API 来配置链路追踪,就可以更加灵活地配置链路追踪了,比如同样只想对 Productpage 应用进行链路追踪,可以通过以下方式来配置:

yaml
apiVersion:telemetry.istio.io/v1alpha1kind:Telemetrymetadata:name:productpagenamespace:default# 指定要配置的工作负载所在命名空间spec:selector:matchLabels:app:productpage# 关联工作负载的标签tracing:- providers:- name:mytracerandomSamplingPercentage:50customTags:"provider":literal:value:"zipkin""cluster_id":environment:name:ISTIO_META_CLUSTER_IDdefaultValue:Kubernetes"group":header:name:x-groupdefaultValue:"unknown"

到这里我们就了解了如何通过 Telemetry API 来配置链路追踪了,这种方式显示是更推荐的方式,更加灵活。

3、访问日志

访问日志提供了一种从单个工作负载实例的角度监控和理解行为的方法,同样访问日志是我们在生产环境中必不可少的一种监控手段,Istio 通过 Envoy 来提供访问日志功能,Envoy Proxy 打印访问信息到标准输出,Envoy 容器的标准输出能够通过 kubectl logs命令打印出来。

Istio 能够以一组可配置的格式为服务流量生成访问日志,使运维人员可以完全控制日志记录的方式、内容、时间和地点。 下面是一个典型的 Istio 访问日志示例:

bash
[2023-12-04T06:17:42.719Z] "GET /productpage HTTP/1.1"200 - via_upstream - "-"0 5289 23 22 "10.244.0.0""Mozilla/5.0 (Macintosh;Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML,like Gecko) Chrome/119.0.0.0 Safari/537.36""f3a98cd1-6970-42c0-9c86-d179b93aa779""192.168.0.100:31896""10.244.1.254:9080"inbound|9080||127.0.0.6:4562910.244.1.254:908010.244.0.0:0outbound_.9080_._.productpage.default.svc.cluster.localdefault

在现在的 Telemetry V2 版本的架构中,访问日志直接通过服务网格的数据平面 Envoy 上生成并上报给日志后端。根据后端日志采集方式的不同,会有不同的通道和方式。Envoy 可以通过控制台或者文件输出,由各种日志代理采集,也可以通过 gRPC 协议直接上报日志给标准的访问日志服务 ALS(Envoy Access Log Service),比如 Skywalking 就支持这种方式。

一般流程如下所示:

  1. Envoy 根据服务网格配置提取应用的访问信息
  2. 上报访问日志,比如通过 gRPC 协议上报给 ALS 服务
  3. ALS 服务 对接后端,将日志写到 Elasticsearch、Kafka 等后端服务中
  4. 通过 Kibanba、Grafana 等工具从后端服务检索日志

开启 Envoy 访问日志

同样的方式在 Istio 中我们可以通过 MeshConfig和 Telemetry API 的方式来启用访问日志。

如果想通过 MeshConfig方式来配置,需要在安装配置中添加以下字段(默认已经配置了):

yaml
spec:meshConfig:accessLogFile:/dev/stdout

或者,在原来的 istioctl install命令中添加相同的设置,例如:

bash
istioctlinstall<flags-you-used-to-install-Istio>--setmeshConfig.accessLogFile=/dev/stdout

此外还可以通过设置 accessLogEncodingJSONTEXT来配置日志的格式。另外还可以设置 accessLogFormat来自定义访问日志的格式,如果没有指定 accessLogFormat的话 Istio 将使用以下默认的访问日志格式:

bash
[%START_TIME%] \"%REQ(:METHOD)% %REQ(X-ENVOY-ORIGINAL-PATH?:PATH)% %PROTOCOL%\"%RESPONSE_CODE% %RESPONSE_FLAGS% %RESPONSE_CODE_DETAILS% %CONNECTION_TERMINATION_DETAILS%\"%UPSTREAM_TRANSPORT_FAILURE_REASON%\"%BYTES_RECEIVED% %BYTES_SENT% %DURATION% %RESP(X-ENVOY-UPSTREAM-SERVICE-TIME)% \"%REQ(X-FORWARDED-FOR)%\"\"%REQ(USER-AGENT)%\"\"%REQ(X-REQUEST-ID)%\"\"%REQ(:AUTHORITY)%\"\"%UPSTREAM_HOST%\"%UPSTREAM_CLUSTER% %UPSTREAM_LOCAL_ADDRESS% %DOWNSTREAM_LOCAL_ADDRESS% %DOWNSTREAM_REMOTE_ADDRESS% %REQUESTED_SERVER_NAME% %ROUTE_NAME%\n

当然我们还是强烈推荐使用 Telemetry API 来开启或关闭访问日志,如下所示:

yaml
apiVersion:telemetry.istio.io/v1alpha1kind:Telemetrymetadata:name:mesh-defaultnamespace:istio-systemspec:accessLogging:- providers:- name:envoy

上面的示例使用默认的 envoy访问日志提供程序,当然我们也可以应用于单独的命名空间或单独的工作负载,以在细粒度级别控制日志记录。

Grafana Loki

接下来我们来将访问日志发送到 Grafana Loki 进行统一的日志管理,Loki 是一个水平可扩展、高可用的多租户日志聚合系统。

  • 首先我们需要先确保 Loki 已经安装,我们这里同样只是为了测试,直接使用下面的方式安装即可,如果在生产环境中使用,则需要参考官方文档进行分布式部署。
bash
kubectlapply-fsamples/addons/loki.yaml-nistio-system

注意:安装这个pod要pv环境哦

bash
Events:TypeReasonAgeFromMessage-------------------------WarningFailedScheduling34s(x2 over35s) default-scheduler 0/3 nodes are available:pod has unbound immediate PersistentVolumeClaims. preemption:0/3 nodes are available:3 Preemption is not helpful forscheduling..

由于 Istio 默认并没有直接支持 Loki 这个 Provider,我们可以查看 MeshConfigExtensionProvider字段,可以看到 Istio 默认支持的 Provider有:

字段类型描述是否必需
namestring必填。用于唯一标识扩展提供商的名称。
envoyExtAuthzHttpEnvoyExternalAuthorizationHttpProvider (oneof)配置实现了 Envoy ext_authz 过滤器授权检查服务的外部授权器,使用 HTTP API。
envoyExtAuthzGrpcEnvoyExternalAuthorizationGrpcProvider (oneof)配置实现了 Envoy ext_authz 过滤器授权检查服务的外部授权器,使用 gRPC API。
zipkinZipkinTracingProvider (oneof)配置使用 Zipkin API 的跟踪提供商。
datadogDatadogTracingProvider (oneof)配置 Datadog 跟踪提供商。
stackdriverStackdriverProvider (oneof)配置 Stackdriver 提供商。
skywalkingSkyWalkingTracingProvider (oneof)配置 Apache SkyWalking 提供商。
opentelemetryOpenTelemetryTracingProvider (oneof)配置 OpenTelemetry 跟踪提供商。
prometheusPrometheusMetricsProvider (oneof)配置 Prometheus 指标提供商。
envoyFileAccessLogEnvoyFileAccessLogProvider (oneof)配置 Envoy 文件访问日志提供商。
envoyHttpAlsEnvoyHttpGrpcV3LogProvider (oneof)针对 HTTP 流量配置 Envoy 访问日志服务提供商。
envoyTcpAlsEnvoyTcpGrpcV3LogProvider (oneof)针对 TCP 流量配置 Envoy 访问日志服务提供商。
envoyOtelAlsEnvoyOpenTelemetryLogProvider (oneof)配置 Envoy Open Telemetry 访问日志服务提供商。

没有 Loki 这个 Provider,那么我们需要怎样才能将日志发送到 Loki 中呢?这里我们可以使用 OpenTelemetry来收集日志,然后再通过 OpenTelemetry Collector 来将日志发送到 Loki 中。

img

OpenTelemetry

OpenTelemetry(简称 OTel) 是一个开源的可观测框架,用于生成、收集和描述应用程序的观测数据。它提供了一组 API、库、Agent 和 Collector,用于捕获分布式跟踪和度量数据,并将其发送到分析软件、存储库或其他服务,OTel的目标是提供一套标准化、与厂商无关的 SDK、API 和工具集,用于将数据摄取、转换和发送到可观测性后端(开源或商业厂商)。

1.OpenTelemetry Collector

OpenTelemetry Collector 提供了一个与厂商无关的实现方式,用于接收、处理和导出遥测数据,它消除了运行、操作和维护多个代理/收集器的需求。

事实上收集器也并不是必需的,有的时候我们可以直接将遥测数据发送到外部的可视化工具中,比如 Jaeger、Zipkin 等等,但是这样的话我们就需要在每个应用中都进行配置,这样的话就会导致配置非常繁琐,而且也不利于统一管理,所以这里我们就可以使用 OpenTelemetry Collector 来解决这个问题。

而且 OpenTelemetry Collector 本身部署起来也非常灵活,可以将其部署为代理或网关。区别在于作为代理时,收集器实例与应用程序在同一主机上运行(sidecar 容器、daemonset 等)。此外一个或多个收集器实例也可以作为独立服务以每个集群、数据中心和地区的网关形式运行。

一般来说建议新应用选择代理部署,现有应用选择网关部署的方式,如果是 Kubernetes 环境,当然更建议部署为守护进程(代理模式)的方式。

收集器由四个组件组成,通过管道(Pipeline)进行启用:

  • 接收器(Receiver)将数据发送到收集器中,可以通过推送或拉取方式发送
  • 处理器(Processor)决定如何处理接收到的数据
  • 导出器(Exporter)决定将数据发送到哪里,可以通过拉取或推送方式完成,上面代码中的 OTLPTraceExporter就是一个导出器
  • 连接器(Connectors):连接器既是输出者又是接收者。连接器连接两个管道:它作为一个管道末端的导出器消耗数据,并作为另一个管道开始处的接收器发出数据。它可以消耗和发出相同数据类型或不同数据类型的数据。

img

当然我们也可以基于社区的组件进行自定义,以增强和扩展收集器管道。例如我们可以创建一个专用的导出器来接收并摄取指标、追踪和日志。

2.OpenTelemetry Collector 部署

在了解了 OpenTelemetry 的相关概念后,接下来我们需要部署 OpenTelemetry Collector,同样我们直接使用 Istio 提供的 samples 中的配置即可:

bash
kubectlapply-fsamples/open-telemetry/loki/otel.yaml-nistio-system

该命令会部署一个 OpenTelemetry 采集器,其中比较重要的是该采集器的配置:

yaml
apiVersion:v1kind:ConfigMapmetadata:name:opentelemetry-collector-conflabels:app:opentelemetry-collectordata:opentelemetry-collector-config:|receivers:otlp:protocols:grpc:http:processors:batch:attributes:actions:- action:insertkey:loki.attribute.labelsvalue:pod,namespace,cluster,meshexporters:loki:endpoint:"http:logging:loglevel:debugextensions:health_check:service:extensions:- health_checkpipelines:logs:receivers:[otlp]processors:[attributes]exporters:[loki,logging]

上面的配置中我们主要关注 exporters字段,其中 loki就是我们要将日志发送到的 Loki 服务,endpoint字段指定了 Loki 服务的地址,这里我们直接使用 Loki 的 Service 名称即可,因为 Loki 服务暴露了 3100 端口,所以我们可以直接使用 http:NAMEREADYSTATUSRESTARTSAGEgrafana-5f9b8c6c5d-jv65v1/1Running16(5h12m ago) 32distio-egressgateway-556f6f58f4-mqp5z1/1Running04h26mistio-ingressgateway-9c8b9b586-p2w671/1Running04h26mistiod-644f5d55fc-dlktv1/1Running04h26mjaeger-db6bdfcb4-9s8lr1/1Running03h47mkiali-7c9d5f9f96-cp4mb1/1Running18(5h12m ago) 32dloki-01/1Running032mopentelemetry-collector-5ccc9c9c55-msg5x1/1Running060sprometheus-5d5d6d6fc-lfz872/2Running2(5h12m ago) 2d19h

接下来我们就需要在 Istio 中添加一个 OpenTelemetry 访问日志服务的 Provider,添加如下配置:

yaml
# iop.yamlapiVersion:install.istio.io/v1alpha1kind:IstioOperatorspec:profile:demomeshConfig:extensionProviders:- name:otelenvoyOtelAls:service:opentelemetry-collector.istio-system.svc.cluster.localport:4317logFormat:labels:pod:"%ENVIRONMENT(POD_NAME)%"namespace:"%ENVIRONMENT(POD_NAMESPACE)%"cluster:"%ENVIRONMENT(ISTIO_META_CLUSTER_ID)%"mesh:"%ENVIRONMENT(ISTIO_META_MESH_ID)%"

在上面的配置中我们添加了一个名为 otel的 Provider,该 Provider 是一个 envoyOtelAls,表示使用 OpenTelemetry 的访问日志服务,对应的后端服务为 opentelemetry-collector.istio-system.svc.cluster.local,端口为 4317,这里我们直接使用 OpenTelemetry Collector 的 Service 名称即可。最后我们还配置了 logFormat,表示日志的格式,这里我们添加了一些自定义的属性,比如 podnamespaceclustermesh等等,然后在 OpenTelemetry 采集器中会把这些属性转换为 Loki 的标签,这样我们在 Loki 中就可以通过这些属性来进行检索了。

直接使用 istioctl命令来安装配置该对象即可:

bash
istioctlinstall-fiop.yaml-y

到这里我们的准备工作就完成了。

3.使用 Telemetry API 配置访问日志

接下来我们只需要通过 Telemetry API 来启用上面我们配置的日志 Provider 就可以开始收集日志了,如下所示:

yaml
apiVersion:telemetry.istio.io/v1alpha1kind:Telemetrymetadata:name:mesh-logging-defaultnamespace:istio-systemspec:accessLogging:- providers:- name:otel

应用该资源对象后,整个服务网格的日志就都会被上报到 OTel 采集器,然后在 Loki 中就可以看到日志了。

这里我们直接打开 Grafana 的 Loki Dashboard 即可:

bash
istioctldashboardgrafana

首先要在 Grafana 中添加 Loki 数据源:

img

然后接下来我们去访问 Productpage 应用产生一些日志数据,再切换回到 Grafana 中,切换到 Explore页面,然后选择 Loki 数据源,就可以看到 Loki 中的日志了:

img

同样的我们还可以使用 Telemetry API 来做一些更加细粒度的配置。

比如可以使用以下配置禁用 sleep 服务的访问日志:

yaml
apiVersion:telemetry.istio.io/v1alpha1kind:Telemetrymetadata:name:disable-sleep-loggingnamespace:defaultspec:selector:matchLabels:app:sleepaccessLogging:- providers:- name:oteldisabled:true

还可以使用 match字段来指定要过滤的流量,比如可以使用以下配置禁用 httpbin服务的入站访问日志:

yaml
apiVersion:telemetry.istio.io/v1alpha1kind:Telemetrymetadata:name:disable-httpbin-loggingspec:selector:matchLabels:app:httpbinaccessLogging:- providers:- name:otelmatch:mode:SERVER# 入站模式disabled:true# 禁用

此外我们可以通过 CEL表达式过滤访问日志。只有响应码大于等于 500 时,才会显示访问日志,如下所示:

yaml
apiVersion:telemetry.istio.io/v1alpha1kind:Telemetrymetadata:name:filter-sleep-loggingspec:selector:matchLabels:app:sleepaccessLogging:- providers:- name:otelfilter:expression:response.code >=500

比如只有响应码大于等于 400 或请求转到 BlackHoleClusterPassthroughCluster时,才显示访问日志:

yaml
apiVersion:telemetry.istio.io/v1alpha1kind:Telemetrymetadata:name:default-exception-loggingnamespace:istio-systemspec:accessLogging:- providers:- name:otelfilter:expression:"response.code >=400 ||xds.cluster_name =='BlackHoleCluster'||xds.cluster_name =='PassthroughCluster'"

关于赋值表达式的使用和前面指标中的使用方式一致,这里就不再赘述了。

关于我

我的博客主旨:

  • 排版美观,语言精炼;
  • 文档即手册,步骤明细,拒绝埋坑,提供源码;
  • 本人实战文档都是亲测成功的,各位小伙伴在实际操作过程中如有什么疑问,可随时联系本人帮您解决问题,让我们一起进步!

🍀 微信二维码 x2675263825 (舍得), qq:2675263825。

image-20230107215114763

🍀 微信公众号 《云原生架构师实战》

image-20230107215126971

🍀 个人博客站点

http:

版权:此文章版权归 One 所有,如有转载,请注明出处!

链接:可点击右上角分享此页面复制文章链接

上次更新时间:

最近更新