Tutoriel NGINX : comment utiliser le traçage OpenTelemetry pour comprendre vos microservices

NGINX | 28 mars 2023

Cet article fait partie d’une série de quatre tutoriels qui vous aideront à mettre en pratique les concepts de Microservices de mars 2023 : commencez à déployer des microservices :

Une architecture de microservices offre de nombreux avantages, notamment une plus grande autonomie des équipes et davantage de flexibilité en matière de mise à l’échelle et de déploiement. En revanche, plus un système comporte de services (une application de microservices peut en compter des dizaines, voire des centaines), plus il devient difficile de conserver une vision claire du fonctionnement global du système. En tant que concepteurs et responsables de la maintenance de systèmes logiciels complexes, nous savons à quel point il est essentiel de disposer de cette vision claire. Observabilité Les outils d’observabilité nous permettent de construire cette vision à travers de nombreux services et l’infrastructure sous-jacente.

Dans ce tutoriel, nous mettons en lumière un type d’observabilité particulièrement important pour les applications basées sur des microservices : le traçage. Avant de commencer, définissons quelques termes couramment utilisés lorsque l’on parle d’observabilité :

  • Observabilité – La capacité à comprendre l’état ou la condition interne d’un système complexe (tel qu’une application de microservices) en se fondant uniquement sur la connaissance de ses sorties externes (telles que les traces, les journaux et les métriques).
  • Surveillance – La capacité à observer et à vérifier l’évolution ou l’état d’un objet sur une période donnée. Par exemple, vous pouvez surveiller le trafic reçu par votre application pendant les périodes de pointe et utiliser ces informations pour adapter sa capacité en conséquence.
  • Télémétrie – Action de collecter des métriques, des traces et des journaux, puis de les transférer de leur point d’origine vers un autre système à des fins de stockage et d’analyse. Désigne également les données elles-mêmes.
  • Traçage/traces – Description du parcours d’une requête ou d’une action à travers tous les nœuds d’un système distribué.
  • Segment – Enregistrement, au sein d’une trace, d’une opération et des métadonnées qui lui sont associées. Les traces sont constituées de nombreux segments imbriqués.
  • Journalisation des événements/journaux – Un enregistrement textuel horodaté accompagné de métadonnées.
  • Métrique – Une mesure relevée lors de l’exécution. Par exemple, la quantité de mémoire utilisée par une application à un instant donné.

Nous pouvons utiliser tous ces concepts pour mieux comprendre les performances de nos microservices. Le traçage est particulièrement utile dans le cadre d’une stratégie d’observabilité, car les traces offrent une « vue d’ensemble » de ce qui se passe entre plusieurs composants, souvent faiblement couplés, lorsqu’une requête est effectuée. Il s’agit également d’un moyen particulièrement efficace d’identifier les goulots d’étranglement qui nuisent aux performances.

Ce tutoriel utilise la boîte à outils de traçage d’OpenTelemetry (OTel), une norme open source indépendante des fournisseurs, de plus en plus populaire, qui permet de collecter, de traiter et d’exporter des données de télémétrie. Selon le modèle d’OTel, une trace découpe un flux de données, qui peut impliquer plusieurs services, en une série de « segments » classés par ordre chronologique qui vous permettent de comprendre facilement :

  • Toutes les étapes qui se sont déroulées dans un « segment »
  • Combien de temps toutes ces étapes ont pris
  • Métadonnées relatives à chaque étape

Si vous ne connaissez pas OTel, consultez « Qu’est-ce qu’OpenTelemetry ? » pour découvrir en détail cette norme et les éléments à prendre en compte lors de sa mise en œuvre.

Présentation du tutoriel

Ce tutoriel porte sur le traçage des opérations d’une application de microservices avec OTel. Au fil des quatre exercices proposés, vous apprendrez à suivre une requête dans votre système et à répondre à des questions sur vos microservices :

Ces défis illustrent le processus que nous recommandons lors de la configuration initiale du traçage. Les étapes sont les suivantes :

  1. Comprenez le système ainsi que l’opération spécifique que vous instrumentez.
  2. Déterminez ce que vous devez savoir sur le système en cours d’exécution.
  3. Instrumentez le système « naïvement » — c’est-à-dire en utilisant une configuration par défaut, sans chercher à éliminer les informations dont vous n’avez pas besoin ni à collecter des points de données personnalisés — puis évaluez si cette instrumentation vous aide à répondre à vos questions.
  4. Ajustez les informations transmises afin de pouvoir répondre plus rapidement à ces questions.

Remarque : Dans ce tutoriel, notre objectif est d’illustrer certains concepts fondamentaux de la télémétrie, et non de montrer la bonne façon de déployer des microservices en production. Bien qu’il repose sur une véritable architecture de « microservices », il convient de garder à l’esprit certaines réserves importantes :

  • Ce tutoriel n’utilise pas de framework d’orchestration de conteneurs tel que Kubernetes ou Nomad. Vous pouvez ainsi vous familiariser avec les concepts liés aux microservices sans vous perdre dans les spécificités d’un framework particulier. Les modèles présentés ici peuvent être transposés à un système exécutant l’un de ces frameworks.
  • Les services sont conçus pour être faciles à comprendre plutôt que pour respecter des principes rigoureux d’ingénierie logicielle. L’objectif est d’examiner le rôle d’un service dans le système et ses modes de communication, et non les détails du code. Pour plus d’informations, consultez les fichiers README de chaque service.

Architecture du tutoriel et objectifs de télémétrie

Architecture et parcours utilisateur

Ce schéma illustre l’architecture globale et le flux de données entre les microservices et les autres éléments utilisés dans le tutoriel.

Schéma illustrant la topologie utilisée dans le tutoriel, avec le traçage OpenTelemetry d’un système de messagerie comprenant deux microservices, NGINX et RabbitMQ

Les deux microservices sont les suivants :

  • Le service messenger service – Une API de chat simple avec des fonctionnalités de stockage des messages
  • Le service notifier service – Un écouteur qui déclenche des événements pour alerter les utilisateurs en fonction de leurs préférences

Les trois éléments de l’infrastructure de support sont les suivants :

  • NGINX Open Source – Un point d’entrée vers le service messenger et, plus largement, vers l’ensemble du système
  • RabbitMQ – Un courtier de messages open source populaire qui permet aux services de communiquer de manière asynchrone
  • Jaeger – Un système open source de traçage distribué de bout en bout permettant de collecter et de visualiser les données de télémétrie provenant des composants du système qui les produisent

En laissant OTel de côté un instant, nous pouvons nous concentrer sur la séquence d’événements que nous traçons : ce qui se passe lorsqu’un utilisateur envoie un nouveau message de chat et que le destinataire en est informé.

Schéma illustrant le flux d’informations dans le système de messagerie utilisé dans le tutoriel

Le flux se décompose comme suit :

  1. Un utilisateur envoie un message au service messenger. Le proxy inverse NGINX intercepte le message et le transmet à l’une des nombreuses instances du service messenger.
  2. Le service messenger enregistre le nouveau message dans sa base de données.
  3. Le service messenger publie un événement dans une file de messages RabbitMQ appelée chat_queue pour indiquer qu’un message a été envoyé. Cet événement est générique et ne cible aucun destinataire en particulier.
  4. En même temps :
    • 4a. Le service messenger renvoie une réponse à l’expéditeur pour lui indiquer que le message a bien été envoyé.
    • 4b. Le service notifier détecte le nouvel événement dans la file chat_queue et le consomme.
  5. Le service notifier consulte sa base de données pour connaître les préférences de notification du destinataire du nouveau message.
  6. Le service notifier utilise le moyen de communication préféré du destinataire pour lui envoyer une ou plusieurs notifications (dans ce tutoriel, les moyens proposés sont les SMS et les e-mails).

Objectifs de télémétrie

Lors de la mise en place de l’instrumentation de télémétrie, il est préférable de commencer par définir des objectifs plus précis que « tout envoyer et espérer en tirer des enseignements ». Dans ce tutoriel, nous avons trois objectifs clés en matière de télémétrie :

  1. Comprendre toutes les étapes par lesquelles passe une requête lors du flux de création d’un message
  2. Avoir la certitude que le flux s’exécute de bout en bout en moins de cinq secondes dans des conditions normales
  3. Vérifiez combien de temps le service notifier met à commencer à traiter l’événement envoyé par le service messenger (un délai excessif peut indiquer que le service notifier rencontre des difficultés pour lire la file d’attente des événements et que ceux-ci s’y accumulent)

Notez que ces objectifs concernent à la fois le fonctionnement technique du système et l’expérience utilisateur.

Prérequis et configuration du tutoriel

Prérequis

Pour suivre ce tutoriel dans votre propre environnement, vous avez besoin des éléments suivants :

  • Un environnement compatible avec Linux/Unix Remarque : les activités de ce tutoriel qui impliquent le traçage de NGINX ne fonctionnent pas sur les processeurs ARM, car le module OpenTelemetry pour NGINX n’est pas compatible. (Cela inclut les architectures Linux aarch64 et les ordinateurs Apple équipés d’une puce M1 ou M2.) Les activités qui impliquent les services messenger et notifier fonctionnent sur toutes les architectures.
  • Connaissances de base de la ligne de commande Linux, de JavaScript et de bash (mais l’ensemble du code et des commandes est fourni et expliqué, ce qui vous permettra de réussir même avec des connaissances limitées)
  • Docker et Docker Compose
  • Node.js 19.x ou version ultérieure
    • Nous avons testé la version 19.x, mais les versions plus récentes de Node.js devraient également fonctionner.
    • Pour obtenir des informations détaillées sur l’installation de Node.js, consultez le fichier README dans le dépôt du service messenger. Vous pouvez également installer asdf afin d’utiliser exactement la même version de Node.js que celle employée dans le tutoriel.
  • curl (déjà installé sur la plupart des systèmes)
  • Les technologies répertoriées dans « Architecture et parcours utilisateur » : messenger et notifier (que vous téléchargerez dans la section suivante), NGINX Open Source, Jaeger et RabbitMQ.

Remarque : ce tutoriel utilise le SDK JavaScript, car les services messenger et notifier sont écrits en Node.js. Vous configurez également la fonctionnalité d’instrumentation automatique d’OTel (également appelée « auto-instrumentation ») afin de vous faire une idée du type d’informations fournies par OTel. Ce tutoriel vous explique tout ce que vous devez savoir sur le SDK Node.js d’OTel. Pour en savoir plus, consultez la documentation d’OTel.

Configuration

  1. Ouvrez une session de terminal.
  2. Dans votre répertoire personnel, créez le répertoire microservices-march et clonez-y les dépôts GitHub de ce tutoriel. (Vous pouvez également utiliser un autre nom de répertoire et adapter les instructions en conséquence.) Remarque : tout au long du tutoriel, l’invite de commande Linux est omise afin de faciliter le copier-coller des commandes dans votre terminal. Le tilde (~) représente votre répertoire personnel.
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

Défi 1 : configurer une instrumentation OTel de base

Dans ce défi, vous allez démarrer le service messenger service et configurer l’auto-instrumentation OTel afin d’envoyer les données de télémétrie vers la console.

Lancer le service messenger

  1. Accédez au dépôt platform et démarrez Docker Compose :
cd ~/microservices-march/platformdocker compose up -d --build
  1. Cela démarre RabbitMQ et Jaeger, qui seront utilisés dans les défis suivants.
    • L’option « ‑d » indique à Docker Compose de se détacher des conteneurs une fois qu’ils ont démarré (sinon, les conteneurs resteront attachés à votre terminal).
    • L’option « --build » indique à Docker Compose de reconstruire toutes les images au lancement. Cela garantit que les images que vous exécutez restent à jour en cas de modification éventuelle des fichiers.
  2. Accédez au répertoire app du dépôt messenger et installez Node.js (vous pouvez utiliser une autre méthode si vous le souhaitez) :
cd ~/microservices-march/messenger/appasdf install
  1. Installez les dépendances :
npm install
  1. Démarrez la base de données PostgreSQL pour le service messenger :
docker compose up -d
  1. Créez le schéma et les tables de la base de données, puis insérez quelques données initiales :
npm run refresh-db

Configurer l’auto-instrumentation OTel envoyée à la console

Avec l’auto-instrumentation OTel, vous n’avez rien à modifier dans la base de code de messenger pour configurer le traçage. Au lieu d’être importée directement dans le code de l’application, toute la configuration du traçage est définie dans un script importé dans le processus Node.js lors de l’exécution.

Ici, vous configurez l’auto-instrumentation du service messenger avec la destination la plus simple pour les traces : la console. Dans le défi 2, vous modifierez la configuration afin d’envoyer les traces à Jaeger en tant que collecteur externe.

  1. Toujours dans le répertoire app du dépôt messenger, installez les principaux packages OTel pour Node.js :
npm install @opentelemetry/sdk-node@0.36.0 \ @opentelemetry/auto-instrumentations-node@0.36.4
  1. Ces bibliothèques fournissent les fonctionnalités suivantes : Remarque : L’une des particularités d’OTel est que ses SDK JavaScript sont divisés en de très nombreux petits composants. Vous devrez donc installer quelques paquets supplémentaires, même pour l’exemple de base de ce tutoriel. Pour savoir quels paquets pourraient vous être nécessaires afin d’effectuer des tâches d’instrumentation allant au-delà de celles abordées dans ce tutoriel, consultez les (très bons) guides de démarrage d’OTel et parcourez le dépôt GitHub d’OTel.
    • @opentelemetry/sdk-node – Génération et exportation des données OTel
    • @opentelemetry/auto-instrumentations-node – Configuration automatique avec les paramètres par défaut de toutes les instrumentations Node.js les plus courantes
  2. Créez un nouveau fichier nommé « tracing.mjs » pour y placer le code d’installation et de configuration du traçage OTel :
touch tracing.mjs
  1. Dans votre éditeur de texte préféré, ouvrez « tracing.mjs » et ajoutez ce code :
//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. Le code effectue les opérations suivantes :
    1. Importe les fonctions et les objets requis depuis le SDK OTel.
    2. Crée une nouvelle instance du NodeSDK et la configure pour :
      • Envoyer les spans vers la console (ConsoleSpanExporter).
      • Utilisez l’auto-instrumenteur comme ensemble d’instrumentation de base. Celui-ci charge toutes les bibliothèques d’auto-instrumentation les plus courantes. Dans ce tutoriel, les bibliothèques pertinentes sont les suivantes :
        • @opentelemetry/instrumentation-pg pour la bibliothèque de base de données PostgreSQL (pg)
        • @opentelemetry/instrumentation-express pour le framework Express de Node.js
        • @opentelemetry/instrumentation-amqplib pour la bibliothèque RabbitMQ (amqplib)
    3. Démarre le SDK.
  2. Démarrez le service messenger en important le script d’auto-instrumentation que vous avez créé à l’étape 3.
node --import ./tracing.mjs index.mjs
  1. Au bout d’un moment, de nombreuses informations liées au traçage commencent à s’afficher dans la console (votre terminal) :
...
{
  traceId: '9c1801593a9d3b773e5cbd314a8ea89c',
  parentId: undefined,
  traceState: undefined,
  name: 'fs statSync',
  id: '2ddf082c1d609fbe',
  kind: 0,
  timestamp: 1676076410782000,
  duration: 3,
  attributes: {},
  status: { code: 0 },
  events: [],
  links: []
}
...

Remarque : Laissez la session du terminal ouverte afin de pouvoir la réutiliser dans le défi 2.

Défi 2 : configurer l’instrumentation OTel et la visualisation des traces pour tous les services

Il existe de nombreux outils permettant de visualiser et d’analyser les traces, mais ce tutoriel utilise Jaeger. Jaeger est une solution open source simple de traçage distribué de bout en bout, dotée d’une interface utilisateur web intégrée permettant de visualiser les segments et d’autres données de traçage. L’infrastructure fournie dans le dépôt platform inclut Jaeger (que vous avez démarré à l’étape 1 du défi 1), ce qui vous permet de vous concentrer sur l’analyse des données plutôt que sur la gestion d’outils complexes.

Jaeger est accessible à l’adresse http://localhost:16686 dans votre navigateur, mais si vous y accédez maintenant, vous ne verrez aucune donnée concernant votre système. En effet, les traces que vous collectez actuellement sont envoyées à la console ! Pour afficher les données de traçage dans Jaeger, vous devez exporter les traces au format du protocole OpenTelemetry (OTLP).

Dans ce défi, vous instrumentez le parcours utilisateur principal en configurant l’instrumentation pour :

Configurer l’auto-instrumentation OTel envoyée à un collecteur externe

Pour rappel, l’utilisation de l’auto-instrumentation OTel signifie que vous n’avez rien à modifier dans le code de messenger pour configurer le traçage. Toute la configuration du traçage se trouve dans un script importé dans le processus Node.js lors de l’exécution. Ici, vous remplacez la destination des traces générées par le service messenger, qui ne sont plus envoyées vers la console, mais vers un collecteur externe (Jaeger dans ce tutoriel).

  1. Toujours dans le même terminal que pour le défi 1 et dans le répertoire app du dépôt messenger, installez le package Node.js de l’exportateur OTLP :
npm install @opentelemetry/exporter-trace-otlp-http@0.36.0
  1. La bibliothèque @opentelemetry/exporter-trace-otlp-http exporte les informations de traçage au format OTLP via HTTP. Elle est utilisée pour envoyer des données de télémétrie à un collecteur OTel externe.
  2. Ouvrez tracing.mjs (que vous avez créé et modifié dans le défi 1) et apportez les modifications suivantes : Remarque : par souci de simplicité, ce tutoriel suppose que le collecteur se trouve à l’emplacement par défaut, http://localhost:4318/v1/traces. Dans un système réel, il est recommandé de définir explicitement cet emplacement.
    • Ajoutez cette ligne à la liste des instructions « import » en haut du fichier :
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
  • Remplacez l’« exportateur » que vous fournissez au SDK OTel — actuellement l’exportateur de console utilisé dans le défi 1 — par un exportateur capable d’envoyer des données OTLP via HTTP à un collecteur compatible OTLP. Remplacez :
traceExporter:new opentelemetry.tracing.ConsoleSpanExporter(),
  • avec :
traceExporter: new OTLPTraceExporter({ headers: {} }),
  1. Appuyez sur Ctrl+c pour arrêter le service messenger, que vous avez démarré dans ce terminal à l’étape 4 de la section « Configurer l’envoi de l’auto-instrumentation OTel vers la console ». Redémarrez-le ensuite afin d’utiliser le nouvel exportateur configuré à l’étape 2 :
^cnode --import ./tracing.mjs index.mjs
  1. Ouvrez une deuxième session de terminal distincte. (Dans les instructions suivantes, celle-ci est appelée le « terminal client », tandis que le terminal d’origine, utilisé aux étapes 1 et 3, est appelé le « terminal de messagerie ».) Attendez environ dix secondes, puis envoyez une requête de contrôle d’intégrité au service messenger (vous pouvez l’exécuter plusieurs fois si vous souhaitez voir plusieurs traces) :
curl -X GET http://localhost:4000/health
  1. Attendre dix secondes avant d’envoyer la requête permet de retrouver plus facilement votre trace, car elle apparaît après les nombreuses traces générées par l’instrumentation automatique au démarrage du service.
  2. Dans un navigateur, accédez à l’interface utilisateur de Jaeger à l’adresse http://localhost:16686 et vérifiez que l’exportateur OTLP fonctionne comme prévu. Cliquez sur Rechercher dans la barre de titre, puis, dans le menu déroulant du champ Service sélectionnez le service dont le nom commence par unknown_service. Cliquez sur le bouton Rechercher des traces :
  3. Cliquez sur une trace dans la partie droite de la fenêtre pour afficher la liste des spans qu’elle contient. Chaque span décrit les opérations, impliquant parfois plusieurs services, qui ont été exécutées dans le cadre de la trace. Le span jsonParser de la capture d’écran indique le temps nécessaire à l’exécution de la partie jsonParser du code de traitement des requêtes du service messenger.
    Capture d’écran de l’interface graphique de Jaeger affichant la liste des segments pour unknown_service, avant que l’auto-instrumentation ne soit modifiée afin d’afficher les noms de services corrects
  4. Comme indiqué à l’étape 5, le nom du service exporté par le SDK OTel (unknown_service) n’est pas explicite. Pour y remédier, dans le terminal de messenger, appuyez sur Ctrl+c pour arrêter le service messenger. Installez ensuite quelques paquets Node.js supplémentaires :
^c npm install @opentelemetry/semantic-conventions@1.10.0 \ @opentelemetry/resources@1.10.0
  1. Ces deux bibliothèques fournissent les fonctionnalités suivantes :
    • @opentelemetry/semantic-conventions – Définit les attributs standard des traces tels qu’ils sont définis dans la spécification OTel.
    • @opentelemetry/resources – Définit un objet (ressource) qui représente la source générant les données OTel (dans ce tutoriel, le service messenger).
  2. Ouvrez « tracing.mjs » dans un éditeur de texte et apportez les modifications suivantes :
    • Ajoutez ces lignes à la liste des instructions « import » en haut du fichier :
import { Resource } from "@opentelemetry/resources";import { SemanticResourceAttributes } from "@opentelemetry/semantic-conventions";
  • Créez une ressource appelée messenger sous la clé appropriée dans la spécification OTel en ajoutant la ligne suivante après la dernière instruction import :
const resource = new Resource({  [SemanticResourceAttributes.SERVICE_NAME]: "messenger",
});
  • Transmettez l’objet « resource » au constructeur NodeSDK en ajoutant la ligne surlignée en orange entre les lignes en noir :
const sdk = new opentelemetry.NodeSDK({  resource,
  traceExporter: new OTLPTraceExporter({ headers: {} }),
  instrumentations: [getNodeAutoInstrumentations()],
});
  1. Redémarrez le service messenger :
node --import ./tracing.mjs index.mjs
  1. Attendez environ dix secondes, puis, dans le terminal client (que vous avez ouvert à l’étape 4), envoyez une nouvelle requête de contrôle d’intégrité au serveur (vous pouvez exécuter la commande plusieurs fois si vous souhaitez voir plusieurs traces) :
curl -X GET http://localhost:4000/health
  1. Remarque : Laissez le terminal client ouvert afin de pouvoir le réutiliser dans la section suivante, et le terminal de messagerie ouvert afin de pouvoir le réutiliser dans le défi 3.
  2. Vérifiez qu’un nouveau service appelé « messenger » apparaît dans l’interface utilisateur de Jaeger dans le navigateur (cela peut prendre quelques secondes et vous devrez peut-être actualiser l’interface utilisateur de Jaeger) :
    Capture d’écran de l’interface graphique de Jaeger montrant messenger dans la liste des services disponibles pour une inspection approfondie de psans
  3. Sélectionnez messenger dans le menu déroulant Service et cliquez sur le bouton Find Traces pour afficher toutes les traces récentes provenant du service messenger (la capture d’écran montre les 2 plus récentes sur 20) :
    Capture d’écran de l’interface graphique de Jaeger montrant les 2 traces les plus récentes du service messenger
  4. Cliquez sur une trace pour afficher les spans qu’elle contient. Chaque span est correctement identifié comme provenant du service messenger :
    Capture d’écran de l’interface graphique de Jaeger affichant les détails d’un span unique du service messenger

Configurer l’auto-instrumentation OTel du service notifier

Lancez et configurez maintenant l’auto-instrumentation pour le service notifier, en exécutant essentiellement les mêmes commandes que dans les deux sections précédentes pour le service messenger.

  1. Ouvrez une nouvelle session de terminal (appelée le « terminal notifier » dans les étapes suivantes). Accédez au répertoire app du dépôt notifier et installez Node.js (vous pouvez utiliser une autre méthode si vous le souhaitez) :
cd ~/microservices-march/notifier/appasdf install
  1. Installez les dépendances :
npm install
  1. Démarrez la base de données PostgreSQL pour le service notifier :
docker compose up -d
  1. Créez le schéma et les tables de la base de données, puis insérez quelques données initiales :
npm run refresh-db
  1. Installez les packages OTel pour Node.js (pour savoir à quoi servent ces packages, consultez les étapes 1 et 3 de la section « Configurer l’envoi de l’auto-instrumentation OTel vers la console ») :
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. Créez un nouveau fichier nommé « tracing.mjs » :
touch tracing.mjs
  1. Dans votre éditeur de texte préféré, ouvrez tracing.mjs et ajoutez le script suivant pour lancer le SDK 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. Remarque : ce script est exactement le même que celui du service messenger, à ceci près que la valeur du champ SemanticResourceAttributes.SERVICE_NAME est notifier.
  2. Démarrez le service notifier avec l’auto-instrumentation OTel :
node --import ./tracing.mjs index.mjs
  1. Attendez environ dix secondes, puis, dans le terminal client, envoyez une requête de vérification de l’état au service notifier. Ce service écoute sur le port 5000 afin d’éviter tout conflit avec le service messenger qui écoute sur le port 4000 :
curl http://localhost:5000/health
  1. Remarque : Laissez les terminaux du client et du notificateur ouverts afin de pouvoir les réutiliser dans le défi 3.
  2. Vérifiez qu’un nouveau service appelé « notifier » apparaît dans l’interface utilisateur de Jaeger dans le navigateur :
    Capture d’écran de l’interface graphique de Jaeger montrant notifier dans la liste des services disponibles pour une inspection approfondie des spans

Configurer l’instrumentation OTel de NGINX

Pour NGINX, vous configurez le traçage manuellement au lieu d’utiliser la méthode d’auto-instrumentation d’OTel. Actuellement, la méthode la plus courante pour instrumenter NGINX avec OTel consiste à utiliser un module écrit en C. Les modules tiers jouent un rôle important dans l’écosystème NGINX, mais leur configuration demande un certain travail. Ce tutoriel s’en charge pour vous. Pour plus d’informations, consultez l’article Compilation de modules dynamiques tiers pour NGINX et NGINX Plus sur notre blog.

  1. Ouvrez une nouvelle session de terminal (le terminal NGINX), placez-vous dans le répertoire racine du dépôt messenger et créez un nouveau répertoire nommé load-balancer, ainsi que de nouveaux fichiers nommés Dockerfile, nginx.conf et opentelemetry_module.conf :
cd ~/microservices-march/messenger/mkdir load-balancer cd load-balancer touch Dockerfile touch nginx.conf touch opentelemetry_module.conf
  1. Dans votre éditeur de texte préféré, ouvrez Dockerfile et ajoutez ce qui suit (les commentaires expliquent le rôle de chaque ligne, mais vous pouvez créer et exécuter le conteneur Docker sans tout comprendre) :
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. Ouvrez « nginx.conf » et ajoutez ce qui suit :
events {}
http {
    include /etc/nginx/conf.d/opentelemetry_module.conf;

    upstream messenger {
        server localhost:4000;
    }

    server {
        listen 8085;

        location / {
            proxy_pass http://messenger;
        }
    }
}
  1. Ce fichier de configuration NGINX extrêmement basique indique à NGINX de : Remarque : cette configuration est assez proche de celle réellement utilisée pour faire de NGINX un proxy inverse et un répartiteur de charge dans un environnement de production. La seule différence majeure est que l’argument de la directive server dans le bloc upstream est généralement un nom de domaine ou une adresse IP plutôt que localhost.
    • Configurez un groupe en amont appelé « messenger », qui représente le groupe d’instances du service messenger service instances
    • Écouter les requêtes HTTP sur le port 8085
    • Transférer toutes les requêtes entrantes dont le chemin commence par / (c’est-à-dire toutes les requêtes entrantes) vers le serveur en amont messenger upstream
  2. Ouvrez « opentelemetry_module.conf » et ajoutez ce qui suit :
NginxModuleEnabled ON;NginxModuleOtelSpanExporter otlp;
NginxModuleOtelExporterEndpoint localhost:4317;
NginxModuleServiceName messenger-lb;
NginxModuleServiceNamespace MicroservicesMarchDemoArchitecture;
NginxModuleServiceInstanceId DemoInstanceId;
NginxModuleResolveBackends ON;
NginxModuleTraceAsError ON;
  1. Créez une image Docker contenant NGINX ainsi que le module OTel de NGINX :
docker build -t messenger-lb .
  1. Démarrez le conteneur Docker pour le proxy inverse et l’équilibreur de charge NGINX :
docker run --rm --name messenger-lb -p 8085:8085 --network="host" messenger-lb
  1. Dans le terminal client, envoyez une requête de vérification de l’état au service messenger via le proxy inverse et l’équilibreur de charge NGINX (il n’est pas nécessaire d’attendre avant d’envoyer cette requête) :
curl http://localhost:8085/health
  1. Remarque : Laissez les terminaux NGINX et client ouverts afin de les réutiliser dans le défi 3.
  2. Dans le navigateur, vérifiez que le nouveau service « messenger-lb » figure dans l’interface utilisateur de Jaeger avec les services que vous avez démarrés précédemment. Vous devrez peut-être actualiser l’interface utilisateur de Jaeger dans votre navigateur.
    Capture d’écran de l’interface graphique de Jaeger affichant la liste des services disponibles pour une inspection approfondie des spans, incluant désormais messenger-lb

Défi 3 : apprendre à lire les traces OTel

Dans « Architecture et parcours utilisateur », nous avons présenté les différentes étapes du parcours utilisateur. Pour rappel :

  1. Un utilisateur entame une conversation en envoyant un message à un autre utilisateur.
  2. Le proxy inverse NGINX intercepte le message et le transmet au service messenger.
  3. Le service messenger enregistre le message dans sa base de données, puis envoie un événement via RabbitMQ.
  4. Le service notifier consomme cet événement, consulte les préférences de notification du destinataire (le deuxième utilisateur), puis lui envoie une notification selon la méthode qu’il privilégie.

Les objectifs de la mise en œuvre de la télémétrie sont les suivants :

  1. Comprendre toutes les étapes par lesquelles passe une requête pour mener à bien le nouveau flux de messages.
  2. Avoir la certitude que le flux s’exécute de bout en bout en moins de cinq secondes dans des conditions normales.
  3. Observez le temps nécessaire au service notifier pour commencer à traiter l’événement envoyé par le service messenger.

In this challenge, you learn how to evaluate whether the traces generated by the OTel instrumentation satisfy the aforementioned goals. First, you take the system for a spin and create some traces. You then inspect the trace for a message flow and the sections of it generated by NGINX, the messenger service, and the notifier service.

Créer des données de trace

Dans le terminal client, créez une conversation et échangez quelques messages entre deux utilisateurs :

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'

Une sortie semblable à la suivante est générée par le service notifier et s’affiche dans son terminal :

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

Préparez-vous à lire les traces

Ouvrez l’interface utilisateur de Jaeger dans le navigateur, sélectionnez « messenger-lb » dans le menu déroulant « Service », puis cliquez sur le bouton « Find Traces ». Une liste de traces s’affiche, en commençant par le tout début du flux. Cliquez sur une trace pour en afficher les détails, comme dans cette capture d’écran :

Capture d’écran de l’interface graphique de Jaeger montrant l’ensemble des segments du flux

Cliquez sur les différents éléments et explorez un peu. Puis, avant de poursuivre, réfléchissez à la manière dont les informations contenues dans les traces contribuent à vos objectifs d’instrumentation, tels qu’ils sont présentés dans l’introduction du défi 3. Voici quelques questions pertinentes :

  • Quelles informations permettent d’atteindre les objectifs ?
  • Quelles informations manquent ?
  • Quelles informations ne sont pas pertinentes ?

Examinez la section NGINX (messenger-lb) de la trace

Objectif 1 : voir toutes les étapes par lesquelles passe une requête dans le nouveau flux de messages

Commencez par le segment NGINX, qui comporte 11 segments enfants au sein du segment parent. La configuration NGINX actuelle étant très simple, les segments enfants ne sont pas particulièrement intéressants et indiquent simplement le temps nécessaire à chaque étape du cycle de traitement des requêtes par NGINX. Toutefois, le segment parent (le tout premier) fournit quelques informations intéressantes :

Capture d’écran de l’interface graphique de Jaeger montrant le span parent dans la section NGINX (messenger-lb) de la trace

  • Sous Tags figurent les attributs suivants : Pris ensemble, ces trois éléments indiquent qu’« une requête POST a été envoyée à /conversations/1/messages et que la réponse était 201 (création réussie) ». Cela correspond aux étapes 1 et 4a de la section Architecture et flux utilisateur.
    • http.method champ – POST (en termes REST, cela signifie une création)
    • http.status_code champ – 201 (indiquant que la création a réussi)
    • http.target champ – conversations/1/messages (le point de terminaison des messages)
  • Sous « Process », le champ webengine.name indique qu’il s’agit de la partie NGINX de la requête.

De plus, comme les segments de messenger et notifier sont imbriqués dans le segment messenger-lb conversations/1 (comme le montre la capture d’écran dans Préparez-vous à lire les traces), vous pouvez en déduire que la requête envoyée au service messenger via le proxy inverse NGINX a bien atteint tous les composants attendus du flux.

Ces informations répondent à l’objectif, car elles permettent de constater que le proxy inverse NGINX faisait partie du flux.

Objectif 2 : vérifier que le flux s’exécute en moins de cinq secondes

Dans la liste des segments portant le libellé « messenger-lb », examinez le segment le plus récent (il se trouve en bas de la liste) pour connaître la durée de la partie de la requête traitée par NGINX. Dans la capture d’écran, le segment a commencé à 589 microsecondes (µs) et a duré 24 µs, ce qui signifie que l’opération complète de proxy inverse n’a pris que 613 µs, soit environ 0,6 milliseconde (ms). (Les valeurs exactes seront bien entendu différentes lorsque vous suivrez vous-même le tutoriel.)

Capture d’écran de l’interface graphique de Jaeger montrant les segments de la section NGINX (messenger-lb) de la trace

Dans une configuration de ce type, les valeurs ne sont généralement utiles que lorsqu’elles sont comparées à d’autres mesures, et elles varient d’un système à l’autre. Dans ce cas, toutefois, cette opération est clairement loin de prendre cinq secondes.

Ces informations répondent à l’objectif, car vous pouvez constater que les opérations NGINX ont pris bien moins de cinq secondes. Si une opération très lente se produit dans le flux, elle intervient nécessairement plus tard.

Objectif 3 : déterminer le temps nécessaire au service notifier pour lire l’événement envoyé par le service messenger

La couche de proxy inverse NGINX ne contient aucune information à ce sujet. Vous pouvez donc passer aux spans du service messenger.

Examiner la section messenger de la trace

Objectif 1 : voir toutes les étapes par lesquelles passe une requête dans le nouveau flux de messages

La section du service messenger dans la trace contient 11 spans supplémentaires. Là encore, la plupart des spans enfants concernent les étapes de base suivies par le framework Express lors du traitement d’une requête et ne présentent guère d’intérêt. Cependant, le span parent (le tout premier) fournit à nouveau quelques informations intéressantes :

Capture d’écran de l’interface graphique de Jaeger montrant le span parent dans la section « messenger » de la trace

Sous « Tags », vous voyez les attributs suivants :

  • http.method champ – POST (là encore, en termes REST, cela signifie une création)
  • http.route champ – /conversations/:conversationId/messages (la route des messages)
  • http.target champ – /conversations/1/messages (le point de terminaison des messages)

Ces informations répondent à l’objectif, car elles nous montrent que le service messenger faisait partie du flux et que le point de terminaison appelé était celui des nouveaux messages.

Objectif 2 : vérifier que le flux s’exécute en moins de cinq secondes

Comme l’indique la capture d’écran suivante, la partie messenger de la trace a commencé à 1,28 ms et s’est terminée à 36,28 ms, soit une durée totale de 35 ms. La majeure partie de ce temps a été consacrée à l’analyse du JSON (middleware - jsonParser) et, dans une bien plus large mesure, à la connexion à la base de données (pg-pool.connect et tcp.connect).

Cela paraît logique, étant donné que plusieurs requêtes SQL sont également exécutées lors de l’écriture du message. Cela suggère donc que vous pourriez enrichir la configuration de l’instrumentation automatique afin de mesurer la durée de ces requêtes. (Le tutoriel ne présente pas cette instrumentation supplémentaire, mais dans le défi 4, vous créez manuellement des spans qui peuvent ensuite servir à encapsuler les requêtes de base de données.)

Capture d’écran de l’interface graphique de Jaeger montrant les segments de la section Messenger de la trace et leur durée

Ces informations répondent à l’objectif, car elles montrent que les opérations du messenger sont loin de prendre cinq secondes. Si une opération très lente se produit dans le flux, elle intervient nécessairement plus tard.

Objectif 3 : déterminer le temps nécessaire au service notifier pour lire l’événement envoyé par le service messenger

Comme les spans NGINX, les spans du service messenger ne contiennent pas ces informations. Vous pouvez donc passer aux spans du service notifier.

Examiner la section notifier de la trace

Objectif 1 : voir toutes les étapes suivies par une requête dans le nouveau flux de messages

La section notifier de la trace ne contient que deux spans :

Capture d’écran de l’interface graphique de Jaeger montrant les deux spans dans la section notifier de la trace

  • Le span chat_queue process – Confirme que le service notifier a traité un événement provenant de la file de messages chat_queue message queue
  • Le span pg-pool.connect indique qu’après avoir traité l’événement, le service notifier a établi une connexion quelconque à sa base de données

Les informations fournies par ces spans ne permettent que partiellement de comprendre chaque étape. Vous pouvez constater que le service notifier est parvenu jusqu’à la consommation de l’événement dans la file d’attente, mais vous ne savez pas si :

  • La notification de message envoyée par ce service correspond à l’événement émis par le service messenger service
  • Les notifications pertinentes concernant le message ont bien été envoyées à son destinataire

Cela signifie que vous devez effectuer les opérations suivantes pour bien comprendre le fonctionnement du service notifier :

  • Instrumentez manuellement les spans qui indiquent l’envoi d’une notification
  • Assurez-vous qu’il existe un lien explicite entre l’événement envoyé par le service messenger et l’événement consommé par le service notifier, sous la forme d’un ID de trace

Objectif 2 : vérifier que le flux s’exécute en moins de cinq secondes

En examinant la durée globale des segments du service notifier, vous constatez que la requête a passé 30,77 ms dans la section notifier du flux. Toutefois, comme aucun segment ne signale la « fin » de l’ensemble du flux (l’envoi des notifications au destinataire), vous ne pouvez déterminer ni la durée totale de cette section du flux ni le temps nécessaire à l’exécution complète de l’opération.

Objectif 3 : déterminer le temps nécessaire au service notifier pour lire l’événement envoyé par le service messenger

Vous pouvez toutefois constater qu’un span chat_queue de type process pour le service notifier a commencé à 6,12 ms, soit 2 ms après le début d’un span chat_queue de type send pour le service messenger à 4,12 ms.

Capture d’écran de l’interface graphique de Jaeger montrant le service notifier consommant un événement envoyé par le service messenger

Cet objectif est atteint, car vous savez que le service notifier a consommé un événement 2 ms après son envoi par le service messenger. Contrairement à l’objectif 2, il n’est pas nécessaire de savoir si l’événement a été entièrement traité ni combien de temps ce traitement a pris pour atteindre cet objectif.

Conclusions

D’après notre analyse des traces produites par l’auto-instrumentation OTel actuelle, il apparaît clairement que :

  • Bon nombre de ces spans ne sont pas utiles dans leur forme actuelle :
    • NGINX produit des spans liés à des fonctions – telles que la vérification des autorisations et la mise à disposition de fichiers – qui ne sont pas pertinentes pour le rôle qui vous intéresse, à savoir le proxy inverse. Toutefois, à ce stade, l’instrumentation OTel pour NGINX ne permet pas d’omettre les spans non pertinents ; il n’est donc pas possible d’y remédier.
    • Parmi les spans des services Node.js (les services messenger et notifier), certains semblent pertinents au regard des objectifs : les spans correspondant à l’analyse du JSON, au request handler et à toutes les opérations de base de données. Certains spans des middlewares (comme expressInit et corsMiddleware) ne semblent pas pertinents et peuvent être supprimés.
  • Des spans critiques sont manquants pour les éléments suivants :
    • Notifications envoyées par le service « notifier »
    • Une correspondance claire entre l’événement RabbitMQ envoyé par le service messenger et celui traité par le service notifier service

Cela signifie que l’instrumentation de base remplit le dernier objectif :

  • Mesurer le temps nécessaire au service notifier pour commencer à traiter l’événement envoyé par le service messenger service

Cependant, les informations disponibles ne sont pas suffisantes pour atteindre les deux premiers objectifs :

  • Comprendre toutes les étapes par lesquelles passe une requête au cours du nouveau flux de messages
  • Avoir la certitude que, dans des conditions normales, le flux s’exécute de bout en bout en moins de cinq secondes

Défi 4 : optimiser l’instrumentation à partir des données de traçage

Dans ce défi, vous optimisez l’instrumentation OTel en vous appuyant sur les analyses de traces que vous avez effectuées dans le défi 3. Cela consiste à la fois à supprimer les spans inutiles, à créer de nouveaux spans personnalisés et à confirmer que l’événement consommé par le service de notification est bien celui généré par le service de messagerie.

Supprimer les spans inutiles

  1. Dans votre éditeur de texte préféré, ouvrez le fichier tracing.mjs situé dans le répertoire app du dépôt messenger, puis ajoutez ce qui suit à la fin de la liste des instructions d’importation en haut du fichier :
const IGNORED_EXPRESS_SPANS = new Set([  "middleware - expressInit",
  "middleware - corsMiddleware",
]);
  1. Ceci définit un ensemble de noms de spans, établi à partir de la liste des spans affichés dans la capture d’écran suivante de l’interface utilisateur de Jaeger, qui doivent être omis de la trace, car ils ne fournissent aucune information utile pour ce flux. Vous pouvez estimer que d’autres spans figurant dans la capture d’écran ne sont pas non plus nécessaires et les ajouter à la liste IGNORED_EXPRESS_SPANS.
    Capture d’écran de l’interface graphique de Jaeger affichant une liste de plusieurs spans du service de messagerie susceptibles de fournir des informations pertinentes et pouvant donc être omis de la trace
  2. Ajoutez des filtres à la configuration de l’instrumentation automatique afin d’omettre les spans dont vous ne voulez pas, en modifiant la ligne surlignée en orange :
const sdk = new opentelemetry.NodeSDK({  resource,
  traceExporter: new OTLPTraceExporter({ headers: {} }),
  instrumentations: [getNodeAutoInstrumentations()],
});
  1. par ceci :
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 fonction getNodeAutoInstrumentations fait référence à l’ensemble des spans défini à l’étape 1 afin de les exclure de la trace générée par @opentelemetry/instrumentation-express. En d’autres termes, l’instruction return renvoie true pour un span appartenant à IGNORED_EXPRESS_SPANS, et l’instruction ignoreLayers supprime ce span de la trace.
  2. Dans le terminal de messenger, appuyez sur Ctrl+c pour arrêter le service messenger. Redémarrez-le ensuite :
^cnode --import ./tracing.mjs index.mjs
  1. Attendez environ dix secondes, puis envoyez un nouveau message dans le terminal client :
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. Vérifiez à nouveau les spans messenger dans l’interface utilisateur de Jaeger. Les deux spans middleware, expressInit et corsMiddleware, n’apparaissent plus (vous pouvez comparer avec la capture d’écran de l’objectif 2 de la section Examiner la section messenger de la trace du défi 3.
    Capture d’écran de l’interface graphique de Jaeger montrant que la trace n’inclut plus deux spans après la modification de l’instrumentation visant à les exclure de la trace

Configurer des spans personnalisés

Dans cette section, vous intervenez pour la première fois sur le code de l’application. L’instrumentation automatique génère une quantité considérable d’informations sans nécessiter de modifier l’application, mais certaines analyses ne sont possibles qu’en instrumentant des parties spécifiques de la logique métier.

Pour le nouveau flux de messages que vous instrumentez, vous pouvez par exemple tracer l’envoi de notifications au destinataire du message.

  1. Ouvrez index.mjs dans le répertoire app du dépôt notifier. Ce fichier contient toute la logique métier du service. Ajoutez la ligne suivante à la fin de la liste des instructions import située en haut du fichier :
import { trace } from "@opentelemetry/api";
  1. Remplacez ce code (vers la ligne 91 du fichier) :
for (let pref of preferences) {
  console.log(
    `Sending notification of new message via ${pref.address_type} to ${pref.address}`
  );
}
  1. avec :
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. Le nouveau code effectue l’opération suivante : la présence d’un span parent garantit que chaque opération « envoyer une notification » est signalée, même si aucune préférence de notification n’est trouvée pour l’utilisateur.
    1. Récupère le tracer, qui est un objet global permettant d’interagir avec les traces OTel.
    2. Démarre un nouveau span parent appelé « notification.send_all » et définit l’attribut « user_id » afin d’identifier l’expéditeur du message.
    3. Entre dans une boucle qui parcourt les préférences de notification du destinataire et crée une nouvelle étendue enfant appelée « notification.send » sous « notification.send_all ». Chaque notification génère une nouvelle étendue.
    4. Définit des attributs supplémentaires pour le span enfant :
      • notification_type – L’une des valeurs suivantes : sms ou email
      • user_id – L’identifiant de l’utilisateur qui reçoit la notification
    5. Ferme tour à tour chaque span enfant « notification.send ».
    6. Ferme le span parent « notification.send_all ».
  2. Dans le terminal du service de notification, appuyez sur Ctrl+c pour arrêter le service notifier. Redémarrez-le ensuite :
^cnode --import ./tracing.mjs index.mjs
  1. Attendez environ dix secondes, puis envoyez un nouveau message dans le terminal client :
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. Vérifiez à nouveau les spans du service notifier dans l’interface utilisateur de Jaeger. Vous voyez le span parent et deux spans enfants, chacun comportant une opération « send notification » :
    Capture d’écran de l’interface graphique de Jaeger montrant le résultat de la définition de trois nouveaux spans dans le code du service de notification

Vous pouvez désormais atteindre pleinement les deux premiers objectifs, car vous pouvez voir toutes les étapes par lesquelles passe une requête au cours du nouveau flux de messages. Les informations de durée de chaque span permettent de repérer tout délai entre ces étapes.

Vérifier que le service de messagerie et le service de notification traitent le même événement

Il vous reste un dernier point à vérifier pour avoir une vue d’ensemble complète du flux. L’événement traité par le service notifier est-il bien celui qui a été envoyé par le service messenger ?

Vous n’avez aucune modification explicite à apporter pour relier les deux traces, mais mieux vaut ne pas non plus faire aveuglément confiance à la magie de l’instrumentation automatique.

Dans cette optique, ajoutez rapidement du code de débogage pour vérifier que la trace qui commence dans le service NGINX est bien la même (avec le même ID de trace) que celle utilisée par le service notifier.

  1. Ouvrez le fichier « index.mjs » dans le répertoire « app » du dépôt « messenger » et apportez les modifications suivantes :
    • Ajoutez la ligne suivante à la fin de la liste des instructions d’importation située en haut du fichier :
import { trace } from "@opentelemetry/api";
  • Ajoutez les lignes surlignées en orange sous la ligne existante en noir :
async function createMessageInConversation(req, res) {
  const tracer = trace.getActiveSpan(); console.log("TRACE_ID: ", tracer.spanContext().traceId);
  • Les nouvelles lignes affichent le TRACE_ID depuis la fonction de messenger qui gère la création d’un nouveau message.
  1. Ouvrez le fichier « index.mjs » dans le répertoire « app » du dépôt « notifier » et ajoutez la ligne surlignée en orange sous la ligne existante en noir :
export async function handleMessageConsume(channel, msg, handlers) {  console.log("RABBIT_MQ_MESSAGE: ", msg);
  1. La nouvelle ligne affiche l’intégralité du contenu de l’événement AMQP reçu par le service notifier.
  2. Arrêtez et redémarrez les services messenger et notifier en exécutant ces commandes dans les terminaux de messenger et de notifier :
^cnode --import ./tracing.mjs index.mjs
  1. Attendez environ dix secondes, puis envoyez à nouveau un message dans le terminal client :
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. Consultez les journaux des services messenger et notifier. Le journal du service messenger contient une ligne comme celle-ci, qui indique l’identifiant de trace d’un message (l’identifiant réel sera différent lorsque vous suivrez le tutoriel) :
TRACE_ID:  29377a9b546c50be629c8e64409bbfb5
  1. De même, le journal du service notifier indique un ID de trace dans une sortie comme celle-ci :
_spanContext: {
  traceId: '29377a9b546c50be629c8e64409bbfb5',
  spanId: 'a94e9462a39e6dbf',
  traceFlags: 1,
  traceState: undefined
},
  1. Les identifiants de trace correspondent dans la console, mais pour terminer, vous pouvez les comparer à l’identifiant de trace affiché dans l’interface utilisateur de Jaeger. Ouvrez l’interface à l’URL correspondant à l’identifiant de trace concerné (le vôtre sera différent, mais dans cet exemple, il s’agit de http://localhost:16686/trace/29377a9b546c50be629c8e64409bbfb5) pour afficher la trace complète. La trace Jaeger confirme que :
    • L’auto-instrumentation AMQP du service messenger ajoute cet identifiant de trace aux métadonnées lors de l’envoi de l’événement.
    • L’auto-instrumentation AMQP du service notifier attend ces métadonnées et définit le contexte de trace de manière appropriée.

Remarque : Dans un véritable système de production, vous supprimeriez le code ajouté dans cette section après avoir vérifié que le flux fonctionne comme prévu.

Nettoyage des ressources

Vous avez créé plusieurs conteneurs et images au cours de ce tutoriel ! Suivez ces instructions pour les supprimer.

  • Pour supprimer tous les conteneurs Docker en cours d’exécution :
docker rm $(docker stop messenger-lb)
  • Pour supprimer le service « platform » ainsi que les services de base de données « messenger » et « notifier » :
cd ~/microservices-march/platform && docker compose down cd ~/microservices-march/notifier && docker compose down cd ~/microservices-march/messenger && docker compose down

Étapes suivantes

Félicitations, vous avez terminé le tutoriel !

  • Vous avez configuré l’instrumentation OTel sur un proxy inverse NGINX et deux services Node.js.
  • Vous avez examiné d’un œil critique les données fournies par l’auto-instrumentation OTel et ajouté certaines données de télémétrie manquantes afin d’atteindre les objectifs du TP OTel :
    • Vous avez obtenu une vue suffisamment précise du cheminement d’une requête spécifique dans un système de messagerie, sans modifier directement le code de l’application.
    • Vous avez confirmé que le flux s’exécutait de bout en bout en moins de cinq secondes dans des conditions normales.

Et pourtant, vous avez à peine effleuré les possibilités qu’offre une configuration de traçage idéale ! Dans un environnement de production, vous pourriez notamment ajouter des spans personnalisés pour chaque requête de base de données, ainsi que des métadonnées supplémentaires à tous les spans afin de décrire des informations d’exécution telles que l’ID du conteneur de chaque service. Vous pourriez également mettre en œuvre les deux autres types de données OTel (métriques et journaux) afin d’obtenir une vue d’ensemble de l’état de santé de votre système.

Pour approfondir vos connaissances sur les microservices, consultez Microservices March 2023. Dans l’Unité 4 : Gérer le chaos et la complexité des microservices grâce à l’observabilité, vous découvrirez les trois principales catégories de données d’observabilité, l’importance de l’alignement entre l’infrastructure et les applications, ainsi que différentes façons de commencer à analyser des données approfondies.


Partager

Articles de blog associés

Tendances actuelles des technologies cloud natives, de l’ingénierie de plateforme et de l’IA
NGINX | 13/10/2025

Tendances actuelles des technologies cloud natives, de l’ingénierie de plateforme et de l’IA

Découvrez les principaux enseignements tirés des réponses à l’enquête annuelle F5 NGINX sur les dernières tendances en matière de technologies cloud natives, de sécurité, d’ingénierie de plateforme et d’IA.

Quels contrôleurs Ingress NGINX sont affectés par les vulnérabilités CVE-2022-4886, CVE-2023-5043 et CVE-2023-5044 ?
NGINX | 03/11/2023

Quels contrôleurs Ingress NGINX sont affectés par les vulnérabilités CVE-2022-4886, CVE-2023-5043 et CVE-2023-5044 ?

Lorsque de nouvelles CVE sont signalées, il est facile de ne plus savoir si elles concernent un outil de contrôleur Ingress NGINX particulier, car il existe plusieurs projets. Cet article présente les CVE et explique comment distinguer les différents projets de contrôleurs Ingress afin de dissiper toute confusion.

Votre API est-elle en temps réel ? Testez sa latence avec l’outil rtapi de NGINX
NGINX | 11/03/2020

Votre API est-elle en temps réel ? Testez sa latence avec l’outil rtapi de NGINX

Votre API répond-elle aux requêtes en moins de 30 ms ? Si ce n’est pas le cas, il ne s’agit pas d’une API en temps réel offrant la faible latence exigée par la grande majorité des organisations. Mesurez la latence de votre API avec l’outil rtapi de NGINX et découvrez comment NGINX peut l’aider à offrir une réactivité en temps réel.

Un testeur d’expressions régulières pour NGINX et NGINX Plus
NGINX | 11/06/2019

Un testeur d’expressions régulières pour NGINX et NGINX Plus

Le testeur d’expressions régulières (regex) pour NGINX et NGINX Plus vous évite de tâtonner en vous indiquant si une expression régulière utilisée dans un bloc location ou map correspond aux valeurs comme prévu. Il génère la configuration appropriée et affiche les valeurs des groupes de capture.

Caches partagés avec les clusters de cache NGINX Plus, partie 1
NGINX | 24/01/2017

Caches partagés avec les clusters de cache NGINX Plus, partie 1

Optimisez la capacité de votre cache web en le répartissant entre plusieurs serveurs de cache web NGINX ou NGINX Plus. Découvrez comment procéder dans cet article.

Introduction aux microservices
NGINX | 19/05/2015

Introduction aux microservices

Les microservices suscitent actuellement beaucoup d’intérêt. Cet article de blog est le premier d’une série de sept consacrée à la conception, au développement et au déploiement de microservices.