Dieser Beitrag ist eines von vier Tutorials, die Ihnen dabei helfen, die Konzepte aus „Microservices March 2023: Erste Schritte bei der Bereitstellung von Microservices“ in die Praxis umzusetzen:
- So stellen Sie Microservices bereit und konfigurieren sie
- So verwalten Sie Geheimnisse in Containern sicher
- So automatisieren Sie Canary-Bereitstellungen von Microservices mit GitHub Actions
- So nutzen Sie OpenTelemetry-Tracing, um Ihre Microservices besser zu verstehen (dieser Beitrag)
Eine Microservices-Architektur bietet viele Vorteile, darunter eine größere Autonomie der Teams sowie mehr Flexibilität bei Skalierung und Bereitstellung. Andererseits wird es mit zunehmender Anzahl von Diensten in einem System – und eine Microservices-Anwendung kann Dutzende oder sogar Hunderte davon umfassen – immer schwieriger, einen klaren Überblick über den Gesamtbetrieb des Systems zu behalten. Als Entwickler und Betreiber komplexer Softwaresysteme wissen wir, wie wichtig ein solcher Überblick ist. Observability-Tools ermöglichen es uns, diesen Überblick über zahlreiche Dienste und die unterstützende Infrastruktur hinweg zu gewinnen.
In diesem Tutorial beleuchten wir eine besonders wichtige Form der Observability für Microservices-Anwendungen: Tracing. Bevor wir beginnen, definieren wir einige Begriffe, die im Zusammenhang mit Observability häufig verwendet werden:
- Observability – Die Fähigkeit, den internen Zustand oder die Verfassung eines komplexen Systems (z. B. einer Microservices-Anwendung) allein anhand seiner externen Ausgaben (z. B. Traces, Protokolle und Metriken) zu verstehen.
- Monitoring – Die Fähigkeit, den Fortschritt oder Status eines Objekts über einen bestimmten Zeitraum hinweg zu beobachten und zu überprüfen. Beispielsweise können Sie den Datenverkehr zu Ihrer App während der Spitzenzeiten überwachen und diese Informationen nutzen, um die App entsprechend zu skalieren.
- Telemetrie – Das Erfassen von Metriken, Traces und Protokollen sowie deren Übertragung von ihrem Ursprungsort an ein anderes System zur Speicherung und Analyse. Der Begriff bezeichnet auch die Daten selbst.
- Tracing/Traces – Eine Aufzeichnung des Weges, den eine Anfrage oder Aktion durch alle Knoten eines verteilten Systems nimmt.
- Span – Ein Datensatz innerhalb eines Traces, der einen Vorgang und die zugehörigen Metadaten erfasst. Traces bestehen aus vielen verschachtelten Spans.
- Ereignisprotokollierung/Protokolle – Ein mit einem Zeitstempel versehener Texteintrag mit Metadaten.
- Metrik – Ein zur Laufzeit erfasster Messwert, beispielsweise die Speichermenge, die eine Anwendung zu einem bestimmten Zeitpunkt belegt.
Wir können all diese Konzepte nutzen, um Einblicke in die Performance unserer Microservices zu gewinnen. Tracing ist ein besonders nützlicher Bestandteil einer Observability-Strategie, da Traces einen umfassenden Überblick darüber bieten, was bei einer Anfrage über mehrere, häufig lose gekoppelte Komponenten hinweg geschieht. Zudem lassen sich damit Performance-Engpässe besonders effektiv identifizieren.
In diesem Tutorial wird das Tracing-Toolkit von OpenTelemetry (OTel) verwendet, einem herstellerneutralen Open-Source-Standard zur Erfassung, Verarbeitung und zum Export von Telemetriedaten, der sich rasch wachsender Beliebtheit erfreut. Nach dem Konzept von OTel unterteilt ein Trace einen Datenfluss, an dem mehrere Dienste beteiligt sein können, in eine Reihe chronologisch angeordneter „Abschnitte“, anhand derer Sie Folgendes leicht nachvollziehen können:
- Alle Schritte, die in einem „Abschnitt“ ausgeführt wurden
- Wie lange all diese Schritte gedauert haben
- Metadaten zu jedem Schritt
Wenn Sie mit OTel noch nicht vertraut sind, lesen Sie Was ist OpenTelemetry? Dort finden Sie eine ausführliche Einführung in den Standard sowie wichtige Hinweise zu seiner Implementierung.
Tutorialübersicht
Dieses Tutorial konzentriert sich auf das Tracing der Abläufe einer Microservices-Anwendung mit OTel. In den vier Aufgaben dieses Tutorials erfahren Sie, wie Sie eine Anfrage durch Ihr System nachverfolgen und Fragen zu Ihren Microservices beantworten können:
- Grundlegende OTel-Instrumentierung einrichten
- OTel-Instrumentierung und Trace-Visualisierung für alle Dienste einrichten
- Lernen Sie, OTel-Traces zu lesen
- Instrumentierung anhand der Trace-Auswertung optimieren
Diese Herausforderungen veranschaulichen unseren empfohlenen Prozess für die erstmalige Einrichtung von Tracing. Die einzelnen Schritte sind:
- Machen Sie sich sowohl mit dem System als auch mit dem konkreten Vorgang vertraut, den Sie instrumentieren.
- Legen Sie fest, was Sie über das laufende System wissen müssen.
- Instrumentieren Sie das System „naiv“ – verwenden Sie also eine Standardkonfiguration, ohne zu versuchen, nicht benötigte Informationen herauszufiltern oder benutzerdefinierte Datenpunkte zu erfassen – und prüfen Sie, ob die Instrumentierung Ihnen hilft, Ihre Fragen zu beantworten.
- Passen Sie die ausgegebenen Informationen so an, dass Sie diese Fragen schneller beantworten können.
Hinweis: In diesem Tutorial möchten wir einige grundlegende Konzepte der Telemetrie veranschaulichen und nicht zeigen, wie Microservices in einer Produktionsumgebung richtig bereitgestellt werden. Obwohl eine echte „Microservices“-Architektur verwendet wird, gibt es einige wichtige Einschränkungen:
- In diesem Tutorial wird kein Container-Orchestrierungs-Framework wie Kubernetes oder Nomad verwendet. So können Sie sich mit den Konzepten von Microservices vertraut machen, ohne sich in den Besonderheiten eines bestimmten Frameworks zu verlieren. Die hier vorgestellten Muster lassen sich auf ein System übertragen, auf dem eines dieser Frameworks ausgeführt wird.
- Die Dienste sind eher auf leichte Verständlichkeit als auf strenge Softwareentwicklungsprinzipien ausgelegt. Es geht darum, die Rolle eines Dienstes im System und seine Kommunikationsmuster zu betrachten, nicht die Einzelheiten des Codes. Weitere Informationen finden Sie in den README-Dateien der einzelnen Dienste.
Architektur des Tutorials und Ziele der Telemetrie
Architektur und Benutzerablauf
Dieses Diagramm veranschaulicht die Gesamtarchitektur und den Datenfluss zwischen den Microservices und anderen im Tutorial verwendeten Elementen.

Die beiden Microservices sind:
- Der Messenger-Dienst – eine einfache Chat-API mit Funktionen zum Speichern von Nachrichten
- Der Benachrichtigungsdienst – Ein Listener, der basierend auf den Einstellungen der Benutzer Ereignisse auslöst, um sie zu benachrichtigen
Die unterstützende Infrastruktur besteht aus drei Komponenten:
- NGINX Open Source – Ein Einstiegspunkt für den messenger-Dienst und das Gesamtsystem
- RabbitMQ – Ein beliebter Open-Source-Message-Broker, der Diensten die asynchrone Kommunikation ermöglicht
- Jaeger – Ein quelloffenes End-to-End-System für verteiltes Tracing, das Telemetriedaten von den Systemkomponenten erfasst und visualisiert, die diese erzeugen
Wenn wir OTel für einen Moment außer Acht lassen, können wir uns auf die Abfolge der Ereignisse konzentrieren, die wir nachverfolgen: Was passiert, wenn ein Benutzer eine neue Chatnachricht sendet und der Empfänger darüber benachrichtigt wird?

Der Ablauf gliedert sich wie folgt:
- Ein Benutzer sendet eine Nachricht an den Dienst „messenger“. Der NGINX-Reverse-Proxy fängt die Nachricht ab und leitet sie an eine von vielen Instanzen des Dienstes „messenger“ weiter.
- Der Messenger-Dienst schreibt die neue Nachricht in seine Datenbank.
- Der messenger-Dienst erzeugt ein Ereignis in einer RabbitMQ-Nachrichtenwarteschlange namens chat_queue, um anzuzeigen, dass eine Nachricht gesendet wurde. Das Ereignis ist generisch und hat kein bestimmtes Ziel.
- Gleichzeitig:
- 4a. Der Messenger-Dienst sendet eine Antwort an den Absender zurück, die bestätigt, dass die Nachricht erfolgreich gesendet wurde.
- 4b. Der notifier-Dienst erkennt das neue Ereignis in der chat_queue und verarbeitet es.
- Der Benachrichtigungsdienst prüft in seiner Datenbank die Benachrichtigungseinstellungen des Empfängers der neuen Nachricht.
- Der notifier-Dienst verwendet die vom Empfänger bevorzugte Methode, um eine oder mehrere Benachrichtigungen zu senden (in diesem Tutorial stehen SMS und E-Mail zur Auswahl).
Telemetrieziele
Beim Einrichten der Telemetrieinstrumentierung empfiehlt es sich, zunächst konkretere Ziele festzulegen, als einfach „alles zu senden und auf Erkenntnisse zu hoffen“. Für dieses Tutorial verfolgen wir drei zentrale Telemetrieziele:
- Alle Schritte nachvollziehen, die eine Anfrage beim Ablauf für neue Nachrichten durchläuft
- Darauf vertrauen können, dass der Ablauf unter normalen Bedingungen innerhalb von fünf Sekunden vollständig ausgeführt wird
- Prüfen Sie, wie lange es dauert, bis der notifier-Dienst mit der Verarbeitung des vom messenger-Dienst gesendeten Ereignisses beginnt (eine übermäßige Verzögerung könnte darauf hindeuten, dass der notifier-Dienst Probleme beim Lesen aus der Ereigniswarteschlange hat und sich Ereignisse aufstauen)
Beachten Sie, dass sich diese Ziele sowohl auf den technischen Betrieb des Systems als auch auf die Benutzererfahrung beziehen.
Voraussetzungen und Einrichtung für das Tutorial
Voraussetzungen
Um das Tutorial in Ihrer eigenen Umgebung durchzuführen, benötigen Sie:
- Eine Linux-/Unix-kompatible Umgebung Hinweis: Die Aktivitäten in diesem Tutorial, bei denen NGINX-Traces erfasst werden, funktionieren nicht auf ARM-basierten Prozessoren, da das OpenTelemetry-Modul für NGINX nicht kompatibel ist. (Dies betrifft Linux-Systeme mit aarch64-Architektur sowie Apple-Computer mit M1- oder M2-Chip.) Die Aktivitäten mit den Diensten messenger und notifier funktionieren auf allen Architekturen.
- Grundkenntnisse der Linux-Befehlszeile, JavaScript und
bash(der gesamte Code und alle Befehle werden jedoch bereitgestellt und erklärt, sodass Sie auch mit geringen Vorkenntnissen erfolgreich sein können) - Docker und Docker Compose
- Node.js 19.x oder höher
- Wir haben Version 19.x getestet, gehen aber davon aus, dass auch neuere Versionen von Node.js funktionieren.
- Ausführliche Informationen zur Installation von Node.js finden Sie in der README-Datei im Repository des messenger-Dienstes. Sie können auch
asdfinstallieren, um genau dieselbe Node.js-Version zu verwenden, die im Tutorial verwendet wird.
curl(auf den meisten Systemen bereits installiert)- Die unter Architektur und Benutzerablauf aufgeführten Technologien: messenger und notifier (die Sie im nächsten Abschnitt herunterladen werden), NGINX Open Source, Jaeger und RabbitMQ.
Hinweis: In diesem Tutorial wird das JavaScript SDK verwendet, da die Dienste messenger und notifier in Node.js geschrieben sind. Außerdem richten Sie die OTel-Funktion zur automatischen Instrumentierung (auch Auto-Instrumentierung genannt) ein, damit Sie einen Eindruck davon bekommen, welche Informationen OTel bereitstellt. Das Tutorial erklärt alles, was Sie über das OTel Node.js SDK wissen müssen. Weitere Einzelheiten finden Sie in der OTel-Dokumentation.
Einrichtung
- Starten Sie eine Terminalsitzung.
- Erstellen Sie in Ihrem Home-Verzeichnis das Verzeichnis „microservices-march“ und klonen Sie die GitHub-Repositorys für dieses Tutorial dorthin. (Sie können auch einen anderen Verzeichnisnamen verwenden und die Anweisungen entsprechend anpassen.) Hinweis: In diesem Tutorial wird die Eingabeaufforderung der Linux-Befehlszeile weggelassen, damit Sie die Befehle leichter kopieren und in Ihr Terminal einfügen können. Die Tilde (
~) steht für Ihr Home-Verzeichnis.
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-startAufgabe 1: Grundlegende OTel-Instrumentierung einrichten
In dieser Aufgabe starten Sie den messenger-Dienst und konfigurieren die automatische OTel-Instrumentierung, um Telemetriedaten an die Konsole zu senden.
Messenger-Dienst starten
- Wechseln Sie zum platform-Repository und starten Sie Docker Compose:
cd ~/microservices-march/platformdocker compose up -d --build- Dadurch werden RabbitMQ und Jaeger gestartet, die in den folgenden Aufgaben verwendet werden.
- Das Flag „
‑d“ weist Docker Compose an, die Verbindung zu den Containern zu trennen, sobald sie gestartet wurden (andernfalls bleiben die Container mit Ihrem Terminal verbunden). - Das
--build-Flag weist Docker Compose an, beim Start alle Images neu zu erstellen. Dadurch wird sichergestellt, dass die ausgeführten Images bei möglichen Änderungen an Dateien auf dem neuesten Stand bleiben.
- Das Flag „
- Wechseln Sie in das Verzeichnis app im Repository messenger und installieren Sie Node.js (Sie können auch eine andere Methode verwenden):
cd ~/microservices-march/messenger/appasdf install- Abhängigkeiten installieren:
npm install- Starten Sie die PostgreSQL-Datenbank für den messenger-Dienst:
docker compose up -d- Erstellen Sie das Datenbankschema und die Tabellen und fügen Sie einige Beispieldaten ein:
npm run refresh-dbAn die Konsole gesendete automatische OTel-Instrumentierung konfigurieren
Mit der automatischen OTel-Instrumentierung müssen Sie zum Einrichten des Tracings keine Änderungen an der messenger-Codebasis vornehmen. Statt direkt in den Anwendungscode importiert zu werden, wird die gesamte Tracing-Konfiguration in einem Skript definiert, das zur Laufzeit in den Node.js-Prozess importiert wird.
Hier konfigurieren Sie die automatische Instrumentierung des Dienstes messenger mit dem einfachsten Ziel für Traces: der Konsole. In Aufgabe 2 ändern Sie die Konfiguration so, dass Traces an Jaeger als externen Collector gesendet werden.
- Installieren Sie weiterhin im Verzeichnis app des messenger-Repositorys die zentralen OTel-Pakete für Node.js:
npm install @opentelemetry/sdk-node@0.36.0 \ @opentelemetry/auto-instrumentations-node@0.36.4- Diese Bibliotheken bieten die folgenden Funktionen: Hinweis: Eine Besonderheit von OTel besteht darin, dass seine JavaScript-SDKs in sehr, sehr kleine Komponenten aufgeteilt sind. Daher müssen Sie selbst für das einfache Beispiel in diesem Tutorial einige zusätzliche Pakete installieren. Um herauszufinden, welche Pakete Sie möglicherweise für Instrumentierungsaufgaben benötigen, die über die in diesem Tutorial behandelten hinausgehen, lesen Sie die (sehr guten) OTel-Leitfäden für den Einstieg und sehen Sie sich das OTel-GitHub-Repository an.
@opentelemetry/sdk-node– Generierung und Export von OTel-Daten@opentelemetry/auto-instrumentations-node– Automatische Einrichtung aller gängigen Node.js-Instrumentierungen mit der Standardkonfiguration
- Erstellen Sie eine neue Datei namens „tracing.mjs“, die den Einrichtungs- und Konfigurationscode für das OTel-Tracing enthält:
touch tracing.mjs- Öffnen Sie in Ihrem bevorzugten Texteditor die Datei „tracing.mjs“ und fügen Sie diesen Code hinzu:
//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();- Der Code führt Folgendes aus:
- Importiert die erforderlichen Funktionen und Objekte aus dem OTel SDK.
- Erstellt eine neue Instanz des NodeSDK und konfiguriert sie wie folgt:
- Spans an die Konsole senden (
ConsoleSpanExporter). - Verwenden Sie den Auto-Instrumentierer als grundlegenden Instrumentierungssatz. Diese Instrumentierung lädt die gängigsten Bibliotheken für die automatische Instrumentierung. Für dieses Tutorial sind folgende relevant:
@opentelemetry/instrumentation-pgfür die Postgres-Datenbankbibliothek (pg)@opentelemetry/instrumentation-expressfür das Node.js-Express-Framework@opentelemetry/instrumentation-amqplibfür die RabbitMQ-Bibliothek (amqplib)
- Spans an die Konsole senden (
- Startet das SDK.
- Starten Sie den Dienst messenger und importieren Sie dabei das in Schritt 3 erstellte Skript für die automatische Instrumentierung.
node --import ./tracing.mjs index.mjs- Nach kurzer Zeit werden in der Konsole (Ihrem Terminal) zahlreiche Ausgaben zum Tracing angezeigt:
...
{
traceId: '9c1801593a9d3b773e5cbd314a8ea89c',
parentId: undefined,
traceState: undefined,
name: 'fs statSync',
id: '2ddf082c1d609fbe',
kind: 0,
timestamp: 1676076410782000,
duration: 3,
attributes: {},
status: { code: 0 },
events: [],
links: []
}
...Hinweis: Lassen Sie die Terminalsitzung geöffnet, damit Sie sie in Aufgabe 2 erneut verwenden können.
Aufgabe 2: OTel-Instrumentierung und Trace-Visualisierung für alle Dienste einrichten
Es gibt viele Tools, mit denen Sie Traces anzeigen und analysieren können. In diesem Tutorial wird jedoch Jaeger verwendet. Jaeger ist ein einfaches, quelloffenes Framework für verteiltes End-to-End-Tracing mit einer integrierten webbasierten Benutzeroberfläche zur Anzeige von Spans und anderen Tracing-Daten. Die im Repository platform bereitgestellte Infrastruktur umfasst Jaeger (das Sie in Schritt 1 von Aufgabe 1 gestartet haben), sodass Sie sich auf die Datenanalyse konzentrieren können, anstatt sich mit komplexen Tools befassen zu müssen.
Jaeger ist in Ihrem Browser unter dem Endpunkt http://localhost:16686 erreichbar. Wenn Sie den Endpunkt jedoch jetzt aufrufen, sind dort noch keine Informationen zu Ihrem System zu sehen. Das liegt daran, dass die derzeit erfassten Traces an die Konsole gesendet werden! Um Trace-Daten in Jaeger anzuzeigen, müssen Sie die Traces im Format des OpenTelemetry-Protokolls (OTLP) exportieren.
In dieser Aufgabe instrumentieren Sie den zentralen Benutzerablauf, indem Sie die Instrumentierung für Folgendes konfigurieren:
- Der messenger-Dienst, wobei das Ziel für Traces von der Konsole auf Jaeger umgestellt wird
- Der notifier-Dienst
- NGINX
An einen externen Collector gesendete automatische OTel-Instrumentierung konfigurieren
Zur Erinnerung: Wenn Sie die automatische OTel-Instrumentierung verwenden, müssen Sie zum Einrichten des Tracings keine Änderungen an der Codebasis von messenger vornehmen. Stattdessen befindet sich die gesamte Tracing-Konfiguration in einem Skript, das zur Laufzeit in den Node.js-Prozess importiert wird. Hier ändern Sie das Ziel für die vom Dienst messenger erzeugten Traces von der Konsole zu einem externen Collector (in diesem Tutorial Jaeger).
- Arbeiten Sie weiterhin im selben Terminal wie in Aufgabe 1 und installieren Sie im Verzeichnis app des messenger-Repositorys das Node.js-Paket für den OTLP-Exporter:
npm install @opentelemetry/exporter-trace-otlp-http@0.36.0- Die Bibliothek
@opentelemetry/exporter-trace-otlp-httpexportiert Trace-Informationen im OTLP-Format über HTTP. Sie wird verwendet, wenn Telemetriedaten an einen externen OTel-Collector gesendet werden. - Öffnen Sie tracing.mjs (die Datei, die Sie in Aufgabe 1 erstellt und bearbeitet haben), und nehmen Sie die folgenden Änderungen vor: Hinweis: Der Einfachheit halber geht dieses Tutorial davon aus, dass der Collector unter der Standardadresse http://localhost:4318/v1/traces erreichbar ist. In einem realen System empfiehlt es sich, die Adresse explizit festzulegen.
- Fügen Sie diese Zeile zu den
import-Anweisungen am Anfang der Datei hinzu:
- Fügen Sie diese Zeile zu den
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";- Ändern Sie den „Exporter“, den Sie dem OTel SDK bereitstellen, vom in Aufgabe 1 verwendeten Konsolen-Exporter zu einem Exporter, der OTLP-Daten über HTTP an einen OTLP-kompatiblen Collector senden kann. Ersetzen Sie:
traceExporter:new opentelemetry.tracing.ConsoleSpanExporter(),- mit:
traceExporter: new OTLPTraceExporter({ headers: {} }),- Drücken Sie
Ctrl+c, um den messenger-Dienst zu beenden, den Sie in diesem Terminal in Schritt 4 unter „An die Konsole gesendete automatische OTel-Instrumentierung konfigurieren“ gestartet haben. Starten Sie ihn anschließend neu, damit der in Schritt 2 konfigurierte neue Exporter verwendet wird:
^cnode --import ./tracing.mjs index.mjs- Starten Sie eine zweite, separate Terminalsitzung. (In den folgenden Anweisungen wird diese als „Client-Terminal“ und das ursprüngliche Terminal – das in den Schritten 1 und 3 verwendet wird – als „Messenger-Terminal“ bezeichnet.) Warten Sie etwa zehn Sekunden und senden Sie dann eine Anfrage zur Integritätsprüfung an den Dienst messenger (Sie können dies mehrmals ausführen, wenn Sie mehrere Traces sehen möchten):
curl -X GET http://localhost:4000/health- Wenn Sie vor dem Senden der Anfrage zehn Sekunden warten, lässt sich Ihr Trace leichter finden, da er nach den zahlreichen Traces erscheint, die von der automatischen Instrumentierung beim Start des Dienstes erzeugt werden.
- Rufen Sie in einem Browser die Jaeger-Benutzeroberfläche unter http://localhost:16686 auf und überprüfen Sie, ob der OTLP-Exporter wie erwartet funktioniert. Klicken Sie in der Titelleiste auf „Search“ und wählen Sie im Dropdown-Menü des Felds „Service“ den Dienst aus, dessen Name mit „unknown_service“ beginnt. Klicken Sie auf die Schaltfläche „Find Traces“:

- Klicken Sie auf der rechten Seite des Fensters auf einen Trace, um eine Liste der darin enthaltenen Spans anzuzeigen. Jeder Span beschreibt die Vorgänge, die im Rahmen des Trace ausgeführt wurden und mitunter mehrere Services umfassen. Der jsonParser-Span im Screenshot zeigt, wie lange die Ausführung des
jsonParser-Teils des Codes zur Anfrageverarbeitung des messenger-Service gedauert hat.
- Wie in Schritt 5 erwähnt, ist der vom OTel SDK exportierte Name des Dienstes (unknown_service) nicht aussagekräftig. Um dies zu beheben, drücken Sie im Messenger-Terminal
Ctrl+c, um den Dienst messenger zu beenden. Installieren Sie anschließend noch einige weitere Node.js-Pakete:
^c npm install @opentelemetry/semantic-conventions@1.10.0 \ @opentelemetry/resources@1.10.0- Diese beiden Bibliotheken bieten die folgenden Funktionen:
@opentelemetry/semantic-conventions– Definiert die Standardattribute für Traces gemäß der OTel-Spezifikation.@opentelemetry/resources– Definiert ein Objekt (eine Ressource), das die Quelle darstellt, die die OTel-Daten erzeugt (in diesem Tutorial der Dienst „messenger“).
- Öffnen Sie tracing.mjs in einem Texteditor und nehmen Sie die folgenden Änderungen vor:
- Fügen Sie diese Zeilen zu den
import-Anweisungen am Anfang der Datei hinzu:
- Fügen Sie diese Zeilen zu den
import { Resource } from "@opentelemetry/resources";import { SemanticResourceAttributes } from "@opentelemetry/semantic-conventions";- Erstellen Sie eine
resourcenamensmessengerunter dem richtigen Schlüssel in der OTel-Spezifikation, indem Sie nach der letztenimport-Anweisung die folgende Zeile hinzufügen:
const resource = new Resource({ [SemanticResourceAttributes.SERVICE_NAME]: "messenger",
});- Übergeben Sie das
resource-Objekt an den NodeSDK-Konstruktor, indem Sie die orange hervorgehobene Zeile zwischen den schwarzen Zeilen einfügen:
const sdk = new opentelemetry.NodeSDK({ resource,
traceExporter: new OTLPTraceExporter({ headers: {} }),
instrumentations: [getNodeAutoInstrumentations()],
});- Starten Sie den messenger-Dienst neu:
node --import ./tracing.mjs index.mjs- Warten Sie etwa zehn Sekunden und senden Sie dann im Client-Terminal (das Sie in Schritt 4 geöffnet haben) eine weitere Health-Check-Anfrage an den Server (Sie können den Befehl mehrmals ausführen, wenn Sie mehrere Traces sehen möchten):
curl -X GET http://localhost:4000/health- Hinweis: Lassen Sie das Client-Terminal zur Wiederverwendung im nächsten Abschnitt und das Messenger-Terminal zur Wiederverwendung in Aufgabe 3 geöffnet.
- Vergewissern Sie sich, dass ein neuer Dienst namens „messenger“ in der Jaeger-Benutzeroberfläche im Browser angezeigt wird. Dies kann einige Sekunden dauern und möglicherweise müssen Sie die Jaeger-Benutzeroberfläche aktualisieren:

- Wählen Sie messenger im Dropdown-Menü Service aus und klicken Sie auf die Schaltfläche Find Traces, um alle aktuellen Traces anzuzeigen, die vom Dienst messenger stammen (der Screenshot zeigt die 2 neuesten von 20):

- Klicken Sie auf einen Trace, um die darin enthaltenen Spans anzuzeigen. Jeder Span ist ordnungsgemäß als vom Dienst messenger stammend gekennzeichnet:

Automatische OTel-Instrumentierung des notifier-Dienstes konfigurieren
Starten und konfigurieren Sie nun die automatische Instrumentierung für den Dienst notifier, indem Sie im Wesentlichen dieselben Befehle wie in den beiden vorherigen Abschnitten für den Dienst messenger ausführen.
- Öffnen Sie eine neue Terminalsitzung (in den folgenden Schritten als „Notifier-Terminal“ bezeichnet). Wechseln Sie in das Verzeichnis app im Repository notifier und installieren Sie Node.js (Sie können auf Wunsch auch eine andere Methode verwenden):
cd ~/microservices-march/notifier/appasdf install- Abhängigkeiten installieren:
npm install- Starten Sie die PostgreSQL-Datenbank für den notifier-Dienst:
docker compose up -d- Erstellen Sie das Datenbankschema und die Tabellen und fügen Sie einige Beispieldaten ein:
npm run refresh-db- Installieren Sie die OTel-Node.js-Pakete (eine Beschreibung der Funktion der Pakete finden Sie in den Schritten 1 und 3 unter „An die Konsole gesendete automatische OTel-Instrumentierung konfigurieren“):
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- Erstellen Sie eine neue Datei namens „tracing.mjs“:
touch tracing.mjs- Öffnen Sie in Ihrem bevorzugten Texteditor die Datei tracing.mjs und fügen Sie das folgende Skript hinzu, um das OTel SDK zu initialisieren:
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();- Hinweis: Dieses Skript entspricht genau dem Skript für den messenger-Dienst, mit dem einzigen Unterschied, dass der Wert im Feld
SemanticResourceAttributes.SERVICE_NAMEaufnotifiergesetzt ist. - Starten Sie den notifier-Dienst mit automatischer OTel-Instrumentierung:
node --import ./tracing.mjs index.mjs- Warten Sie etwa zehn Sekunden und senden Sie dann im Client-Terminal eine Zustandsprüfungsanfrage an den Dienst notifier. Dieser Dienst lauscht auf Port 5000, um einen Konflikt mit dem Dienst messenger zu vermeiden, der auf Port 4000 lauscht:
curl http://localhost:5000/health- Hinweis: Lassen Sie die Terminals für den Client und den Notifier geöffnet, damit Sie sie in Aufgabe 3 wiederverwenden können.
- Vergewissern Sie sich, dass im Browser in der Jaeger-Benutzeroberfläche ein neuer Dienst namens „notifier“ angezeigt wird:

OTel-Instrumentierung von NGINX konfigurieren
Für NGINX richten Sie das Tracing manuell ein, anstatt die automatische Instrumentierung von OTel zu verwenden. Derzeit erfolgt die Instrumentierung von NGINX mit OTel am häufigsten mithilfe eines in C geschriebenen Moduls. Drittanbietermodule sind ein wichtiger Bestandteil des NGINX-Ökosystems, ihre Einrichtung ist jedoch mit einem gewissen Aufwand verbunden. In diesem Tutorial wird die Einrichtung für Sie durchgeführt. Hintergrundinformationen finden Sie im Beitrag Kompilieren dynamischer Drittanbietermodule für NGINX und NGINX Plus in unserem Blog.
- Starten Sie eine neue Terminalsitzung (das NGINX-Terminal). Wechseln Sie in das Stammverzeichnis des Repositorys messenger und erstellen Sie ein neues Verzeichnis namens load-balancer sowie neue Dateien namens Dockerfile, nginx.conf und opentelemetry_module.conf:
cd ~/microservices-march/messenger/mkdir load-balancer cd load-balancer touch Dockerfile touch nginx.conf touch opentelemetry_module.conf- Öffnen Sie in Ihrem bevorzugten Texteditor die Datei „Dockerfile“ und fügen Sie Folgendes hinzu (die Kommentare erläutern, was die einzelnen Zeilen bewirken; Sie können den Docker-Container jedoch auch erstellen und ausführen, ohne alles im Detail zu verstehen):
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- Öffnen Sie „nginx.conf“ und fügen Sie Folgendes hinzu:
events {}
http {
include /etc/nginx/conf.d/opentelemetry_module.conf;
upstream messenger {
server localhost:4000;
}
server {
listen 8085;
location / {
proxy_pass http://messenger;
}
}
}- Diese äußerst einfache NGINX-Konfigurationsdatei weist NGINX an, Folgendes zu tun: Hinweis: Dies entspricht weitgehend der tatsächlichen Konfiguration von NGINX als Reverse-Proxy und Load-Balancer in einer Produktionsumgebung. Der einzige wesentliche Unterschied besteht darin, dass das Argument der
server-Direktive imupstream-Block normalerweise ein Domainname oder eine IP-Adresse anstelle vonlocalhostist.- Richten Sie eine Upstream-Gruppe namens messenger ein, welche die Gruppe der messenger-Dienstinstanzen repräsentiert
- Auf HTTP-Anfragen an Port 8085 warten
- Alle eingehenden Anfragen für Pfade, die mit / beginnen (also alle eingehenden Anfragen), an den Upstream messenger weiterleiten
- Öffnen Sie „opentelemetry_module.conf“ und fügen Sie Folgendes hinzu:
NginxModuleEnabled ON;NginxModuleOtelSpanExporter otlp;
NginxModuleOtelExporterEndpoint localhost:4317;
NginxModuleServiceName messenger-lb;
NginxModuleServiceNamespace MicroservicesMarchDemoArchitecture;
NginxModuleServiceInstanceId DemoInstanceId;
NginxModuleResolveBackends ON;
NginxModuleTraceAsError ON;- Erstellen Sie ein Docker-Image, das sowohl NGINX als auch das NGINX-OTel-Modul enthält:
docker build -t messenger-lb .- Starten Sie den Docker-Container für den NGINX-Reverse-Proxy und Load-Balancer:
docker run --rm --name messenger-lb -p 8085:8085 --network="host" messenger-lb- Senden Sie im Client-Terminal eine Health-Check-Anfrage an den Dienst messenger über den NGINX-Reverse-Proxy und Load-Balancer (Sie müssen nicht warten, bevor Sie diese Anfrage senden):
curl http://localhost:8085/health- Hinweis: Lassen Sie die NGINX- und Client-Terminals zur Wiederverwendung in Aufgabe 3 geöffnet.
- Vergewissern Sie sich im Browser, dass der neue Dienst „messenger-lb“ zusammen mit den zuvor gestarteten Diensten in der Jaeger-Benutzeroberfläche aufgeführt wird. Möglicherweise müssen Sie die Jaeger-Benutzeroberfläche in Ihrem Browser neu laden.

Aufgabe 3: OTel-Traces lesen lernen
Unter „Architektur und Benutzerablauf“ haben wir die einzelnen Phasen des Benutzerablaufs beschrieben. Hier noch einmal eine kurze Zusammenfassung:
- Ein Benutzer beginnt eine Unterhaltung, indem er einem anderen Benutzer eine Nachricht sendet.
- Der NGINX-Reverse-Proxy fängt die Nachricht ab und leitet sie an den messenger-Dienst weiter.
- Der messenger-Dienst schreibt die Nachricht in seine Datenbank und sendet anschließend ein Ereignis über RabbitMQ.
- Der notifier-Dienst verarbeitet dieses Ereignis, ruft die Benachrichtigungseinstellungen des Empfängers (des zweiten Benutzers) ab und sendet dem Empfänger über die bevorzugte Methode eine Benachrichtigung.
Die Ziele der Implementierung von Telemetrie sind:
- Alle Schritte nachvollziehen, die eine Anfrage durchläuft, um den neuen Nachrichtenfluss umzusetzen.
- Vergewissern Sie sich, dass der Ablauf unter normalen Bedingungen innerhalb von fünf Sekunden vollständig ausgeführt wird.
- Ermitteln Sie, wie lange der notifier-Dienst benötigt, um mit der Verarbeitung des vom messenger-Dienst gesendeten Ereignisses zu beginnen.
In dieser Aufgabe lernen Sie, zu beurteilen, ob die von der OTel-Instrumentierung erzeugten Traces die zuvor genannten Ziele erfüllen. Zunächst probieren Sie das System aus und erzeugen einige Traces. Anschließend untersuchen Sie den Trace eines Nachrichtenflusses sowie die darin enthaltenen Abschnitte, die von NGINX, dem messenger-Dienst, und dem notifier-Dienst erzeugt wurden.
Trace-Daten erstellen
Richten Sie im Client-Terminal eine Unterhaltung ein und senden Sie einige Nachrichten zwischen zwei Benutzern:
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'Eine Ausgabe wie die folgende wird vom notifier-Dienst erzeugt und im Notifier-Terminal angezeigt:
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 19147379938Machen Sie sich bereit, die Traces zu lesen
Öffnen Sie die Jaeger-Benutzeroberfläche im Browser, wählen Sie messenger-lb im Dropdown-Menü Service aus und klicken Sie auf die Schaltfläche Find Traces. Daraufhin wird eine Liste von Traces angezeigt, beginnend ganz am Anfang des Ablaufs. Klicken Sie auf einen beliebigen Trace, um Details dazu anzuzeigen, wie in diesem Screenshot:

Klicken Sie sich ein wenig durch und sehen Sie sich um. Bevor Sie fortfahren, überlegen Sie dann, inwiefern die Informationen in den Traces Ihre Ziele für die Instrumentierung unterstützen, die in der Einführung zu Aufgabe 3 aufgeführt sind. Relevante Fragen sind unter anderem:
- Welche Informationen helfen dabei, die Ziele zu erreichen?
- Welche Informationen fehlen?
- Welche Informationen sind nicht relevant?
Untersuchen Sie den NGINX-Abschnitt (messenger-lb) des Traces
Ziel 1: Alle Schritte anzeigen, die eine Anfrage im neuen Nachrichtenfluss durchläuft
Beginnen Sie mit dem NGINX-Span, der innerhalb des übergeordneten Spans 11 untergeordnete Spans umfasst. Da die aktuelle NGINX-Konfiguration sehr einfach ist, sind die untergeordneten Spans nicht besonders interessant und zeigen lediglich die Dauer der einzelnen Schritte im Lebenszyklus der NGINX-Anfrageverarbeitung. Der übergeordnete Span (der allererste) liefert jedoch einige interessante Erkenntnisse:

- Unter Tags sehen Sie die folgenden Attribute: Zusammengenommen besagen diese drei Informationen: „Eine
POST-Anfrage wurde an/conversations/1/messagesgesendet und die Antwort lautete201(erfolgreich erstellt).“ Dies entspricht den Schritten 1 und 4a unter Architektur und Benutzerablauf).http.method-Feld –POST(in der REST-Terminologie bedeutet dies Erstellung)http.status_code-Feld –201(zeigt eine erfolgreiche Erstellung an)http.target-Feld –conversations/1/messages(der Nachrichtenendpunkt)
- Unter „Prozess“ zeigt das Feld „
webengine.name“, dass dies der NGINX-Teil der Anfrage ist.
Da die Spans für messenger und notifier außerdem innerhalb des messenger-lb-Spans conversations/1 verschachtelt sind (wie im Screenshot unter „Vorbereitung auf das Lesen der Traces“ gezeigt), können Sie erkennen, dass die über den NGINX-Reverse-Proxy an den Dienst messenger gesendete Anfrage alle erwarteten Komponenten im Ablauf erreicht hat.
Diese Informationen erfüllen das Ziel, da Sie erkennen können, dass der NGINX-Reverse-Proxy Teil des Ablaufs war.
Ziel 2: Überprüfen, ob der Ablauf innerhalb von fünf Sekunden ausgeführt wird
Suchen Sie in der Liste der mit messenger-lb gekennzeichneten Spans den neuesten Span (er befindet sich am Ende der Liste), um zu sehen, wie lange der NGINX-Teil der Anfrage gedauert hat. Im Screenshot begann der Span bei 589 Mikrosekunden (µs) und dauerte 24 µs. Das bedeutet, dass der gesamte Reverse-Proxy-Vorgang nur 613 µs dauerte – etwa 0,6 Millisekunden (ms). (Die genauen Werte werden natürlich abweichen, wenn Sie das Tutorial selbst durchführen.)

Bei einer solchen Konfiguration sind die Werte größtenteils nur im Verhältnis zu anderen Messungen aussagekräftig und unterscheiden sich von System zu System. In diesem Fall besteht jedoch eindeutig keine Gefahr, dass dieser Vorgang auch nur annähernd fünf Sekunden dauert.
Diese Informationen erfüllen das Ziel, da Sie erkennen können, dass die NGINX-Vorgänge bei Weitem keine fünf Sekunden gedauert haben. Wenn es im Ablauf einen sehr langsamen Vorgang gibt, muss er zu einem späteren Zeitpunkt stattfinden.
Ziel 3: Ermitteln, wie lange der notifier-Dienst benötigt, um das vom messenger-Dienst gesendete Ereignis zu lesen
Die NGINX-Reverse-Proxy-Schicht enthält hierzu keine Informationen. Daher können Sie mit den messenger-Spans fortfahren.
Untersuchen Sie den Abschnitt „messenger“ des Traces
Ziel 1: Alle Schritte anzeigen, die eine Anfrage im neuen Nachrichtenfluss durchläuft
Der Abschnitt des messenger-Dienstes im Trace enthält weitere 11 Spans. Auch hier betreffen die meisten untergeordneten Spans die grundlegenden Schritte, die das Express-Framework bei der Verarbeitung einer Anfrage ausführt, und sind daher nicht besonders interessant. Der übergeordnete Span (der allererste) liefert jedoch erneut einige interessante Erkenntnisse:

Unter „Tags“ sehen Sie die folgenden Attribute:
http.method-Feld –POST(auch hier bedeutet dies in der REST-Terminologie eine Erstellung)http.route-Feld –/conversations/:conversationId/messages(die Nachrichtenroute)http.target-Feld –/conversations/1/messages(der Nachrichtenendpunkt)
Diese Informationen erfüllen das Ziel, da sie uns zeigen, dass der messenger-Dienst Teil des Ablaufs war und der aufgerufene Endpunkt der Endpunkt für neue Nachrichten war.
Ziel 2: Überprüfen, ob der Ablauf innerhalb von fünf Sekunden ausgeführt wird
Wie im folgenden Screenshot zu sehen ist, begann der messenger-Abschnitt des Traces bei 1,28 ms und endete bei 36,28 ms, was einer Gesamtdauer von 35 ms entspricht. Der Großteil dieser Zeit wurde für das Parsen von JSON (middleware - jsonParser) und in noch wesentlich größerem Umfang für die Verbindung mit der Datenbank (pg-pool.connect und tcp.connect) aufgewendet.
Das ist nachvollziehbar, da beim Schreiben der Nachricht auch mehrere SQL-Abfragen ausgeführt werden. Dies wiederum legt nahe, die Konfiguration der automatischen Instrumentierung zu erweitern, um die Ausführungsdauer dieser Abfragen zu erfassen. (Im Tutorial wird diese zusätzliche Instrumentierung nicht gezeigt, aber in Aufgabe 4 erstellen Sie manuell Spans, mit denen sich wiederum Datenbankabfragen umschließen lassen.)

Diese Informationen erfüllen das Ziel, da sie zeigen, dass die Messenger-Vorgänge bei Weitem keine fünf Sekunden dauern. Wenn es im Ablauf einen sehr langsamen Vorgang gibt, muss er zu einem späteren Zeitpunkt stattfinden.
Ziel 3: Ermitteln, wie lange der Dienst notifier benötigt, um das vom Dienst messenger gesendete Ereignis zu lesen
Wie die NGINX-Spans enthalten auch die messenger-Spans diese Informationen nicht. Daher können Sie mit den notifier-Spans fortfahren.
Untersuchen Sie den notifier-Abschnitt des Traces
Ziel 1: Alle Schritte anzeigen, die eine Anfrage im neuen Nachrichtenfluss durchläuft
Der Notifier-Abschnitt des Traces enthält nur zwei Spans:

- Der
chat_queue-process-Span – Bestätigt, dass der notifier-Dienst ein Ereignis aus der Nachrichtenwarteschlange chat_queue verarbeitet hat - Der
pg-pool.connect-Span – Zeigt, dass der notifier-Dienst nach der Verarbeitung des Ereignisses eine Verbindung zu seiner Datenbank hergestellt hat
Die aus diesen Spans verfügbaren Informationen reichen nur teilweise aus, um jeden einzelnen Schritt nachvollziehen zu können. Sie können erkennen, dass der notifier-Dienst das Ereignis aus der Warteschlange abgerufen hat, wissen aber nicht, ob:
- Die von diesem Dienst gesendete Nachrichtenbenachrichtigung entspricht dem Ereignis, das vom Dienst messenger ausgelöst wurde
- Die entsprechenden Nachrichtenbenachrichtigungen wurden ordnungsgemäß an den Empfänger der Nachricht gesendet
Das bedeutet, dass Sie Folgendes tun müssen, um den Ablauf des Benachrichtigungsdienstes vollständig zu verstehen:
- Instrumentieren Sie die Spans, die den Versand einer Benachrichtigung zeigen, manuell.
- Stellen Sie sicher, dass zwischen dem vom Dienst messenger ausgelösten Ereignis und dem vom Dienst notifier verarbeiteten Ereignis eine explizite Verbindung in Form einer Trace-ID besteht.
Ziel 2: Überprüfen, ob der Ablauf innerhalb von fünf Sekunden ausgeführt wird
Wenn Sie die Gesamtdauer der Spans des Dienstes notifier betrachten, sehen Sie, dass die Anfrage 30,77 ms im Abschnitt notifier des Ablaufs verbracht hat. Da jedoch keine Spans das „Ende“ des gesamten Ablaufs (das Senden von Benachrichtigungen an den Empfänger) signalisieren, können Sie weder die Gesamtdauer dieses Abschnitts noch die Gesamtzeit bis zum Abschluss des Vorgangs ermitteln.
Ziel 3: Ermitteln, wie lange der notifier-Dienst benötigt, um das vom messenger-Dienst gesendete Ereignis zu lesen
Sie können jedoch sehen, dass ein chat_queue-process-Span für den Dienst notifier bei 6,12 ms begann, 2 ms nachdem ein chat_queue-send-Span für den Dienst messenger bei 4,12 ms begonnen hatte.

Dieses Ziel ist erreicht, weil Sie wissen, dass der Notifier ein Ereignis 2 ms nach dessen Versand durch den Messenger-Dienst empfangen hat. Anders als bei Ziel 2 müssen Sie hierfür weder wissen, ob das Ereignis vollständig verarbeitet wurde, noch, wie lange die Verarbeitung gedauert hat.
Fazit
Aus unserer Analyse der von der aktuellen automatischen OTel-Instrumentierung erzeugten Traces geht eindeutig hervor:
- Viele dieser Spans sind in ihrer aktuellen Form nicht hilfreich:
- NGINX erzeugt Spans für Funktionen wie Autorisierungsprüfungen und die Bereitstellung von Dateien, die für die für Sie relevante Rolle als Reverse-Proxy nicht von Bedeutung sind. Derzeit können irrelevante Spans mit der OTel-Instrumentierung für NGINX jedoch nicht ausgelassen werden, sodass sich daran nichts ändern lässt.
- Unter den Spans für die Node.js-Dienste (die Dienste messenger und notifier) scheinen einige für die Ziele relevant zu sein: die Spans für das JSON-Parsing,
requesthandlerund alle Datenbankoperationen. Einige der Middleware-Spans (wieexpressInitundcorsMiddleware) scheinen nicht relevant zu sein und können entfernt werden.
- Für Folgendes fehlen kritische Spans:
- Vom notifier-Dienst gesendete Benachrichtigungen
- Eine eindeutige Zuordnung zwischen dem vom messenger-Dienst gesendeten RabbitMQ-Ereignis und dem vom notifier-Dienst verarbeiteten Ereignis
Das bedeutet, dass die grundlegende Instrumentierung das letzte Ziel erfüllt:
- Ermitteln Sie, wie lange der notifier-Dienst benötigt, um mit der Verarbeitung des vom messenger-Dienst gesendeten Ereignisses zu beginnen
Allerdings liegen nicht genügend Informationen vor, um die ersten beiden Ziele zu erreichen:
- Alle Schritte verstehen, die eine Anfrage während des neuen Nachrichtenflusses durchläuft
- Darauf vertrauen können, dass der Ablauf unter normalen Umständen in weniger als fünf Sekunden vollständig ausgeführt wird
Aufgabe 4: Instrumentierung anhand von Trace-Daten optimieren
In dieser Aufgabe optimieren Sie die OTel-Instrumentierung auf Grundlage der Trace-Analysen, die Sie in Aufgabe 3 durchgeführt haben. Dazu gehören sowohl das Entfernen unnötiger Spans, das Erstellen neuer benutzerdefinierter Spans als auch die Bestätigung, dass das vom Notifier-Dienst verarbeitete Ereignis tatsächlich vom Messenger-Dienst erzeugt wurde.
Unnötige Spans entfernen
- Öffnen Sie in Ihrem bevorzugten Texteditor die Datei tracing.mjs im Verzeichnis app des Repositorys messenger und fügen Sie am Ende der Importanweisungen am Anfang der Datei Folgendes hinzu:
const IGNORED_EXPRESS_SPANS = new Set([ "middleware - expressInit",
"middleware - corsMiddleware",
]);- Hiermit wird eine Gruppe von Span-Namen definiert, die aus der Liste der im folgenden Screenshot der Jaeger-Benutzeroberfläche gezeigten Spans abgeleitet wurden und aus dem Trace ausgeschlossen werden sollen, da sie für diesen Ablauf keine nützlichen Informationen liefern. Möglicherweise kommen Sie zu dem Schluss, dass auch andere im Screenshot aufgeführte Spans nicht benötigt werden, und fügen diese der Liste
IGNORED_EXPRESS_SPANShinzu.
- Fügen Sie der Konfiguration für die automatische Instrumentierung Filter hinzu, um nicht benötigte Spans auszuschließen. Ändern Sie dazu die orange hervorgehobene Zeile:
const sdk = new opentelemetry.NodeSDK({ resource,
traceExporter: new OTLPTraceExporter({ headers: {} }),
instrumentations: [getNodeAutoInstrumentations()],
});- zu diesem:
const sdk = new opentelemetry.NodeSDK({ resource,
traceExporter: new OTLPTraceExporter({ headers: {} }),
instrumentations: [ getNodeAutoInstrumentations({ "@opentelemetry/instrumentation-express": { ignoreLayers: [ (name) => { return IGNORED_EXPRESS_SPANS.has(name); }, ], }, }), ],
});- Die Funktion
getNodeAutoInstrumentationsgreift auf die in Schritt 1 definierte Gruppe von Spans zurück, um diese aus dem von@opentelemetry/instrumentation-expressgenerierten Trace herauszufiltern. Anders ausgedrückt: Die Anweisungreturnergibt für einen Span, der zuIGNORED_EXPRESS_SPANSgehört, den Werttrue, und die AnweisungignoreLayersentfernt diesen Span aus dem Trace. - Drücken Sie im Messenger-Terminal
Strg+C, um den Messenger-Dienst zu beenden. Starten Sie ihn anschließend neu:
^cnode --import ./tracing.mjs index.mjs- Warten Sie etwa zehn Sekunden und senden Sie dann im Client-Terminal eine neue Nachricht:
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'- Überprüfen Sie erneut die messenger-Spans in der Jaeger-Benutzeroberfläche. Die beiden
middleware-SpansexpressInitundcorsMiddlewarewerden nicht mehr angezeigt (Sie können dies mit dem Screenshot für Ziel 2 unter „Untersuchen des messenger-Abschnitts des Traces“ in Aufgabe 3 vergleichen.
Benutzerdefinierte Spans einrichten
In diesem Abschnitt arbeiten Sie zum ersten Mal mit dem Anwendungscode. Die automatische Instrumentierung liefert zahlreiche Informationen, ohne dass Änderungen an der Anwendung erforderlich sind. Bestimmte Erkenntnisse lassen sich jedoch nur gewinnen, indem gezielt Teile der Geschäftslogik instrumentiert werden.
Für den neuen Nachrichtenfluss, den Sie instrumentieren, ist die Nachverfolgung des Versands von Benachrichtigungen an den Empfänger der Nachricht ein Beispiel dafür.
- Öffnen Sie index.mjs im Verzeichnis app des Repositorys notifier. Diese Datei enthält die gesamte Geschäftslogik des Dienstes. Fügen Sie die folgende Zeile am Ende der Liste der
import-Anweisungen am Anfang der Datei hinzu:
import { trace } from "@opentelemetry/api";- Ersetzen Sie diesen Code (etwa in Zeile 91 der Datei):
for (let pref of preferences) {
console.log(
`Sending notification of new message via ${pref.address_type} to ${pref.address}`
);
}- mit:
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
}
);- Der neue Code bewirkt Folgendes: Eine übergeordnete Span stellt sicher, dass jeder Vorgang zum Senden einer Benachrichtigung erfasst wird, selbst wenn für den Benutzer keine Benachrichtigungseinstellungen gefunden werden.
- Ruft den
tracerab, ein globales Objekt für die Interaktion mit OTel-Traces. - Startet einen neuen übergeordneten Span namens „
notification.send_all“ und legt das Attribut „user_id“ fest, um den Absender der Nachricht zu identifizieren. - Beginnt eine Schleife, in der die Benachrichtigungseinstellungen des Empfängers durchlaufen werden und ein neuer untergeordneter Span namens „
notification.send“ unter „notification.send_all“ erstellt wird. Für jede Benachrichtigung wird ein neuer Span erzeugt. - Legt weitere Attribute für den untergeordneten Span fest:
notification_type– Entwedersmsoderemailuser_id– Die ID des Benutzers, der die Benachrichtigung erhält
- Schließt nacheinander jeden untergeordneten „
notification.send“-Span. - Schließt den übergeordneten
notification.send_all-Span.
- Ruft den
- Drücken Sie im Notifier-Terminal
Ctrl+c, um den Notifier-Dienst zu beenden. Starten Sie ihn anschließend neu:
^cnode --import ./tracing.mjs index.mjs- Warten Sie etwa zehn Sekunden und senden Sie dann im Client-Terminal eine neue Nachricht:
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'- Überprüfen Sie erneut die notifier-Spans in der Jaeger-Benutzeroberfläche. Sie sehen den übergeordneten Span und zwei untergeordnete Spans, jeweils mit einer „send notification“-Operation:

Sie können nun das erste und zweite Ziel vollständig erreichen, da Sie alle Schritte sehen können, die eine Anfrage während des neuen Nachrichtenflusses durchläuft. Die Zeitangaben der einzelnen Spans machen Verzögerungen zwischen diesen Schritten sichtbar.
Bestätigen, dass Messenger und Notifier dasselbe Ereignis verarbeiten
Für einen vollständigen Einblick in den Ablauf müssen Sie noch eine weitere Frage klären: Ist das Ereignis, das vom notifier-Dienst verarbeitet wird, tatsächlich dasjenige, das vom messenger-Dienst gesendet wurde?
Sie müssen keine expliziten Änderungen vornehmen, um die beiden Traces miteinander zu verknüpfen – allerdings sollten Sie auch nicht blind auf die Magie der automatischen Instrumentierung vertrauen.
Fügen Sie vor diesem Hintergrund kurz etwas Debug-Code hinzu, um zu überprüfen, ob der Trace, der im NGINX-Dienst beginnt, tatsächlich derselbe ist (also dieselbe Trace-ID hat) wie der vom notifier-Dienst verarbeitete Trace.
- Öffnen Sie die Datei „index.mjs“ im Verzeichnis „app“ des Repositorys „messenger“ und nehmen Sie die folgenden Änderungen vor:
- Fügen Sie die folgende Zeile am Ende der Liste der Importanweisungen am Anfang der Datei hinzu:
import { trace } from "@opentelemetry/api";- Fügen Sie die orange hervorgehobenen Zeilen unterhalb der vorhandenen schwarzen Zeile hinzu:
async function createMessageInConversation(req, res) {
const tracer = trace.getActiveSpan(); console.log("TRACE_ID: ", tracer.spanContext().traceId);- Die neuen Zeilen geben die
TRACE_IDinnerhalb der Funktion in messenger aus, die das Erstellen einer neuen Nachricht verarbeitet.
- Öffnen Sie die Datei index.mjs im Verzeichnis app des Repositorys notifier und fügen Sie die orange hervorgehobene Zeile unterhalb der vorhandenen schwarzen Zeile hinzu:
export async function handleMessageConsume(channel, msg, handlers) { console.log("RABBIT_MQ_MESSAGE: ", msg);- Die neue Zeile gibt den vollständigen Inhalt des vom notifier-Dienst empfangenen AMQP-Ereignisses aus.
- Beenden Sie die Dienste messenger und notifier und starten Sie sie neu, indem Sie diese Befehle sowohl im messenger- als auch im notifier-Terminal ausführen:
^cnode --import ./tracing.mjs index.mjs- Warten Sie etwa zehn Sekunden und senden Sie dann im Client-Terminal erneut eine Nachricht:
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'- Sehen Sie sich die Protokolle der Dienste messenger und notifier an. Das Protokoll des Dienstes messenger enthält eine Zeile wie die folgende, in der die Trace-ID für eine Nachricht angegeben wird (die tatsächliche ID wird beim Durchführen des Tutorials anders lauten):
TRACE_ID: 29377a9b546c50be629c8e64409bbfb5- Ebenso gibt das Protokoll des notifier-Dienstes eine Trace-ID in einer Ausgabe wie dieser aus:
_spanContext: {
traceId: '29377a9b546c50be629c8e64409bbfb5',
spanId: 'a94e9462a39e6dbf',
traceFlags: 1,
traceState: undefined
},- Die Trace-IDs in der Konsole stimmen überein. Abschließend können Sie sie jedoch noch mit der Trace-ID in der Jaeger-Benutzeroberfläche vergleichen. Öffnen Sie dazu die Benutzeroberfläche über den Endpunkt der entsprechenden Trace-ID (Ihre wird anders lauten, in diesem Beispiel ist es http://localhost:16686/trace/29377a9b546c50be629c8e64409bbfb5), um den gesamten Trace anzuzeigen. Der Jaeger-Trace bestätigt Folgendes:
- Die automatische AMQP-Instrumentierung im messenger-Dienst fügt diese Trace-ID beim Senden des Ereignisses als Teil der Metadaten hinzu.
- Die automatische AMQP-Instrumentierung im Dienst notifier erwartet diese Metadaten und legt den Trace-Kontext entsprechend fest.
Hinweis: In einem echten Produktionssystem würden Sie den in diesem Abschnitt hinzugefügten Code entfernen, sobald Sie sich vergewissert haben, dass der Ablauf wie erwartet funktioniert.
Ressourcenbereinigung
Im Laufe dieses Tutorials haben Sie einige Container und Images erstellt. Befolgen Sie diese Anweisungen, um sie zu entfernen.
- So entfernen Sie alle laufenden Docker-Container:
docker rm $(docker stop messenger-lb)- So entfernen Sie den platform-Dienst sowie die Datenbankdienste messenger und notifier:
cd ~/microservices-march/platform && docker compose down cd ~/microservices-march/notifier && docker compose down cd ~/microservices-march/messenger && docker compose downNächste Schritte
Herzlichen Glückwunsch, Sie haben das Tutorial abgeschlossen!
- Sie haben die OTel-Instrumentierung für einen NGINX-Reverse-Proxy und zwei Node.js-Dienste eingerichtet.
- Sie haben die von der automatischen OTel-Instrumentierung bereitgestellten Daten kritisch geprüft und einige fehlende Telemetriedaten ergänzt, um die Ziele des OTel-Labors zu erreichen:
- Sie haben einen guten Überblick über den Ablauf einer bestimmten Anfrage durch ein Messaging-System erhalten, ohne den Anwendungscode direkt zu ändern.
- Sie haben bestätigt, dass der Ablauf unter normalen Umständen in weniger als fünf Sekunden vollständig ausgeführt wurde.
Und dennoch haben Sie bisher kaum an der Oberfläche dessen gekratzt, wie eine ideale Tracing-Konfiguration aussehen könnte! In einer Produktionsumgebung könnten Sie beispielsweise benutzerdefinierte Spans für jede Datenbankabfrage sowie zusätzliche Metadaten zu allen Spans hinzufügen, die Laufzeitdetails wie die Container-ID des jeweiligen Dienstes beschreiben. Sie könnten außerdem die beiden anderen Arten von OTel-Daten (Metriken und Protokolle) implementieren, um sich ein vollständiges Bild vom Zustand Ihres Systems zu verschaffen.
Wenn Sie Ihr Wissen über Microservices weiter vertiefen möchten, sehen Sie sich den Microservices March 2023 an. In Einheit 4: Chaos und Komplexität von Microservices mit Observability bewältigen erfahren Sie mehr über die drei wichtigsten Kategorien von Observability-Daten, die Bedeutung der Abstimmung von Infrastruktur und Anwendung sowie über Möglichkeiten, mit der Analyse detaillierter Daten zu beginnen.

Über den Autor
Ähnliche Blogbeiträge
Die Zukunft von NGINX: Zurück zu unseren Open-Source-Wurzeln
Beim NGINX Sprint 2022 besinnen wir uns auf unsere Open-Source-Wurzeln. Erfahren Sie mehr über die Initiativen, die wir im kommenden Jahr verfolgen, und teilen Sie uns mit, was Sie sich wünschen.

Ein Leitfaden zur Auswahl eines Ingress-Controllers, Teil 1: Ermitteln Sie Ihre Anforderungen
Ein Ingress-Controller kann eines der leistungsstärksten Tools in Ihrem Kubernetes-Stack sein. Erfahren Sie, wie Sie Ihre Ingress-Anforderungen ermitteln, um die beste Option auszuwählen.
Bereitstellung des NGINX Ingress Controllers auf Amazon EKS: So haben wir getestet
Wir haben kürzlich den Lösungsleitfaden zum NGINX Ingress Controller um Empfehlungen zur Dimensionierung für Amazon Elastic Kubernetes Service (EKS) ergänzt. In diesem Blogbeitrag erläutern wir, wie wir diese Empfehlungen erarbeitet haben, und stellen Ihnen alle Informationen zur Verfügung, die Sie benötigen, um selbst ähnliche Tests durchzuführen.

Protokollierung des Datenverkehrs zwischen NGINX und Upstream-Servern bei CDN77
Bei der Fehlersuche bei Datenverkehrsproblemen zwischen NGINX und Upstream-Servern konnte CDN77 die benötigten Daten nur aus dem Fehlerprotokoll mit aktivierter Debug-Ausgabe beziehen, was die Leistung beeinträchtigt und riesige Dateien erzeugt. Daher entwickelte das Team ein neues benutzerdefiniertes Modul, das ausschließlich die benötigten Daten protokolliert.
Ankündigung von NGINX Plus R16
NGINX Plus R16 ist eine einheitliche, elastische Ingress- und Egress-Schicht für Anwendungen, die die Funktionen eines Load Balancers, API-Gateways und einer WAF vereint und neue Features wie clusterfähige Ratenbegrenzung, Schlüssel-Wert-Speicher, „Random with Two Choices“, verbessertes UDP-Load-Balancing und vieles mehr bietet.
5 Gründe für den Umstieg auf softwarebasiertes Load Balancing
Entdecken Sie fünf Gründe für den Wechsel zu einem softwarebasierten Load Balancer wie NGINX Plus, darunter Kosteneinsparungen, die Einführung von DevOps und eine einheitliche Lösung für alle Umgebungen.