Tutorial de NGINX: cómo usar el rastreo de OpenTelemetry para comprender tus microservicios

NGINX | 28 de marzo de 2023

Esta publicación forma parte de una serie de cuatro tutoriales que te ayudarán a poner en práctica los conceptos de Microservices March 2023: Empieza a implementar microservicios:

Una arquitectura de microservicios ofrece muchas ventajas, como una mayor autonomía de los equipos y una mayor flexibilidad a la hora de escalar y desplegar. Como contrapartida, cuantos más servicios haya en un sistema (y una aplicación de microservicios puede tener decenas o incluso cientos), más difícil resulta tener una visión clara del funcionamiento global del sistema. Como desarrolladores y responsables del mantenimiento de sistemas de software complejos, sabemos que disponer de esa visión clara es de vital importancia. Observabilidad nos proporciona las herramientas necesarias para obtener esa visión de numerosos servicios y de la infraestructura que los sustenta.

En este tutorial, destacamos un tipo de observabilidad muy importante para las aplicaciones de microservicios: el rastreo. Antes de empezar, definamos algunos términos que se utilizan habitualmente al hablar de observabilidad:

  • Observabilidad – La capacidad de comprender el estado o la condición interna de un sistema complejo (como una aplicación de microservicios) basándose únicamente en el conocimiento de sus resultados externos (como trazas, registros y métricas).
  • Monitorización – La capacidad de observar y comprobar el progreso o el estado de un objeto durante un periodo de tiempo. Por ejemplo, puede monitorizar el tráfico que recibe su aplicación durante las horas punta y utilizar esa información para ajustar su escala adecuadamente.
  • Telemetría – El proceso de recopilar métricas, trazas y registros, y transferirlos desde su punto de origen a otro sistema para su almacenamiento y análisis. También se refiere a los propios datos.
  • Trazado/trazas – Un registro del recorrido de una solicitud o acción a medida que pasa por todos los nodos de un sistema distribuido.
  • Intervalo – Un registro dentro de una traza de una operación y sus metadatos asociados. Las trazas se componen de numerosos intervalos anidados.
  • Registro de eventos/registros – Un registro de texto con marca de tiempo y metadatos.
  • Métrica – Una medición registrada durante la ejecución. Por ejemplo, la cantidad de memoria que utiliza una aplicación en un momento determinado.

Podemos utilizar todos estos conceptos para comprender mejor el rendimiento de nuestros microservicios. El rastreo es una parte especialmente útil de una estrategia de observabilidad, ya que las trazas ofrecen una visión global de lo que sucede en varios componentes, a menudo débilmente acoplados, cuando se realiza una solicitud. También es una forma especialmente eficaz de identificar cuellos de botella en el rendimiento.

Este tutorial utiliza el conjunto de herramientas de rastreo de OpenTelemetry (OTel), un estándar de código abierto independiente de proveedores para recopilar, procesar y exportar datos de telemetría que está ganando popularidad rápidamente. Según el modelo de OTel, una traza divide un flujo de datos, que puede involucrar varios servicios, en una serie de «fragmentos» ordenados cronológicamente que permiten comprender fácilmente:

  • Todos los pasos que tuvieron lugar en un «fragmento»
  • Cuánto tiempo llevaron todos estos pasos
  • Metadatos de cada paso

Si no conoces OTel, consulta «¿Qué es OpenTelemetry?» para obtener una introducción detallada al estándar y conocer los aspectos que deben tenerse en cuenta al implementarlo.

Descripción general del tutorial

Este tutorial se centra en el seguimiento de las operaciones de una aplicación de microservicios con OTel. A lo largo de los cuatro retos del tutorial, aprenderás a rastrear una solicitud a través de tu sistema y a responder preguntas sobre tus microservicios:

Estos retos ilustran el proceso que recomendamos seguir al configurar el rastreo por primera vez. Los pasos son:

  1. Comprenda tanto el sistema como la operación concreta que está instrumentando.
  2. Decida qué necesita saber del sistema en ejecución.
  3. Instrumente el sistema de forma «ingenua», es decir, utilice una configuración predeterminada sin intentar descartar la información que no necesita ni recopilar datos personalizados, y evalúe si la instrumentación le ayuda a responder a sus preguntas.
  4. Ajusta la información que se proporciona para poder responder a esas preguntas más rápidamente.

Nota: En este tutorial pretendemos ilustrar algunos conceptos fundamentales sobre la telemetría, no mostrar la forma correcta de desplegar microservicios en producción. Aunque utiliza una arquitectura real de «microservicios», hay algunas salvedades importantes:

  • El tutorial no utiliza ningún framework de orquestación de contenedores, como Kubernetes o Nomad. De este modo, puedes aprender los conceptos relacionados con los microservicios sin atascarte en los detalles específicos de un framework concreto. Los patrones que se presentan aquí se pueden trasladar a un sistema que utilice uno de estos frameworks.
  • Los servicios están optimizados para facilitar su comprensión, en lugar de priorizar el rigor de la ingeniería de software. El objetivo es analizar la función de cada servicio en el sistema y sus patrones de comunicación, no los detalles específicos del código. Para obtener más información, consulte los archivos README de cada servicio.

Arquitectura del tutorial y objetivos de telemetría

Arquitectura y flujo de usuario

Este diagrama ilustra la arquitectura general y el flujo de datos entre los microservicios y otros elementos utilizados en el tutorial.

Diagrama que muestra la topología utilizada en el tutorial, con el seguimiento mediante OpenTelemetry de un sistema de mensajería con dos microservicios, NGINX y RabbitMQ

Los dos microservicios son:

  • El servicio messenger service – Una API de chat sencilla con capacidad para almacenar mensajes
  • El servicio notifier service – Un proceso de escucha que activa eventos para alertar a los usuarios en función de sus preferencias

Los tres componentes de la infraestructura de soporte son:

  • NGINX Open Source – Un punto de entrada al servicio messenger y al sistema en su conjunto
  • RabbitMQ – Un popular bróker de mensajes de código abierto que permite que los servicios se comuniquen de forma asíncrona
  • Jaeger – Un sistema de código abierto de seguimiento distribuido de extremo a extremo para recopilar y visualizar la telemetría de los componentes del sistema que la generan

Dejando OTel de lado por un momento, podemos centrarnos en la secuencia de eventos que estamos rastreando: qué ocurre cuando un usuario envía un nuevo mensaje de chat y se notifica al destinatario.

Diagrama que muestra el flujo de información en el sistema de mensajería utilizado en el tutorial

El flujo se desglosa de la siguiente manera:

  1. Un usuario envía un mensaje al servicio messenger. El proxy inverso NGINX intercepta el mensaje y lo reenvía a una de las múltiples instancias del servicio messenger.
  2. El servicio messenger guarda el nuevo mensaje en su base de datos.
  3. El servicio messenger genera un evento en una cola de mensajes de RabbitMQ denominada chat_queue para indicar que se ha enviado un mensaje. El evento es genérico y no tiene ningún destinatario específico.
  4. Al mismo tiempo:
    • 4a. El servicio messenger devuelve una respuesta al remitente para informarle de que el mensaje se ha enviado correctamente.
    • 4b. El servicio notifier detecta el nuevo evento en la cola chat_queue y lo consume.
  5. El servicio notifier consulta en su base de datos las preferencias de notificación del destinatario del nuevo mensaje.
  6. El servicio notifier utiliza el método preferido del destinatario para enviar una o varias notificaciones (en este tutorial, las opciones disponibles son SMS y correo electrónico).

Objetivos de telemetría

Al configurar la instrumentación de telemetría, es mejor empezar con una serie de objetivos de instrumentación más concretos que «enviarlo todo y esperar obtener información útil». En este tutorial, tenemos tres objetivos clave de telemetría:

  1. Comprender todos los pasos por los que pasa una solicitud durante el flujo de un mensaje nuevo
  2. Tener la certeza de que el flujo se ejecuta de principio a fin en menos de cinco segundos en condiciones normales
  3. Compruebe cuánto tarda el servicio notifier en empezar a procesar el evento enviado por el servicio messenger (un retraso excesivo podría indicar que el servicio notifier tiene problemas para leer la cola de eventos y que los eventos se están acumulando)

Ten en cuenta que estos objetivos están relacionados tanto con el funcionamiento técnico del sistema como con la experiencia del usuario.

Requisitos previos y configuración del tutorial

Requisitos previos

Para completar el tutorial en tu propio entorno, necesitas:

  • Un entorno compatible con Linux/Unix Nota: Las actividades de este tutorial que implican el seguimiento de NGINX no funcionan en procesadores basados en ARM porque el módulo OpenTelemetry para NGINX no es compatible. (Esto incluye las arquitecturas aarch64 de Linux y los equipos Apple con chip M1 o M2). Las actividades relacionadas con los servicios messenger y notifier funcionan en todas las arquitecturas.
  • Conocimientos básicos de la línea de comandos de Linux, JavaScript y bash (pero se proporciona y explica todo el código y los comandos, por lo que podrás completar el tutorial aunque tus conocimientos sean limitados)
  • Docker y Docker Compose
  • Node.js 19.x o posterior
    • Hemos probado la versión 19.x, pero esperamos que las versiones más recientes de Node.js también funcionen.
    • Para obtener información detallada sobre la instalación de Node.js, consulta el archivo README del repositorio del servicio messenger. También puedes instalar asdf para obtener exactamente la misma versión de Node.js que se utiliza en el tutorial.
  • curl (ya instalado en la mayoría de los sistemas)
  • Las tecnologías enumeradas en Arquitectura y flujo de usuario: messenger y notifier (las descargarás en la siguiente sección), NGINX Open Source, Jaeger y RabbitMQ.

Nota: En este tutorial se utiliza el SDK de JavaScript porque los servicios messenger y notifier están escritos en Node.js. También configurará la función de instrumentación automática de OTel (también denominada autoinstrumentación) para hacerse una idea del tipo de información que ofrece OTel. En el tutorial se explica todo lo que necesita saber sobre el SDK de OTel para Node.js, pero, si desea obtener más información, consulte la documentación de OTel.

Configuración

  1. Inicia una sesión de terminal.
  2. En su directorio personal, cree el directorio microservices-march y clone en él los repositorios de GitHub de este tutorial. (También puede usar otro nombre de directorio y adaptar las instrucciones según corresponda). Nota: A lo largo del tutorial se omite el símbolo del sistema de la línea de comandos de Linux para facilitar que pueda copiar y pegar los comandos en su terminal. La virgulilla (~) representa su directorio personal.
mkdir ~/microservices-marchcd ~/microservices-march git clone https://github.com/microservices-march/messenger --branch mm23-metrics-start git clone https://github.com/microservices-march/notifier --branch mm23-metrics-start git clone https://github.com/microservices-march/platform --branch mm23-metrics-start

Reto 1: Configurar la instrumentación básica de OTel

En este reto, iniciarás el servicio messenger service y configurarás la instrumentación automática de OTel para enviar la telemetría a la consola.

Inicia el servicio messenger

  1. Cambie al repositorio platform e inicie Docker Compose:
cd ~/microservices-march/platformdocker compose up -d --build
  1. Esto inicia RabbitMQ y Jaeger, que se utilizarán en los siguientes retos.
    • La opción «‑d» indica a Docker Compose que se desvincule de los contenedores una vez que se hayan iniciado (de lo contrario, los contenedores permanecerán vinculados a tu terminal).
    • La opción --build indica a Docker Compose que vuelva a compilar todas las imágenes al iniciarse. Esto garantiza que las imágenes que se están ejecutando se mantengan actualizadas ante cualquier posible cambio en los archivos.
  2. Ve al directorio app del repositorio messenger e instala Node.js (puedes utilizar otro método si lo prefieres):
cd ~/microservices-march/messenger/appasdf install
  1. Instala las dependencias:
npm install
  1. Inicia la base de datos PostgreSQL para el servicio messenger:
docker compose up -d
  1. Crea el esquema y las tablas de la base de datos e inserta algunos datos iniciales:
npm run refresh-db

Configurar el envío de la instrumentación automática de OTel a la consola

Con la instrumentación automática de OTel, no es necesario modificar nada en el código de messenger para configurar el rastreo. En lugar de importarse en el propio código de la aplicación, toda la configuración del rastreo se define en un script que se importa en el proceso de Node.js durante la ejecución.

Aquí se configura la instrumentación automática del servicio messenger con el destino más básico para las trazas: la consola. En el Reto 2, se cambiará la configuración para enviar las trazas a Jaeger como recopilador externo.

  1. Sin salir del directorio app del repositorio messenger, instala los paquetes principales de OTel para Node.js:
npm install @opentelemetry/sdk-node@0.36.0 \ @opentelemetry/auto-instrumentations-node@0.36.4
  1. Estas bibliotecas ofrecen las siguientes funcionalidades: Nota: Una peculiaridad de OTel es que sus SDK de JavaScript están divididos en componentes muy, muy pequeños. Por eso, tendrá que instalar algunos paquetes adicionales incluso para el ejemplo básico de este tutorial. Para saber qué paquetes podría necesitar para realizar tareas de instrumentación distintas de las que se explican en este tutorial, consulte las (excelentes) guías de introducción de OTel y eche un vistazo al repositorio de GitHub de OTel.
    • @opentelemetry/sdk-node – Generación y exportación de datos de OTel
    • @opentelemetry/auto-instrumentations-node – Configuración automática con los ajustes predeterminados de todas las instrumentaciones más comunes de Node.js
  2. Crea un archivo nuevo llamado tracing.mjs que contenga el código de instalación y configuración del seguimiento de OTel:
touch tracing.mjs
  1. En tu editor de texto preferido, abre tracing.mjs y añade este código:
//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. El código hace lo siguiente:
    1. Importa las funciones y los objetos necesarios del SDK de OTel.
    2. Crea una nueva instancia de NodeSDK y la configura para:
      • Enviar los spans a la consola (ConsoleSpanExporter).
      • Utiliza el autoinstrumentador como conjunto base de instrumentación. Esta instrumentación carga todas las bibliotecas de autoinstrumentación más habituales. En el tutorial, las relevantes son:
        • @opentelemetry/instrumentation-pg para la biblioteca de la base de datos PostgreSQL (pg)
        • @opentelemetry/instrumentation-express para el framework Express de Node.js
        • @opentelemetry/instrumentation-amqplib para la biblioteca de RabbitMQ (amqplib)
    3. Inicia el SDK.
  2. Inicia el servicio messenger, importando el script de instrumentación automática que creaste en el paso 3.
node --import ./tracing.mjs index.mjs
  1. Al cabo de un momento, empieza a aparecer en la consola (el terminal) una gran cantidad de información relacionada con el seguimiento:
...
{
  traceId: '9c1801593a9d3b773e5cbd314a8ea89c',
  parentId: undefined,
  traceState: undefined,
  name: 'fs statSync',
  id: '2ddf082c1d609fbe',
  kind: 0,
  timestamp: 1676076410782000,
  duration: 3,
  attributes: {},
  status: { code: 0 },
  events: [],
  links: []
}
...

Nota: Deja abierta la sesión del terminal para volver a utilizarla en el reto 2.

Desafío 2: Configurar la instrumentación de OTel y la visualización de trazas para todos los servicios

Hay muchas herramientas que puedes utilizar para visualizar y analizar trazas, pero en este tutorial se utiliza Jaeger. Jaeger es un framework sencillo y de código abierto para el rastreo distribuido de extremo a extremo, con una interfaz de usuario web integrada para visualizar spans y otros datos de rastreo. La infraestructura proporcionada en el repositorio platform incluye Jaeger (que iniciaste en el paso 1 del reto 1), por lo que puedes centrarte en analizar los datos en lugar de lidiar con herramientas complejas.

Puedes acceder a Jaeger desde el endpoint http://localhost:16686 en tu navegador, pero si accedes ahora mismo, no verás ningún dato sobre tu sistema. Esto se debe a que las trazas que estás recopilando actualmente se envían a la consola. Para ver los datos de las trazas en Jaeger, debes exportarlas utilizando el formato del protocolo OpenTelemetry (OTLP).

En este reto, instrumentarás el flujo principal de usuario configurando la instrumentación para:

Configurar el envío de la instrumentación automática de OTel a un recopilador externo

Como recordatorio, usar la instrumentación automática de OTel significa que no es necesario modificar nada en el código de messenger para configurar el rastreo. En su lugar, toda la configuración del rastreo se encuentra en un script que se importa en el proceso de Node.js durante la ejecución. Aquí se cambia el destino de las trazas generadas por el servicio messenger, que pasan de enviarse a la consola a enviarse a un recopilador externo (Jaeger en este tutorial).

  1. Sigue trabajando en el mismo terminal que en el Reto 1 y, en el directorio app del repositorio messenger, instala el paquete de Node.js del exportador OTLP:
npm install @opentelemetry/exporter-trace-otlp-http@0.36.0
  1. La biblioteca @opentelemetry/exporter-trace-otlp-http exporta información de trazas en formato OTLP a través de HTTP. Se utiliza para enviar telemetría a un recopilador externo de OTel.
  2. Abre tracing.mjs (que creaste y editaste en el Reto 1) y realiza estos cambios: Nota: Para simplificar, el tutorial presupone que el recopilador se encuentra en la ubicación predeterminada, http://localhost:4318/v1/traces. En un sistema real, es recomendable especificar la ubicación explícitamente.
    • Añade esta línea al conjunto de sentencias import situadas al principio del archivo:
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
  • Cambia el «exportador» que proporcionas al SDK de OTel, sustituyendo el exportador de consola utilizado en el Reto 1 por uno que pueda enviar datos OTLP a través de HTTP a un recopilador compatible con OTLP. Sustituye:
traceExporter:new opentelemetry.tracing.ConsoleSpanExporter(),
  • con:
traceExporter: new OTLPTraceExporter({ headers: {} }),
  1. Pulsa Ctrl+c para detener el servicio messenger, que iniciaste en este terminal en el paso 4 de Configurar el envío de la instrumentación automática de OTel a la consola. A continuación, reinícialo para utilizar el nuevo exportador configurado en el paso 2:
^cnode --import ./tracing.mjs index.mjs
  1. Inicie una segunda sesión de terminal independiente. (En las instrucciones siguientes, esta se denomina terminal del cliente y la terminal original —utilizada en los pasos 1 y 3—, terminal del mensajero.) Espere unos diez segundos y, a continuación, envíe una solicitud de comprobación de estado al servicio messenger (puede ejecutarla varias veces si desea ver varias trazas):
curl -X GET http://localhost:4000/health
  1. Esperar diez segundos antes de enviar la solicitud facilita la localización de la traza, ya que aparece después de las numerosas trazas que genera la instrumentación automática al iniciarse el servicio.
  2. En un navegador, acceda a la interfaz de usuario de Jaeger en http://localhost:16686 y compruebe que el exportador OTLP funciona según lo previsto. Haga clic en Buscar en la barra de título y, en el menú desplegable del campo Servicio, seleccione el servicio cuyo nombre empiece por unknown_service. Haga clic en el botón Buscar trazas:
  3. Haz clic en una traza en el lado derecho de la ventana para mostrar una lista de los spans que contiene. Cada span describe las operaciones, que en ocasiones implican varios servicios, que se ejecutaron como parte de la traza. El span jsonParser de la captura de pantalla muestra cuánto tiempo tardó en ejecutarse la parte jsonParser del código de gestión de solicitudes del servicio messenger.
    Captura de pantalla de la interfaz gráfica de Jaeger que muestra la lista de spans de unknown_service antes de modificar la instrumentación automática para que muestre los nombres correctos de los servicios
  4. Como se indicó en el paso 5, el nombre del servicio exportado por el SDK de OTel (unknown_service) no es descriptivo. Para corregirlo, pulsa Ctrl+c en el terminal de messenger para detener el servicio messenger. A continuación, instala un par de paquetes más de Node.js:
^c npm install @opentelemetry/semantic-conventions@1.10.0 \ @opentelemetry/resources@1.10.0
  1. Estas dos bibliotecas ofrecen las siguientes funcionalidades:
    • @opentelemetry/semantic-conventions – Define los atributos estándar para las trazas según lo establecido en la especificación de OTel.
    • @opentelemetry/resources – Define un objeto (recurso) que representa la fuente que genera los datos de OTel (en este tutorial, el servicio messenger).
  2. Abre tracing.mjs en un editor de texto y realiza estos cambios:
    • Añade estas líneas al conjunto de sentencias import situadas al principio del archivo:
import { Resource } from "@opentelemetry/resources";import { SemanticResourceAttributes } from "@opentelemetry/semantic-conventions";
  • Crea un recurso llamado messenger bajo la clave correcta en la especificación de OTel añadiendo la siguiente línea después de la última instrucción import:
const resource = new Resource({  [SemanticResourceAttributes.SERVICE_NAME]: "messenger",
});
  • Pasa el objeto resource al constructor de NodeSDK añadiendo la línea resaltada en naranja entre las líneas en negro:
const sdk = new opentelemetry.NodeSDK({  resource,
  traceExporter: new OTLPTraceExporter({ headers: {} }),
  instrumentations: [getNodeAutoInstrumentations()],
});
  1. Reinicia el servicio messenger:
node --import ./tracing.mjs index.mjs
  1. Espera unos diez segundos y, a continuación, en el terminal del cliente (que abriste en el paso 4), envía otra solicitud de comprobación de estado al servidor (puedes ejecutar el comando varias veces si quieres ver varias trazas):
curl -X GET http://localhost:4000/health
  1. Nota: Deja abierto el terminal del cliente para volver a utilizarlo en la siguiente sección y el terminal del mensajero para volver a utilizarlo en el Reto 3.
  2. Confirma que aparece un nuevo servicio llamado «messenger» en la interfaz de usuario de Jaeger en el navegador (puede tardar unos segundos y quizá tengas que actualizar la interfaz de usuario de Jaeger):
    Captura de pantalla de la interfaz gráfica de Jaeger que muestra messenger en la lista de servicios disponibles para una inspección detallada de psans
  3. Selecciona messenger en el menú desplegable Servicio y haz clic en el botón Buscar trazas para ver todas las trazas recientes procedentes del servicio messenger (la captura de pantalla muestra las 2 más recientes de un total de 20):
    Captura de pantalla de la interfaz gráfica de Jaeger que muestra las 2 trazas más recientes del servicio messenger
  4. Haz clic en una traza para mostrar los spans que contiene. Cada span está correctamente etiquetado como procedente del servicio messenger:
    Captura de pantalla de la interfaz gráfica de Jaeger que muestra los detalles de un único intervalo de messenger

Configurar la instrumentación automática de OTel para el servicio notifier

Ahora, inicia y configura la instrumentación automática para el servicio notifier, ejecutando básicamente los mismos comandos que en las dos secciones anteriores para el servicio messenger.

  1. Abre una nueva sesión de terminal (denominada «terminal de notifier» en los pasos siguientes). Ve al directorio app del repositorio notifier e instala Node.js (puedes utilizar otro método si lo prefieres):
cd ~/microservices-march/notifier/appasdf install
  1. Instala las dependencias:
npm install
  1. Inicia la base de datos PostgreSQL para el servicio notifier:
docker compose up -d
  1. Crea el esquema y las tablas de la base de datos e inserta algunos datos iniciales:
npm run refresh-db
  1. Instale los paquetes de OTel para Node.js (para obtener una descripción de lo que hacen los paquetes, consulte los pasos 1 y 3 de «Configurar el envío de la instrumentación automática de OTel a la consola»):
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
  1. Crea un archivo nuevo llamado «tracing.mjs»:
touch tracing.mjs
  1. En tu editor de texto preferido, abre tracing.mjs y añade el siguiente script para poner en marcha el SDK de OTel:
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();
  1. Nota: Este script es exactamente igual que el del servicio messenger, salvo que el valor del campo SemanticResourceAttributes.SERVICE_NAME es notifier.
  2. Inicia el servicio notifier con la instrumentación automática de OTel:
node --import ./tracing.mjs index.mjs
  1. Espera unos diez segundos y, a continuación, envía desde el terminal del cliente una solicitud de comprobación de estado al servicio notifier. Este servicio escucha en el puerto 5000 para evitar conflictos con el servicio messenger, que escucha en el puerto 4000:
curl http://localhost:5000/health
  1. Nota: Deja abiertas las terminales del cliente y del notificador para volver a utilizarlas en el reto 3.
  2. Confirma que aparece un nuevo servicio llamado «notifier» en la interfaz de usuario de Jaeger en el navegador:
    Captura de pantalla de la interfaz gráfica de Jaeger que muestra notifier en la lista de servicios disponibles para inspeccionar los spans en profundidad

Configurar la instrumentación de OTel en NGINX

En NGINX, el seguimiento se configura manualmente en lugar de utilizar el método de instrumentación automática de OTel. Actualmente, la forma más habitual de instrumentar NGINX con OTel es utilizar un módulo escrito en C. Los módulos de terceros son una parte importante del ecosistema de NGINX, pero su configuración requiere cierto trabajo. Este tutorial se encarga de configurarlos. Para obtener más información, consulte Compilación de módulos dinámicos de terceros para NGINX y NGINX Plus en nuestro blog.

  1. Inicie una nueva sesión de terminal (el terminal de NGINX), cambie al directorio raíz del repositorio messenger y cree un nuevo directorio llamado load-balancer, además de nuevos archivos llamados Dockerfile, nginx.conf y opentelemetry_module.conf:
cd ~/microservices-march/messenger/mkdir load-balancer cd load-balancer touch Dockerfile touch nginx.conf touch opentelemetry_module.conf
  1. En tu editor de texto preferido, abre Dockerfile y añade lo siguiente (los comentarios explican qué hace cada línea, pero puedes compilar y ejecutar el contenedor de Docker sin entenderlo todo):
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
  1. Abre nginx.conf y añade lo siguiente:
events {}
http {
    include /etc/nginx/conf.d/opentelemetry_module.conf;

    upstream messenger {
        server localhost:4000;
    }

    server {
        listen 8085;

        location / {
            proxy_pass http://messenger;
        }
    }
}
  1. Este archivo de configuración de NGINX extremadamente básico indica a NGINX que: Nota: Esta configuración se aproxima bastante a la configuración real de NGINX como proxy inverso y equilibrador de carga en un entorno de producción. La única diferencia importante es que el argumento de la directiva server del bloque upstream suele ser un nombre de dominio o una dirección IP en lugar de localhost.
    • Configure un grupo de servidores upstream llamado «messenger» que represente el grupo de instancias del servicio «messenger»
    • Escuchar las solicitudes HTTP en el puerto 8085
    • Reenviar todas las solicitudes entrantes cuyas rutas comiencen por / (es decir, todas las solicitudes entrantes) al servidor upstream messenger upstream
  2. Abre opentelemetry_module.conf y añade lo siguiente:
NginxModuleEnabled ON;NginxModuleOtelSpanExporter otlp;
NginxModuleOtelExporterEndpoint localhost:4317;
NginxModuleServiceName messenger-lb;
NginxModuleServiceNamespace MicroservicesMarchDemoArchitecture;
NginxModuleServiceInstanceId DemoInstanceId;
NginxModuleResolveBackends ON;
NginxModuleTraceAsError ON;
  1. Crea una imagen de Docker que contenga NGINX y el módulo OTel de NGINX:
docker build -t messenger-lb .
  1. Inicia el contenedor de Docker para el proxy inverso y el balanceador de carga NGINX:
docker run --rm --name messenger-lb -p 8085:8085 --network="host" messenger-lb
  1. En el terminal del cliente, envía una solicitud de comprobación de estado al servicio messenger a través del proxy inverso y balanceador de carga NGINX (no es necesario esperar antes de enviar esta solicitud):
curl http://localhost:8085/health
  1. Nota: Deja abiertas las terminales de NGINX y del cliente para volver a utilizarlas en el reto 3.
  2. En el navegador, confirme que el nuevo servicio messenger-lb aparece en la interfaz de usuario de Jaeger junto con los servicios que inició anteriormente. Es posible que tenga que volver a cargar la interfaz de usuario de Jaeger en el navegador.
    Captura de pantalla de la interfaz gráfica de Jaeger que muestra la lista de servicios disponibles para inspeccionar los spans en profundidad, ahora también con messenger-lb

Desafío 3: Aprende a interpretar trazas de OTel

En «Arquitectura y flujo de usuario», describimos las etapas del flujo de usuario, pero, a modo de resumen:

  1. Un usuario inicia una conversación enviando un mensaje a otro usuario.
  2. El proxy inverso NGINX intercepta el mensaje y lo reenvía al servicio messenger.
  3. El servicio messenger guarda el mensaje en su base de datos y, a continuación, envía un evento a través de RabbitMQ.
  4. El servicio notifier consume ese evento, consulta las preferencias de notificación del destinatario (el segundo usuario) y le envía una notificación mediante el método que haya elegido.

Y los objetivos de implementar la telemetría son:

  1. Comprender todos los pasos por los que pasa una solicitud para completar el nuevo flujo de mensajes.
  2. Tenga la certeza de que el flujo se ejecuta de principio a fin en menos de cinco segundos en condiciones normales.
  3. Comprueba cuánto tarda el servicio notifier en empezar a procesar el evento enviado por el servicio messenger.

En este reto, aprenderá a evaluar si las trazas generadas por la instrumentación de OTel cumplen los objetivos mencionados anteriormente. Primero, probará el sistema y creará algunas trazas. A continuación, examinará la traza de un flujo de mensajes y las secciones generadas por NGINX, el servicio messenger service, y el servicio notifier service.

Crear datos de trazas

En el terminal del cliente, configura una conversación y envía un par de mensajes entre dos usuarios:

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'

El servicio notifier genera una salida como la siguiente, que aparece en el terminal del notificador:

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

Prepárate para leer las trazas

Abra la interfaz de usuario de Jaeger en el navegador, seleccione messenger-lb en el menú desplegable Servicio y haga clic en el botón Buscar trazas. Aparecerá una lista de trazas desde el principio del flujo. Haga clic en cualquier traza para ver sus detalles, como se muestra en esta captura de pantalla:

Captura de pantalla de la interfaz gráfica de Jaeger que muestra el conjunto completo de spans del flujo

Haz clic en distintos elementos y explora un poco. Antes de continuar, piensa en cómo la información de las trazas contribuye a alcanzar los objetivos de instrumentación indicados en la introducción del Reto 3. Entre las preguntas relevantes se incluyen:

  • ¿Qué información ayuda a alcanzar los objetivos?
  • ¿Qué información falta?
  • ¿Qué información no es relevante?

Examine la sección de NGINX (messenger-lb) de la traza

Objetivo 1: Ver todos los pasos por los que pasa una solicitud en el nuevo flujo de mensajes

Empiece por el tramo de NGINX, que contiene 11 tramos secundarios dentro del tramo principal. Dado que la configuración actual de NGINX es muy sencilla, los tramos secundarios no resultan especialmente interesantes y se limitan a mostrar el tiempo empleado en cada etapa del ciclo de vida del procesamiento de solicitudes de NGINX. Sin embargo, el tramo principal (el primero de todos) ofrece algunos datos interesantes:

Captura de pantalla de la interfaz gráfica de Jaeger que muestra el intervalo principal en la sección NGINX (messenger-lb) de la traza

  • En Etiquetas se muestran los siguientes atributos: En conjunto, estos tres datos indican lo siguiente: «Se envió una solicitud POST a /conversations/1/messages y la respuesta fue 201 (creado correctamente)». Esto se corresponde con los pasos 1 y 4a de Arquitectura y flujo de usuario).
    • http.method campo – POST (en términos de REST, esto significa creación)
    • http.status_code campo – 201 (indica que la creación se ha realizado correctamente)
    • http.target campo – conversations/1/messages (el endpoint de mensajes)
  • En Proceso, el campo webengine.name muestra que esta es la parte de la solicitud correspondiente a NGINX.

Además, dado que los spans de messenger y notifier están anidados dentro del span conversations/1 de messenger-lb (como se muestra en la captura de pantalla de Prepararse para leer las trazas), se puede determinar que la solicitud enviada al servicio messenger a través del proxy inverso NGINX pasó por todos los componentes previstos del flujo.

Esta información cumple el objetivo, ya que permite comprobar que el proxy inverso NGINX formó parte del flujo.

Objetivo 2: Verificar que el flujo se ejecuta en menos de cinco segundos

En la lista de spans con la etiqueta «messenger-lb», consulta el span más reciente (se encuentra al final de la lista) para ver cuánto tardó la parte de NGINX de la solicitud. En la captura de pantalla, el span comenzó a los 589 microsegundos (µs) y duró 24 µs, lo que significa que la operación completa del proxy inverso tardó solo 613 µs, es decir, unos 0,6 milisegundos (ms). (Por supuesto, los valores exactos serán diferentes cuando realices el tutorial).

Captura de pantalla de la interfaz gráfica de Jaeger que muestra los intervalos de NGINX (messenger-lb) en la traza

En una configuración como esta, por lo general los valores solo resultan útiles en relación con otras mediciones y varían de un sistema a otro. Sin embargo, en este caso está claro que esta operación no corre el riesgo de acercarse a los cinco segundos de duración.

Esta información cumple el objetivo, ya que permite comprobar que las operaciones de NGINX no tardaron ni de lejos cinco segundos. Si hay alguna operación muy lenta en el flujo, debe de producirse más adelante.

Objetivo 3: comprobar cuánto tarda el servicio notifier en leer el evento enviado por el servicio messenger

La capa de proxy inverso de NGINX no incluye ninguna información al respecto, por lo que puede pasar a los intervalos de messenger.

Examine la sección messenger de la traza

Objetivo 1: Ver todos los pasos por los que pasa una solicitud en el nuevo flujo de mensajes

La sección del servicio messenger de la traza contiene otros 11 spans. De nuevo, la mayoría de los spans secundarios corresponden a los pasos básicos que utiliza el framework Express al procesar una solicitud y no resultan muy interesantes. Sin embargo, el span principal (el primero de todos) vuelve a aportar información interesante:

Captura de pantalla de la interfaz gráfica de Jaeger que muestra el intervalo principal en la sección «messenger» de la traza

En Etiquetas se muestran los siguientes atributos:

  • http.method campo – POST (de nuevo, en términos de REST, esto significa creación)
  • http.route campo – /conversations/:conversationId/messages (la ruta de mensajes)
  • http.target campo – /conversations/1/messages (el endpoint de mensajes)

Esta información cumple el objetivo porque nos muestra que el servicio messenger formó parte del flujo y que el endpoint al que se accedió fue el de mensajes nuevos.

Objetivo 2: Verificar que el flujo se ejecuta en menos de cinco segundos

Como se muestra en la siguiente captura de pantalla, la parte messenger de la traza comenzó en 1,28 ms y terminó en 36,28 ms, con un tiempo total de 35 ms. La mayor parte de ese tiempo se dedicó a analizar el JSON (middleware - jsonParser) y, en mucha mayor medida, a conectarse a la base de datos (pg-pool.connect y tcp.connect).

Esto tiene sentido, dado que durante el proceso de escritura del mensaje también se realizan varias consultas SQL. Esto, a su vez, sugiere que quizá convenga ampliar la configuración de instrumentación automática para registrar la duración de dichas consultas. (El tutorial no muestra esta instrumentación adicional, pero en el Reto 4 se crean manualmente spans que, a su vez, pueden utilizarse para encapsular consultas a la base de datos).

Captura de pantalla de la interfaz gráfica de Jaeger que muestra los spans de la sección del servicio de mensajería de la traza y cuánto tardaron

Esta información cumple el objetivo porque demuestra que las operaciones de mensajería no tardan ni de lejos cinco segundos. Si hay una operación muy lenta en el flujo, debe de producirse más adelante.

Objetivo 3: comprobar cuánto tarda el servicio notifier en leer el evento enviado por el servicio messenger

Al igual que los spans de NGINX, los spans de messenger no incluyen esta información, por lo que puedes pasar a los spans de notifier.

Examine la sección notifier de la traza

Objetivo 1: Ver todos los pasos por los que pasa una solicitud en el nuevo flujo de mensajes

La sección notifier de la traza contiene solo dos spans:

Captura de pantalla de la interfaz gráfica de Jaeger que muestra los dos spans de la sección notifier de la traza

  • El intervalo chat_queue process confirma que el servicio notifier procesó un evento de la cola de mensajes chat_queue message queue
  • El span pg-pool.connect muestra que, tras procesar el evento, el servicio notifier estableció algún tipo de conexión con su base de datos

La información disponible en estos spans solo permite alcanzar parcialmente el objetivo de comprender cada paso. Se puede ver que el servicio notifier llegó a consumir el evento de la cola, pero no se sabe si:

  • La notificación del mensaje enviada por este servicio corresponde al evento emitido por el servicio messenger service
  • Las notificaciones pertinentes del mensaje se enviaron correctamente al destinatario del mensaje

Esto indica que debe hacer lo siguiente para comprender plenamente el flujo del servicio notifier:

  • Instrumenta manualmente los spans que muestran el envío de una notificación
  • Asegúrate de que haya una conexión explícita entre el evento enviado por el servicio messenger y el evento consumido por el servicio notifier, en forma de un ID de traza

Objetivo 2: Verificar que el flujo se ejecuta en menos de cinco segundos

Al observar la duración total de los intervalos del servicio notifier, se aprecia que la solicitud pasó 30,77 ms en la sección notifier del flujo. Sin embargo, como no hay intervalos que indiquen el «final» de todo el flujo (el envío de notificaciones al destinatario), no se puede determinar la duración total de esta sección del flujo ni el tiempo total que tarda en completarse la operación.

Objetivo 3: comprobar cuánto tarda el servicio notifier en leer el evento enviado por el servicio messenger

Sin embargo, puede ver que un intervalo de chat_queue de tipo process del servicio notifier comenzó a los 6,12 ms, 2 ms después de que comenzara un intervalo de chat_queue de tipo send del servicio messenger a los 4,12 ms.

Captura de pantalla de la interfaz gráfica de Jaeger que muestra el servicio notifier consumiendo un evento enviado por el servicio messenger

Este objetivo se cumple porque sabes que el servicio notifier consumió un evento 2 ms después de que lo enviara el servicio messenger. A diferencia del objetivo 2, para alcanzar este objetivo no necesitas saber si el evento se procesó por completo ni cuánto tiempo tardó.

Conclusiones

Según nuestro análisis de las trazas generadas por la instrumentación automática actual de OTel, está claro que:

  • Muchos de estos spans no resultan útiles en su forma actual:
    • NGINX está generando spans relacionados con funciones —como la comprobación de autorizaciones y el servicio de archivos— que no son relevantes para la función que le interesa: el proxy inverso. Sin embargo, actualmente la instrumentación OTel para NGINX no permite omitir los spans irrelevantes, por lo que no se puede hacer nada.
    • Entre los spans de los servicios de Node.js (los servicios messenger y notifier), algunos parecen relevantes para los objetivos: los spans correspondientes al análisis de JSON, request handler y todas las operaciones de base de datos. Algunos de los spans del middleware (como expressInit y corsMiddleware) no parecen relevantes y se pueden eliminar.
  • Faltan spans críticos para lo siguiente:
    • Notificaciones enviadas por el servicio notifier service
    • Una correspondencia clara entre el evento de RabbitMQ enviado por el servicio messenger y el procesado por el servicio notifier service

Esto significa que la instrumentación básica cumple el último objetivo:

  • Comprueba cuánto tarda el servicio notifier en empezar a procesar el evento enviado por el servicio messenger service

Sin embargo, no hay suficiente información para cumplir los dos primeros objetivos:

  • Comprender todos los pasos por los que pasa una solicitud durante el nuevo flujo de mensajes
  • Tener la certeza de que el flujo se ejecuta de principio a fin en menos de cinco segundos en circunstancias normales

Desafío 4: Optimizar la instrumentación a partir de las lecturas de trazas

En este reto, optimizarás la instrumentación de OTel basándote en los análisis de trazas que realizaste en el reto 3. Esto incluye tanto eliminar spans innecesarios, crear nuevos spans personalizados como confirmar que el evento que consume el servicio de notificaciones es el generado por el servicio de mensajería.

Eliminar spans innecesarios

  1. En el editor de texto que prefiera, abra el archivo tracing.mjs del directorio app del repositorio messenger y añada lo siguiente al final de la lista de sentencias de importación situada en la parte superior:
const IGNORED_EXPRESS_SPANS = new Set([  "middleware - expressInit",
  "middleware - corsMiddleware",
]);
  1. Esto define un conjunto de nombres de spans, extraídos de la lista de spans que se muestra en la siguiente captura de pantalla de la interfaz de Jaeger, que se omitirán de la traza porque no aportan información útil para este flujo. Puede que decida que tampoco se necesitan otros spans incluidos en la captura de pantalla y los añada a la lista de IGNORED_EXPRESS_SPANS.
    Captura de pantalla de la interfaz gráfica de Jaeger que muestra una lista de varios spans del servicio de mensajería que podrían no aportar información relevante y, por tanto, pueden omitirse de la traza
  2. Añada filtros a la configuración de instrumentación automática para omitir los spans que no desee cambiando la línea resaltada en naranja:
const sdk = new opentelemetry.NodeSDK({  resource,
  traceExporter: new OTLPTraceExporter({ headers: {} }),
  instrumentations: [getNodeAutoInstrumentations()],
});
  1. a esto:
const sdk = new opentelemetry.NodeSDK({  resource,
  traceExporter: new OTLPTraceExporter({ headers: {} }),
  instrumentations: [ getNodeAutoInstrumentations({ "@opentelemetry/instrumentation-express": { ignoreLayers: [ (name) => { return IGNORED_EXPRESS_SPANS.has(name); }, ], }, }), ],
});
  1. La función getNodeAutoInstrumentations hace referencia al conjunto de spans definido en el paso 1 para excluirlos de la traza generada por @opentelemetry/instrumentation-express. En otras palabras, la instrucción return se evalúa como true para un span que pertenece a IGNORED_EXPRESS_SPANS, y la instrucción ignoreLayers elimina ese span de la traza.
  2. En el terminal de messenger, pulsa Ctrl+c para detener el servicio messenger. A continuación, reinícialo:
^cnode --import ./tracing.mjs index.mjs
  1. Espera unos diez segundos y, a continuación, envía un mensaje nuevo desde el terminal del cliente:
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'
  1. Vuelve a comprobar los spans de messenger en la interfaz de usuario de Jaeger. Los dos spans de middleware, expressInit y corsMiddleware, ya no aparecen (puedes compararlo con la captura de pantalla del Objetivo 2 de Examinar la sección messenger de la traza en el Reto 3.
    Captura de pantalla de la interfaz gráfica de Jaeger que muestra que la traza ya no incluye dos spans después de cambiar la instrumentación para excluirlos de la traza

Configurar spans personalizados

En esta sección, trabajará por primera vez con el código de la aplicación. La instrumentación automática genera una gran cantidad de información sin necesidad de modificar la aplicación, pero algunos datos solo pueden obtenerse instrumentando partes específicas de la lógica de negocio.

En el nuevo flujo de mensajes que está instrumentando, un ejemplo de ello es el seguimiento del envío de notificaciones al destinatario del mensaje.

  1. Abre index.mjs en el directorio app del repositorio notifier. Este archivo contiene toda la lógica de negocio del servicio. Añade la siguiente línea al final de la lista de sentencias import situada al principio del archivo:
import { trace } from "@opentelemetry/api";
  1. Sustituye este código (aproximadamente en la línea 91 del archivo):
for (let pref of preferences) {
  console.log(
    `Sending notification of new message via ${pref.address_type} to ${pref.address}`
  );
}
  1. con:
const tracer = trace.getTracer("notifier"); // 1tracer.startActiveSpan( // 2
  "notification.send_all",
  {
    attributes: {
      user_id: msg.user_id,
    },
  },
  (parentSpan) => {
    for (let pref of preferences) {
      tracer.startActiveSpan(  // 3
        "notification.send",
        {
          attributes: { // 4
            notification_type: pref.address_type,
            user_id: pref.user_id,
          },
        },
        (span) => {
          console.log(
            `Sending notification of new message via ${pref.address_type} to ${pref.address}`
          );
          span.end(); // 5
        }
      );
    }
    parentSpan.end(); // 6
  }
);
  1. El nuevo código hace lo siguiente: tener un span principal garantiza que se registre cada operación de «envío de notificación», incluso si no se encuentra ninguna preferencia de notificación para el usuario.
    1. Obtiene el objeto tracer, que es un objeto global para interactuar con las trazas de OTel.
    2. Inicia un nuevo intervalo principal llamado «notification.send_all» y establece el atributo «user_id» para identificar al remitente del mensaje.
    3. Entra en un bucle en el que se enumeran las preferencias de notificación del destinatario y se crea un nuevo intervalo secundario llamado notification.send bajo notification.send_all. Cada notificación genera un nuevo intervalo.
    4. Establece más atributos para el span secundario:
      • notification_type – Uno de los siguientes: sms o email
      • user_id – El ID del usuario que recibe la notificación
    5. Cierra sucesivamente cada tramo secundario notification.send.
    6. Cierra el span principal notification.send_all.
  2. En el terminal del notificador, pulsa Ctrl+c para detener el servicio notifier. A continuación, reinícialo:
^cnode --import ./tracing.mjs index.mjs
  1. Espera unos diez segundos y, a continuación, envía un nuevo mensaje desde el terminal del cliente:
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'
  1. Vuelve a comprobar los intervalos de notifier en la interfaz de usuario de Jaeger. Verás el intervalo principal y dos intervalos secundarios, cada uno con una operación «send notification»:
    Captura de pantalla de la interfaz gráfica de Jaeger que muestra el resultado de definir tres nuevos spans en el código del servicio de notificaciones

Ahora puede cumplir plenamente el primer y el segundo objetivo, ya que puede ver todos los pasos por los que pasa una solicitud durante el nuevo flujo de mensajes. Los tiempos de cada tramo revelan cualquier retraso entre estos pasos.

Confirma que el mensajero y el notificador gestionan el mismo evento

Hay una cosa más que necesitas para comprender plenamente el flujo. ¿El evento que está procesando el servicio notifier es realmente el que ha enviado el servicio messenger?

No tienes que hacer ningún cambio explícito para conectar las dos trazas, pero tampoco conviene confiar ciegamente en la magia de la instrumentación automática.

Teniendo esto en cuenta, añade un breve código de depuración para comprobar que la traza que se inicia en el servicio NGINX es realmente la misma (tiene el mismo ID de traza) que la que consume el servicio notifier.

  1. Abre el archivo «index.mjs» del directorio «app» del repositorio «messenger» y realiza estos cambios:
    • Añade la siguiente línea al final de la lista de sentencias de importación situada en la parte superior:
import { trace } from "@opentelemetry/api";
  • Añade las líneas resaltadas en naranja debajo de la línea existente en negro:
async function createMessageInConversation(req, res) {
  const tracer = trace.getActiveSpan(); console.log("TRACE_ID: ", tracer.spanContext().traceId);
  • Las nuevas líneas muestran el TRACE_ID desde dentro de la función de messenger que se encarga de crear un nuevo mensaje.
  1. Abre el archivo index.mjs del directorio app del repositorio notifier y añade la línea resaltada en naranja debajo de la línea existente en negro:
export async function handleMessageConsume(channel, msg, handlers) {  console.log("RABBIT_MQ_MESSAGE: ", msg);
  1. La nueva línea muestra el contenido completo del evento AMQP recibido por el servicio notifier.
  2. Detenga y reinicie los servicios messenger y notifier ejecutando estos comandos en los terminales de messenger y notifier:
^cnode --import ./tracing.mjs index.mjs
  1. Espera unos diez segundos y, a continuación, vuelve a enviar un mensaje desde el terminal del cliente:
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'
  1. Consulta los registros de los servicios messenger y notifier. El registro del servicio messenger incluye una línea como esta, que muestra el ID de traza de un mensaje (el ID real será diferente cuando sigas el tutorial):
TRACE_ID:  29377a9b546c50be629c8e64409bbfb5
  1. Del mismo modo, el registro del servicio notifier muestra un ID de seguimiento en una salida como esta:
_spanContext: {
  traceId: '29377a9b546c50be629c8e64409bbfb5',
  spanId: 'a94e9462a39e6dbf',
  traceFlags: 1,
  traceState: undefined
},
  1. Los ID de traza coinciden en la consola, pero, como último paso, puedes compararlos con el ID de traza de la interfaz de usuario de Jaeger. Abre la interfaz de usuario en el endpoint del ID de traza correspondiente (el tuyo será diferente, pero en este ejemplo es http://localhost:16686/trace/29377a9b546c50be629c8e64409bbfb5) para ver la traza completa. La traza de Jaeger confirma que:
    • La instrumentación automática de AMQP en el servicio messenger añade este ID de traza como parte de los metadatos cuando se envía el evento.
    • La instrumentación automática de AMQP del servicio notifier espera esos metadatos y configura adecuadamente el contexto de la traza.

Nota: En un sistema de producción real, eliminaría el código que ha añadido en esta sección una vez que haya confirmado que el flujo funciona según lo previsto.

Limpieza de recursos

¡A lo largo de este tutorial has creado varios contenedores e imágenes! Sigue estas instrucciones para eliminarlos.

  • Para eliminar cualquier contenedor de Docker que esté en ejecución:
docker rm $(docker stop messenger-lb)
  • Para eliminar el servicio platform y los servicios de base de datos messenger y notifier:
cd ~/microservices-march/platform && docker compose down cd ~/microservices-march/notifier && docker compose down cd ~/microservices-march/messenger && docker compose down

Próximos pasos

¡Enhorabuena, has completado el tutorial!

  • Has configurado la instrumentación de OTel en un proxy inverso NGINX y dos servicios de Node.js.
  • Analizaste de forma crítica los datos proporcionados por la instrumentación automática de OTel y añadiste algunos datos de telemetría que faltaban para alcanzar los objetivos del laboratorio de OTel:
    • Obtuvo una visión razonablemente clara del recorrido de una solicitud específica a través de un sistema de mensajería sin modificar directamente el código de la aplicación.
    • Confirmaste que, en circunstancias normales, el flujo se ejecutaba de principio a fin en menos de cinco segundos.

Y, aun así, ¡apenas has empezado a descubrir cómo podría ser una configuración de trazabilidad ideal! En un entorno de producción, quizá te interese añadir elementos como spans personalizados para cada consulta a la base de datos y metadatos adicionales en todos los spans que describan detalles del entorno de ejecución, como el ID del contenedor de cada servicio. También podrías implementar los otros dos tipos de datos de OTel (métricas y registros) para obtener una visión completa del estado de tu sistema.

Para seguir aprendiendo sobre microservicios, consulta Microservices March 2023. En la Unidad 4: Gestiona el caos y la complejidad de los microservicios mediante la observabilidad, conocerás las tres clases principales de datos de observabilidad, la importancia de alinear la infraestructura y las aplicaciones, y distintas formas de empezar a analizar datos en profundidad.


Compartir

Artículos relacionados del blog

Únete a F5 NGINX en AppWorld 2024
NGINX | 04/01/2024

Únete a F5 NGINX en AppWorld 2024

Descubre las numerosas razones para asistir al evento insignia de F5, que incluirá un emocionante anuncio sobre la cartera de productos NGINX.

Rotación de certificados SSL/TLS sin reinicios en NGINX Open Source
NGINX | 26/09/2023

Rotación de certificados SSL/TLS sin reinicios en NGINX Open Source

La función de diccionario compartido permite configurar NGINX sin necesidad de reiniciarlo al renovar los certificados SSL/TLS.

Exposición de APIs en Kubernetes
NGINX | 10/03/2022

Exposición de APIs en Kubernetes

La unidad 2 de Microservices March 2022 se centra en cómo exponer tus API en Kubernetes. Aquí tienes enlaces a los recursos que necesitas: la retransmisión en directo del 14 de marzo, artículos de blog y otros recursos de preparación, y el laboratorio práctico «Protege las API de Kubernetes mediante la limitación de solicitudes».

NGINX Sprint 2.0: visión clara, código renovado y nuevos compromisos con el código abierto
NGINX | 23/08/2021

NGINX Sprint 2.0: visión clara, código renovado y nuevos compromisos con el código abierto

Hoy, en NGINX Sprint 2.0, anunciamos cómo respaldaremos nuestra visión del futuro de las aplicaciones modernas con una mayor presencia en la comunidad de código abierto, incluida la participación en el proyecto comunitario Kubernetes, nuevos proyectos de software de código abierto y la arquitectura de referencia para aplicaciones modernas.

Presentamos NGINX Plus R22
NGINX | 09/06/2020

Presentamos NGINX Plus R22

NGINX Plus R22 incorpora nuevas funcionalidades, como la compatibilidad con OCSP para verificar la revocación de certificados en TLS mutuo, el uso de varios proveedores de identidad OIDC en una configuración, el seguimiento en tiempo real de la limitación de solicitudes y conexiones en el panel de NGINX Plus y mejoras en NGINX JavaScript.

Presentamos NGINX Plus R17
NGINX | 11/12/2018

Presentamos NGINX Plus R17

NGINX Plus R17 incorpora compatibilidad con la limitación de velocidad en dos fases y con TLS 1.3, la versión más reciente del protocolo Transport Layer Security. La configuración de OpenID Connect es más sencilla y NGINX ModSecurity WAF es el doble de rápido que antes. También se ha actualizado el módulo JavaScript de NGINX.