博客 | NGINX

NGINX 教程: 如何使用 OpenTelemetry 跟踪来了解您的微服务

NGINX-F5-horiz-black-type-RGB 的一部分
Javier Evans 缩略图
哈维尔·埃文斯
2023 年 3 月 28 日发布

这篇文章是帮助您将2023 年 3 月微服务中的概念付诸实践的四个教程之一: 开始交付微服务

 

微服务架构具有许多好处,包括增强团队自主性以及提高扩展和部署的灵活性。 不利的一面是,系统中的服务越多(微服务应用程序可能有几十个甚至几百个服务),就越难以清晰地了解系统的整体运行情况。 作为复杂软件系统的编写者和维护者,我们知道拥有清晰的图景至关重要。 可观察性工具使我们能够在众多服务和支持基础设施中构建该图像。

在本教程中,我们重点介绍微服务应用程序的一种非常重要的可观察性类型:跟踪。 在开始之前,让我们定义讨论可观察性时常用的一些术语:

  • 可观察性——仅基于对复杂系统(如微服务应用程序)的外部输出(如跟踪、日志和指标)的了解来了解其内部状态或条件的能力。
  • 监控——在一段时间内观察和检查对象的进度或状态的能力。 例如,您可以监控高峰时段进入您应用程序的流量,并使用该信息进行适当的扩展。
  • 遥测——收集指标、跟踪和日志并将它们从原点传输到另一个系统进行存储和分析的行为。 还有数据本身。
  • 追踪/踪迹– 请求或操作在分布式系统的所有节点中移动时的历程的记录。
  • 跨度– 操作跟踪内的记录及其相关元数据。 踪迹由许多嵌套的跨度组成。
  • 事件日志/日志——带有元数据的时间戳文本记录。
  • 度量——在运行时捕获的测量值。 例如,应用在某个时间点使用的内存量。

我们可以使用所有这些概念来深入了解微服务的性能。 跟踪是可观察性策略中特别有用的部分,因为跟踪提供了在发出请求时多个通常是松散耦合的组件中发生的情况的“全景”。 这也是识别性能瓶颈的一种特别有效的方法。

本教程使用OpenTelemetry (OTel) 的跟踪工具包,这是一个用于收集、处理和导出遥测数据的开源供应商中立标准,正在迅速普及。 在 OTel 的构想中,跟踪将涉及多个服务的数据流分割成一系列按时间顺序排列的“块”,可以帮助您轻松理解:

  • 在一个“块”中发生的所有步骤
  • 所有这些步骤花了多长时间
  • 每个步骤的元数据

如果您不熟悉 OTel,请参阅什么是 OpenTelemetry?以获得有关该标准的详细介绍以及实施该标准的注意事项。

教程概述

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

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

  1. 了解系统以及您正在进行的特定操作。
  2. 确定您需要从运行系统中了解什么。
  3. “天真地” 对系统进行检测——意味着使用默认配置,而不试图剔除您不需要的信息或收集自定义数据点——并评估该检测是否有助于您回答您的问题。
  4. 调整报告的信息以便您更快地回答这些问题。

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

  • 本教程不使用 Kubernetes 或 Nomad 等容器编排框架。 这样,您就可以了解微服务概念,而不会陷入某个框架的细节中。 这里介绍的模式可以移植到运行这些框架之一的系统上。
  • 这些服务是为了易于理解而不是软件工程的严谨性而优化的。 重点是看服务在系统中的作用及其通信模式,而不是代码的细节。 有关更多信息,请参阅各个服务的README文件。

教程架构和遥测目标

架构和用户流程

该图说明了本教程中使用的微服务和其他元素之间的总体架构和数据流。

该图显示了教程中使用的拓扑,其中 OpenTelemetry 跟踪了包含两个微服务(NGINX 和 RabbitMQ)的消息传递系统

这两个微服务是:

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

三个支持基础设施是:

  • NGINX 开源信息服务和整个系统的入口点
  • RabbitMQ – 一个流行的开源消息代理,支持服务异步通信
  • Jaeger – 一个开源的端到端分布式跟踪系统,用于收集和可视化来自生成数据的系统组件的遥测数据

暂时不考虑 OTel,我们可以集中精力关注我们正在追踪的事件序列:当用户发送新的聊天消息并且收件人收到通知时会发生什么。

本教程中使用的消息传递系统中的信息流图表

流程分解如下:

  1. 用户向通讯服务发送消息。 NGINX 反向代理拦截该消息并将其转发到通讯服务的众多实例之一。
  2. 信使服务将新消息写入其数据库。
  3. 信使服务在名为chat_queue的 RabbitMQ 消息队列上生成一个事件来指示已发送消息。 该事件是通用的,没有特定目标。
  4. 同时:

    • 4a. 信使服务向发送者返回响应,报告消息已成功发送。
    • 4b. 通知服务注意到chat_queue上的新事件并使用它。
  5. 通知服务会检查其数据库以查找新消息收件人的通知偏好。
  6. 通知服务使用收件人的首选方法发送一个或多个通知(在本教程中,方法选择为短信和电子邮件)。

遥测目标

在设置遥测仪器时,最好从一组更明确的仪器目标开始,而不是“发送所有内容并希望获得见解”。 本教程有三个关键遥测目标:

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

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

教程先决条件和设置

先决条件

要在您自己的环境中完成本教程,您需要:

  • Linux/Unix 兼容环境

    笔记: 本教程中涉及跟踪 NGINX 的活动不适用于基于 ARM 的处理器,因为 NGINX 的 OpenTelemetry 模块不兼容。 (这包括 Linux aarch64 架构和带有 M1 或 M2 芯片的 Apple 机器。) 涉及信使通知服务的活动适用于所有架构。

  • 对 Linux 命令行、JavaScript 和bash有基本的了解(但提供并解释了所有代码和命令,因此即使知识有限,您仍然可以成功)
  • DockerDocker Compose
  • Node.js 19.x 或更高版本

    • 我们测试了 19.x 版本,但预计 Node.js 的较新版本也能运行。
    • 有关安装 Node.js 的详细信息,请参阅Messenger服务存储库中的README 。 您还可以安装asdf以获取与教程中使用的完全相同的 Node.js 版本。
  • curl (大多数系统上已安装)
  • 架构和用户流程中列出的技术: messengernotifier (您将在下一部分下载它们)、 NGINX 开源JaegerRabbitMQ

笔记: 本教程使用 JavaScript SDK,因为信使通知服务是用 Node.js 编写的。 您还可以设置 OTel自动检测功能(也称为自动检测),以便了解 OTel 提供的信息类型。 本教程解释了有关 OTel Node.js SDK 所需了解的所有内容,但有关更多详细信息,请参阅OTel 文档

设置

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

    笔记: 在整个教程中,省略了 Linux 命令行上的提示,以便更容易地将命令复制并粘贴到终端中。 波浪号 ( ~ ) 代表您的主目录。

    mkdir ~/microservices-marchcd ~/microservices-march
    git 克隆 https://github.com/microservices-march/messenger --branch mm23-metrics-start
    git 克隆 https://github.com/microservices-march/notifier --branch mm23-metrics-start
    git 克隆 https://github.com/microservices-march/platform --branch mm23-metrics-start
    

挑战1: 设置基本 OTel 仪器

在本次挑战中,您将启动信使服务配置 OTel 自动仪表以将遥测数据发送到控制台。

启动 Messaging 服务

  1. 切换到平台存储库并启动 Docker Compose:

    cd ~/microservices-march/platformdocker compose up -d --build
    

    这将启动 RabbitMQ 和 Jaeger,它们将在后续的挑战中使用。

    • ‑d标志指示 Docker Compose 在容器启动时与容器分离(否则容器将保持连接到您的终端)。
    • --build标志指示 Docker Compose 在启动时重建所有映像。 这可以确保您运行的图像在文件发生任何潜在变化时保持更新。
  2. 切换到messenger存储库中的应用程序目录并安装 Node.js(如果您愿意,可以替换为其他方法):

    cd ~/microservices-march/messenger/appasdf 安装
    
  3. 安装依赖项:

    npm 安装
    
  4. 启动信使服务的 PostgreSQL 数据库:

    docker compose up -d
    
  5. 创建数据库模式和表并插入一些种子数据:

    npm 运行刷新数据库
    

配置发送到控制台的 OTel 自动仪器

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

在这里,您可以配置信使服务的自动检测,其最基本的跟踪目的地是控制台。 在挑战 2中,您将更改配置以将跟踪发送到 Jaeger 作为外部收集器。

  1. 仍然在messenger repo 的应用程序目录中工作,安装核心 OTel Node.js 包:

    npm 安装 @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的新文件,包含 OTel 跟踪的设置和配置代码:

    触摸追踪.mjs
    
  3. 在您喜欢的文本编辑器中,打开tracing.mjs并添加以下代码:

    //1
    从“@opentelemetry/sdk-node”导入 opentelemetry;
    从“@opentelemetry/auto-instrumentations-node”导入 { getNodeAutoInstrumentations };
    
    //2
    const sdk = new opentelemetry.NodeSDK({
    traceExporter: new opentelemetry.tracing.ConsoleSpanExporter(),
    Instrumentations: [getNodeAutoInstrumentations()],
    });
    
    //3
    sdk.start();
    

    该代码执行以下操作:

    1. 从 OTel SDK 导入所需的函数和对象。
    2. 创建 NodeSDK 的新实例并将其配置为:

      • 将跨度发送到控制台( ConsoleSpanExporter )。
      • 使用自动仪器作为仪器的基础集。 该仪器加载了所有最常见的自动仪器库。 本教程中相关的内容有:

        • @opentelemetry/instrumentation-pg用于 Postgres 数据库库 ( pg )
        • @opentelemetry/instrumentation-express用于 Node.js Express 框架
        • @opentelemetry/instrumentation-amqplib用于 RabbitMQ 库 ( amqplib )
    3. 启动 SDK。
  4. 启动信使服务,导入您在步骤 3 中创建的自动检测脚本。

    节点 --import ./tracing.mjs 索引.mjs
    

    片刻之后,大量与跟踪相关的输出开始出现在控制台(您的终端)中:

    ...
    {
    跟踪 ID: '9c1801593a9d3b773e5cbd314a8ea89c',
    parentId: 未定义,
    traceState: 未定义,
    name: 'fs statSync',
    id: '2ddf082c1d609fbe',
    种类: 0,
    时间戳: 1676076410782000,
    持续时间: 3,
    属性:{},
    状态:{ 代码: 0 },
    事件:[],
    链接:[]
    }
    ...
    

笔记: 保持终端会话开放,以便在挑战 2 中重复使用。

挑战2: 为所有服务设置 OTel 仪器和跟踪可视化

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

您可以通过浏览器中的http://localhost:16686端点访问 Jaeger,但如果您现在访问该端点,则看不到有关您的系统的任何信息。 这是因为您当前正在收集的跟踪正在被发送到控制台! 要在 Jaeger 中查看跟踪数据,您需要使用OpenTelemetry 协议(OTLP) 格式导出跟踪。

在此挑战中,您将通过配置以下检测来检测核心用户流程:

配置发送到外部收集器的 OTel 自动检测

提醒一下,使用 OTel 自动检测意味着您无需修改 Messenger代码库中的任何内容来设置跟踪。 相反,所有跟踪配置都在运行时导入到 Node.js 进程的脚本中。 在这里,您可以将信使服务生成的跟踪的目标从控制台更改为外部收集器(本教程中为 Jaeger)。

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

    npm 安装@opentelemetry/exporter-trace-otlp-http@0.36.0
    

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

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

    • 将此行添加到文件顶部的导入语句集中:

      从“@opentelemetry/exporter-trace-otlp-http”导入 {OTLPTraceExporter};
      
    • 将您提供给 OTel SDK 的“导出器”从挑战 1 中使用的控制台导出器更改为可以通过 HTTP 将 OTLP 数据发送到 OTLP 兼容收集器的导出器。 代替:

      traceExporter:新的opentelemetry.tracing.ConsoleSpanExporter(),
      

      和:

      traceExporter:新的OTLPTraceExporter({headers:{}}),
      

    笔记: 为了简单起见,本教程假设收集器位于默认位置http://localhost:4318/v1/traces 。 在实际系统中,明确设置位置是一个好主意。

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

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

    curl -X 获取 http://localhost:4000/health
    

    发送请求之前等待十秒钟有助于更容易地找到您的跟踪,因为它是在服务启动时自动检测生成的许多跟踪之后出现的。

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

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

    Jaeger GUI 的屏幕截图显示了 unknown_service 的跨度列表,在自动检测更改为显示正确的服务名称之前

  7. 如步骤 5 所述,OTel SDK 导出的服务名称 ( unknown_service ) 没有意义。 为了解决此问题,请在通讯终端中按Ctrl+c停止通讯服务。 然后安装更多 Node.js 包:

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

    这两个库提供以下功能:

    • @opentelemetry/semantic-conventions — 定义 OTel 规范中定义的跟踪的标准属性。
    • @opentelemetry/resources – 定义一个对象(资源),代表生成 OTel 数据的源(在本教程中为信使服务)。
  8. 在文本编辑器中打开tracing.mjs并进行以下更改:

    • 将这些行添加到文件顶部的导入语句集中:

      从“@opentelemetry/resources”导入{Resource};从“@opentelemetry/semantic-conventions”导入{SemanticResourceAttributes};
      
    • 通过在最后一个导入语句后添加以下行,在 OTel 规范中的正确键下创建名为messenger资源

      const resource = new Resource({ [SemanticResourceAttributes.SERVICE_NAME]: "messenger",
      });
      
    • 通过在黑色行之间添加以橙色突出显示的行,将资源对象传递给 NodeSDK 构造函数:

      const sdk = new opentelemetry.NodeSDK({资源, traceExporter:new OTLPTraceExporter({ 标头: {} }), 仪器:[getNodeAutoInstrumentations()], });
      
  9. 重新启动信使服务:

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

    curl -X 获取 http://localhost:4000/health
    

    笔记: 保持客户端终端开放以供在下一部分中重复使用,并保持通讯终端开放以供在挑战 3 中重复使用。

  11. 确认浏览器中的 Jaeger UI 中出现了一个名为messenger的新服务(这可能需要几秒钟,并且您可能需要刷新 Jaeger UI):

    Jaeger GUI 的屏幕截图显示了可用于深入检查 psans 的服务列表中的 Messenger

  12. 服务下拉菜单中选择messenger ,然后单击查找痕迹按钮以查看来自messenger服务的所有最近痕迹(屏幕截图显示了 20 条痕迹中最近的 2 条):

    Jaeger GUI 的屏幕截图,显示了 Messenger 服务的 2 条最新跟踪

  13. 单击一条轨迹可以显示其中的跨度。 每个跨度都被正确标记为源自信使服务:

    Jaeger GUI 的屏幕截图显示了单个 Messenger 跨度的详细信息

配置通知服务 OTel 自动检测

现在启动并配置通知服务的自动检测,运行与前两节中信使服务基本相同的命令。

  1. 打开一个新的终端会话(在后续步骤中称为通知程序终端)。 切换到通知程序存储库中的应用程序目录并安装 Node.js(如果您愿意,可以替换为其他方法):

    cd ~/microservices-march/notifier/appasdf install
    
  2. 安装依赖项:

    npm 安装
    
  3. 启动通知服务的 PostgreSQL 数据库:

    docker compose up -d
    
  4. 创建数据库模式和表并插入一些种子数据:

    npm 运行刷新数据库
    
  5. 安装 OTel Node.js 包(有关包功能的描述,请参阅配置发送到控制台的 OTel 自动仪器中的步骤 1 和 3):

    npm 安装 @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的新文件:

    触摸追踪.mjs
    
  7. 在您喜欢的文本编辑器中,打开tracing.mjs并添加以下脚本以启动并运行 OTel SDK:

    从“@opentelemetry/sdk-node”导入 opentelemetry;
    从“@opentelemetry/auto-instrumentations-node”导入 { getNodeAutoInstrumentations };
    从“@opentelemetry/exporter-trace-otlp-http”导入 { OTLPTraceExporter };
    从“@opentelemetry/resources”导入 { Resource };
    从“@opentelemetry/semantic-conventions”导入 { SemanticResourceAttributes };
    
    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 自动检测启动通知服务:

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

    curl http://localhost:5000/health
    

    笔记: 保持客户端和通知器终端开放,以便在挑战 3 中重复使用。

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

    Jaeger GUI 的屏幕截图显示了可用于深入检查跨度的服务列表中的通知程序

配置 NGINX 的 OTel Instrumentation

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

  1. 启动一个新的终端会话( NGINX 终端),将目录更改为messenger存储库的根目录并创建一个名为load-balancer的新目录,以及名为Dockerfilenginx.confopentelemetry_module.conf的新文件:

    cd ~/microservices-march/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 #用我们自己的文件替换 nginx.conf 文件COPY nginx.conf /etc/nginx/nginx.conf #定义 NGINX OTel 模块的版本ARG OPENTELEMETRY_CPP_VERSION=1.0.3 #定义编译和运行 NGINX 时使用的共享库的搜索路径ENV LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/opt/opentelemetry-webserver-sdk/sdk_lib/lib # 1. 下载最新版本的 Consul 模板和 OTel C++ Web 服务器模块 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 运行 apt-get update \ && apt-get install -y --no-install-recommends dumb-init unzip \ # 2. 提取模块文件&& tar xvfz /tmp/opentelemetry-webserver-sdk-x64-linux.tgz -C /opt \ && rm -rf /tmp/opentelemetry-webserver-sdk-x64-linux.tgz \ # 3. 在主 NGINX 配置文件顶部安装并添加“load_module”指令&& /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. 复制 NGINX OTel 模块的配置文件COPY opentelemetry_module.conf /etc/nginx/conf.d/opentelemetry_module.conf EXPOSE 8085 STOPSIGNAL SIGQUIT
    
  3. 打开nginx.conf并添加以下内容:

    事件 {}
    http {
    包括 /etc/nginx/conf.d/opentelemetry_module.conf;
    
    上游信使 {
    服务器 localhost:4000;
    }
    
    服务器 {
    监听 8085;
    
    位置 / {
    proxy_pass http://messenger;
    }
    }
    }
    

    这个非常基本的NGINX 配置文件告诉 NGINX :

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

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

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

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

    docker build-t messenger-lb 。
    
  6. 启动 NGINX 反向代理和负载均衡器的 Docker 容器:

    docker run --rm --name messenger-lb -p 8085:8085 --network="host" messenger-lb
    
  7. 在客户端,通过 NGINX 反向代理和负载均衡器向消息服务发送健康检查请求(发送此请求前无需等待):

    curl http://localhost:8085/health
    

    笔记: 保持 NGINX 和客户端终端开放,以便在挑战 3 中重复使用。

  8. 在浏览器中,确认新的messenger-lb服务与您之前启动的服务一起列在 Jaeger UI 中。 您可能需要在浏览器中重新加载 Jaeger UI。

    Jaeger GUI 的屏幕截图显示了可用于深入检查跨度的服务列表,现在包括 messenger-lb

挑战3: 学习阅读 OTel 踪迹

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

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

实施遥测的目标是:

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

在本次挑战中,您将学习如何评估 OTel 仪器生成的跟踪是否满足上述目标。 首先,您让系统运转起来并创建一些痕迹。 然后,您检查消息流的跟踪以及由NGINX信使服务通知服务生成的消息流的各个部分。

创建跟踪数据

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

curl -X POST \
-H“内容类型:应用/json”\
-d'{“participant_ids”:[1,2]}' \
' <a href="http://localhost:8085/conversations">http://localhost:8085/对话</a>'

curl -X POST \
-H "用户 ID: 1英寸 \
-H“内容类型:应用/json”\
-d'{“内容”: “这是第一条消息”}' \
'http://localhost:8085/conversations/1/messages'

curl -X POST \
-H “用户 ID: 2英寸 \
-H“内容类型:应用/json”\
-d'{“内容”: “这是第二条消息”}' \
'http://localhost:8085/conversations/1/messages'

通知程序服务会生成如下输出,并显示在通知程序终端中:

收到新消息:{"type":"new_message","channel_id":1,"user_id":1,"index":1,"participant_ids":[1,2]}通过短信向 12027621401 发送新消息通知

收到新消息:{"type":"new_message","channel_id":1,"user_id":2,"index":2,"participant_ids":[1,2]}

通过电子邮件向 the_hotstepper@kamo.ze 发送新消息通知

通过短信向 19147379938 发送新消息通知

准备读取痕迹

在浏览器中打开 Jaeger UI,从服务下拉菜单中选择messenger-lb ,然后单击查找痕迹按钮。 出现一个跟踪列表,从流程的最开始处开始。 单击任何跟踪即可显示有关它的详细信息,如下图所示:

Jaeger GUI 的屏幕截图显示了流程中的全部跨度

点击并进行一些探索。 然后在继续之前,请考虑一下跟踪中的信息如何支持挑战 3介绍中所列出的检测目标。 相关问题包括:

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

检查跟踪的 NGINX (messenger-lb) 部分

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

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

Jaeger GUI 的屏幕截图显示了跟踪的 NGINX(messenger-lb)部分中的父跨度

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

    • http.method字段 – POST (在REST术语中,这意味着创建)
    • http.status_code字段 –201 (表示创建成功)
    • http.target字段 – dialogues/1/messages (消息端点)

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

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

此外,由于messengernotifier的跨度嵌套在messenger-lb dialogues/1跨度内(如准备读取跟踪中的屏幕截图所示),您可以知道通过 NGINX 反向代理发送到messenger服务的请求击中了流中所有预期的组件。

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

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

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

Jaeger GUI 的屏幕截图显示了跟踪的 NGINX(messenger-lb)部分中的跨度

在这样的设置中,大多数值仅相对于其他测量值有用,并且它们在不同系统之间有所不同。 不过,在这种情况下,这个操作的时间显然不会超过五秒钟。

这些信息满足了目标,因为您可以看到 NGINX 操作所用的时间并不接近五秒钟。 如果流程中有一个非常缓慢的操作,那么它一定会在稍后发生。

目标3: 查看通知服务读取消息服务发送的事件需要多长时间

NGINX 反向代理层不包含有关此内容的任何信息,因此您可以继续使用Messenger跨度。

检查跟踪的信使部分

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

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

Jaeger GUI 的屏幕截图显示了跟踪的 Messenger 部分中的父跨度

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

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

此信息满足目标,因为它向我们展示了信使服务是流程的一部分,并且端点命中是新的消息端点。

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

如下面的屏幕截图所示,跟踪的信使部分从 1.28 毫秒开始,到 36.28 毫秒结束,总时间为 35 毫秒。 大部分时间都花在解析 JSON(中间件- jsonParser )上,并且更大程度上花在连接数据库( pg-pool.connecttcp.connect )上。

这是有道理的,因为在编写消息的过程中也会进行多个 SQL 查询。 这反过来表明您可能想要增强自动检测配置来捕获这些查询的时间。 (本教程未展示这种额外的检测,但在挑战 4 中,您可以手动创建跨度,然后可以用来包装数据库查询。)

Jaeger GUI 的屏幕截图,显示了跟踪的 Messenger 部分中的跨度以及它们花费的时间

该信息满足了目标,因为它表明信使操作不会花费接近五秒钟的时间。 如果流程中有一个非常缓慢的操作,那么它一定会在稍后发生。

目标3: 查看通知服务读取消息服务发送的事件需要多长时间

与 NGINX 跨度一样,信使跨度不包含此信息,因此您可以转到通知器跨度。

检查跟踪的通知程序部分

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

跟踪的通知程序部分仅包含两个跨度:

Jaeger GUI 的屏幕截图显示了跟踪的通知程序部分中的两个跨度

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

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

  • 该服务发送的消息通知与消息服务发送的事件相对应
  • 相关消息通知已正确发送给消息接收者

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

  • 手动检测显示已发送通知的 span
  • 确保消息服务发送的事件与通知服务使用的事件之间存在显式联系,以跟踪 ID 的形式

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

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

目标3: 查看通知服务读取消息服务发送的事件需要多长时间

但是,您可以看到,通知服务chat_queue进程跨度在 6.12ms 处启动,比信使服务chat_queue发送跨度在 4.12ms 处启动晚 2ms。

Jaeger GUI 的屏幕截图显示了通知服务正在使用由消息服务发送的事件

这个目标已经实现,因为您知道通知程序消息服务发送事件 2 毫秒后就消耗了该事件。 与目标 2 不同,实现此目标不需要您知道事件是否已被完全处理或花费了多长时间。

结论

根据我们对当前 OTel 自动检测生成的跟踪的分析,很明显:

  • 许多这些 span 在当前形式下是无用的:

    • NGINX 正在生成与功能相关的跨度(例如授权检查和文件服务),这些跨度与您关心的角色、反向代理无关。 然而,此时 OTel 对 NGINX 的检测不允许您省略不相关的跨度,因此什么也做不了。
    • 在 Node.js 服务(信使服务和通知服务)的跨度中,有些似乎与目标相关:JSON 解析、请求处理程序和所有数据库操作的跨度。 一些中间件范围(例如expressInitcorsMiddleware )似乎不相关,可以被删除。
  • 下列内容缺少关键跨度:

    • 通知服务发送的通知
    • 消息服务发送的 RabbitMQ 事件与通知服务处理的事件之间的清晰映射

这意味着基本仪器实现了最后一个目标:

  • 查看通知服务需要多长时间才能开始处理信使服务发送的事件

然而,没有足够的信息来实现前两个目标:

  • 了解请求在新消息流中经历的所有步骤
  • 确保流程在正常情况下在五秒内端到端执行

挑战4: 根据跟踪读数优化仪表

在本次挑战中,您将根据在挑战 3 中进行的跟踪分析来优化 OTel 仪器。 这包括删除不必要的跨度创建新的自定义跨度,以及确认通知服务所使用的事件是由信使服务生成的事件。

删除不必要的 Span

  1. 在您喜欢的文本编辑器中,打开messenger存储库app目录中的tracing.mjs文件,并在顶部的导入语句列表末尾添加以下内容:

    const IGNORED_EXPRESS_SPANS = new Set([ "中间件 - expressInit",
    "中间件 - corsMiddleware",
    ]);
    

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

    Jaeger GUI 的屏幕截图显示了来自 Messenger 服务的几个跨度列表,这些跨度可能提供相关信息,因此可以从跟踪中省略

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

    const sdk = new opentelemetry.NodeSDK({资源, traceExporter:new OTLPTraceExporter({ 标头: {} }), 仪器:[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停止通讯服务。 然后重新启动它:

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

    curl -X POST \ -H "用户 ID: 2英寸 \
    -H“内容类型:应用/json”\
    -d'{“内容”: “这是第二条消息”}' \
    'http://localhost:8085/conversations/1/messages'
    
  5. 在 Jaeger UI 中重新检查messenger spans。两个中间件spans expressInitcorsMiddleware不再出现(您可以将其与挑战 3 中检查跟踪的 messenger 部分的目标 2 的屏幕截图进行比较)。

    Jaeger GUI 的屏幕截图显示,在您更改检测以将它们从跟踪中过滤掉后,跟踪不再包含两个跨度

设置自定义跨度

在本节中,您将第一次接触应用代码。 自动检测不需要改变应用就能生成大量信息,但有些见解只有通过检测特定的业务逻辑才有可能获得。

对于您正在检测的新消息流,一个例子是跟踪向消息接收者发送通知的过程。

  1. 打开通知程序存储库app目录中的index.mjs 。 该文件包含该服务的所有业务逻辑。 在文件顶部的导入语句列表末尾添加以下行:

    从“@opentelemetry/api”导入{trace};
    
  2. 替换此代码(位于文件第 91 行左右):

    for (let pref of preference) { console.log(`通过发送新消息通知${pref.address_type}到${pref.address}` ); }
    

    和:

    const tracer = trace.getTracer("notifier"); // 1tracer.startActiveSpan( // 2 "notification.send_all", { 属性: { user_id: msg.user_id, }, }, (parentSpan) => { for (let pref of preference) { tracer.startActiveSpan( // 3 "notification.send", { 属性: { // 4 notification_type: pref.address_type, user_id: pref.user_id, }, }, (span) => { console.log( `通过 发送新消息通知${pref.address_type}到${pref.address}` ); span.end(); // 5 } ); } parentSpan.end(); // 6 } );
    

    新代码执行以下操作:

    1. 获取跟踪器,它是与 OTel 跟踪交互的全局对象。
    2. 启动一个名为notification.send_all的新父跨度,并设置user_id属性来识别消息的发送者。
    3. 进入一个循环,其中枚举收件人的通知偏好,并在notification.send_all下创建一个名为notification.send的新子跨度。 每个通知都会生成一个新的跨度。
    4. 为子跨度设置更多属性:

      • notification_type短信电子邮件之一
      • user_id – 接收通知的用户的 ID
    5. 依次关闭每个子通知发送跨度。
    6. 关闭父通知.send_all跨度。

    拥有父跨度可以保证即使未发现用户的通知偏好,也会报告每个“发送通知”操作。

  3. 在通知程序终端中,按Ctrl+c停止通知程序服务。 然后重新启动:

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

    curl -X POST \ -H "用户 ID: 2英寸 \
    -H“内容类型:应用/json”\
    -d'{“内容”: “这是第二条消息”}' \
    'http://localhost:8085/conversations/1/messages'
    
  5. 在 Jaeger UI 中重新检查通知器跨度。您会看到父跨度和两个子跨度,每个跨度都有一个“发送通知”操作:

    Jaeger GUI 的屏幕截图展示了在通知服务代码中定义三个新跨度的结果

现在您可以完全实现第一个和第二个目标,因为您可以看到请求在新消息流中经历的所有步骤。 每个跨度上的时间显示了这些步骤之间的任何滞后。

确认消息传递器和通知器正在处理同一事件

为了全面了解流程,您还需要做一件事。 通知服务处理的事件实际上是信使服务发送的事件吗?

您不必做任何明确的更改来连接这两条轨迹——但您也不想盲目地相信自动检测的魔力。

考虑到这一点,添加一些快速调试代码来验证 NGINX 服务中启动的跟踪确实与通知服务使用的跟踪相同(具有相同的跟踪 ID)。

  1. 打开messenger存储库app目录中的index.mjs文件并进行以下更改:

    • 在顶部的导入语句列表末尾添加以下行:

      从“@opentelemetry/api”导入{trace};
      
    • 在现有的黑色行下方添加以橙色突出显示的行:

      异步函数 createMessageInConversation(req,res) { const tracer = trace.getActiveSpan(); console.log("TRACE_ID: ", tracer.spanContext().traceId);
      

      新行从messenger中处理新消息创建的函数内部打印出TRACE_ID

  2. 打开通知程序存储库app目录中的index.mjs文件,并在现有的黑色行下方添加以橙色突出显示的行:

    导出异步函数 handleMessageConsume(channel, msg, handlers) { console.log("RABBIT_MQ_MESSAGE: ", msg);
    

    新行打印通知服务收到的 AMQP 事件的完整内容。

  3. 通过在 messenger 和 notifier终端中运行以下命令来停止并重新启动messengernotifier服务:

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

    curl -X POST \ -H "用户 ID: 2英寸 \
    -H“内容类型:应用/json”\
    -d'{“内容”: “这是第二条消息”}' \
    'http://localhost:8085/conversations/1/messages'
    
  5. 查看信使通知服务的日志。 信使服务日志包含如下一行,报告消息的跟踪 ID(运行本教程时实际 ID 会有所不同):

    跟踪 ID:  29377a9b546c50be629c8e64409bbfb5
    
  6. 类似地,通知程序服务日志在输出中报告跟踪 ID,如下所示:

    _spanContext:{
    traceId: '29377a9b546c50be629c8e64409bbfb5',
      spanId: 'a94e9462a39e6dbf',
      跟踪标志: 1,
    traceState:未定义
    },
    
  7. 控制台中的跟踪 ID 是匹配的,但最后一步是,您可以将它们与 Jaeger UI 中的跟踪 ID 进行比较。在相关跟踪 ID 端点处打开 UI(您的端点会有所不同,但在此示例中为http://localhost:16686/trace/29377a9b546c50be629c8e64409bbfb5 )以查看整个跟踪。 Jaeger 的追踪结果证实:

    • 当事件被分派时,消息服务中的 AMQP 自动检测会将此跟踪 ID 添加为元数据的一部分。
    • 通知服务中的 AMQP 自动检测需要元数据并适当地设置跟踪上下文。

笔记: 在实际的生产系统中,一旦确认流程按预期工作,您就可以删除此部分中添加的代码。

资源清理

您已经在本教程中创建了一些容器和图像! 使用这些说明来删除它们。

  • 要删除所有正在运行的 Docker 容器:

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

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

下一步

恭喜您,您已完成本教程!

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

然而,您仅仅触及了理想跟踪配置的表面! 在生产环境中,您可能希望添加诸如每个数据库查询的自定义跨度之类的内容,以及所有跨度上的附加元数据,以描述运行时详细信息(例如每个服务的容器 ID)。您还可以实现其他两种类型的 OTel 数据(指标和日志记录),以便全面了解系统的运行状况。

要继续您的微服务教育,请查看 2023 年 3 月的微服务。 在第 4 单元中: 通过可观察性管理微服务混乱和复杂性,您将了解可观察性数据的三个主要类别、基础设施和应用程序协调的重要性,以及开始分析深度数据的方法。


“这篇博文可能引用了不再可用和/或不再支持的产品。 有关 F5 NGINX 产品和解决方案的最新信息,请探索我们的NGINX 产品系列。 NGINX 现在是 F5 的一部分。 所有之前的 NGINX.com 链接都将重定向至 F5.com 上的类似 NGINX 内容。”