跳转至

可观测性

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

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

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

可观测性

使用 Kiali 观测微服务

由于微服务之间的调用关系错综复杂,排查问题就更加困难了,为了使服务之间的关系更加清晰明了,了解应用的行为和状态,我们有必要使用一些可视化的方案来观测我们的微服务应用,其中 Kiali 就是这样的一个工具。

Kiali 是 Istio 的一个可观测工具,提供服务拓扑展示服务网格的结构,提供网格的健康状态视图,配置信息验证功能,此外还具有服务网格配置的功能。

在安装 Istio 的时候我们就已经安装了 Kiali 插件,要验证服务是否已经在集群中运行,可以通过下面的命令来查看:

➜  ~ kubectl -n istio-system get svc kiali
NAME    TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)              AGE
kiali   ClusterIP   10.104.59.133   <none>        20001/TCP,9090/TCP   13d

安装完成后我们可以通过 istioctl 命令来访问,比如我们可以使用如下命令来打开 kiali 的 web UI:

➜  ~ istioctl dashboard kiali
http://localhost:20001/kiali

然后就可以通过 20001 端口访问到 kiali 服务了。此外我们也可以通过修改 Kiali 的服务为 NodePort 类型来访问:

➜  ~ kubectl get pods -n istio-system -l app=kiali
NAME                     READY   STATUS    RESTARTS   AGE
kiali-85c8cdd5b5-kdcbv   1/1     Running   9          8d
➜  ~ kubectl get svc -n istio-system -l app=kiali
NAME    TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)              AGE
kiali   ClusterIP   10.104.59.133   <none>        20001/TCP,9090/TCP   13d
➜  ~ kubectl edit svc kiali -n istio-system
......
spec:
  clusterIP: 10.104.59.133
  externalTrafficPolicy: Cluster
  ports:
  - name: http
    nodePort: 31617
    port: 20001
    protocol: TCP
    targetPort: 20001
  - name: http-metrics
    nodePort: 32729
    port: 9090
    protocol: TCP
    targetPort: 9090
  selector:
    app.kubernetes.io/instance: kiali-server
    app.kubernetes.io/name: kiali
  sessionAffinity: None
  type: NodePort
......
➜  ~ kubectl get svc -n istio-system -l app=kiali
NAME    TYPE       CLUSTER-IP      EXTERNAL-IP   PORT(S)                          AGE
kiali   NodePort   10.104.59.133   <none>        20001:31617/TCP,9090:32729/TCP   13d

然后我们可以通过 http://<node-ip>:31617 来访问 Kiali:

kiali webui

默认会安装命名空间进行分类,我们的 BookInfo 应用部署在 default 命名空间之下的,我们可以点击 default 命名空间之下的应用:

default app

还可以切换到 Graph 页面下面查看微服务应用的整个调用链:

app graph

我们可以看到上图非常清晰的把 BookInfo 应用的调用链给展示出来了,这对于我们了解我们的应用是非常有帮助的。还可以查看服务的详细信息:

service detail

在该页面之中还可以查看应用的指标、链路追踪等信息。

监控指标

此外我们还部署了用于收集监控指标的 Prometheus 应用:

➜  ~ kubectl get pods -n istio-system -l app=prometheus
NAME                          READY   STATUS    RESTARTS   AGE
prometheus-54b5dcf6bf-f48ks   3/3     Running   20         8d
➜  ~ kubectl get svc -n istio-system -l app=prometheus
NAME         TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)    AGE
prometheus   ClusterIP   10.107.177.160   <none>        9090/TCP   13d

为了测试方便我们可以将 Prometheus 的 Service 更改为 NodePort 类型:

➜  ~ kubectl edit svc prometheus -n istio-system
......
spec:
  type: NodePort
......
➜  ~ kubectl get svc -n istio-system -l app=prometheus
NAME         TYPE       CLUSTER-IP       EXTERNAL-IP   PORT(S)          AGE
prometheus   NodePort   10.107.177.160   <none>        9090:30730/TCP   13d

然后我们就可以通过 http://<NodeIP>:30730 访问 Prometheus 页面,默认情况下就已经有了很多抓取任务了:

Prometheus Targets

默认就已经收集了很多网格的指标,包括服务级别和代理级别(sidecar)的数据。

比如我们可以查询 istio_requests_total 这个指标,该指标是一个用于累加每个由 Istio 代理所处理请求的 COUNTER 指标。

Prometheus 查询

也可以切换到 Graph 标签查看对应的图形:

Prometheus Query Graph

我们也可以来筛选一些关于 productpage 服务的指标数据,比如查询请求 productpage 服务的总次数:

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

请求 reviews 服务 V3 版本的总次数,可以使用下面的 promql 语句,该语句返回所有请求 reviews 服务 v3 版本的当前总次数。

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

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

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

使用 Grafana 可视化系统监控

上面我们了解到了 Istio 网格通过 Prometheus 收集了很多服务和代理相关的指标数据,此外我们还可以通过 Grafana 来可视化查看网格的监控状态。

➜  ~ kubectl get pods -n istio-system -l app=grafana
NAME                      READY   STATUS    RESTARTS   AGE
grafana-f766d6c97-w9w7x   1/1     Running   5          8d

默认没创建 Grafana 的 Service 对象,这里我们可以手动创建一个:

➜  ~ cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Service
metadata:
  labels:
    app: grafana
  name: grafana
  namespace: istio-system
spec:
  ports:
  - name: http
    port: 3000
    protocol: TCP
    targetPort: 3000
  selector:
    app: grafana
  type: NodePort
EOF
➜  ~ kubectl get svc -n istio-system -l app=grafana
NAME      TYPE       CLUSTER-IP     EXTERNAL-IP   PORT(S)          AGE
grafana   NodePort   10.100.5.240   <none>        3000:30605/TCP   13d

然后就可以通过 http://<NodeIP>:30605 访问 Grafana 应用了:

grafana search dashboards

默认情况下 Grafana 中就已经导入了 Istio 的几个 Dashboard:

  • Mesh Dashboard:主要是查看应用的数据,包括网格数据总览、服务视图、工作负载视图等
  • Performance Dashboard:主要是用于查看 Istio 本身的监控数据。

Control Plane Dashboard

访问日志

日志也是应用可观测性中一个非常重要的方式,也是我们传统调试应用非常重要的手段,在 Istio 中使用 Sidecar 容器对请求进行了拦截,无疑也增大了调试难度,但是同时 Istio 也可以监测到网格内的服务通信的流转情况,并生成详细的遥测日志数据,任何请求与事件的元信息都可以获取到,所以我们也非常有必要来查看下 Istio 中的代理日志数据。Istio 最简单的日志类型是 Envoy 的访问日志,Envoy 代理打印访问日志信息到标准输出,然后我们就可以通过 kubectl logs 命令打印出来查看了。

默认情况下 Istio 已经开启了 Envoy 访问日志,我们也可以通过 istioctl install 命令来配置日志:

➜  ~ istioctl install --set profile=demo --set meshConfig.accessLogFile=/dev/stdout --set  meshConfig.accessLogEncoding=JSON -y

默认情况下日志就是输出到 stdout 上的 TEXT 文本格式,为了方便显示,这里我们将其设置为 JSON 格式,如果要想修改访问日志的格式可以设置 accessLogFormat 属性,具体的访问日志格式可以查看 Envoy 官方文档了解配置规则。

istio log config

现在我们去访问 BookInfo 服务,同时来查看 productpage 的 sidecar 日志:

➜  ~ kubectl get pods -l app=productpage
NAME                              READY   STATUS    RESTARTS   AGE
productpage-v1-6b746f74dc-xjzgh   2/2     Running   20         14d
➜  ~ kubectl logs -f productpage-v1-6b746f74dc-xjzgh -c istio-proxy
{"response_code":200,"connection_termination_details":null,"requested_server_name":null,"upstream_local_address":"10.244.2.85:35414","upstream_host":"10.244.2.63:9080","response_code_details":"via_upstream","method":"GET","bytes_sent":178,"authority":"details:9080","response_flags":"-","path":"/details/0","protocol":"HTTP/1.1","downstream_remote_address":"10.244.2.85:56486","duration":11,"downstream_local_address":"10.99.137.40:9080","bytes_received":0,"route_name":"default","user_agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36","upstream_cluster":"outbound|9080||details.default.svc.cluster.local","x_forwarded_for":null,"upstream_service_time":"11","request_id":"f190ed3e-411b-96d3-a59e-abe93982a89e","start_time":"2021-08-03T09:58:41.142Z","upstream_transport_failure_reason":null}
{"downstream_local_address":"10.105.81.29:9080","upstream_host":"10.244.2.78:9080","user_agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36","upstream_local_address":"10.244.2.85:50694","path":"/reviews/0","upstream_service_time":"219","request_id":"f190ed3e-411b-96d3-a59e-abe93982a89e","response_flags":"-","duration":220,"method":"GET","requested_server_name":null,"response_code":200,"x_forwarded_for":null,"bytes_received":0,"protocol":"HTTP/1.1","connection_termination_details":null,"authority":"reviews:9080","downstream_remote_address":"10.244.2.85:45902","upstream_transport_failure_reason":null,"upstream_cluster":"outbound|9080||reviews.default.svc.cluster.local","route_name":"default","response_code_details":"via_upstream","start_time":"2021-08-03T09:58:41.160Z","bytes_sent":375}
{"response_code_details":"via_upstream","duration":292,"path":"/productpage","bytes_sent":5179,"x_forwarded_for":"10.244.0.0","downstream_remote_address":"10.244.0.0:0","response_code":200,"upstream_cluster":"inbound|9080||","user_agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36","authority":"192.168.31.30:31529","downstream_local_address":"10.244.2.85:9080","route_name":"default","upstream_local_address":"127.0.0.6:58099","request_id":"f190ed3e-411b-96d3-a59e-abe93982a89e","protocol":"HTTP/1.1","start_time":"2021-08-03T09:58:41.127Z","upstream_service_time":"283","upstream_host":"10.244.2.85:9080","bytes_received":0,"connection_termination_details":null,"response_flags":"-","upstream_transport_failure_reason":null,"method":"GET","requested_server_name":"outbound_.9080_._.productpage.default.svc.cluster.local"}

这里我们查询的容器名 istio-proxy 其实就是 Envoy sidecar 代理,Envoy 将请求和响应日志都进行了打印并输出至 stdout ,所以可以通过 kubectl logs 查询。

正常就会收到3条 JSON 格式的日志,productpage 服务会调用 details 与 reviews 服务,所以我们可以看到一条访问 /reviews/0/details/0outbound 请求,和最终的 /productpageinbound 请求记录。

为了方便查看,这里我们将第一条日志进行格式化,如下所示:

json format

这里面的日志信息其实是一个标准的 Envoy 流量模型,所以如果我们要深入研究 Istio,对于 Envoy 的了解是必不可少的,其中最重要的几个字段就是 Envoy 流量五元组中的几个信息。

在 Envoy 中接受请求流量叫做 Downstream(下游),Envoy 发出请求流量叫做 Upstream(上游),在处理 DownstreamUpstream 过程中,分别会涉及2个流量端点,即请求的发起端和接收端:

envoy request model

在这个过程中,Envoy 会根据用户规则,计算出符合条件的转发目的主机集合,这个集合叫做 UPSTREAM_CLUSTER, 并根据负载均衡规则,从这个集合中选择一个 host 作为流量转发的接收端点,这个 host 就是 UPSTREAM_HOST。这就是 Envoy 请求处理的流量五元组信息,这是 Envoy 日志里最重要的部分,通过这个五元组我们可以准确的观测流量「从哪里来」和「到哪里去」

  • downstream_remote_address
  • downstream_local_address
  • upstream_local_address
  • upstream_host
  • upstream_cluster

除了五元组信息之外,还有 request_id 属性,该属性主要用于链路追踪,可以将一条请求在不同的服务中的调用串联起来。

还有一个非常重要的属性 response_flags,可以通过该属性来查看当前请求的状态,用于调试请求的时候非常有用: response flag

不过需要注意的是,当 Pod 被销毁后,旧的日志将不复存在,也无法通过 kubectl logs 就行查看了。所以如果要查看历史的的日志数据,我们可以使用 EFK 方案将日志进行收集,前面我们已经仔细讲解过了,这里就不再赘述了。

分布式追踪

相比传统的单体应用,微服务的一个主要变化是将应用中的不同模块拆分为了独立的服务,在微服务架构下,原来进程内的方法调用成为了跨进程的远程方法调用。相对于单一进程内的方法调用而言,跨进程调用的调试和故障分析是非常困难的,难以使用传统的代码调试程序或者日志打印来对分布式的调用过程进行查看和分析。一个来自客户端的请求在其业务处理过程中很有可能需要经过多个微服务,我们如果想要对该请求的端到端调用过程进行完整的分析,则必须将该请求经过的所有进程的相关信息都收集起来并关联在一起,这就是分布式追踪,也是应用可观测性中非常重要的手段。

在 Istio 中通过分布式追踪我们可以让用户对跨多个分布式服务网格的一个请求进行追踪分析,进而可以通过可视化的方式更加深入地了解请求的延迟、序列化和并行度等。

Istio 利用 Envoy 的分布式追踪功能提供了开箱即用的追踪集成,默认 Istio 提供了集成各种追踪后端服务的选项,并且可以通过配置代理来自动发送追踪 span 到追踪后端服务。

在使用 Istio 的分布式追踪之前,我们需要先了解两个重要的概念:SpanTrace

tracing

  • Span:分布式追踪的基本组成单元,表示一个分布式系统中的单独的工作单元,每一个 Span 可以包含其它 Span 的引用。多个 Span 在一起构成了 Trace。
  • Trace:微服务中记录的完整的请求执行过程,一个完整的 Trace 由一个或多个 Span 组成。

随着分布式追踪技术的发展,社区推出了 OpenTracing 这个规范,提供了标准的 API 规范、框架以及一些公共的库。目前比较知名的追踪工具基本上都通过 OpenTracing 进行实现,比如 Jaeger、Zipkin 等。

我们这里主要来给大家介绍比较流行的 JaegerJaeger 是由 Uber 开源的分布式追踪系统,采用 Go 语言编写,主要借鉴了 Google Dapper 论文和 Zipkin 的设计,兼容 OpenTracing 以及 Zipkin 追踪格式,目前已成为 CNCF 基金会的开源项目。

jaeger logo

前面我们已经安装了 Jaeger 插件,我们可以查看默认已经安装的 Jaeger 服务:

➜  ~ kubectl get pods -l app=jaeger -n istio-system
NAME                      READY   STATUS    RESTARTS   AGE
jaeger-7f78b6fb65-bxbh9   1/1     Running   18         13d
➜  ~ kubectl get svc -n istio-system -l app=jaeger
NAME               TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)               AGE
jaeger-collector   ClusterIP   10.111.142.80   <none>        14268/TCP,14250/TCP   13d
tracing            ClusterIP   10.108.47.32    <none>        80/TCP                13d

我们可以通过 tracing 这个 Service 来访问 Jaeger 的 Dashboard 页面,同样为了方便测试将其修改为 NodePort 类型:

➜  ~ kubectl edit svc tracing -n istio-system
......
spec:
  type: NodePort
......
service/tracing edited
➜  ~ kubectl get svc tracing -n istio-system
NAME      TYPE       CLUSTER-IP     EXTERNAL-IP   PORT(S)        AGE
tracing   NodePort   10.108.47.32   <none>        80:31734/TCP   13d

修改完成后就可以在浏览器中通过 http://<NodeIP>:31734 访问 Jaeger 的 Dashboard 页面了:

Jaeger Dashboard

接下来我们重新访问 BookInfo 应用产生流量请求生成追踪数据。请求完成后回到 Jaeger 页面,在左侧 Servcie 区域选择一个我们要追踪的服务,比如 details.default,点击 "Find Traces" 按钮查看追踪结果。

Find Traces

点击列表项目还可以查看追踪详细信息,记录了一次请求涉及到的 Services、深度、Span 总数、请求总时长等信息,也可以对下方的单项服务展开,观察每一个服务的请求耗时和详情。

Trace Detail

此外我们还可以切换 Trace 的显示方式为 Graph,在右上角点击 Trace Timeline 切换为 Trace Graph 模式,该模式下可以更加清晰查看到每一个调用详细信息:

Trace Graph

此外,Jaeger 还可以展示服务依赖,点击顶部的 System Architecture 菜单查看,该页面可以查看服务的完整依赖调用关系:

Jeager 依赖

这些都是 Jaeger 的基本使用,不过需要注意的是 Istio 默认提供的 Jaeger 采用内存的存储方式,Pod 被销毁后数据也就丢失了。在生产环境中需要单独配置持久化存储数据库,具体可查看 Jaeger 官方文档

到这里我们就完成了 Istio 中可观测性的实验:指标、日志、追踪,对于微服务应用可观测性对于我们监控和调试应用是非常重要的手段,我们非常有必要掌握这些方式。