OpenTelemetry funktioniert – aber warum ist es so kompliziert?
(iconsolutions.com)- Die Anfrage eines Kunden nach OpenTelemetry-Unterstützung zeigte, dass die bestehende Observability auf Basis von Prometheus, Jaeger und OpenTracing nicht vollständig den OTel-Anforderungen für Metriken, Logs und Traces entsprach
- Logs und Metriken ließen sich durch das Hinzufügen eines Appenders und zusätzlicher Konfigurationen relativ einfach migrieren, aber verteiltes Tracing war deutlich schwieriger, weil Spring und Akka innerhalb derselben JVM denselben Trace-Kontext gemeinsam nutzen mussten
- OTel und Lightbend Telemetry/OpenTracing verwenden unterschiedliche Tracer-APIs und SpanContext-Implementierungen, wodurch dieselbe Transaktion in separate Traces zerfiel
- Die Lösung bestand darin, den OTel-Kontext in eine Java-
Mapzu injecten, ihn als Jaeger-SpanContext zu extrahieren und dann mitGlobalExtendedTracer.get().local().activateContext()von Lightbend manuell zu aktivieren - OTel hilft bei der Standardisierung von Observability, aber beim Mischen mit bestehender Akka- und Jaeger-basierter Instrumentierung kann eine explizite Verknüpfung der Kontexte nötig sein
Die Observability-Signale, die OTel vereinheitlichen will
- OpenTelemetry (OTel) ist ein Observability-Framework und Toolkit, das zuvor getrennte Tool-Bereiche über die drei Signale Metriken, Logs und Traces standardisieren will
- Die bisherige Kombination war nach Rollen aufgeteilt
- Prometheus: Metriken
- Zentrale Aggregatoren wie Logstash und Elasticsearch: Logs
- OpenTracing: verteiltes Tracing
- OTel liefert nicht nur Spezifikationen, sondern auch zentrale Komponenten
- OpenTelemetry Protocol (OTLP): das Protokoll, mit dem Anwendungen Telemetriedaten melden
- OpenTelemetry Collector: eine herstellerneutrale Komponente zum Empfangen, Verarbeiten und Exportieren von Telemetriedaten
- SDKs für mehr als 10 Sprachen: Implementierungen für OTLP und Telemetry-Export
- Anwendungen senden Signale über automatische Instrumentierung, APIs und Sprach-SDKs an den OTel Collector; auch die Infrastruktur kann Signale an den Collector senden
Die bestehende Unterstützung in IPF und die Kundenanfrage
- IPF hatte bereits ELK-Stack-Integration, Monitoring und Observability-Dokumentation, und Kunden nutzten dies schon in Produktionsumgebungen
- Aufgrund der Natur des Frameworks war nicht absehbar, in welche Umgebungen Anwendungen mit der Bibliothek deployt werden würden; deshalb durfte kein bestimmter Vendor erzwungen werden und es mussten Abstraktionen sowie überschreibbare Defaults bereitgestellt werden
- Die Standardempfehlung war, Logs an Aggregatoren wie Elasticsearch oder LogScale zu senden und Prometheus als de-facto-Standard für Metriken zu aktivieren und zu unterstützen
- Kunden fragten nahezu gleichzeitig in drei Fällen OTel-basiertes Tracing an, und im Zuge dessen entstand auch der Trend, Logs und Metriken gemeinsam auf OTel umzustellen
- Logs und Metriken ließen sich mit einem neuen Appender und zusätzlicher Konfiguration relativ einfach umsetzen, doch Tracing blieb ein separates Problem
Warum Kontextpropagierung im verteilten Tracing nötig ist
- Um in einem verteilten System eine einzelne Transaktion nachzuverfolgen, müssen Informationen weitergegeben werden, die bestimmte Aufrufe und die Transaktion zwischen Systemen verknüpfen; das nennt man context propagation
- Ein Trace ist eine übergeordnete Struktur, die mehrere Arbeitseinheiten, sogenannte Spans, umfasst; es kann mehrere Spans geben, und sie können verschachtelt sein
- In einer E-Commerce-Site kann der Ablauf nach einem Klick auf den „buy now“-Button zu einem einzigen Trace zusammengefasst werden
- ein Span für die Kommunikation des Frontends mit dem Backend
- Kind-Spans für die Kommunikation des Backends mit Zahlungs-, Versand- und Auftragsverwaltungssystemen
- zusätzliche Kind-Spans, wenn die jeweiligen Downstream-Services wiederum mit anderen Systemen kommunizieren
- Bei Verwendung von OTel meldet jeder verteilte Service seinen Teil des Trace an den OTel Collector, und OTel setzt den Gesamtfluss unter einer einzigen Trace-ID zusammen
Vier Standards für Kontextpropagierung und das Erbe von OpenTracing
- OTel muss vier Implementierungen der Kontextpropagierung unterstützen
- Der Akka-Bereich von IPF unterstützte bereits das zur Ausmusterung vorgesehene OpenTracing und nutzte die OpenTracing-Unterstützung von Lightbend Telemetry
- Lightbend Telemetry 2.20.0 ergänzte Unterstützung für OpenTelemetry-Logs/Events und Metriken, aber die entscheidende Tracing-Unterstützung fehlte
- Theoretisch schien OTel-Tracing dem Rebranding von OpenTracing nahezukommen, und auch das OpenTracing von Lightbend Telemetry unterstützte die vier Propagierungsformate, sodass es funktionieren sollte – in der Praxis sah das Ergebnis jedoch anders aus
Der unterbrochene Trace zwischen Spring und Akka
- IPF verwendet Spring und Akka gemeinsam
- Spring Boot und Spring IoC: Bootstrap der Anwendung, Konfiguration und Verdrahtung von Abhängigkeiten
- Akka: Event Sourcing, Scheduling, Clustering, Sharding, Integration usw.
- Kunden können einen Zahlungsablauf über einen Spring-REST-Controller oder eine mit
@KafkaListenerannotierte Methode starten - Vor OTel funktionierten die drei Bereiche ohne größere Konflikte
- Metrics: Spring und Akka stellten unterschiedliche Prometheus-Endpunkte bereit
- Logs: Beide Frameworks verwendeten SLF4J und Logback
- Traces: Beide verwendeten OpenTracing
- Mit OTel wurden Metriken und Logs korrekt übertragen, weil Spring und Akka unabhängig voneinander arbeiteten; beim Tracing musste jedoch innerhalb der JVM derselbe Trace-Kontext gemeinsam genutzt werden
- Das eigentliche Problem war, dass innerhalb der Anwendung zwei unterschiedliche APIs dasselbe Tracing-Konzept ausdrückten, aber nicht miteinander kommunizierten
- Das korrekte Verhalten wäre gewesen, dass der Akka-HTTP-Client die vorhandene Trace-ID
123wiederverwendet und nur eine neue Span-ID erzeugt, sodass eine neue Arbeitseinheit innerhalb desselben Trace entsteht; tatsächlich wurden jedoch zwei voneinander unabhängige Traces erzeugt
opentracing-shim und Konflikte zwischen Implementierungen
- Auf OTel-Seite gibt es das
opentracing-shim, das ein OTel-Tracerwie einen OpenTracing-Traceraussehen lässt - Lightbend Telemetry verwendete jedoch eine benutzerdefinierte Tracer-Implementierung, weshalb sowohl das Shim als auch Jaeger scheiterten
- In den Logs erschienen die folgenden Fehler
Expected to have an OpenTelemetry Span but got cinnamon.opentracing.TraceLocal$ContextOnlySpanExpected to have a JaegerSpanContext but got io.opentelemetry.opentracingshim.SpanContextShim
- Jaeger erhielt den OTel-Shim-Kontext und scheiterte, OTel erhielt den Lightbend-Kontext und scheiterte ebenfalls
Untersuchung von Java Agent und internem Tracing von Lightbend
- Sowohl die OTel- als auch die Lightbend-Telemetry-Instrumentierung nutzen einen Java Agent, um sich in Aufrufe bestimmter Methoden bestimmter Klassen einzuhängen und Aktivitäten an den Tracer zu melden
- Die Instrumentierung von Lightbend Telemetry ist nicht Open Source; um das Verhalten zu verstehen, musste daher der dekompilierte Tracer-Code untersucht werden
- Mit einem verkleinerten Reproduktionsbeispiel wurde festgestellt, an welcher Stelle der OTel-Trace-Kontext auf der Spring-Seite beim Übergang zur Akka-Seite verloren ging
- Die Stelle, an der Jaeger scheiterte, war dieser Code in
JaegerTracer.java - Der Aufrufer war die proprietäre Instrumentierungsklasse
OpenTracingAkkaPersistenceActorInstrumentation.classvon Lightbend Telemetry - Im dekompilierten Code war der entscheidende Faktor der Zustand von
var5- Wenn
var5, ein OpenTracing-SpanContext, nicht null war, wurde der neue Span als Kind des aktuell aktiven Span angehängt - Wenn
var5null war, entstand ein Span ohne Beziehung zum bestehenden Trace - Wenn
var5kein Jaeger-SpanContext war, schlug Jaeger fehl
- Wenn
OTel und OpenTracing per manueller Konvertierung verbinden
- Der
SpanContextwurde austhis.traceLocal.currentContext()geholt, undtraceLocalwurde imExtendedTracervon Lightbend initialisiert - In der Lightbend-Dokumentation gab es eine Möglichkeit, global auf den
ExtendedTracerzuzugreifenGlobalExtendedTracer.get()
GlobalExtendedTracer.get()liefert einenExtendedTracerzurück, undlocal()gibt denselbenTraceLocalzurück, den auch die Instrumentierungsbibliothek verwendet- Dieser
TraceLocalbesitzt die MethodeactivateContext, die einen OpenTracing-SpanContextentgegennimmt - Der Lösungsablauf bestand aus vier Schritten
- Das OTel-Shim nicht verwenden
- Den aktuellen OpenTelemetry-Kontext in eine Java-
Mapinjecten - Mit den Werten aus dieser
Mapeinen Jaeger-SpanContext extrahieren - Vor dem Eintritt in Akka den Jaeger-SpanContext im
TraceLocalvon Lightbend aktivieren
- Verwendet wurden dafür die OTel-Propagators-API-Operationen inject and extract operations
- Der zentrale Code folgt diesem Ablauf
- eine leere
HashMaperzeugen - OTel-Kontext mit
GlobalOpenTelemetry.get().getPropagators().getTextMapPropagator().inject(...)in die Map injizieren - mit
new TextMapCodec(false).extract(new TextMapAdapter(otelContext))einen JaegerSpanContext erzeugen - den Kontext mit
GlobalExtendedTracer.get().local().activateContext(openTracingContext)vor dem Eintritt in Akka aktivieren
- eine leere
Bestätigtes Verhalten und Empfehlungen für den Betrieb
- Nach der manuellen Verknüpfung setzte sich der Trace wie erwartet fort
- Beginnend mit dem Spring-REST-Aufruf
/submitwar der gesamte Ablauf zu einem einzigen Trace verbunden - Mit der OTel-API instrumentierte Teile und mit der OpenTracing-API instrumentierte Teile konnten gemischt verwendet werden
- Der Trace wurde über HTTP-Grenzen hinweg propagiert
- Beginnend mit dem Spring-REST-Aufruf
- Im Beispiel wurden unterschiedliche Namen verwendet, um die beiden Instrumentierungsarten zu unterscheiden; in realen Kundenumgebungen wird jedoch empfohlen,
otel.service.nameundcinnamon.applicationaufeinander abzustimmen, damit die Anwendung nach außen als eine einzige erscheint
Warum es so kompliziert war und welche Bedenken bleiben
- Um Lightbend Telemetry auf die OTel-API umzuschreiben, müsste viel bestehende, an die Jaeger-API gebundene Instrumentierung auf die OTel-API portiert werden; das kann ein großer Aufwand sein
- Der OTel Collector unterstützt das Einsammeln von Traces im Legacy-Zipkin-Format, sodass Lightbend über diese Legacy-Unterstützung vollständige OTel-Unterstützung anbieten könnte
- Die unmittelbare Ursache der Komplexität lag darin, zwei Instrumentierungssysteme zu kombinieren, die unterschiedliche Tracing-Bibliotheken verwenden
- Das OTel-Projekt versucht mit Standardisierungsansätzen wie Semantic Conventions, den Observability-Bereich zu ordnen; es ist anfangs etwas komplex zu verstehen, wird aber als nützliches FOSS-Projekt bewertet
- Es bleibt die Sorge, ob Akka den Trace-Kontext innerhalb des Actor-Modells korrekt zwischen Threads weitergibt
- In kleinen Lasttests funktionierte es wie erwartet
- Ein entsprechendes Ticket bei Akka ist bereits eröffnet
1 Kommentare
Meinungen auf Hacker News
Während ich Otel lernte und portierte, fühlte es sich die ganze Zeit so an, als wäre ich wieder in der Java-Welt gelandet. Jedes Mal, wenn ich den Code Zeile für Zeile nachvollzog, wirkte es wie EnterpriseFizzBuzz, es gab keinerlei Discoverability, und die eigene Terminologie sah aus, als hätten sie Leute erfunden, die auf irgendetwas drauf waren.
In NodeJS lag die CPU-Auslastung etwa viermal so hoch wie bei StatsD, und am Ende habe ich eine eigene Aggregation gebaut, um die Nutzung zu senken und auch die Tag-Explosion zu reduzieren. Bei StatsD ist es in Ordnung, wenn mehrere Prozesse dieselben Tags melden, aber OTEL überschreibt sie einfach.
Unter Spitzenlast lief ein CPU-Kern bei 60–80 % Auslastung, und bevor sich etwas änderte, konnten wir nicht einmal vertikal skalieren. Für Sprachen, die einen Prozess pro Core verwenden, wirkt OTEL geradezu aktiv feindlich, fast wie ein Witz. Man fährt besser damit, einfach Prometheus zu verwenden; faktisch gibt es auch keine anderen Wettbewerber.
Als ich in .NET eine einfache otel-Konfiguration ausprobieren wollte, las ich stundenlang die Doku des Vendors, den meine Organisation ausgewählt hatte, und verstand sie trotzdem nicht. Also ging ich in einen Discord, den ein Kollege betreibt. Ein Teil von deren Geschäftsmodell war „Zahl, wenn du ordentliches otel in einem Open-Source-Produkt nutzen willst“, und mir war sofort klar, dass es den Preis wert wäre, egal wie hoch er ist.
Statt eine OTEL-Implementierung zu bauen, würde ich lieber ohne Vorerfahrung noch eine stabile Event-/Publish-Subscribe-Library entwickeln.
Auch die Minimalkonfiguration ist tatsächlich ziemlich klein: Web-UI, Serverinstanz und eine DB, die man bereits kennt, reichen: https://skywalking.apache.org/docs/main/latest/en/setup/back...
Es hat gewisse Ähnlichkeit mit Zabbix im Monitoring-Bereich. Beides wird niemanden vom Hocker hauen, ist aber praktisch genug.
process.vpidangelegt. Man kann es etwa über ein globales Objekt so koordinieren, dass der Wert während der Lebensdauer der App eindeutig ist.Danach braucht man etwas, das aufsummiert und dieses Attribut entfernt. Bei statsd/delta bringt ein verlorenes Signal die gesamten Daten durcheinander, beim kumulativen Ansatz verliert man nur Präzision.
Mein Anwendungsfall sind Push-basierte Metriken aus „Batch“-Tools, keine scrapebaren, lang laufenden Prozesse.
Otel wirkt komplex, weil verschiedene Observability-Vendors die Umsetzung von Observability mit ihren proprietären SDKs, Agents und APIs extrem einfach gemacht haben. Otel versucht, dieses Problem zu lösen, und ich finde, die Leute dahinter machen das hervorragend.
Es ist auch lobenswert, dass Grafana OpenTelemetry als erstklassigen Bestandteil des Ökosystems angenommen hat.
Ich habe jahrelang die Nutzung von Datadog vorangetrieben, aber für eine Größenordnung zwischen Mittelstand und Großunternehmen wurde der Preis schwer tragbar. Mit der Zeit, als die OpenTelemetry-APIs und -SDKs stabiler wurden, machten wir es zum Standard für Application Observability.
Allerdings könnte die Dokumentation insgesamt besser sein, und dass die Onboarding-Dokumente je nach Sprache unterschiedlich sind, ist nicht ideal.
Unser Team nutzt derzeit einen NodeJS/Typescript-Stack, und wir haben ein Paket-Bundle sowie einen beispielhaften Grafana-Stack gebaut, damit man schnell mit OpenTelemetry starten kann: https://github.com/zonneplan/open-telemetry-js
Das größte Problem bei allen APM-Vendors ist: Sobald man mit einem magischen Agent Kernel-Hooks bekommt, passieren alle möglichen Dinge, die Entwickler nicht erklären können.
In einem früheren Unternehmen führten wir Dynatrace ein; zum Glück hatte die App bereits ausreichend eingebaute Metriken, sodass der Lead-SRE sie als „vorbildlich“ instrumentiert ansah. Doch sobald der Dynatrace-Agent auf den App-Hosts installiert war, tauchten mehrere Heisenbugs auf, die Node-Neustarts erforderten, und auch Performance-Einbußen wurden direkt gemessen.
Ironischerweise haben uns die Metriken den Schmerz erspart, aber niemand wusste, wie man das beheben sollte. Am schlimmsten war der Fall, dass bei MSSQL-Updates ein Failover den ADO.NET-Connection-Pool auf seltsame Weise kontaminierte.
Die Struktur wird nur so komplex, wie es nötig ist. Man kann magische Funktionen vermeiden und nur die Teilmenge nutzen, die im jeweiligen Kontext den größten Wert liefert und leicht zu verstehen ist.
In unserem Team ist es sehr simpel. Wir verwenden eine Library, die nur Traces sendet; Traces liefern den größten Mehrwert für die Beobachtbarkeit von Anwendungen und können auch andere Datentypen enthalten. Im Grunde verwendet man also Hashmaps statt Strings und Floats.
Statt automatischer Instrumentierung nutzen wir manuelle Instrumentierung, legen bewusst fest, was wir beobachten wollen, und verstehen gut, welcher Code Spans ausgibt. Außerdem haben wir Namenskonventionen, die zur Code-Struktur passen.
Als Backend nutzen wir einen günstigen Third-Party-Service zusammen mit einer All-in-one-Jaeger-Installation für die lokale Entwicklung. Letztere braucht nur eine einzelne ausführbare Datei oder einen einzelnen Docker-Container und speichert Spans nicht auf der Festplatte. Sie dient vor allem dazu, Teammitglieder zu beruhigen, damit sie den Third-Party-Service nicht überfluten.
Für Infrastruktur-Monitoring haben wir ein bestehendes Setup, und in unserem Fall sehen wir keinen großen Mehrwert darin, alle Infrastruktur-Logs und -Metriken zu sammeln. OTEL-Metriken und -Logs halte ich noch für in einer frühen Phase, auch wenn die Anbieter das nicht so sagen.
Ich suche noch nach einem Endpoint, um einfache einmalige Metriken aus Teilen der Infrastruktur zu senden, die sich noch nicht scrapen lassen.
Otel kann für neue Projekte okay sein, aber es in einem produktiven Service mit bereits vorhandener Telemetrie einzuschalten, fühlte sich an, als würde man bei einem fahrenden Auto die Reifen wechseln.
Ich glaube gern, dass es in einem Greenfield-Projekt, das nur otel und keine Nicht-otel-Frameworks verwendet, gut sein kann. Aber in so einer Welt lebe ich noch nicht.
Eines der größten Probleme war die lokale Developer Experience. Ich wollte lokal Logs, Traces und Metriken unterstützen, aber dafür nicht jede Menge Docker-Images hochfahren. Vor dem Deployment wollte ich in Logs sehen, wie Metriken, Traces, Baggage und Activity-Spans aussehen.
Kürzlich hat das .NET-Team .NET Aspire veröffentlicht, und das ist sehr gut. Im lokalen Entwicklungs-Stack lässt sich alles leicht an einem Ort visualisieren, und es funktioniert wie ein Orchestrator in Code.
Beim Deployment auf k8s richtet man den OTEL-Endpoint auf den DataDog Agent, und alles funktioniert einfach. Wir vermeiden die Custom-Trace-Libraries und SDKs von DataDog und nutzen nur OTEL.
Die Developer Experience ist inzwischen ziemlich gut.
https://learn.microsoft.com/en-us/dotnet/aspire/fundamentals...
https://docs.datadoghq.com/opentelemetry/#overview
Die Einrichtung auf der lokalen Entwicklungsmaschine dauert beim ersten Mal 5 Minuten, danach reicht es, in einem separaten Terminal-Tab
/path/to/openobserveauszuführen. Wenn man die enorme Komplexität eines einzelnen statisch gelinkten Binary vermeiden will, gibt es auch Docker-Images für lokale und entfernte Ausführung :PEs ist ein All-in-one-OpenTelemetry-Backend mit hübschen Graphen, und in meinen Projekten ist es bisher nie auf eine für mich erkennbare Weise ausgefallen.
Mit Aspire fügt man auf App-Ebene unnötige Komplexität hinzu und bindet sich an ein enges Ökosystem. Für lokale Entwicklung gibt es viele bewährte Alternativen wie docker compose, und Aspire ist auch nicht wirklich deutlich einfacher als docker compose und Umgebungsvariablen.
Wenn man otel in Python macht, sollte man den Client von Logfire verwenden. Das gilt auch dann, wenn man den Logfire-Service nicht nutzt.
Er ist Open Source und kann an jeden otel-kompatiblen Endpoint senden. Außerdem ist der vom pydantic-Team entwickelte Client zehnmal besser und einfacher als die offiziellen otel-Libraries.
Es gibt auch ein interessantes Interview, in dem Samuel Colvin erklärt, wie es dazu kam: https://www.bitecode.dev/p/samuel-colvin-on-logfire-mixing-p...
Deshalb habe ich ein Open-Source-Projekt gestartet, das darauf abzielt, die Einführung von OpenTelemetry so einfach wie einen einzigen Befehl zu machen: https://github.com/odigos-io/odigos
Heutzutage übernehmen viele Web-Frameworks den Großteil der Instrumentierung. Wenn man zum Beispiel opentelemetry-js nutzt und etwas wie https://signoz.io selbst hostet, kann man es in weniger als einer Stunde zum Laufen bringen und bekommt auch ohne eigenen Code viele Daten
OpenTelemetry ist aus Traces heraus gewachsen, aber Metrics und Logs sollte man viel besser spezialisierten Lösungen überlassen
Es fühlt sich wie das Problem einer „leaky abstraction“ oder eines „leaky framework“ an. Wenn man alles unter ein Dach bringen will, kann eine SQL-Datenbank das alles auch gleichzeitig. Das heißt aber nicht, dass man es tun sollte
https://cra.mr/the-problem-with-otel/
Trotzdem mag ich den Umgang mit OTel jedes Mal immer noch nicht
Wenn man bis zum Ende liest, merkt man, dass der Schmerz größtenteils selbst verursacht ist. Im Standard-Stack von Python (mysql, flask, redis, requests usw.) war es sehr einfach. Ein paar Imports oben im Service, und alles verband sich automatisch und verfolgte ohne großes Aufheben alles
aiohttpist zum Beispiel in der neuesten Version bei 3.11.X, und die Auto-Instrumentierung gibt zwar an,3.Xzu unterstützen [0], aber das tatsächliche Ergebnis hängt davon ab, wie aktuell das verwendeteaiohttpist und in welchem Zustand die Auto-Instrumentierung selbst istWenn alles zusammenpasst, funktioniert es wie Magie, aber dieses Nadelöhr ist ziemlich eng
[0]: https://github.com/open-telemetry/opentelemetry-python-contr...
Traces funktionieren, der spanmetrics exporter ist ebenfalls konfiguriert, und wenn ich direkt in prometheus abfrage, sehe ich die spanmetrics. Aber egal, was ich tue: Im „monitor“-Tab von jaeger tauchen sie nicht auf
Nachdem ich drei Tage damit verbracht hatte, sagte mein Chef: „Warum instrumentieren wir nicht einfach manuell, schicken alles an einen SQL Server und bauen ein Grafana-Dashboard?“ — aber das möchte ich auch nicht
Selbst der einfachste Use Case funktioniert nicht. Muss ich jetzt noch Grafana obendrauf setzen?
Der Grund für die Komplexität ist, dass es nicht für die Engineers entwickelt wurde, die es implementieren, sondern für Unternehmen, die Otel-kompatible Software verkaufen
Eher wirkten die Backends bei der Einführung ziemlich langsam
Bedeutungen ändern sich, also ist das in Ordnung, aber es ist interessant, dass uns jetzt ein Wort für Software gemäß einer Spezifikation schreiben fehlt und nur noch „vorhandene Software auf einem Server bereitstellen“ übrig geblieben ist