如何使用OpenTelemetry进行端对端跟踪

译文 精选
开发 架构
本文通过简单的示例,向您展示了如何使用OpenTelemetry,跨过微服务或数据库网关,实现对某个请求路径的端对端跟踪。

译者 | 陈峻

审校 | 孙淑娟

无论您是否已经实现了微服务,您的系统往往会由反向代理、应用程序、以及数据库等多个组件组成。只要服务请求流经的组件数量越多,您对于监控的需求就越强烈。当然,监控只是状态跟踪的开始,您更需要一个能够横跨所有组件的聚合性视图,通过指标和日志两个维度,来实现可观察性。

1.W3C的规范

具有跟踪能力的解决方案通常能够通过异构技术栈,来规范化监控的标准格式。目前,市场上已经存在了几个不同的实现规范。而在大多数情况下,正如下面著名的XKCD漫画所描述的那样,一个新的规范需求往往会导致另一个额外规范的实施。

图片

在此,我向你介绍一个新的、市场正在遵守的W3C规范--Trace Context。该规范定义了标准的HTTP标头和一种数值的格式,来支持分布式跟踪场景的上下文信息。毕竟,上下文信息不但能够唯一地标识分布式系统中的各种请求,而且定义了一种添加和传播提供者特定信息的方法。而Trace Context规范恰好标准化了上下文信息在服务之间的发送和修改方式。其中也包含了两个如下图所示的关键性概念:

  • 跟踪(Trace)需要遵循跨越多个组件的请求路径。
  • 跨越(Span)被绑定到了单个组件上,并通过父子关系,链接到另一个跨越。

图片

目前Trace Context已经有了多种实现,而其中一种便是OpenTelemetry。

2.OpenTelemetry可作为黄金标准

OpenTelemetry是一种工具、API和SDK的集合,适用于多种语言。用户可以使用它来检测、生成、收集和导出指标、日志和跟踪等遥测类型的数据,以便分析软件的性能和行为。

OpenTelemetry是一个由CNCF管理的项目。在它之前已有如下两个项目:

  • OpenTracing,顾名思义是专注于跟踪的
  • OpenCensus,其目标是管理指标和跟踪

通过合并两个项目的日志功能,OpenTelemetry目前提供了一组专注于可观察性的层面。该层面具有如下特征:

  • 通过多种语言检测API
  • 用不同的语言规范化实现
  • 提供诸如收集器等基础设施组件
  • 提供诸如W3C Trace Context的互操作性格式

值得注意的是,OpenTelemetry虽然是Trace Context的一种实现,但是其功能更为广泛。Trace Context将自身限制在HTTP场景中,而OpenTelemetry则可以跨越到Kafka等非Web组件上。

3.用例

下面,让我们以某个电商网站为例,进行深入探讨。假设该电商网站是围绕微服务设计的,其中包含了管理产品的catalog和处理产品价格的pricing,这两个微服务。如下图所示,catalog是一个用Kotlin编写的Spring Boot应用,而pricing一个Python Flask应用。

图片

每个微服务都可以通过REST API被访问到,并且受到API网关的保护。当用户访问该应用时,主页会从后台获取所有产品、及其价格的信息,予以呈现。而跟踪则可以让我们跨过微服务或数据库网关,去跟踪某个请求的路径。

4.网关处的跟踪

我们将使用Apache APISIX在入口点(也就是网关)处生成跟踪ID。此处的Apache APISIX提供了诸如:负载均衡、动态上游、金丝雀发布、断路器、身份验证、可观察性等,丰富的流量管理功能。同时,基于插件架构的Apache APISIX,通过提供OpenTelemetry插件,来根据OpenTelemetry的规范产生跟踪数据。不过,该插件仅支持基于HTTP的二进制编码--OLTP。

下面,让我们来配置opentelemetry插件:

YAML
apisix:
enable_admin: false #1
config_center: yaml #1
plugins:
-OpenTelemetry #2
plugin_attr:
opentelemetry:
resource:
service.name: APISIX #3
collector:
address: jaeger:4318 #4

#1:在独立模式下运行Apache APISIX,以便应用易于被理解。

#2:将opentelemetry配置为全局插件。

#3:设置服务的名称。它将成为出现在跟踪显示组件中的名称。

#4:将跟踪发送到jaeger服务处。我们将在下文中详细讨论。

为了跟踪每一条路由,我们需要通过如下代码,将插件设置为全局插件,而无需向每条路由添加插件:

YAML
global_rules:
- id: 1
plugins:
opentelemetry:
sampler:
name:

#1:由于跟踪本身也会对性能产生影响,而且追踪的内容越多,影响也就越大,因此,我们全面仔细平衡性能影响与可观察性的收益。不过,在本例中,我们仍然希望能够跟踪每个请求。

5.收集、存储和显示跟踪

虽然Trace Context是一种W3C规范,而且OpenTelemetry是事实上的标准,但是目前市场上仍存在许多收集、存储和显示跟踪的解决方案。例如,Elastic技术栈虽然可以处理各种存储和显示,但是用户必须依靠其他产品进行收集;而Jaeger和Zipkin则能够通过一个完整的套件,全面实现收集、存储和显示跟踪。

早于OpenTelemetry的Jaeger和Zipkin虽然有着各自不同的跟踪传输格式,但是它们都能够与OpenTelemetry的格式相集成。鉴于Jaeger能够提供一个一体化的Docker镜像,而且每个功能都有其对应的组件,同时它们被嵌入在同一个镜像中,以方便配置。因此,我在此选用Jaeger。其镜像的相关端口分配如下:

端口

协议

组件

功能

16686

HTTP

查询

服务前端

4317

HTTP

收集器

如果启用的话,接受通过gRPC的OpenTelemetry协议(OTLP)

4318

HTTP

收集器

如果启用的话,接受通过HTTP的OpenTelemetry协议

其Docker Compose的代码为:

YAML
services:
jaeger:
image: jaegertracing/all-in-one:1.37 #1
environment:
- COLLECTOR_OTLP_ENABLED=true #2
ports:
- "16686:16686" #3

#1:使用all-in-one的镜像。

#2:启用OpenTelemetry格式的收集器(非常重要)。

#3:暴露UI端口。

至此,我们已经完成了基础设施的构建,下面我们将专注于在应用中启用跟踪。

6.Flask应用的跟踪

pricing服务是一个简单的Flask应用程序。它提供了从数据库中获取单个产品与价格的端点。下面是对应的代码:

Python
@app.route('/price/<product_str>') #1-2
def price(product_str: str) -> Dict[str, object]:
product_id = int(product_str)
price: Price = Price.query.get(product_id) #3
if price is None:
return jsonify({'error': 'Product not found'}), 404
else:
low: float = price.value - price.jitter #4
high: float = price.value + price.jitter #4
return {
'product_id': product_id,
'price': round(uniform(low, high), 2) #4
}

#1:端点

#2:路由需要产品的ID。

#3:使用SQLAlchemy从数据库中获取数据。

#4:在此,我们会随机产生价格。当然,真实定价引擎并非如此。

值得注意的是,为了观察跟踪,我们在此采用的是低效的、每次仅调用并获取单一价格的方式。而在现实生活中,路由应该能够接受多个产品的ID,并在一个“请求-响应”中获取所有相关的价格。

我们可以用自动和手动两种方式来检测应用。由于手动需要付出开发时间,而自动既省力又快速,因此我建议您从自动开始,如需添加手动的方式。

首先,我们需要添加几个Python包:

  • opentelemetry-distro[otlp]==0.33b0
  • opentelemetry-instrumentation
  • opentelemetry-instrumentation-flask

接着,我们需要配置如下参数:

YAML
pricing:
build: ./pricing
environment:
OTEL_EXPORTER_OTLP_ENDPOINT: http://jaeger:4317 #1
OTEL_RESOURCE_ATTRIBUTES: service.name=pricing #2
OTEL_METRICS_EXPORTER: none #3
OTEL_LOGS_EXPORTER:

左右滑动查看完整代码

#1:将跟踪发送给Jaeger。

#2:设置服务的名称。它将成为出现在跟踪显示组件中的名称。

#3:在此,我们暂时忽略日志和指标。

然后,我们并不使用标准的flask run命令,而是将其进行如下包装:

Shell
opentelemetry-instrument flask run

至此,我们已经从方法调用和Flask路由中,收集到了跨越。

下面,我们以手动的方式,按需添加额外的跨度:

Python
fromOpenTelemetryimport trace
@app.route('/price/<product_str>')
def price(product_str: str) -> Dict[str, object]:
product_id = int(product_str)
with tracer.start_as_current_span("SELECT * FROM PRICE WHERE ID=:id", attributes={":id": product_id}) as span: #1
price: Price = Price.query.get(product_id)
# ...

#1:使用配置的标签和属性添加一个额外的跨度。

7.Spring Boot应用的跟踪

catalog服务是用Kotlin开发的Reactive Spring Boot应用。它提供了如下两个端点:

  • 获取单个产品
  • 获取所有产品

两者都是先查看产品数据库,再查询上述pricing服务的价格。

而对于Python而言,我们同样可以利用自动和手动两种检测方法。在此,让我们先从唾手可得的自动化开始。在JVM上,我们通过一个代理来实现:

Shell
java -javaagent :opentelemetry-javaagent.jar -jar catalog.jar

与Python一样,它为每个方法的调用和HTTP的入口点创建了跨越。同时,它还会检测JDBC的调用。在本例的Reactive栈中,我们使用的是R2DBC。因此,我们需要配置如下默认行为:

YAML
catalog:
build: ./catalog
environment:
APP_PRICING_ENDPOINT: http://pricing:5000/price
OTEL_EXPORTER_OTLP_ENDPOINT: http://jaeger:4317 #1
OTEL_RESOURCE_ATTRIBUTES: service.name=orders #2
OTEL_METRICS_EXPORTER: none #3
OTEL_LOGS_EXPORTER:

#1:将跟踪发送给Jaeger。

#2:设置服务的名称。它将成为出现在跟踪显示组件中的名称。

#3:在此,我们暂时忽略日志和指标。

而对于Python而言,我们直接添加手动检测。当然,目前有两个选项可被采用:程序化和基于注释。除非我们引入Spring Cloud Sleuth,否则前者会略显复杂。下面,让我们来添加额外的依赖性:

XML
<dependency>
<groupId>io.opentelemetry.instrumentation</groupId>
<artifactId>opentelemetry-instrumentation-annotations</artifactId>
<version>1.17.0-alpha</version>
</dependency>

其对应的注释性代码为:

Kotlin
@WithSpan("ProductHandler.fetch") //1
suspend fun fetch(@SpanAttribute("id") id: Long): Result<Product> { //2
val product = repository.findById(id)
return if (product == null) Result.failure(IllegalArgumentException("Product $id not found"))
else Result.success(product)
}

#1:使用配置的标签添加一个额外的跨越。

#2:将参数用作属性,将键(key)设置为id,其对应的值则设置为参数的运行时数值。

8.输出结果

下面,我们通过如下命令来查看该示例的结果:

Shell
curl localhost:9080/products
curl localhost:9080/products/1

通过如下Jaeger UI,我们可以找到两条跟踪(每次调用一条):

图片

我们也可以深入研究单个跟踪的跨越:

图片

值得注意的是,我们还可以在没有前文那张UML图表的情况下,推断出其数据流图。此类数据流图很好地显示了组件的内部调用。而且,每个跨越都包含了由自动与手动检测添加进来的属性:

图片

9.小结

综上所述,我通过简单的示例,展示了如何使用OpenTelemetry,跨过微服务或数据库网关,实现对某个请求路径的端对端跟踪。

虽然在现实世界中,跟踪可能会涉及与HTTP无关的组件,例如Kafka和消息队列等,但是大多数系统仍然会以某种方式去依赖HTTP。而且,跨组件地跟踪HTTP请求,是实现系统可观察性的良好开端。

原文链接:https://dzone.com/articles/end-to-end-tracing-with-opentelemetry

责任编辑:武晓燕 来源: 51CTO技术栈
相关推荐

2017-01-05 20:11:34

大数据技术审计系统

2016-09-13 19:51:01

移动应用图片流量优化

2016-04-18 18:09:43

2022-10-12 08:42:37

物联网

2023-10-06 13:22:50

自动驾驶技术

2021-03-29 10:56:51

人工智能深度学习

2019-08-22 11:16:41

云计算数据中心技术

2021-03-19 17:59:30

深度学习音频程序

2010-01-05 14:32:01

JSON 数据

2010-11-25 22:09:23

康普端对端

2013-09-16 09:21:59

Orange远程医疗M2M

2024-02-19 16:06:53

人工智能AI声音克隆Python

2022-09-02 10:20:44

网络切片网络5G

2010-06-01 14:35:16

SVN仓库导出

2023-10-30 11:28:33

Kubernetes负载均衡

2021-09-27 16:39:10

PythonGif压缩

2010-12-17 10:16:33

OpenVAS

2023-10-11 10:52:26

微软Playwright

2016-10-26 22:07:06

macaca自动化测试javascript

2016-10-26 22:24:00

macaca自动化测试javascript
点赞
收藏

51CTO技术栈公众号