生产环境里的Istio和Kubernetes(二):Tracing

生产环境里的Istio和Kubernetes(二):Tracing

原文地址 https://medium.com/avitotech/istio-and-kubernetes-in-production-part-2-tracing-6304a5af82e9

上一篇博文里,我们介绍了 Service Mesh(服务网格)Istio 的组成部分,并回答了 Istio 新手经常会问的问题。本文会研究如何整理这些在网络上收集到的 tracing 信息。

当听到 Service Mesh 这个新概念的时候,开发人员和系统管理员首先考虑的事情是 tracing。我们会为每个微服务添加特定的代理服务器来处理所有 TCP 流量。你可能会自然地认为很容易收集所有网络事件的信息。但不幸的是,实际需要考虑很多细节。让我们一起研究一下吧。

一大误解:可以轻松得到网络流量上的网络交互数据

实际上,相对容易的仅仅是得到由箭头连接的系统节点图,以及服务间的数据速率(实际上,仅仅是单位时间内的比特数)。但是,绝大多数情况下,服务通过应用层协议来通信,比如 HTTP,gRPC,Redis 等。当然,我们想看到这些协议的 tracing 信息,想看到应用级别请求的速率,而不是单纯的数据速率。另外,我们想知道协议的请求延时。最终,则想看到从用户输入触发的请求到收到回复这之间的全路径。不过,这可不是一件容易的事情。

首先,从 Istio 的架构角度看怎么发送 tracing 信息。上文提到,Istio 有一个特定的组件收集 Telemetry,称为 Mixer。但是,在当前的 1.1.*的版本里,代理服务器,也就是 Envoy 代理,直接发送 tracing span。Envoy 代理支持使用 Zipkin 协议发送 tracing span。其他协议则要求单独的插件。Istio 自带预编译并且预先配置好的 Envoy 代理,仅支持 Zipkin 协议。比如,如果用户想使用 Jaeger 协议并通过 UDP 发送 tracing span,那么他需要构建自定义的 istio-proxy 镜像。Istio-proxy 的确支持自定义插件,但是,还仅仅有 alpha 版本。因此,如果想避免多个自定义的设置,那么接受并存储 tracing span 的方案里并没有太多的选择。可以使用最受欢迎的协议,Zipkin 或者 Jaeger,但是如果使用后者,所有东西都需要使用 Zipkin 兼容的协议(性能会差一些)来上传。Zipkin 协议通过 HTTP 协议将所有 tracing 信息发送给收集器,消耗更大。

如上文所说,我们需要跟踪应用层协议。这意味着每个服务旁的代理服务器必须理解当时发生的网络事件。Istio 默认所有端口使用 plain TCP,这意味着不会发送 trace。要发送 trace,首先需要在 main mesh config 里启用相关的配置,然后依据服务内协议的实现来命名所有 Kubernetes 服务实体的所有端口。比如:

1
2
3
4
5
6
7
8
9
10
11
apiVersion: v1
kind: Service
metadata:
  name: nginx
spec:
  ports:
    - port: 80
      targetPort: 80
      name: http
  selector:
    app: nginx

还可以使用组合名称,比如 http-magic(Istio 能够识别 http 并且将端口识别为 http 端口)。

为了避免修补多个配置来定义协议,有如下 workaround:在 Pilot 组件执行协议决策逻辑的时候修改它的配置。然后,当然需要切回标准逻辑并且切换所有端口的命名惯例。

为了理解协议是否定义正确,可以从 Envoy 代理进入任意 Sidecar 容器,从 location/config_dump 发送请求给 Envoy 接口的 admin 端口。在最终的配置里,检查相应服务的 Operation 字段。在 Istio 里,它就相当于请求目的地的标识。在 Istio 里自定义这个参数(之后可以在 tracing 系统里看到),启动 Sidecar 容器的时候设置 serviceCluster 标记。比如,它可以从 Kubernetes API 得到的变量里计算出来:

1
--serviceCluster ${POD_NAMESPACE}.$(echo ${POD_NAME} | sed -e 's/-[a-z0-9]*-[a-z0-9]*$//g')

这里很好地解释了 Envoy 的 trace 是如何工作的。

在 Envoy 代理的启动标记里必须指定发送 tracing span 的端口,比如:—-zipkinAddress tracing-collector.tracing:9411

另一个误解:用户可以轻松获取系统内请求的所有 trace

不幸的是事实并非如此。实现的复杂度取决于服务是如何交互的。为什么是这样呢?

问题在于要让 Istio 代理理解服务的入站和出站请求的匹配关系,仅仅截获所有流量是不够的。你需要某种匹配标识符。HTTP Envoy 代理使用特别的 header,这样 Envoy 能够准确理解服务的哪个请求生成对其他服务的特定请求。这些 header 包括:

  • x-request-id,
  • x-b3-traceid,
  • x-b3-spanid,
  • x-b3-parentspanid,
  • x-b3-sampled,
  • x-b3-flags,
  • x-ot-span-context.

如果你只有一个单点,比如,一个基础的客户端,这里可以加入逻辑,然后需要做的就是等待 library 在所有客户端里更新完毕。但是如果你面对的是一个复杂的异构系统,没有统一的服务-服务的网络流量,那么很可能就会有问题。不加这样的逻辑,所有的 tracing 信息都是单层级的。你可以得到所有服务-服务的交互,但是却无法形成网络流量链。

Conclusion

Istio 提供了方便的工具收集网络上的所有 tracing 信息,但是它的实现要求系统的变更,要考虑到 Istio 的实现的独特性。这是要解决的两大问题:定义应用层的协议(Envoy 代理必须支持)以及设置转发信息,匹配入站和出站请求(如果是 HTTP 协议的话,使用 header)。如果这两大问题都解决了,那么你就有了强大的工具,可以从网络上透明地收集信息,即使这是个高度异构的系统,可能由多种语言使用多种架构组成。

下一篇博文里,我们会讨论 Istio 最大的挑战之一每个 Sidecar 代理容器所带来的高 RAM 使用率的问题并且讨论如何解决这个问题。

Rating: