Als wir die NGINX Modern Apps Reference Architecture vorstellten (MARA) im vergangenen Herbst auf der Sprint 2.0 vorstellten, betonten wir, dass sie nicht wie manche Architekturen nur ein „Spielzeug“ sein sollte, sondern vielmehr eine Lösung, die „robust, getestet und bereit für den Einsatz in produktiven Anwendungen in Kubernetes-Umgebungen“ ist. Für ein solches Projekt sind Observability-Tools unverzichtbar. Alle Mitglieder des MARA-Teams haben selbst erlebt, wie mangelnde Einblicke in Status und Leistung die Entwicklung und Bereitstellung von Anwendungen zu einer frustrierenden Angelegenheit machen. Wir waren uns sofort einig, dass MARA Instrumentierung für Debugging und Tracing in einer Produktionsumgebung umfassen muss.
Ein weiteres Leitprinzip der MARA ist die Bevorzugung von Open-Source-Lösungen. In diesem Beitrag beschreiben wir, wie uns die Suche nach einem multifunktionalen Open-Source-Tool für Observability zu OpenTelemetry führte. Anschließend erläutern wir ausführlich die Abwägungen, Designentscheidungen, Verfahren und Technologien, mit denen wir OpenTelemetry in eine mit Python, Java und NGINX entwickelte Microservices-Anwendung integriert haben.
Wir hoffen, dass unsere Erfahrungen Ihnen helfen, potenzielle Fallstricke zu vermeiden und die Einführung von OpenTelemetry in Ihrem Unternehmen zu beschleunigen. Bitte beachten Sie, dass dieser Beitrag eine zeitgebundene Momentaufnahme des aktuellen Entwicklungsstands darstellt. Wir gehen davon aus, dass die hier behandelten Technologien innerhalb eines Jahres ausgereifter sein werden. Auch wenn wir auf derzeitige Schwächen einiger Projekte hinweisen, sind wir für die geleistete Open-Source-Arbeit außerordentlich dankbar und freuen uns darauf, ihre weitere Entwicklung zu verfolgen.
Unsere Anwendung
Als Anwendung, die in eine Observability-Lösung integriert werden sollte, wählten wir die Bank of Sirius, unseren Fork der Bank of Anthos-Beispielanwendung von Google. Es handelt sich um eine Webanwendung mit einer Microservices-Architektur, die mittels Infrastructure as Code bereitgestellt werden kann. Die Anwendung bietet in puncto Performance und Zuverlässigkeit noch zahlreiche Verbesserungsmöglichkeiten, ist aber ausgereift genug, um sie mit gutem Grund als Brownfield-Anwendung zu betrachten. Daher halten wir sie für ein gutes Beispiel, um zu zeigen, wie OpenTelemetry in eine Anwendung integriert werden kann, denn theoretisch liefert verteiltes Tracing wertvolle Einblicke in die Schwachstellen einer Anwendungsarchitektur.
Wie im Diagramm dargestellt, sind die Dienste, auf denen die Anwendung basiert, relativ unkompliziert.

Warum wir uns für OpenTelemetry entschieden haben
Unser Weg zur Entscheidung für OpenTelemetry war ziemlich verschlungen und verlief in mehreren Etappen.
Erstellen einer Wunschliste für Funktionen
Bevor wir die verfügbaren Open-Source-Observability-Tools selbst bewerteten, ermittelten wir, welche Aspekte der Observability für uns wichtig sind. Auf Grundlage unserer bisherigen Erfahrungen erstellten wir die folgende Liste.
- Protokollierung – In unserem Sprachgebrauch bezeichnet dies die Erzeugung klassischer, durch Zeilenumbrüche getrennter Meldungen aus Anwendungen; die Bank-of-Sirius-App strukturiert ihre Protokolle im Bunyan-Format
- Verteiltes Tracing – Zeitmessungen und Metadaten für jede Komponente der gesamten Anwendung, wie sie von Anbietern für Application Performance Management (APM) bereitgestellt werden
- Metriken – Messwerte, die über einen bestimmten Zeitraum erfasst und als Zeitreihendaten grafisch dargestellt werden
- Aggregation und Benachrichtigung von Ausnahmen/Fehlern – Eine aggregierte Sammlung der häufigsten Ausnahmen und Fehler, die durchsuchbar sein muss, damit wir ermitteln können, welche Anwendungsfehler am häufigsten auftreten
- Integritätsprüfungen – Regelmäßige Prüfanfragen an Dienste, um festzustellen, ob sie innerhalb der Anwendung ordnungsgemäß funktionieren
- Laufzeitstatus-Introspektion – Eine Reihe von APIs, die nur für Administratoren sichtbar sind und Informationen über den Laufzeitstatus der Anwendung liefern
- Heap-/Core-Dumps – Umfassende Momentaufnahmen des Laufzeitzustands eines Dienstes; für unsere Zwecke ist entscheidend, wie einfach sich diese Dumps bei Bedarf oder beim Absturz eines Dienstes erstellen lassen
Vergleich der Tool-Funktionen mit der Wunschliste
Natürlich haben wir nicht erwartet, dass ein einziges Open-Source-Tool oder ein einzelner Ansatz all diese Funktionen bietet. Doch zumindest hatten wir damit eine ideale Grundlage, um die verfügbaren Tools miteinander zu vergleichen. Anhand der Dokumentation der einzelnen Tools haben wir ermittelt, welche der sieben Funktionen auf unserer Wunschliste sie unterstützen. Die Tabelle fasst unsere Ergebnisse zusammen.
Technologie Protokollierung Verteiltes Tracing Metriken Fehleraggregation Zustandsprüfungen Laufzeit-Introspektion Heap-/Core-DumpsELK + Elastic APM ✅ ✅ ✅ ✅ ✅ ❌ ❌Grafana ✅ ✅ ✅ ✅ ✅ ❌ ❌Graylog ✅ ❌ ❌ ❌ ❌ ❌ ❌Jaeger ❌ ✅ ❌ ✅ ❌ ❌ ❌OpenCensus ❌ ❌ ✅ ❌ ❌ ❌ ❌OpenTelemetry Beta ✅ ✅ ✅ ✅ * ❌ ❌Prometheus ❌ ❌ ✅ ❌ ❌ ❌ ❌StatsD ❌ ❌ ✅ ❌ ❌ ❌ ❌Zipkin ❌ ✅ ❌ ✅ ❌ ❌ ❌
* Als Erweiterung
Die Erstellung dieser Tabelle war ein böses Erwachen. Die verschiedenen Tools unterscheiden sich in ihren Funktionen und Einsatzzwecken so stark, dass wir sie unmöglich alle derselben Kategorie zuordnen konnten – das war, als würde man Äpfel mit Toastern vergleichen!
Zum Beispiel erfüllen ELK (Elasticsearch-Logstash-Kibana plus Filebeat) und Zipkin so grundlegend unterschiedliche Aufgaben, dass ein Vergleich eher für Verwirrung sorgt als Klarheit schafft. Leider trübt eine schleichende Ausweitung des Funktionsumfangs das Bild zusätzlich: Zweifellos als Reaktion auf Benutzerwünsche wurden Funktionen hinzugefügt, die für den Hauptzweck des jeweiligen Tools zweitrangig sind und Überschneidungen mit anderen Tools schaffen. Oberflächlich betrachtet dient ELK der Speicherung und Visualisierung von Protokolldaten, während Zipkin verteiltes Tracing ermöglicht. Doch schon bei einem etwas genaueren Blick auf das Produktportfolio von Elastic stößt man schnell auf Elastic APM, das verteiltes Tracing unterstützt und sogar mit Jaeger kompatibel ist.
Abgesehen von der schleichenden Ausweitung des Funktionsumfangs lassen sich viele der Tools miteinander integrieren, wodurch unterschiedliche Kombinationen aus Datensammlern, Aggregatoren, Dashboards und Ähnlichem entstehen. Einige der Technologien sind miteinander kompatibel, andere hingegen nicht.
Durchführung einer qualitativen Untersuchung
Daher vermittelte uns diese Vergleichstabelle kein ausreichend genaues Bild, auf das wir unsere Entscheidung hätten stützen können. Wir mussten die Ziele, Leitprinzipien und mögliche zukünftige Ausrichtung jedes Projekts qualitativ untersuchen. Dabei gingen wir davon aus, dass wir langfristig umso eher kompatibel bleiben würden, je ähnlicher die Werte eines Projekts unseren eigenen sind. Beim Besuch der Projektseiten fiel uns dies sofort auf der OpenCensus-Seite auf.
OpenCensus und OpenTracing wurden zu OpenTelemetry zusammengeführt, das als nächste Hauptversion von OpenCensus und OpenTracing dient. OpenTelemetry wird abwärtskompatibel mit bestehenden OpenCensus-Integrationen sein, und wir werden bestehende OpenCensus-Bibliotheken noch zwei Jahre lang mit Sicherheitsupdates versorgen.
Das war für uns eine entscheidende Information. Uns war klar, dass wir nicht garantieren konnten, dass unsere Wahl zukunftssicher sein würde. Wir wollten aber zumindest wissen, dass sie auch künftig auf eine starke Community zählen kann. Mit dieser Information konnten wir OpenCensus von unserer Kandidatenliste streichen. Auch Jaeger zu verwenden, war wahrscheinlich keine gute Idee, da es sich dabei um die Referenzimplementierung für das inzwischen offiziell eingestellte Projekt OpenTracing handelt – der Großteil der neuen Beiträge wird in OpenTelemetry einfließen.
Dann informierten wir uns über den OpenTelemetry Collector.
Der OpenTelemetry Collector bietet eine herstellerunabhängige Implementierung für den Empfang, die Verarbeitung und den Export von Telemetriedaten. Darüber hinaus entfällt die Notwendigkeit, mehrere Agents bzw. Collectors auszuführen, zu betreiben und zu warten, um Open-Source-Formate für Telemetriedaten (z. B. Jaeger, Prometheus usw.) zu unterstützen und die Daten an verschiedene Open-Source- oder kommerzielle Backends zu senden.
Der OpenTelemetry Collector fungiert als Aggregator, mit dem wir verschiedene Methoden zur Erfassung und Instrumentierung von Observability-Daten mit unterschiedlichen Backends kombinieren können. So kann unsere Anwendung beispielsweise Traces mit Zipkin und Metriken von Prometheus erfassen, die wir anschließend an ein konfigurierbares Backend senden und mit Grafana visualisieren können. Dieses Konzept lässt sich auf viele weitere Arten umsetzen. So können wir verschiedene Ansätze ausprobieren, um herauszufinden, welche Technologien sich für unseren Anwendungsfall am besten eignen.
OpenTelemetry-Integration als zukunftsweisender Ansatz
Im Grunde hat uns der OpenTelemetry Collector überzeugt, weil er uns theoretisch ermöglicht, zwischen verschiedenen Observability-Technologien zu wechseln. Trotz der relativen Unreife des Projekts beschlossen wir, den Sprung ins kalte Wasser zu wagen und OpenTelemetry ausschließlich mit Open-Source-Integrationen einzusetzen, denn die Technologielandschaft lässt sich nur verstehen, wenn man die Technologien selbst nutzt.
Allerdings fehlen dem OpenTelemetry Collector einige Komponenten, sodass wir für diese Funktionen auf andere Technologien zurückgreifen müssen. In den folgenden Abschnitten fassen wir unsere Entscheidungen und die Gründe dafür zusammen.
- Protokollierung implementieren
- Verteiltes Tracing implementieren
- Implementierung der Metrikerfassung
- Implementierung der Fehleraggregation
- Implementierung von Zustandsprüfungen und Laufzeit-Introspektion
- Implementieren von Heap- und Core-Dumps
Protokollierung implementieren
Die Protokollierung ist ein vermeintlich einfacher Bestandteil der Observability, der jedoch schnell komplexe Entscheidungen nach sich zieht. Einfach ist sie, weil Sie lediglich die Protokollausgaben Ihrer Container erfassen. Kompliziert wird es jedoch, weil Sie anschließend entscheiden müssen, wo die Daten gespeichert werden, wie sie dorthin übertragen werden, wie sie indexiert werden müssen, damit sie sinnvoll nutzbar sind, und wie lange sie aufbewahrt werden sollen. Damit Protokolldateien nützlich sind, müssen sie sich anhand ausreichend vieler unterschiedlicher Kriterien einfach durchsuchen lassen, um den Anforderungen verschiedener Benutzer gerecht zu werden.
Wir haben uns die Logging-Unterstützung des OpenTelemetry Collectors angesehen, die zum Zeitpunkt der Erstellung dieses Textes noch in einem sehr frühen Betastadium ist. Wir haben uns entschieden, kurzfristig ELK für das Logging zu verwenden und gleichzeitig weitere Optionen zu prüfen.
Da keine zwingenden Gründe dagegensprachen, entschieden wir uns standardmäßig für die Tools von Elasticsearch, wodurch wir die Bereitstellung in Ingest-, Koordinierungs-, Master- und Datenknoten aufteilen konnten. Um die Bereitstellung zu vereinfachen, verwendeten wir ein Bitnami-Chart. Für den Datentransport stellten wir Filebeat als Teil eines Kubernetes-DaemonSets bereit. Die Suchfunktion wurde durch die Bereitstellung von Kibana und die Nutzung der vorinstallierten Indizes, Suchabfragen, Visualisierungen und Dashboards ergänzt.
Schon bald zeigte sich, dass diese Lösung zwar funktionierte, die Standardkonfiguration jedoch recht ressourcenintensiv war, was den Betrieb in einer Umgebung mit geringerem Ressourcenbedarf wie K3S oder Microk8s erschwerte. Durch die Möglichkeit, die Anzahl der Replikate für jede Komponente anzupassen, ließ sich dieses Problem beheben. Allerdings kam es dadurch zu einigen Ausfällen, die möglicherweise auf Ressourcenerschöpfung oder übermäßig große Datenmengen zurückzuführen waren.
Wir sind darüber keineswegs enttäuscht, sondern sehen darin eine Gelegenheit, die Leistung unseres Protokollierungssystems mit unterschiedlichen Konfigurationen zu vergleichen und weitere Optionen wie Grafana Loki und Graylog zu untersuchen. Möglicherweise stellen wir fest, dass schlankere Protokollierungslösungen nicht den vollen Funktionsumfang bieten, den manche Benutzer benötigen und den ressourcenintensivere Tools bereitstellen. Aufgrund des modularen Aufbaus von MARA werden wir wahrscheinlich zusätzliche Module für diese Optionen entwickeln, um den Benutzern mehr Auswahl zu bieten.
Implementierung von Distributed Tracing
Neben der Frage, welches Tool die von uns gewünschte Tracing-Funktionalität bietet, mussten wir auch überlegen, wie die Lösung implementiert werden sollte und welche Technologien darin integriert werden müssen.
Zunächst wollten wir sicherstellen, dass die Instrumentierung die Dienstqualität der Anwendung selbst nicht beeinträchtigt. Wir alle hatten bereits mit Systemen gearbeitet, deren Leistung vorhersehbar einmal pro Stunde einbrach, während Protokolle exportiert wurden, und wir wollten diese Erfahrung keinesfalls wiederholen. Die Architektur des OpenTelemetry Collectors war in dieser Hinsicht überzeugend, da pro Host eine Instanz ausgeführt werden kann. Jeder Collector empfängt Daten von Clients und Agents aus all den verschiedenen Anwendungen, die auf dem Host ausgeführt werden – unabhängig davon, ob sie containerisiert sind oder nicht. Der Collector aggregiert und komprimiert die Daten gegebenenfalls, bevor er sie an ein Speicher-Backend sendet, was für uns ideal klang.
Als Nächstes untersuchten wir, inwieweit OpenTelemetry von den verschiedenen Programmiersprachen und Frameworks unterstützt wird, die wir in der Anwendung einsetzen. Hier wurde es etwas komplizierter. Obwohl wir nur die beiden in der Tabelle aufgeführten Programmiersprachen und die zugehörigen Frameworks verwendeten, war die Komplexität überraschend hoch.
Sprache Framework Anzahl der DiensteJava Spring Boot 3Python Flask 3
Um Tracing auf Sprachebene hinzuzufügen, haben wir zunächst die automatische Instrumentierung von OpenTelemetry ausprobiert, fanden deren Ausgabe jedoch unübersichtlich und verwirrend. Wir sind sicher, dass sich dies mit zunehmender Reife der Bibliotheken für die automatische Instrumentierung verbessern wird. Vorerst haben wir uns jedoch gegen die OpenTelemetry-Agenten entschieden und beschlossen, das Tracing direkt in unseren Code zu integrieren.
Bevor wir das Tracing direkt im Code implementierten, konfigurierten wir zunächst den OpenTelemetry Collector so, dass er sämtliche Trace-Daten an eine lokal ausgeführte Jaeger-Instanz ausgab, in der wir die Ergebnisse leichter einsehen konnten. Das war sehr hilfreich, da wir mit der visuellen Darstellung der Tracing-Daten experimentieren konnten, während wir herausfanden, wie sich OpenTelemetry vollständig integrieren ließ. Wenn wir beispielsweise feststellten, dass eine HTTP-Clientbibliothek bei Aufrufen eines abhängigen Dienstes keine Tracing-Daten übermittelte, nahmen wir das Problem sofort in unsere Fehlerbehebungsliste auf. Jaeger bietet eine übersichtliche Darstellung aller verschiedenen Spans innerhalb eines einzelnen Traces:

Verteiltes Tracing für Python
Das Hinzufügen von Tracing zu unserem Python-Code war relativ unkompliziert. Wir haben zwei Python-Quelldateien hinzugefügt, auf die alle unsere Dienste verweisen, und die jeweiligen requirements.txt-Dateien um die relevanten opentelemetry-instrumentation-*-Abhängigkeiten ergänzt. Dadurch konnten wir für alle Python-Dienste dieselbe Tracing-Konfiguration verwenden, die Trace-ID jeder Anfrage in Protokollmeldungen aufnehmen und die Trace-ID in Anfragen an abhängige Dienste einbetten.
Verteiltes Tracing für Java
Als Nächstes wandten wir uns den Java-Diensten zu. Die direkte Verwendung der OpenTelemetry-Java-Bibliotheken in einem neuen Projekt ist relativ unkompliziert – Sie müssen lediglich die erforderlichen Bibliotheken importieren und die Tracing-API direkt verwenden. Wenn Sie jedoch wie wir Spring verwenden, müssen Sie weitere Entscheidungen treffen.
Spring verfügt bereits über eine API für verteiltes Tracing: Spring Cloud Sleuth. Sie stellt eine Fassade für die zugrunde liegende Implementierung des verteilten Tracings bereit, die laut Dokumentation Folgendes leistet:
- Fügt der Slf4J-MDC Trace- und Span-IDs hinzu, sodass Sie in einem Log-Aggregator alle Protokolleinträge eines bestimmten Traces oder Spans extrahieren können.
- Instrumentiert gängige Ein- und Ausgangspunkte von Spring-Anwendungen (Servlet-Filter, RestTemplate, geplante Aktionen, Nachrichtenkanäle, Feign-Client).
- Wenn
spring-cloud-sleuth-zipkinverfügbar ist, … [generiert und meldet es] Zipkin-kompatible Traces über HTTP. Standardmäßig sendet es diese an einen Zipkin-Collector-Dienst auf localhost (Port 9411). Konfigurieren Sie den Speicherort des Dienstes mitspring.zipkin.baseUrl.
Die API ermöglicht es uns außerdem, Traces zu mit @Scheduled annotierten Aufgaben hinzuzufügen.
Mit anderen Worten: Allein durch den Einsatz von Spring Cloud Sleuth erhalten wir standardmäßig Traces auf der Ebene der HTTP-Service-Endpunkte, was ein großer Vorteil ist. Da unser Projekt bereits Spring verwendet, haben wir uns entschieden, alles innerhalb dieses Frameworks zu belassen und die bereitgestellten Funktionen zu nutzen. Bei der Einbindung aller Komponenten mit Maven sind wir jedoch auf einige Probleme gestoßen:
- Das Spring Cloud Sleuth Autoconfigure-Modul befindet sich noch in einer Milestone-Version.
- Das Spring Cloud Sleuth Autoconfigure-Modul ist von einer veralteten Alpha-Version von
opentelemetry-instrumentation-apiabhängig. Derzeit gibt es keine aktuelle Nicht-Alpha-Version 1.x dieser Bibliothek. - Das Projekt muss das übergeordnete Project Object Model (POM)
spring-cloud-buildaus den Spring-Snapshot-Repositorys abrufen, da Spring Cloud Sleuth seine Abhängigkeitsreferenzen für Meilenstein-Releases auf eine bestimmte Weise definiert.
Dadurch wurde die Definition unseres Maven-Projekts etwas komplizierter, da Maven sowohl auf die Spring-Repositorys als auch auf Maven Central zugreifen musste – ein klares Zeichen dafür, wie unausgereift die OpenTelemetry-Unterstützung in Spring Cloud damals noch war. Dennoch machten wir weiter und erstellten ein gemeinsames Telemetriemodul, um verteiltes Tracing mit Spring Cloud Sleuth und OpenTelemetry zu konfigurieren, einschließlich verschiedener telemetriebezogener Hilfsfunktionen und Erweiterungen.
Im gemeinsamen Telemetriemodul erweitern wir die von den Bibliotheken Spring Cloud Sleuth und OpenTelemetry bereitgestellte Tracing-Funktionalität um folgende Funktionen:
- Spring-fähige
Autokonfigurationsklassen zur Einrichtung von Tracing und erweiterten Funktionen für das Projekt sowie zum Laden zusätzlicher Ressourcenattribute für Traces. - NoOp-Implementierungen der Schnittstellen, sodass wir das Tracing beim Start deaktivieren können, indem wir NoOp-Instanzen in alle Spring-Cloud-Sleuth-Schnittstellen injizieren.
- Ein Interceptor zur Benennung von Traces zur Vereinheitlichung von Trace-Namen.
- Ein Fehlerhandler, der Fehler über slf4j und Spring Cloud Sleuth Tracing ausgibt.
- Eine erweiterte Implementierung von Tracing-Attributen, die zusätzliche Informationen in jeden ausgegebenen Trace codiert, darunter die Dienstversion, die Dienstinstanz-ID, die Rechner-ID, der Pod-Name, die Container-ID, der Containername und der Namespace-Name.
- Ein Tracing Statement Inspector fügt Trace-IDs in Kommentare ein, die den von Hibernate ausgegebenen SQL-Anweisungen vorangestellt werden. Offenbar ist dies inzwischen mit SQLCommenter möglich, aber wir sind noch nicht darauf umgestiegen.
Darüber hinaus haben wir einen Spring-kompatiblen HTTP-Client implementiert, der auf dem Apache HTTP Client basiert, da wir uns bei HTTP-Aufrufen zwischen Diensten mehr Metriken und Anpassungsmöglichkeiten wünschten. Bei dieser Implementierung werden die Trace- und Span-IDs beim Aufruf abhängiger Dienste als Header übergeben, sodass sie in die Tracing-Ausgabe aufgenommen werden können. Außerdem stellt diese Implementierung Metriken für den HTTP-Verbindungspool bereit, die von OpenTelemetry aggregiert werden.
Alles in allem war es ein mühsamer Weg, das Tracing mit Spring Cloud Sleuth und OpenTelemetry einzurichten, aber wir glauben, dass sich der Aufwand gelohnt hat. Wir hoffen, dass dieses Projekt und dieser Beitrag anderen, die diesen Weg einschlagen möchten, als Orientierung dienen.
Verteiltes Tracing für NGINX
Um Traces zu erhalten, die alle Services über den gesamten Lebenszyklus einer Anfrage hinweg miteinander verknüpfen, mussten wir OpenTelemetry in NGINX integrieren. Zu diesem Zweck verwendeten wir das OpenTelemetry-NGINX-Modul (noch in der Betaphase). Da es möglicherweise schwierig sein könnte, funktionsfähige Binärdateien des Moduls zu erhalten, die mit allen NGINX-Versionen kompatibel sind, erstellten wir ein GitHub-Repository mit Container-Images, die nicht unterstützte NGINX-Module enthalten. Wir führen jede Nacht Builds aus und stellen die Binärdatei des Moduls über Docker-Images bereit, aus denen sie sich einfach importieren lässt.
Wir haben diesen Prozess noch nicht in den Build-Prozess für den NGINX Ingress Controller im MARA-Projekt integriert, planen dies aber in Kürze.
Implementierung der Metrikerfassung
Nach Abschluss der OpenTelemetry-Integration für das Tracing konzentrierten wir uns als Nächstes auf Metriken. Für unsere Python-basierten Anwendungen gab es noch keine Metriken, und wir beschlossen, deren Einführung vorerst aufzuschieben. Für die Java-Anwendungen unterstützt der ursprüngliche Bank of Anthos-Quellcode, der Micrometer in Verbindung mit Stackdriver von Google Cloud verwendet, Metriken. Nach dem Fork von Bank of Anthos entfernten wir diesen Code jedoch aus Bank of Sirius, da er es uns nicht ermöglichte, ein Metrik-Backend zu konfigurieren. Die Tatsache, dass bereits Metrik-Hooks vorhanden waren, verdeutlichte dennoch die Notwendigkeit einer ordnungsgemäßen Metrikintegration.
Um eine konfigurierbare und pragmatische Lösung zu finden, betrachteten wir zunächst die Metrikunterstützung in den OpenTelemetry-Java-Bibliotheken und in Micrometer. Bei der Suche nach Vergleichen zwischen den Technologien fanden sich mehrere Ergebnisse, die die Schwächen von OpenTelemetry aufzeigten, wenn es als Metrik-API in der JVM eingesetzt wird, wobei sich OpenTelemetry Metrics zum Zeitpunkt der Erstellung dieses Artikels noch im Alpha-Stadium befand. Micrometer ist eine ausgereifte Metrik-Fassadenschicht für die JVM, ähnlich wie slf4j: Es stellt einen einheitlichen API-Wrapper bereit, hinter dem eine konfigurierbare Metrikimplementierung steht, anstatt selbst eine Metrikimplementierung zu sein. Interessanterweise ist es die standardmäßige Metrik-API für Spring.
Zu diesem Zeitpunkt wogen wir die folgenden Fakten gegeneinander ab:
- Der OpenTelemetry Collector kann Metriken aus nahezu jeder Quelle erfassen, darunter Prometheus, StatsD und das native OpenTelemetry Protocol (OTLP)
- Micrometer unterstützt außerdem eine Vielzahl von Metrik-Backends, darunter Prometheus und StatsD.
- Das OpenTelemetry Metrics SDK für die JVM unterstützt das Senden von Metriken über OTLP
Nach einigen Experimenten entschieden wir, dass der pragmatischste Ansatz darin bestand, die Micrometer-Fassade mit einer Prometheus-Implementierung im Hintergrund zu verwenden und den OpenTelemetry Collector so zu konfigurieren, dass er über die Prometheus-API Metriken aus der Anwendung abruft. Aus zahlreichen Artikeln wussten wir, dass fehlende Metriktypen in OpenTelemetry Probleme verursachen können. Da wir diese Typen für unsere Anwendungsfälle jedoch nicht benötigen, war dieser Kompromiss vertretbar.
Wir haben eine interessante Eigenschaft des OpenTelemetry Collectors entdeckt: Obwohl wir ihn so konfiguriert hatten, dass er Traces über OTLP und Metriken über die Prometheus-API empfängt, lässt er sich dennoch so konfigurieren, dass er beide Datentypen über OTLP oder ein anderes unterstütztes Protokoll an einen externen Datenempfänger sendet. Dadurch konnten wir unsere Anwendung problemlos mit LightStep testen.
Insgesamt war die Implementierung von Metriken in Java recht einfach, da wir sie gemäß der Micrometer-API geschrieben haben, für die zahlreiche Beispiele und Tutorials verfügbar sind. Am schwierigsten war es sowohl bei den Metriken als auch beim Tracing wahrscheinlich, den Maven-Abhängigkeitsgraphen in der Datei pom.xml für telemetry-common korrekt zu konfigurieren.
Implementierung der Fehleraggregation
Die Aggregation von Fehlern gehört zwar nicht ausdrücklich zum Auftrag des OpenTelemetry-Projekts, und auch die Kennzeichnung von Fehlern ist dort nicht so elegant umgesetzt wie bei Lösungen wie Sentry oder Honeybadger.io. Dennoch haben wir uns entschieden, OpenTelemetry kurzfristig für die Fehleraggregation zu verwenden, anstatt ein weiteres Tool hinzuzufügen. Mit einem Tool wie Jaeger können wir nach error=true suchen, um alle Traces mit einem Fehler zu finden. So erhalten wir zumindest einen Eindruck davon, welche Fehler häufig auftreten. Künftig ziehen wir möglicherweise eine Integration von Sentry in Betracht.

Implementierung von Zustandsprüfungen und Laufzeit-Introspektion
Im Kontext unserer Anwendung teilen Health Checks Kubernetes mit, ob ein Dienst fehlerfrei funktioniert oder seine Startphase abgeschlossen hat. Falls der Dienst nicht fehlerfrei funktioniert, kann Kubernetes so konfiguriert werden, dass Instanzen beendet oder neu gestartet werden. In unserer Anwendung haben wir uns gegen die Verwendung von OpenTelemetry Health Checks entschieden, da wir die Dokumentation für unzureichend hielten.
Stattdessen verwenden wir für die JVM-Dienste ein Spring-Projekt namens Spring Boot Actuator, das nicht nur Endpunkte für Zustandsprüfungen, sondern auch Endpunkte für die Laufzeitinspektion und Heap-Dumps bereitstellt. Für die Python-Dienste verwenden wir das Flask Management Endpoints module, das einen Teil der Funktionen von Spring Boot Actuator bereitstellt. Derzeit bietet es lediglich anpassbare Anwendungsinformationen und Zustandsprüfungen.
Spring Boot Actuator bindet sich in die JVM und Spring ein und stellt Endpunkte für Introspektion, Überwachung und Zustandsprüfungen bereit. Darüber hinaus bietet es ein Framework, mit dem sich die an seinen Endpunkten bereitgestellten Standarddaten um benutzerdefinierte Informationen ergänzen lassen. Die Endpunkte ermöglichen zur Laufzeit Einblicke in Aspekte wie Cache-Status, Laufzeitumgebung, Datenbankmigrationen, Zustandsprüfungen, anpassbare Anwendungsinformationen, Metriken, periodische Aufgaben, den Status von HTTP-Sitzungen und Thread-Dumps.
Die von Spring Boot Actuator implementierten Endpunkte für Zustandsprüfungen sind modular konfiguriert, sodass sich der Zustand eines Dienstes aus mehreren Einzelprüfungen zusammensetzen kann, die entweder der Verfügbarkeit oder der Betriebsbereitschaft zugeordnet werden. Darüber hinaus ist eine vollständige Zustandsprüfung verfügbar, die alle Prüfungsmodule anzeigt und üblicherweise so aussieht.
Die Informationsendpunkte werden in einem JSON-Dokument als einzelnes übergeordnetes JSON-Objekt mit einer Reihe hierarchischer Schlüssel und Werte definiert. In der Regel enthält das Dokument den Namen und die Version des Dienstes, die Architektur, den Hostnamen, Informationen zum Betriebssystem, die Prozess-ID, den Namen der ausführbaren Datei sowie Details zum Host, beispielsweise eine Rechner-ID oder eine eindeutige Dienst-ID.
Implementieren von Heap- und Core-Dumps
Wie Sie sich vielleicht aus der Tabelle in Erinnerung rufen, Vergleich der Tool-Funktionen mit der Wunschliste unterstützt keines der Tools Laufzeit-Introspektion oder Heap-/Core-Dumps. Spring als zugrunde liegendes Framework unterstützt jedoch beides – allerdings war einiger Aufwand erforderlich, um die Observability-Funktionen in die Anwendung zu integrieren. Wie im vorherigen Abschnitt beschrieben, verwenden wir für die Laufzeit-Introspektion ein Python-Modul in Verbindung mit Spring Boot Actuator.
Ebenso verwenden wir für Heap-Dumps die von Spring Boot Actuator bereitgestellten Thread-Dump-Endpunkte, um einen Teil der gewünschten Funktionen umzusetzen. Wir können weder bei Bedarf Core-Dumps noch Heap-Dumps der JVM mit der idealen Detailgenauigkeit erstellen, erhalten aber mit geringem Zusatzaufwand einen Teil der gewünschten Funktionalität. Core-Dumps für die Python-Dienste erfordern leider einen erheblichen zusätzlichen Aufwand, den wir auf einen späteren Zeitpunkt verschoben haben.
Zusammenfassung
Nach vielen Tränen und Zweifeln an unseren Lebensentscheidungen haben wir uns für den Einsatz der folgenden Technologien zur Observability in der MARA entschieden (im Folgenden steht „OTel“ für OpenTelemetry):
- Protokollierung (für alle Container) – Filebeat → Elasticsearch / Kibana
- Verteiltes Tracing
- Java – Spring Cloud Sleuth → Spring Cloud Sleuth-Exporter für OTel → OTel Collector → austauschbare Exporter wie Jaeger, Lightstep, Splunk usw.
- Python – OTel-Python-Bibliotheken → OTel Collector → austauschbarer Speicher
- NGINX und NGINX Plus (NGINX Ingress Controller wird noch nicht unterstützt) – NGINX-OTel-Modul → OTel Collector → austauschbarer Exporter
- Metrikerfassung
- Java – Micrometer über Spring → Prometheus-Exporter → OTel Collector
- Python – Noch keine Implementierung vorhanden
- Python WSGI – GUnicorn StatsD → Prometheus (über StatsD / ServiceMonitor)
- NGINX – Prometheus-Endpunkt → Prometheus (über ServiceMonitor)
- Fehleraggregation – verteilte OTel-Traces → austauschbarer Exporter → Suchfunktion des Exporters zum Auffinden von Traces mit der Kennzeichnung
error - Integritätsprüfungen
- Java – Spring Boot Actuator → Kubernetes
- Python – Flask-Modul für Management-Endpunkte → Kubernetes
- Laufzeit-Introspektion
- Java – Spring Boot Actuator
- Python – Flask-Modul für Verwaltungsendpunkte
- Heap-/Core-Dumps
- Java – Spring Boot Actuator-Unterstützung für Thread-Dumps
- Python – Noch nicht unterstützt
Diese Implementierung ist eine Momentaufnahme. Sie wird sich im Laufe der weiteren Entwicklung definitiv verändern und weiterentwickeln. In Kürze werden wir die Anwendung umfangreichen Lasttests unterziehen. Wir gehen davon aus, dabei viel über die Schwächen unseres Observability-Ansatzes zu lernen und zusätzliche Observability-Funktionen hinzuzufügen.
Probieren Sie bitte die Referenzarchitektur für moderne Anwendungen und die Beispielanwendung (Bank of Sirius) aus. Wenn Sie Ideen haben, wie wir uns verbessern können, freuen wir uns über Ihre Beiträge in unserem GitHub-Repository!
Ähnliche Beiträge
Dieser Beitrag ist Teil einer Reihe. Während wir MARA nach und nach um neue Funktionen erweitern, veröffentlichen wir die Details dazu im Blog:
- Eine neue Open-Source-Referenzarchitektur für moderne Anwendungen
- Integration von OpenTelemetry in die Referenzarchitektur für moderne Anwendungen – ein Fortschrittsbericht (dieser Beitrag)
- MARA: Jetzt auch auf einem Arbeitsplatzrechner in Ihrer Nähe
- Ankündigung von Version 1.0.0 der NGINX Modern Apps Reference Architecture
Ä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.
