Knative Tracing 介绍

前言

为了实现系统组件的水平扩展、敏捷开发、CD(持续集成)、解耦等各种诉求,现代的大型软件架构往往都是微服务架构。 微服务架构本身会面临一系列诸如:后台错误原因分析、各个微服务组件的调用情况诊断等。所以需要有一个 Tracing 系统解决这些问题。

除了微服务以外 Serverless 应用也面临同样的问题。一个完整的业务实现想要基于 Serverless 模型来开发的话可能会分解成多个 Serverless 模块,每一个模块单独通过 Knative 的 Serving 部署,那么这些不同的 Serving 之间就需要调用链进行事务的串联。

而 Knative 本身没有 Tracing 的设计,需要借助 ServiceMesh 层来实现 tracing。对于开源的 Knative 来说 ServiceMesh 层其实就是 Istio。说到 Istio 的 Tracing 就要提到 OpenTracing 了。OpenTracing 现在已经成了云原生的 Tracing 标准。所以本文我们从 OpenTracing 到 Istio 再到 Knative 来层层分析看看 Knative 应用应该如何使用 Tracing。

为什么需要 Opentracing

目前流行的分布式跟踪系统 Zipkin、Jeager 等。如果这些 Tracing 系统使用不兼容的 API 或者他们对开发语言支持不友好那么就会给微服务开发带来困难。

OpenTracing 通过提供平台无关、厂商无关的 API,使得开发人员能够方便的添加(或更换)追踪系统的实现。 OpenTracing 还提供了流行开发语言的 SDK,这样就非常方便非服务的 tracing 实现。常用的开发语言 SDK 如下:

什么是 tracing

首先我们要搞清楚什么是 Trace,在前面的介绍中我们提到 Trace 是为了解决微服务调用的一些链路追踪等问题,所以 Trace 应该是能够表达一个事务在各个微服务中的执行过程。

所以一个 Trace 代表了一个事务或者流程在系统中的执行过程。在 OpenTracing 标准中,Trace 是由多个 Span 组成的一个有向无环图,每一个 Span 代表事务的一个执行节点或者片段,每一个 Span 都是有名字并且有起止时间的。

分布式追踪中的每个组件都一个或者多个 Span。例如,在一个常规的 RPC 调用过程中,OpenTracing 推荐在 RPC 的客户端和服务端,至少各有一个 Span,用于记录 RPC 调用的客户端和服务端信息。

Causal relationships between Spans in a single Trace

        [Span A]  ←←←(the root span)
            |
     +------+------+
     |             |
 [Span B]      [Span C] ←←←(Span C is a `ChildOf` Span A)
     |             |
 [Span D]      +---+-------+
               |           |
           [Span E]    [Span F] >>> [Span G] >>> [Span H]
                                       ↑
                                       ↑
                                       ↑
                         (Span G `FollowsFrom` Span F)

如上所示是一个由 8 个 Span 组成的完整的 Trace 调用流程。

关于 Span 有两个主要的关系需要解释一下:ChildOf 和 FollowsFrom,从上面的示意图中看起来都是前面的 Span 触发后面的 Span,从图上出不出区别。但实际区别还是很大的。

  • ChildOf

表示父子关系。意思是父 Span 需要等待子 Span 结束才能结束。所以子 Span 会 block 父 Span 的执行。如上所示的 [Span A] 必须等待 B 和 C 都结束自己才算结束,同理 C 也要等待 E 和 F 都结束才行。

  • FollowsFrom

表示兄弟关系。兄弟关系和父子关系的根本区别就是不需要等待兄弟的结束。比如上面的 F 和 G 的关系就是 F 结束之后触发的 G,所以 F 要先于 G 结束,不需要等待 G。

ChildOf 有点儿像是函数调用,父函数需要等待子函数结束。FollowsFrom 有点儿像是 Goroutine 父 goroutine 并不需要等待子 goroutine 的结束,两者各自的生命周期是独立的。

上图可以很好的表示各组件的组合关系,但无法表示组件的调用时间、是串行调用还是并行调用。所以一般的 Trace 实现都会用如下所示的方式展示 Trace 的调用过程

Temporal relationships between Spans in a single Trace


––|–––––––|–––––––|–––––––|–––––––|–––––––|–––––––|–––––––|–> time

 [Span A···················································]
   [Span B··············································]
      [Span D··········································]
    [Span C········································]
         [Span E·······]        [Span F··] [Span G··] [Span H··]

Istio Tracing

Istio 本身是基于 OpenTracing 调用链追踪,并且 ZipKin 和 Jeager 两种实现都可以用于 Istio。

image.png

上图是 Istio 官方例子中的 Bookinfo 应用的例子。Bookinfo 应用由如下四个模块组成

  • Product page

Python 语言开发,用来展示书本信息

  • Reviews

Java 语言开发,接收 Product page 的请求,返回评论信息,同时也调用 Ratings 微服务。Reviews 有三个版本:v1、v2、v3,这个三个版本在响应 Product page 的请求时各有不同:v1 的响应没有评分等级、v2 的响应包含黑色的评分等级、v3 的响应包含红色的评分等级

  • Ratings

Nodejs 语言开发,接受 Reviews 的请求,返回评分信息

  • Details

Ruby 语言开发,接受 Product page 的请求,返回书本详细属性

从前端入口 Gateway 那个 Envoy 上进行一次调用,到四个不同语言开发的服务间完成调用,一次调用输出的 Tracing 可能是这样:

image.png
如上图所示有四个 Span:

  • product page Span
  • detail Span
  • reviews Span
  • rationgs Span

而且这四个 Span 构成了一个调用链,也就是说 Ratings Span 知道自己的父 Span 是 reviews,reviews Span 也知道自己的父 Span 是 product page。那么问题来了 reviews Span 和 ratings Span 是怎么知道自己的父 Span 的呢?

我们知道 Istio 是通过 Sidecar 的方式接管流量的,对于 Sidecar 里面的 Envoy 来说是没有办法判断进来的哪个请求和出去的哪个是一个事务。

我们看一下 Istio bookInfo 的代码 https://github.com/istio/istio/blob/master/samples/bookinfo/src/reviews/reviews-application/src/main/java/application/rest/LibertyRestEndpoint.java#L146 就能发现其实这个 tracing 的完整流程是需要应用程序参与的。

image.png

对于上图所示对于 HTTP 请求需求在 Header 中把 Tracing 的信息获取出来并且在调用下游服务的时候还需要继续传递下去。也就是说 Istio 只是负责接管流量并且创建 Span,但是微服务 Span 之间的衔接是需要应用程序来配合的。

Knative Tracing

了解了 Istio Tracing 的原理我们就比较清楚 Knative 应该的玩法了。Knative Service 部署的应用如果有多个微服务模块,那么微服务模块之间的完整调用链的衔接就需要应用程序来完成。

对于 HTTP 协议而言就需要应用程序提取接受到的请求的 Tracing Header 中的信息并且传递到款下一个微服务的调用中。

另外虽然 OpenTracing 是一个通用的标准,但是 OpenTracing 并没有规定 HTTP Header 应该如何设置。也就是说虽然 Istio 支持 Zipkin 和 Jeager 两种 Tracing 实现,但是这两种是不能混用的,因为这两种实现的 Header 设置是不一样的。不过 Jeager 设置一下也是可以支持 ZipKin 标准的(参考这里),所以我们的应用程序开发的时候也要想好是基于 Zipkin 的规范实现还是基于 Jeager 的规范实现。

小结

  • OpenTracing 是一个通用的 Tracing 标准,但是标准规定的边界还有点儿窄,还有一些地方没有标准
  • Istio Tracing 是需要应用程序一起配合实现的,否则就是一堆杂乱的 Span 堆在一起,无法串联整个事务链
  • Istio Envoy 默认是基于 apache/ncubator-zipkin-b3-propagation 标准实现的 HTTP Header 注入,建议应用程序开发也使用这个标准,这也是 ZipKin 遵循的标准
  • Knative Service 部署的服务需要主动参与 Tracing 的实现,否则无法完成这个调用链的串联。所以如果说 Tracing 是 Serverless 必不可少的一部分,那么 Knative Service 部署的应用有其他依赖的微服务模块的话就必须要主动参与 Tracing 的实现

参考文献

猜你喜欢

转载自yq.aliyun.com/articles/701496