NGINX.COM
Web Server Load Balancing with NGINX Plus

本文是“Microservices June 微服务之月 2023”系列教程之一,旨在帮助您将概念付诸实践。

7 月 1 日前免费注册线上教学项目 NGINX 微服务之月,并在 8 月 1 日前按要求完成课程,即可获得 NGINX 独家纪念礼品以及结课证书。

本文末尾包括本实验的验收标准,想要获取礼品和证书的同学,请在 8 月 1 日前随单元小测提交实验结果。

本系列教程包括:

本单元的动手实验让大家能有机会亲手搭建 OTel 的工具,配置应用(模拟即时通信软件)和基础设施(NGINX)的埋点(Instrumentation),体验如何利用 OTel 的分布式追踪了解一个应用端到端的处理过程和用户体验。

 

概述

本实验侧重于使用 OTel 追踪微服务应用程序的操作。在本实验的四个挑战中,您将学习如何通过系统追踪请求,并回答有关微服务的问题:

  • 设置基本的 OTel 埋点
  • 为所有服务设置 OTel 埋点和追踪可视化
  • 学习阅读 OTel 追踪
  • 根据追踪读数优化埋点

这些挑战说明了我们在首次设置追踪时推荐的流程。步骤是:

  1. 了解系统以及您正在测量的特定操作。
  2. 决定你需要从正在运行的系统中知道什么。
  3. “纯粹地”地测量系统——这意味着使用默认配置,而不尝试淘汰您不需要的信息或收集自定义数据点——并评估埋点是否帮助您回答问题。
  4. 调整报告的信息,以便您更快地回答这些问题。

注意:本实验中的意图是说明一些关于遥测的核心概念,而不是展示在生产中部署微服务的正确方法。虽然它使用真正的“微服务”架构,但有一些重要的注意事项:

本实验不使用 Kubernetes 或 Nomad 等容器编排框架。这是为了让您可以了解微服务概念,而不会陷入特定框架的细节中。这里介绍的模式可移植到运行这些框架之一的系统。
这些服务经过优化,以方便理解,而不是软件工程的严谨性。关键是查看服务在系统中的作用及其通信模式,而不是代码的细节。有关更多信息,请参阅各个服务的 README 文件。

 

实验架构和遥测目标

架构和用户流

该图说明了实验中使用的微服务和其他元素之间的整体架构和数据流。

这两个微服务是:

  • 信使服务 messenger -具有消息存储功能的简单聊天 API
  • 通知服务 notifier – 根据用户的偏好触发事件以提醒用户的侦听器

三个支撑基础设施是:

  • NGINX 开源版-信使服务和整个系统的入口点
  • RabbitMQ – 一个流行的开源消息代理,使服务能够异步通信
  • Jaeger – 一个开源的端到端分布式追踪系统,用于从产生遥测的系统组件中收集和可视化遥测

将 OTel 从图片中取出一会儿,我们可以集中精力处理我们正在追踪的事件序列:当用户发送新的聊天消息并通知收件人时会发生什么。

user flow

流量像这样分解:

  1. 用户向信使服务发送消息。NGINX 反向代理拦截消息并将其转发到信使服务的许多实例之一。

  2. 信使服务 messenger 将新消息写入其数据库。

  3. 信使服务 messenger 在 RabbitMQ 消息队列上生成一个名为 chat_queue 的事件,以指示消息已发送。该事件是通用的,没有特定的目标。

  4. 同时:

    • 4a, 信使服务返回对发件人报告消息已成功发送的响应。
    • 4b, 通知服务在chat_queue上注意到新事件并使用它。
  5. 通知服务 notifier 检查其数据库中新消息收件人的通知首选项。

  6. 通知服务 notifier 使用收件人的首选方法发送一个或多个通知(在本实验中,方法的选择是短信和电子邮件)。

遥测目标

在设置遥测埋点时,最好从一套更明确的埋点目标开始,而不是“发送一切并希望获得洞察”。本实验有三个关键遥测目标:

  1. 了解请求在新消息流中经历的所有步骤
  2. 有信心,在正常情况下,流程将在五秒内端到端执行
  3. 查看通知服务开始处理由信使服务 messenger 发送的事件需要多长时间(过度延迟可能意味着通知服务无法从事件队列中读取,并且事件正在备份)

请注意,这些目标与系统的技术操作和用户体验都有关。

 

前置条件和设置

前置条件

要在您自己的环境中完成实验,您需要:

  • Linux/Unix 兼容环境

    注意:本实验中涉及追踪 NGINX 的活动在基于 ARM 的处理器上不起作用,因为 NGINX 的 OpenTelemetry 模块不兼容。(这包括 Linux aarch64 架构和带有 M1 或 M2 芯片的苹果机器。)涉及 messenger 和通知者服务的活动适用于所有架构。

  • 对 Linux 命令行、JavaScript 和 bash 的基本熟悉(但提供并解释了所有代码和命令,因此您仍然可以在有限的知识下取得成功)

  • DockerDocker Compose

  • Node.js 19.x 或更高版本

    • 我们测试了 19.x 版本,但预计新版本的 Node.js 也能工作。
    • 有关安装 Node.js 的详细信息,请参阅信使服务 messenger 存储库中的 README。您还可以安装 asdf,以获得与实验中使用的完全相同的 Node.js 版本。
  • curl(已安装在大多数系统上)

  • 架构和用户流中列出的技术:信使和通知器(您将在下一节中下载)、NGINX 开源版JaegerRabbitMQ

注意:本实验使用 JavaScript SDK,因为信使和通知器服务是用 Node.js 编写的。您还可以设置 OTel 自动埋点功能(也称为自动埋点),以便了解 OTel 提供的信息类型。本实验解释了您需要了解的关于 OTel Node.js SDK 的所有信息,但有关更多详细信息,请参阅 OTel 文档

确认前置条件示例:

xiong@ubuntu22:~$ asdf global nodejs 19.9.0 
xiong@ubuntu22:~$ 
xiong@ubuntu22:~$ 
xiong@ubuntu22:~$ asdf current 
nodejs          19.9.0          /home/xiong/.tool-versions
xiong@ubuntu22:~$ 
xiong@ubuntu22:~$ 
xiong@ubuntu22:~$ node -v 
v19.9.0
xiong@ubuntu22:~$ 
xiong@ubuntu22:~$ 
xiong@ubuntu22:~$ docker -v 
Docker version 23.0.4, build f480fb1
xiong@ubuntu22:~$ 
xiong@ubuntu22:~$ 
xiong@ubuntu22:~$ docker compose version
Docker Compose version v2.17.2
xiong@ubuntu22:~$ 
xiong@ubuntu22:~$ curl --version
curl 7.81.0 (x86_64-pc-linux-gnu) libcurl/7.81.0 OpenSSL/3.0.2 zlib/1.2.11 brotli/1.0.9 zstd/1.4.8 libidn2/2.3.2 libpsl/0.21.0 (+libidn2/2.3.2) libssh/0.9.6/openssl/zlib nghttp2/1.43.0 librtmp/2.3 OpenLDAP/2.5.13
Release-Date: 2022-01-05
Protocols: dict file ftp ftps gopher gophers http https imap imaps ldap ldaps mqtt pop3 pop3s rtmp rtsp scp sftp smb smbs smtp smtps telnet tftp 
Features: alt-svc AsynchDNS brotli GSS-API HSTS HTTP2 HTTPS-proxy IDN IPv6 Kerberos Largefile libz NTLM NTLM_WB PSL SPNEGO SSL TLS-SRP UnixSockets zstd
xiong@ubuntu22:~$ 
xiong@ubuntu22:~$ 

设置

  1. 启动终端会话。
  2. 在您的主目录中,创建 microservices-june 目录,并将本实验的 GitHub 存储库克隆到其中。(您还可以使用不同的目录名称并相应地调整说明。)

注意:在整个实验中,Linux 命令行上的提示被省略,以便更容易将命令复制并粘贴到终端中。波浪号(~)代表您的主目录。

mkdir ~/microservices-june
cd ~/microservices-june
git clone https://jihulab.com/microservices-june-2023/messenger --branch mm23-metrics-start
git clone https://jihulab.com/microservices-june-2023/notifier --branch mm23-metrics-start
git clone https://jihulab.com/microservices-june-2023/platform --branch mm23-metrics-start

 

挑战 1:设置基本的 OTel 埋点

在这个挑战中,您启动信使服务,并配置 OTel 自动埋点以将遥测发送到控制台。

启动信使 messenger 服务

  1. 修改当前目录到 platform repo,并启动 Docker Compose:

    cd ~/microservices-june/platform
    docker compose up -d --build
    

    这启动了 RabbitMQ 和 Jaeger,这将用于后续的挑战。

    • ‑d 标志指示 Docker Compose 在容器启动时从容器中分离(否则容器将保持连接到您的终端)。
    • --build 标志指示 Docker Compose 在启动时重建所有映像。这可以确保您正在运行的图像通过对文件的任何潜在更改保持更新。

    待启动完成后,检查容器状态:

    docker ps
    

    示例:

    xiong@ubuntu22:~/microservices-june/platform$ 
    xiong@ubuntu22:~/microservices-june/platform$ docker ps 
    CONTAINER ID   IMAGE                               COMMAND                  CREATED         STATUS         PORTS                                                                                                                                                                   NAMES
    4ee21a723957   jaegertracing/all-in-one:1.41       "/go/bin/all-in-one-…"   5 seconds ago   Up 3 seconds   5775/udp, 5778/tcp, 14250/tcp, 0.0.0.0:4317-4318->4317-4318/tcp, :::4317-4318->4317-4318/tcp, 0.0.0.0:16686->16686/tcp, :::16686->16686/tcp, 6831-6832/udp, 14268/tcp   jaeger
    2a1ac8729351   rabbitmq:3.11.4-management-alpine   "docker-entrypoint.s…"   5 seconds ago   Up 3 seconds   4369/tcp, 5671/tcp, 0.0.0.0:5672->5672/tcp, :::5672->5672/tcp, 15671/tcp, 15691-15692/tcp, 25672/tcp, 0.0.0.0:15672->15672/tcp, :::15672->15672/tcp                     rabbitmq
    xiong@ubuntu22:~/microservices-june/platform$ 
    
  2. 修改当前目录到信使 messenger repo 中的 app 目录并安装 Node.js(如果您愿意,您可以替换其他方法):

    cd ~/microservices-june/messenger/app
    asdf local nodejs 19.9.0
    node -v
    
  3. 安装依赖项

    npm install
    npm list
    

    示例:

    xiong@ubuntu22:~/microservices-june/messenger/app$ 
    xiong@ubuntu22:~/microservices-june/messenger/app$ npm list
    messenger@0.1.0 /home/xiong/microservices-june/messenger/app
    ├── @opentelemetry/auto-instrumentations-node@0.36.4
    ├── @opentelemetry/exporter-trace-otlp-http@0.36.0
    ├── @opentelemetry/resources@1.10.0
    ├── @opentelemetry/sdk-node@0.36.0
    ├── @opentelemetry/semantic-conventions@1.10.0
    ├── amqplib@0.10.3
    ├── consul@1.2.0
    ├── convict@6.2.4
    ├── cors@2.8.5
    ├── express-promise-router@4.1.1
    ├── express@4.18.2
    ├── ip@1.1.8
    ├── mocha@10.2.0
    ├── pg@8.8.0
    ├── prettier@2.8.3
    ├── supertest@6.3.3
    └── uuid@9.0.0
    
    xiong@ubuntu22:~/microservices-june/messenger/app$ 
    
  4. 启动信使服务 messenger 的 PostgreSQL 数据库:

    docker compose up -d
    docker ps
    

    示例:

    xiong@ubuntu22:~/microservices-june/messenger/app$ 
    xiong@ubuntu22:~/microservices-june/messenger/app$ docker ps 
    CONTAINER ID   IMAGE                               COMMAND                  CREATED          STATUS          PORTS                                                                                                                                                                   NAMES
    ebe96cf8923e   postgres:15.1                       "docker-entrypoint.s…"   2 minutes ago    Up 2 minutes    0.0.0.0:5432->5432/tcp, :::5432->5432/tcp                                                                                                                               messenger-db
    4ee21a723957   jaegertracing/all-in-one:1.41       "/go/bin/all-in-one-…"   11 minutes ago   Up 11 minutes   5775/udp, 5778/tcp, 14250/tcp, 0.0.0.0:4317-4318->4317-4318/tcp, :::4317-4318->4317-4318/tcp, 0.0.0.0:16686->16686/tcp, :::16686->16686/tcp, 6831-6832/udp, 14268/tcp   jaeger
    2a1ac8729351   rabbitmq:3.11.4-management-alpine   "docker-entrypoint.s…"   11 minutes ago   Up 11 minutes   4369/tcp, 5671/tcp, 0.0.0.0:5672->5672/tcp, :::5672->5672/tcp, 15671/tcp, 15691-15692/tcp, 25672/tcp, 0.0.0.0:15672->15672/tcp, :::15672->15672/tcp                     rabbitmq
    xiong@ubuntu22:~/microservices-june/messenger/app$ 
    
    
  5. 创建数据库模式和表,并插入一些种子数据:

    npm run refresh-db
    

配置发送到控制台的 OTel 自动埋点

使用 OTel 自动埋点,您无需修改信使 messenger 代码库中的任何内容来设置追踪。所有追踪配置不是导入到应用程序代码本身,而是在运行时导入 Node.js 进程的脚本中定义。

在这里,您可以配置信使服务 messenger 的自动埋点,使用最基本的追踪目的地,即控制台。在挑战 2 中,您将更改配置,将追踪作为外部收集器发送给 Jaeger。

  1. 仍在信使服务 messenger repo 的 app 目录中工作,安装核心 OTel Node.js 软件包:

    npm install @opentelemetry/sdk-node@0.36.0 \
                @opentelemetry/auto-instrumentations-node@0.36.4
    

    这些库提供以下功能:

    • @opentelemetry/sdk-node– OTel 数据的生成和导出
    • @opentelemetry/auto-instrumentations-node– 使用所有最常见的 Node.js 埋点的默认配置进行自动设置

    注意:OTel 的怪癖是,其 JavaScript SDK 被分解成非常非常小的碎片。因此,您将再安装几个软件包,仅用于本实验中的基本示例。要了解除了本实验所涵盖的任务外,您可能需要哪些软件包来完成埋点任务,请仔细阅读(非常好的)OTel 入门指南,并查看 OTel GitHub 存储库

  2. 创建一个名为 tracing.mjs 的新文件,以包含追踪的设置和配置代码:

    touch tracing.mjs
    
  3. 在您首选的文本编辑器中,打开 tracing.mjs 并添加以下代码:

    //1
    import opentelemetry from "@opentelemetry/sdk-node";
    import { getNodeAutoInstrumentations } from "@opentelemetry/auto-instrumentations-node";
    
    //2
    const sdk = new opentelemetry.NodeSDK({
    traceExporter: new opentelemetry.tracing.ConsoleSpanExporter(),
    instrumentations: [getNodeAutoInstrumentations()],
    });
    
    //3
    sdk.start();
    

    该代码执行以下操作:

    1. 从 OTel SDK 导入所需的功能和对象。

    2. 创建 NodeSDK 的新实例,并将其配置为:

      • 将 span 发送到控制台(ConsoleSpanExporter)。

      • 使用自动埋点作为埋点的基本套件。此埋点加载了所有最常见的自动埋点库。在实验中,相关的是:

        • @opentelemetry/instrumentation-pg 用于 Postgres 数据库库(pg)
        • @opentelemetry/instrumentation-express 对于 Node.js Express 框架
        • @opentelemetry/instrumentation-amqplib 对于 RabbitMQ 库(amqplib)
    3. 启动 SDK。

  4. 启动信使服务,导入您在第 3 步中创建的自动埋点脚本。

    node --import ./tracing.mjs index.mjs
    

    过了一会儿,许多与追踪相关的输出开始出现在控制台(您的终端):

    ...
    {
    traceId: '9c1801593a9d3b773e5cbd314a8ea89c',
    parentId: undefined,
    traceState: undefined,
    name: 'fs statSync',
    id: '2ddf082c1d609fbe',
    kind: 0,
    timestamp: 1676076410782000,
    duration: 3,
    attributes: {},
    status: { code: 0 },
    events: [],
    links: []
    }
    ...
    

注意:在挑战 2 中将终端会话打开以供重复使用。

 

挑战 2:为所有服务设置 OTel 埋点和追踪可视化

您可以使用许多工具来查看和分析追踪,但本实验使用 Jaeger。Jaeger 是一个简单、开源的端到端分布式追踪框架,具有内置基于 Web 的用户界面,用于查看 span 和其他追踪数据。平台 platform 的 repo 中提供的基础设施包括 Jaeger(您在挑战 1 的第 1 步中启动),因此您可以专注于分析数据,而不是处理复杂的工具。

Jaeger 可以在浏览器中的 "http://localhost:16686"端点访问,但如果您现在访问端点,您的系统就没有什么可看的了。这是因为您当前收集的追踪正在发送到控制台!
要在 Jaeger 中查看追踪数据,您需要使用 OpenTelemetry 协议(OTLP)格式导出追踪。

在这个挑战中,您通过配置以下埋点来测量核心用户流:

  • 信使服务 messenger,将追踪目的地从控制台切换到 Jaeger
  • 通知服务 notifier
  • NGINX

配置发送到外部收集器的 OTel 自动埋点

谨此提醒,使用 OTel 自动埋点意味着您不会修改信使代码库中的任何内容来设置追踪。相反,所有追踪配置都在一个脚本中,该脚本在运行时导入到 Node.js 进程中。在这里,您将信使服务 messenger 生成的追踪的目标从控制台更改为外部收集器(本实验中的 Jaeger)。

  1. 仍然在与挑战 1 相同的终端中工作,在信使服务 messenger 的 app 目录中,安装 OTLP 导出器 exporter Node.js 包:

    npm install @opentelemetry/exporter-trace-otlp-http@0.36.0
    

    @opentelemetry/exporter-trace-otlp-http 库通过 HTTP 以 OTLP 格式导出追踪信息。它用于向 OTel 外部收集器发送遥测数据。

  2. 打开 tracing.mjs(您在挑战 1 中创建和编辑),并进行以下更改:

    • 将此行添加到文件顶部的 import 语句集中:
    import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
    
    • 将您提供给 OTel SDK 的“导出器”从挑战 1 中使用的控制台导出器更改为可以通过 HTTP 将 OTLP 数据发送到 OTLP 兼容的收集器。替换:
    traceExporter:new opentelemetry.tracing.ConsoleSpanExporter(),
    

    为:

    traceExporter: new OTLPTraceExporter({ headers: {} }),
    

    注意:为了简单起见,本实验假设收集器位于默认位置,"http://localhost:4318/v1/traces"。在真实系统中,明确设置位置是个好主意。

  3. 按 Ctrl+c 停止信使服务,您在配置 OTel 自动埋点发送到控制台的第4步中在此终端中启动了该服务。然后重新启动它以使用步骤 2 中配置的新导出器:

    ^c
    node --import ./tracing.mjs index.mjs
    
  4. 打开第二个单独的终端会话。(后续说明称其为客户端终端和原始终端——在步骤 1 和 3 中使用——信使终端。)等待大约十秒钟,然后向信使服务发送健康检查请求(如果您想看到多个追踪,您可以运行几次):

    curl -X GET http://localhost:4000/health
    

    在发送请求前等待十秒钟有助于使您的追踪更容易找到,因为它发生在自动埋点在服务启动时生成的许多追踪之后。

  5. 在浏览器中,在 "http://localhost:16686" 访问 Jaeger UI,并验证 OTLP 导出器是否按预期工作。单击标题栏中的搜索,然后从服务字段的下拉菜单中选择名称以 unknown_service 开头的服务。点击 Find Traces 按钮:

    unknown_service

  6. 单击窗口右侧的 trace 以显示其中跨度的列表。每个 span 都描述了作为追踪的一部分运行的操作,有时涉及多个服务。屏幕截图中的 jsonParser 跨度显示了运行信使服务请求处理代码的 jsonParser 部分所花费的时间。

    jsonparser

  7. 如第5步所述,OTel SDK(unknown_service)导出的服务名称没有意义。要解决这个问题,请在信使终端中按 Ctrl+c 停止信使服务。然后再安装几个 Node.js 软件包:

    ^c 
    npm install @opentelemetry/semantic-conventions@1.10.0 \
                @opentelemetry/resources@1.10.0
    

    这两个库提供以下功能:

    @opentelemetry/semantic-conventions– 定义 OTel 规范中定义的 trace 的标准属性。
    @opentelemetry/resources– 定义一个对象(资源),该对象(资源)表示生成 OTel 数据的来源(在本实验中,信使服务)。

  8. 在文本编辑器中打开 tracing.mjs 并进行以下更改:

    • 将这些行添加到文件顶部的 import 语句集中:
    import { Resource } from "@opentelemetry/resources";
    import { SemanticResourceAttributes } from "@opentelemetry/semantic-conventions";
    
    • 通过在最后一个 import 语句后添加以下行,在 OTel 规范中的正确密钥下创建一个名为 messenger 的 resource:
    const resource = new Resource({
        [SemanticResourceAttributes.SERVICE_NAME]: "messenger", 
    });
    
    • 通过文件中添加 "resource" 的行,将 resource 对象传递给 NodeSDK 构造函数:
    const sdk = new opentelemetry.NodeSDK({
        resource,
        traceExporter: new OTLPTraceExporter({ headers: {} }),
        instrumentations: [getNodeAutoInstrumentations()],
    });
    
  9. 重新启动信使服务:

    node --import ./tracing.mjs index.mjs
    
  10. 等待大约十秒钟,然后在客户端终端(您在步骤 4 中打开)中向服务器发送另一个健康检查请求(如果您想查看多个追踪,您可以运行该命令几次):

    curl -X GET http://localhost:4000/health
    
  11. 确认浏览器中出现名为 messenger 的新服务(这可能需要几秒钟,您可能需要刷新 Jaeger UI):

    messenger

  12. 从 servie 下拉菜单中选择信使,然后单击 “Find Traces” 按钮以查看来自信使服务的所有最近追踪(屏幕截图显示了 20 个中最近的 2 个):

    traces

  13. 单击 trace 以显示其中 span。每个 span 都被正确标记为来自 messenger 服务:

    span

配置通知服务 notifier OTel 自动埋点

现在启动并配置通知服务 notifier 自动埋点,运行与信使服务前两个部分中基本相同的命令。

  1. 打开一个新的终端会话(在后续步骤中称为通知终端)。修改当前目录到通知器 repo 中的 app 目录并安装 Node.js(如果您愿意,您可以替换其他方法):

    cd ~/microservices-june/notifier/app
    asdf local nodejs 19.9.0
    node -v
    
  2. 安装依赖项:

    npm install
    npm list
    
  3. 启动通知器服务 notifier 的 PostgreSQL 数据库:

    docker compose up -d
    docker ps
    

    示例:

    xiong@ubuntu22:~/microservices-june/notifier/app$ 
    xiong@ubuntu22:~/microservices-june/notifier/app$ docker ps 
    CONTAINER ID   IMAGE                               COMMAND                  CREATED          STATUS          PORTS                                                                                                                                                                   NAMES
    b3d25ec682f2   postgres:15.1                       "docker-entrypoint.s…"   6 seconds ago    Up 4 seconds    5432/tcp, 0.0.0.0:5433->5433/tcp, :::5433->5433/tcp                                                                                                                     notifier-db-1
    ebe96cf8923e   postgres:15.1                       "docker-entrypoint.s…"   9 minutes ago    Up 9 minutes    0.0.0.0:5432->5432/tcp, :::5432->5432/tcp                                                                                                                               messenger-db
    4ee21a723957   jaegertracing/all-in-one:1.41       "/go/bin/all-in-one-…"   17 minutes ago   Up 17 minutes   5775/udp, 5778/tcp, 14250/tcp, 0.0.0.0:4317-4318->4317-4318/tcp, :::4317-4318->4317-4318/tcp, 0.0.0.0:16686->16686/tcp, :::16686->16686/tcp, 6831-6832/udp, 14268/tcp   jaeger
    2a1ac8729351   rabbitmq:3.11.4-management-alpine   "docker-entrypoint.s…"   17 minutes ago   Up 17 minutes   4369/tcp, 5671/tcp, 0.0.0.0:5672->5672/tcp, :::5672->5672/tcp, 15671/tcp, 15691-15692/tcp, 25672/tcp, 0.0.0.0:15672->15672/tcp, :::15672->15672/tcp                     rabbitmq
    xiong@ubuntu22:~/microservices-june/notifier/app$ 
    
    
  4. 创建数据库模式和表,并插入一些种子数据:

    npm run refresh-db
    
  5. 安装 OTel Node.js 软件包(有关软件包功能的描述,请参阅步骤 1 和 3 配置发送到控制台的 OTel 自动埋点):

    npm install @opentelemetry/auto-instrumentations-node@0.36.4 \
    @opentelemetry/exporter-trace-otlp-http@0.36.0 \
    @opentelemetry/resources@1.10.0 \
    @opentelemetry/sdk-node@0.36.0 \
    @opentelemetry/semantic-conventions@1.10.0
    
  6. 创建一个名为 tracing.mjs 的新文件:

    touch tracing.mjs
    
  7. 在您首选的文本编辑器中,打开 tracing.mjs 并添加以下脚本以启动并运行 OTel SDK:

    import opentelemetry from "@opentelemetry/sdk-node";
    import { getNodeAutoInstrumentations } from "@opentelemetry/auto-instrumentations-node";
    import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
    import { Resource } from "@opentelemetry/resources";
    import { SemanticResourceAttributes } from "@opentelemetry/semantic-conventions";
    
    const resource = new Resource({
    [SemanticResourceAttributes.SERVICE_NAME]: "notifier",
    });
    
    const sdk = new opentelemetry.NodeSDK({
    resource,
    traceExporter: new OTLPTraceExporter({ headers: {} }),
    instrumentations: [getNodeAutoInstrumentations()],
    });
    
    sdk.start();
    

    注意:此脚本与信使服务中的脚本完全相同,只是 SemanticResourceAttributes.SERVICE_NAME 字段中的值是 notifier。

  8. 使用 OTel 自动埋点启动通知器服务 notifier:

    node --import ./tracing.mjs index.mjs
    
  9. 等待大约十秒钟,然后在客户端终端向通知服务发送健康检查请求。此服务正在端口 5000 上监听,以防止与在端口 4000 上监听的信使服务发生冲突:

    curl http://localhost:5000/health
    

    注意:在挑战 3 中保持客户端和通知终端打开以供重复使用。

  10. 确认浏览器中的 Jaeger UI 中出现一个名为 notifier 的新服务:

notifier

配置 NGINX 的 OTel 埋点

对于 NGINX,您可以手动设置追踪,而不是使用 OTel 自动埋点方法。目前,使用 OTel 测量 NGINX 的最常见方法是使用用 C 语言编写的模块。第三方模块是 NGINX 生态系统的重要组成部分,但它们需要一些工作来设置。本实验为您进行设置。有关背景信息,请参阅我们博客上为 NGINX 和 NGINX Plus 编译第三方动态模块

  1. 启动一个新的终端会话(NGINX 终端),将当前目录更改为 messenger repo 的根目录,并创建一个名为 load-balancer 的新目录,以及名为 Dockerfile、nginx.conf 和 opentelemetry_module.conf 的新文件:

    cd ~/microservices-june/messenger/
    mkdir load-balancer
    cd load-balancer
    touch Dockerfile
    touch nginx.conf
    touch opentelemetry_module.conf
    
  2. 在您的首选文本编辑器中,打开 Dockerfile 添加以下内容(注释解释了每行的作用,但您可以在不理解所有内容的情况下构建和运行 Docker 容器):

    FROM --platform=amd64 nginx:1.23.1
    
    # Replace the nginx.conf file with our own
    COPY nginx.conf /etc/nginx/nginx.conf
    
    # Define the version of the NGINX OTel module
    ARG OPENTELEMETRY_CPP_VERSION=1.0.3
    
    # Define the search path for shared libraries used when compiling and running NGINX
    ENV LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/opt/opentelemetry-webserver-sdk/sdk_lib/lib
    
    # 1. Download the latest version of Consul template and the OTel C++ web server module, otel-webserver-module
    ADD https://github.com/open-telemetry/opentelemetry-cpp-contrib/releases/download/webserver%2Fv${OPENTELEMETRY_CPP_VERSION}/opentelemetry-webserver-sdk-x64-linux.tgz /tmp
    
    RUN apt-get update \
    && apt-get install -y --no-install-recommends dumb-init unzip \
    # 2. Extract the module files
    && tar xvfz /tmp/opentelemetry-webserver-sdk-x64-linux.tgz -C /opt \
    && rm -rf /tmp/opentelemetry-webserver-sdk-x64-linux.tgz \
    # 3. Install and add the 'load_module' directive at the top of the main NGINX configuration file
    && /opt/opentelemetry-webserver-sdk/install.sh \
    && echo "load_module /opt/opentelemetry-webserver-sdk/WebServerModule/Nginx/1.23.1/ngx_http_opentelemetry_module.so;\n$(cat /etc/nginx/nginx.conf)" > /etc/nginx/nginx.conf
    
    # 4. Copy in the configuration file for the NGINX OTel module
    COPY opentelemetry_module.conf /etc/nginx/conf.d/opentelemetry_module.conf
    
    EXPOSE 8085
    
    STOPSIGNAL SIGQUIT
    
  3. 打开 nginx.conf 并添加以下内容:

    events {}
    
    http {
        include /etc/nginx/conf.d/opentelemetry_module.conf;
    
        upstream messenger {
            server localhost:4000;
        }
    
        server {
            listen 8085;
    
            location / {
                proxy_pass http://messenger;
            }
        }
    }
    

    这个极其基本的 NGINX 配置文件告诉 NGINX:

    • 设置一个名为信使的上游组,代表信使服务 messenger 实例组
    • 监听端口 8085 上的 HTTP 请求
    • 将以 / 开头的路径的所有传入请求(即所有传入请求)转发到 messenger 上游

    注意:这非常接近 NGINX 作为生产环境中的反向代理和负载平衡器的实际配置。唯一的主要区别是,upstream 块中 server 指令的参数通常是域名或 IP 地址,而不是 localhost。

  4. 打开 opentelemetry_module.conf 并添加以下内容:

    NginxModuleEnabled ON;
    NginxModuleOtelSpanExporter otlp;
    NginxModuleOtelExporterEndpoint localhost:4317;
    NginxModuleServiceName messenger-lb;
    NginxModuleServiceNamespace MicroservicesjuneDemoArchitecture;
    NginxModuleServiceInstanceId DemoInstanceId;
    NginxModuleResolveBackends ON;
    NginxModuleTraceAsError ON;
    
  5. 构建包含 NGINX 和 NGINX OTel 模块的 Docker 映像:

    docker build -t messenger-lb .
    

    注:如果无法访问 github 造成 build 失败,可以直接使用预先 build 好的容器镜像 messenger-lb

    curl -O https://jihulab.com/microservices-june-2023/lab-guide-unit4-observability/-/raw/main/images/messenger-lb-nginx.tar.gz
    gunzip -v messenger-lb-nginx.tar.gz
    docker load < messenger-lb-nginx.tar
    
  6. 启动 NGINX 反向代理和负载均衡器的 Docker 容器:

    docker run --rm --name messenger-lb -p 8085:8085 --network="host" messenger-lb
    docker ps
    

    注:也可使用本项目中预先 build 好的镜像启动:

    docker run --rm --name messenger-lb -p 8085:8085 --network="host" registry.jihulab.com/microservices-june-2023/lab-guide-unit4-observability/messenger-lb
    docker ps
    

    示例:

    xiong@ubuntu22:~/microservices-june/notifier/app$ 
    xiong@ubuntu22:~/microservices-june/notifier/app$ docker ps 
    CONTAINER ID   IMAGE                               COMMAND                  CREATED          STATUS          PORTS                                                                                                                                                                   NAMES
    5c081905d89e   messenger-lb                        "/docker-entrypoint.…"   4 minutes ago    Up 4 minutes                                                                                                                                                                            messenger-lb
    b3d25ec682f2   postgres:15.1                       "docker-entrypoint.s…"   10 minutes ago   Up 10 minutes   5432/tcp, 0.0.0.0:5433->5433/tcp, :::5433->5433/tcp                                                                                                                     notifier-db-1
    ebe96cf8923e   postgres:15.1                       "docker-entrypoint.s…"   19 minutes ago   Up 19 minutes   0.0.0.0:5432->5432/tcp, :::5432->5432/tcp                                                                                                                               messenger-db
    4ee21a723957   jaegertracing/all-in-one:1.41       "/go/bin/all-in-one-…"   28 minutes ago   Up 28 minutes   5775/udp, 5778/tcp, 14250/tcp, 0.0.0.0:4317-4318->4317-4318/tcp, :::4317-4318->4317-4318/tcp, 0.0.0.0:16686->16686/tcp, :::16686->16686/tcp, 6831-6832/udp, 14268/tcp   jaeger
    2a1ac8729351   rabbitmq:3.11.4-management-alpine   "docker-entrypoint.s…"   28 minutes ago   Up 28 minutes   4369/tcp, 5671/tcp, 0.0.0.0:5672->5672/tcp, :::5672->5672/tcp, 15671/tcp, 15691-15692/tcp, 25672/tcp, 0.0.0.0:15672->15672/tcp, :::15672->15672/tcp                     rabbitmq
    xiong@ubuntu22:~/microservices-june/notifier/app$ 
    
    
  7. 在客户端中,通过 NGINX 反向代理和负载均衡器向信使服务 messenger 发送运行状况检查请求(无需等待即可发送此请求):

    curl http://localhost:8085/health
    
  8. 在浏览器中,确认新的 Messenger-lb 服务与之前启动的服务一起列在 Jaeger UI 中。您可能需要在浏览器中重新加载 Jaeger UI。

messenger-lb

 

挑战 3:学习解读 OTel 追踪

架构和用户流中,我们概述了用户流的各个阶段,但要回顾一下:

  1. 用户通过向其他用户发送消息来开始对话。
  2. NGINX 反向代理拦截消息并将其转发给信使服务 messenger。
  3. 信使服务 messenger 将消息写入其数据库,然后通过 RabbitMQ 调度事件。
  4. 通知服务消费该事件,查找收件人(第二个用户)的通知首选项,并通过首选方法向收件人发送通知。

实现遥测的目标是:

  1. 了解请求完成新消息流的所有步骤。
  2. 相信在正常情况下,流程会在五秒内端到端执行。
  3. 查看通知服务开始处理 messenger 服务发送的事件需要多长时间。

在这个挑战中,您将学习如何评估 OTel 埋点生成的 trace 是否满足上述目标。首先,你让系统运转并创建一些 trace。然后,检查消息流的 trace 以及 NGINX、信使服务和通知器服务生成的部分。

创建 trace 数据

在客户端中,设置对话并在两个用户之间发送几条消息:

curl -X POST \
    -H "Content-Type: application/json" \
    -d '{"participant_ids": [1, 2]}' \
    'http://localhost:8085/conversations'

curl -X POST \
    -H "User-Id: 1" \
    -H "Content-Type: application/json" \
    -d '{"content": "This is the first message"}' \
    'http://localhost:8085/conversations/1/messages'

curl -X POST \
    -H "User-Id: 2" \
    -H "Content-Type: application/json" \
    -d '{"content": "This is the second message"}' \
    'http://localhost:8085/conversations/1/messages'

如下所示的输出由通知器服务 notifier 生成,并出现在通知器 notifier 终端中:

Received new_message: {"type":"new_message","channel_id":1,"user_id":1,"index":1,"participant_ids":[1,2]}
Sending notification of new message via sms to 12027621401

Received new_message:  {"type":"new_message","channel_id":1,"user_id":2,"index":2,"participant_ids":[1,2]}

Sending notification of new message via email to the_hotstepper@kamo.ze

Sending notification of new message via sms to 19147379938

准备好解读 trace

在浏览器中打开 Jaeger UI,从服务下拉菜单中选择 Messenger-lb,然后单击 “Find Traces” 按钮。出现一个跟踪列表,从流的开头开始。单击任何跟踪以显示有关它的详细信息,如此屏幕截图所示:

flow

点击并探索一下。然后,在继续之前,请考虑痕迹中的信息如何支持您在挑战 3 导言中列出的埋点目标。相关问题包括:

  • 哪些信息有助于实现目标?
  • 缺少什么信息?
  • 哪些信息不相关?

检查 trace 的 NGINX(messenger-lb)部分

目标 1:查看请求在新消息流中执行的所有步骤

从 NGINX span 开始,它在父 span 中有 11 个子 span。由于当前的 NGINX 配置非常简单,子 span 不是很有趣,只是简单地显示 NGINX 请求处理生命周期中每个步骤所花费的时间。然而,父 span(第一个)包含一些有趣的洞察:

messenger-lb

  • 在标签 tag 下,您会看到以下属性:

    • http.method 字段-POST(在 REST 术语中,这意味着创建)

    • http.status_code 字段–201(表示成功创建)

    • http.target 字段-conversations/1/messages(消息端点)

      综合起来,这三条信息结合在一起说:“POST 请求已发送到 /conversations/1/messages,响应为 201(创建成功)”。这对应于架构和用户流中的步骤 1 和 4a)。

  • 在 Process 下,webengine.name 字段显示这是请求的 NGINX 部分。

此外,由于信使 messenger 和通知器 notifier 的 span 嵌套在 messenger-lb conversations/1 span 中(如准备解读 trace中的屏幕截图所示),您可以告诉通过 NGINX 反向代理发送到信使服务的请求命中了流程中的所有预期组件。

此信息满足了目标,因为您可以看到 NGINX 反向代理是流程的一部分。

目标 2:验证流量是否在五秒内执行

在标记为 Messenger-lb 的 span 列表中,查看最近的跨度(位于列表底部),以查看请求的 NGINX 部分花了多长时间。在屏幕截图中,Span 从 589 微秒(μs)开始,持续 24μs,这意味着完整的反向代理操作只需要 613μs-大约 0.6 毫秒(ms)。(当您自己运行实验时,确切的值当然会有所不同。)

messenger-lb last

在这样的设置中,在大多数情况下,这些值仅相对于其他测量值有用,并且它们因系统而异。然而,在这种情况下,这个操作显然没有接近五秒的危险。

这些信息满足了目标,因为你可以看到 NGINX 操作没有花费近五秒钟。如果流程操作非常慢,它一定在以后发生。

目标 3:查看通知服务需要多长时间才能阅读信使服务发送的事件

NGINX 反向代理层不包含任何与此相关的信息,因此您可以转到 messenger span。

检查追踪 trace 的信使部

目标 1:查看 messenger 中请求在新消息流中执行的所有步骤

Trace 的信使服务部分包含另外 11 个 span。同样,大多数子 span 都涉及 Express 框架在处理请求时使用的基本步骤,并且不是很有趣。然而,父 span(第一个跨度)再次包含一些有趣的洞察:

messenger-parent

在标签 tag 下,您会看到以下属性:

  • http.method 字段-POST(同样,在 REST 术语中,这意味着创建)
  • http.route 字段 – /conversations/:conversationId/messages(消息路由)
  • http.target 字段 – /conversations/1/messages(消息端点)

这些信息满足了目标,因为它向我们表明,信使服务是流程的一部分,而端点命中是新消息端点。

目标 2:验证 messenger 中流量是否在五秒内执行

如以下屏幕截图所示,trace 的信使部分 messenger 从 1.28 毫秒开始,到 36.28 毫秒结束,总时间为 35 毫秒。其中大部分时间用于解析 JSON(middleware-jsonParser),并在更大程度上连接到数据库(pg-pool.connect 和 tcp.connect)。

鉴于在编写消息的过程中也进行了几个 SQL 查询,这是有道理的。这反过来表明,您可能希望增强自动埋点配置,以捕获这些查询的时间。(本实验没有显示此附加埋点,但在挑战 4 中,您可以手动创建跨度,这些 span 又可用于包装数据库查询。)

messenger-span

这些信息满足了目标,因为它表明信使操作不会花费近五秒。如果流程操作非常慢,它一定在以后发生。

目标 3:查看通知服务 notifier 需要多长时间才能阅读信使服务发送的事件

与 NGINX span 一样,信使 span 不包括此信息,因此您可以移动到通知器 span。

检查 trace 的通知 notifier 部分

目标 1 :查看 notifier 中请求在新消息流中执行的所有步骤

Trace 的通知部分仅包含两个跨度 span:

notifier-spans

  • chat_queueprocess 跨度-确认通知服务处理了来自 chat_queue 消息队列的事件
  • pg-pool.connect 跨度-显示在处理事件后,通知服务与其数据库进行了某种连接

从这些跨度中获得的信息只能部分实现理解每个步骤的目标。您可以看到通知服务已从队列中消耗事件,但您不知道是否:

  • 此服务发送的消息通知与 messenger 服务发送的事件相对应
  • 相关消息通知已正确发送给消息的收件人

这表明您需要执行以下操作才能完全了解通知服务流程:

  • 手动测量显示正在发送的通知的跨度
  • 确保信使服务发送的事件与通知服务使用的事件之间以跟踪 ID 的形式存在明确的联系

目标 2:验证 notifier 中流量是否在五秒内执行

查看通知服务跨度的总体时间,您会发现请求在流程的通知部分花费了 30.77 毫秒。然而,由于没有指示整个流程的“结束”的跨度(向收件人发送通知),您无法确定此部分流程的总时间或操作的总体完成时间。

目标 3:查看通知服务 notifier 需要多长时间才能阅读信使服务 messenger 发送的事件

然而,您可以看到通知服务的 chat_queueprocess 跨度从 6.12ms 开始,在信使服务的 chat_queuesend 跨度从 4.12ms 开始后 2ms。

chat-queue

这个目标之所以实现,是因为您知道通知者在由 messenger 服务发送事件后消耗了 2 毫秒。与目标 2 不同,实现此目标不需要您知道事件是否已完全处理或花了多长时间。

结论

根据我们对当前 OTel 自动埋点产生的 trace 的分析,很明显:

  • 其中许多跨度在当前形式上没有用:

    • NGINX 正在生成与您关心的角色、反向代理无关的功能(如授权检查和文件服务)相关的跨度。然而,在这一点上,NGINX 的 OTel 埋点不允许您省略不相关的跨度,因此无法做任何事情。
    • 在 Node.js 服务(信使和通知服务)的跨度中,一些似乎与目标相关:JSON 解析、requesthandler 和所有数据库操作的跨度。一些中间件跨度(如 expressInit 和 corsMiddleware)似乎不相关,可以删除。

 

挑战 4:基于追踪 trace 解读优化埋点

在这个挑战中,您根据您在挑战 3 中所做的跟踪分析优化了 OTel 埋点,在不触碰应用代码的情况下可以删除不必要的跨度 span。

去除不必要的跨度 span

  1. 在您的首选文本编辑器中,在 Messenger repo 的 app 目录中打开 tracing.mjs 文件,并在顶部导入语句列表的末尾添加以下内容:

    const IGNORED_EXPRESS_SPANS = new Set([
        "middleware - expressInit",
        "middleware - corsMiddleware",
    ]);
    

    这定义了一组跨度名称,这些名称来自 Jaeger UI 的以下屏幕截图中显示的跨度列表,将从跟踪中省略,因为它们不为该流提供有用的信息。您可能会决定屏幕截图中列出的其他跨度也不需要,并将它们添加到 IGNORED_EXPRESS_SPANS 列表中。

    unneeded-spans

  2. 通过更改以突出显示的行,将过滤器添加到自埋点器配置中,以省略您不想要的跨度:

    const sdk = new opentelemetry.NodeSDK({
        resource,
        traceExporter: new OTLPTraceExporter({ headers: {} }),
        instrumentations: [getNodeAutoInstrumentations()],
    });
    

    改为:

    const sdk = new opentelemetry.NodeSDK({
        resource,
        traceExporter: new OTLPTraceExporter({ headers: {} }),
        instrumentations: [
            getNodeAutoInstrumentations({
            "@opentelemetry/instrumentation-express": {
                ignoreLayers: [
                (name) => {
                    return IGNORED_EXPRESS_SPANS.has(name);
                },
                ],
            },
            }),
        ],
    });
    

    getNodeAutoInstrumentations 函数引用步骤 1 中定义的跨度集,以从 @opentelemetry/instrumentation-express 生成的跟踪中过滤它们。换句话说,对于属于 IGNORED_EXPRESS_SPANS 的跨度,return 语句解析为 true,而 ignoreLayers 语句从跟踪中删除该跨度。

  3. 在信使终端中,按 Ctrl+c 停止信使服务。然后重新启动它:

    ^c
    node --import ./tracing.mjs index.mjs
    
  4. 等待大约十秒钟,然后在客户端终端发送一条新消息:

    curl -X POST \
        -H "User-Id: 2" \
        -H "Content-Type: application/json" \
        -d '{"content": "This is the second message"}' \
        'http://localhost:8085/conversations/1/messages'
    
  5. 在 Jaeger UI 中重新检查信使跨度。两个 middleware 跨度,expressInit 和 corsMiddleware,不再出现(您可以将其与挑战 3 中检查跟踪的信使部分的目标2的屏幕截图进行比较。

    spans-removed

 

实验验收标准

请同学们在动手实验的时候注意按照此验收标准进行截图并放到 Word 文档中,在参加单元小测时上传文档以便我们做实验验收。

  1. 截屏记录一次完整交易的跨度 span,须包含下列重要信息:

    • trace 的起始时间
    • messenger-lb span
    • messenger span
    • notifier span

    示例如下:
    final-spans

  2. 截屏记录 messenger-lb span ngx_http_otel_mirror_handler_ 的详细信息,须包含下列重要信息:

    • trace 起始时间
    • process 信息
    • SpanID

    示例如下:
    messenger-lb

  3. 截屏记录 messenger span chat_queue_send 的详细信息,须包含下列重要信息:

    • trace 起始时间
    • process 信息
    • SpanID

    示例如下:
    messenger-notifier

 

资源清理

您在整个实验中创建了一些容器和图像!使用这些说明来删除它们。

  • 要删除任何正在运行的 Docker 容器:

    docker rm $(docker stop messenger-lb)
    
  • 要删除平台服务以及信使和通知器数据库服务:

    cd ~/microservices-june/platform && docker compose down
    cd ~/microservices-june/notifier && docker compose down
    cd ~/microservices-june/messenger && docker compose down
    

 

下一步

恭喜你,你已经完成了整个实验!

  • 您通过 NGINX 反向代理和两个 Node.js 服务设置 OTel 埋点。
  • 您以批判的眼光查看了 OTel 自动埋点提供的数据,并添加了一些缺失的遥测,以实现 OTel 实验目标:
    • 您通过消息系统对特定请求的流程进行了合理的了解,而无需直接更改任何应用程序代码。
    • 您确认在正常情况下,流程在不到五秒内完成端到端。

然而,你几乎没有发现理想的追踪配置可能是什么样子的表面!在生产环境中,您可能希望为每个数据库查询添加自定义跨度,并在所有跨度上添加其他元数据,这些跨度描述了运行时详细信息,例如每个服务的容器 ID。您还可以实现其他两种类型的 OTel 数据(指标和日志记录),让您全面了解系统的运行状况。

Hero image
免费 O'Reilly 电子书:
《NGINX 完全指南》

更新于 2022 年,一本书了解关于 NGINX 的一切

关于作者

熊平

NGINX 资深架构师,Greenhouse 创新大使

关于 F5 NGINX

F5, Inc. 是备受欢迎的开源软件 NGINX 背后的商业公司。我们为现代应用的开发和交付提供一整套技术。我们的联合解决方案弥合了 NetOps 和 DevOps 之间的横沟,提供从代码到用户的多云应用服务。访问 nginx-cn.net 了解更多相关信息。