Anzeige
Anzeige
How-To
Artikel merken

Microservices: Mit Tools wie Jaeger die Übersicht behalten

In komplexen Microservice-Architekturen verliert selbst der Senior-Entwickler schnell die Übersicht. Um nachvollziehen zu können, welchen Weg eine einzelne ­Nutzeranfrage eigentlich nimmt und welche Services darin involviert sind, haben sich Tools wie Jaeger etabliert. So funktioniert das Werkzeug für „Distributed Tracing“.

Von Martin Helmich
6 Min. Lesezeit
Anzeige
Anzeige

(Abbildung: Shutterstock)


Die Microservice-Architektur wird derzeit als Allheilmittel für fast alle Probleme in der Softwareentwicklung gehandelt. Tatsächlich löst die Aufteilung einer komplexen Anwendung in ­zahlreiche isolierte und unabhängige Komponenten viele ­Probleme, schafft aber auch neue. Schwierig wird es beispielsweise, wenn ­Microservices sich gegenseitig aufrufen. ­Abbildung 1 zeigt, wie eine einzelne Nutzeranfrage eine Vielzahl an ­Anfragen auslöst, die von einem Microservice zum anderen gestellt werden.

In komplexeren Architekturen wird dieses Geflecht irgendwann ziemlich unübersichtlich. Ein Entwickler, der im obigen Beispiel den „Bestellabwicklungs“-Microservice aufruft, weiß wo­möglich gar nicht mehr, dass dieser wiederum mit dem „USt.ID“-Microservice kommuniziert, der wiederum eine externe API anbindet.

Abbildung 1:Beispiel einer Microservice-­Architektur, in der eine eingehende Nutzeranfrage („Zahlungsdaten aktualisieren“) für eine Vielzahl an Anfragen der Microservices untereinander sorgt. (Grafik: Martin Helmich / t3n)

Anzeige
Anzeige

Diese Komplexität wird genau dann zum Problem, wenn einmal etwas nicht so läuft, wie es soll: Wenn beispielsweise die Verarbeitungsdauer für „Zahlungsdaten aktualisieren“-­Anfragen immer länger und länger wird oder diese Anfragen nur noch Fehler­meldungen auslösen. Dann muss zunächst einmal die ­Ursache (der „Root Cause“) des Problems gefunden werden – und das kann schwierig werden.

Deshalb ist es hilfreich, den vollständigen Weg einer einzelnen Nutzeranfrage über alle beteiligten Services hinweg nachvollziehen zu können. Idealerweise mit den auftretenden Fehlern und mit Informationen darüber, wie lange die einzelnen Operatio­nen gedauert haben. Genau diese Aufgabe erfüllen Tracing-Tools wie etwa das quelloffene Jaeger.

Anzeige
Anzeige

Wie funktioniert ein Tracer?

Tracing-Tools können feingranulare Informationen über den ­genauen Ablauf einer einzelnen Abfrage verarbeiten. Sie stellen dem Entwickler Informationen darüber zur Verfügung, wie lange ein Service für externe Aufrufe, etwa zu anderen Services oder externen API oder für Datenbankabfragen braucht.

Anzeige
Anzeige

Um diese Details bereitstellen zu können, muss in der ­Regel der Quelltext des jeweiligen Services angepasst werden. So kann jeder Service zunächst für sich selbst sogenannte „Spans“ er­fassen, also die Zeit, die er mit bestimmten Operationen verbracht hat. Spans können auch verschachtelt werden, um sichtbar zu machen, dass eine übergeordnete Span (beispiels­weise die Bearbeitung einer einzelnen ­HTTP-Anfrage in einem Service) aus mehreren untergeordneten Einzel­operationen (wie etwa ­einzelnen Datenbankabfragen) besteht.

Der Anbieter des Tracers, also etwa des Jaeger-Projekts, stellt die Bibliotheken bereit, mit denen der Nutzer die Informationen in seinem Programm selbst erfassen kann. Die gesammelten Spans werden vom Service regelmäßig nach jedem Request an den Tracer übermittelt. Diese kann je nach gewähltem Anbieter entweder selbst gehostet werden oder als SaaS-Produkt in der Cloud laufen.

Anzeige
Anzeige

Um eine Nutzeranfrage serviceübergreifend nachverfolgen zu können, muss der Tracer die Spans, die er von verschiedenen ­Services empfangen hat, anhand der ursprünglichen Nutzer­anfrage wieder zusammenfügen. Abbildung 2 zeigt den üblichen Ablauf.

Hierbei erstellt der erste Service, der eine eingehende Nutzer­anfrage verarbeitet (im Beispiel oben also der Stammdaten-­Service) eine eindeutige Trace-ID. Diese ID muss der Service dann in Anfragen einbetten, die er an nachgelagerte Services stellt. Wie genau das funktioniert, hängt vom Kommunikations­protokoll ab, über das die Services miteinander kommunizieren – beispiels­weise also ein HTTP-Header oder ein gRPC­-Metadatum.

Nachgelagerte Services können diese Trace-ID dann ­wieder aus den eingehenden Anfragen extrahieren und ihrerseits weiter­verarbeiten und in andere Anfragen einbetten. Auf ­diese Weise ist es möglich, dass sämtliche Spans, die von irgend­einem Service erfasst werden, mit derselben Trace-ID zurück an den Tracer übermittelt werden. Dieser kann dann einen service­übergreifenden Trace erstellen. Abbildung 3 zeigt, wie das in der Jaeger-Nutzeroberfläche aussieht.

Anzeige
Anzeige

Neben Jaeger gibt es noch weitere Werkzeuge am Markt, die ähnliche Funktionen anbieten. Andere Open-Source-Vertreter sind beispielsweise Elastic APM oder Apache Skywalking. Neben den Lösungen zum Selbst-Hosten gibt es auch SaaS-Angebote aus der Cloud, wie etwa Datadog oder Instana.

Um die eigene Software dazu zu bringen, Tracing-­Informationen zu einem Tracer zu senden, sind – je nach ge­wünschtem Detailgrad – einige Anpassungen am Quelltext notwendig. Damit sich ­Entwickler damit nicht auf alle Zeiten auf eine bestimmte Tracing-Software festlegen müssen, gibt es den Opentracing-­Standard, verfügbar in neun Programmiersprachen. Dabei handelt es sich um einen Satz an Programmierschnittstellen, die als Bibliothek in der eigenen Software installiert werden können. Die Client-Bibliotheken aller Tracer, die den Standard unter­stützen, implementieren diese Interfaces, sodass es auch im Nachhinein recht einfach möglich ist, einen Tracer durch einen anderen auszutauschen.

Lokale Installation von Jaeger

Die Architektur von Jaeger selbst ist nicht ganz einfach: Ein für den Produktivbetrieb geeignetes Jaeger-Setup besteht aus ­mehreren einzelnen Jaeger-Komponenten, und benötigt zudem ein Elasticsearch- oder Cassandra-Cluster als Datenspeicher ­sowie optional ein Kafka-Cluster für den Datenaustausch.

Anzeige
Anzeige

Abbildung 2: Über eine initial vergebene Trace-ID können von verschiedenen Services erfasste Informationen nachträglich wieder zusammengeführt werden. (Grafik: Martin Helmich / t3n)

Wer Jaeger ausprobieren oder schnell eine Instanz zum ­Ent­wickeln und Testen aufsetzen möchte, kann ein spezielles Docker-­Image installieren, mit dem alle benötigten ­Komponenten in ­einem einzelnen Container gestartet werden:

$ docker run -d --name jaeger \
  -p 16686:16686 \
  -p 14268:14268 \
  jaegertracing/all-in-one:1.8

Nach dem Starten dieses Containers kann die Jaeger-Benutzeroberfläche über die URL http://localhost:16686 aufgerufen werden.

Einstieg in OpenTracing mit Go

Die Integration von Jaeger in ein eigenes Programm ist wiederum nicht besonders schwer. Dieser Abschnitt zeigt eine Beispiel-Implementierung in Go. Die Beispieldateien finden sich in GitHub. Zunächst müssen die Jaeger-Clientbibliothek und Opentracing per go get installiert werden:

Anzeige
Anzeige
$ go get github.com/opentracing/opentracing-go
$ go get github.com/uber/jaeger-client-go

Im Anschluss kann gleich zu Programmstart, etwa direkt in der main-Funktion, der Tracer initialisiert werden:

package main

import (
    "github.com/opentracing/opentracing-go"
    "github.com/uber/jaeger-client-go/config"
)

func main() {
    cfg, err := config.FromEnv()
    if err != nil {
       panic(err)
    }

    cfg.ServiceName = "t3n-example"

    tracer, closer, err := cfg.NewTracer()
    if err != nil {
        panic(err)
    }

    defer closer.Close()
    opentracing.SetGlobalTracer(tracer)

    // eigener Programmcode hier...
}

Der Funktionsaufruf config.FromEnv() liest die gewünschte Konfiguration des Jaeger-Clients aus den Umgebungs­variablen des Prozesses aus. Über die kann beispielsweise ganz genau ­konfiguriert werden, zu welcher Netzwerkadresse die gesammelten Spans geschickt werden sollen.

Für einen lokal per Docker gestarteten Jaeger reicht die Umgebungsvariable JAEGER_ENDPOINT, die beim Programmstart mitgegeben werden kann:

Anzeige
Anzeige
$ JAEGER_ENDPOINT=http://localhost:14268/api/traces JAEGER_SAMPLER_PARAM=1 go run main.go

Die Variable JAEGER_SAMPLER_PARAM=1 sorgt dafür, dass jeder erfasste Span auch an den Tracer übermittelt wird. Dies ist zu Entwicklungszwecken sehr sinnvoll – in einer Produktiv­umgebung mit vielen Anfragen kann hier ein Wert <1 gewählt werden, um die vom Tracer verarbeitete Datenmenge zu verkleinern.

Nachdem ein Tracer erstellt und registriert wurde, werden im Programm die neuen Spans erfasst:

span := opentracing.StartSpan("some-operation-name")
span.SetTag("tag", "some-value")

defer span.Finish()

// [...hier eine zeitaufwändige Operation...]

Über SetTag ist es möglich, die Opentracing-Spans zu verschlagworten, um sie in der Nutzeroberfläche des Tracers einfacher zu finden. Als Tag dienen beispielsweise der Name eines auf­gerufenen Services, die IP-Adresse oder die User-ID des Nutzers, der empfangene oder gesendete HTTP-Statuscode.

Anzeige
Anzeige

Spurensuche: Die Übersicht in der Jaeger-UI veranschaulicht, wie eine Nutzeranfrage durch die verschiedene Services „wandert“. (Screenshot: Jaeger)

Um verschachtelte Spans zu erstellen, eignet sich in Go am besten die Context-API. Mit der opentracing.ContextWithSpan(...)-Methode ist es möglich, einen bestehenden Span in ein context.Context-Objekt einzubetten. Dieses Objekt kann dann an nachfolgende Methodenaufrufe durchgereicht werden:

span := opentracing.StartSpan(„some-operation-name“) span.SetTag("tag", "some-value")

defer span.Finish()

ctx := opentracing.ContextWithSpan(
 context.Background(), span)

executeLongRunningOperation(ctx)
executeLongRunningOperation(ctx)

Mit der Methode opentracing.StartSpanFromContext(...) kann dann ein neuer untergeordneter Span erstellt werden:

func executeLongRunningOperation(ctx context.Context) {

   span, subCtx := opentracing.
StartSpanFromContext(ctx, "long-running-operation")
   defer span.Finish()

   // "subCtx" kann nun WIEDER an weitere Funktionen weitergereicht werden

   time.Sleep(5 * time.Second)

}

Fazit

In komplexer verteilten Architekturen ist ein Tracer ein ­ungemein wichtiges Hilfsmittel, um Fehlern und Performance­problemen auf den Grund zu gehen. Aber auch viel einfachere Fragen ­lassen sich damit beantworten: Häufig hat niemand einen kompletten Überblick darüber, welcher Service in einer Microservice­Architektur eigentlich mit welchem kommuniziert. Der Tracer kann diese Dependency Map ganz einfach generieren, denn er muss nur schauen, welche Services gemeinsam in einem Trace aufgerufen wurden.

Das oben genutzte Docker-Image ist nützlich, um Jaeger für Entwicklungs- und Testzwecke schnell an den Start zu bekommen. Für einen robusten Produktivbetrieb eignet sich dieses Image jedoch nicht, denn Jaeger besteht eigentlich aus vielen einzelnen Komponenten und benötigt einen skalierbaren Datenspeicher, wie einen Elasticsearch-Cluster, um die erhobenen Daten auch in größeren Mengen langfristig speichern zu können.

Die ausführliche Beschreibung einer ­Produktiv-Installation von Jaeger ginge zu weit. Aber so viel sei gesagt: Kubernetes-­Nutzer haben Glück! Mit dem Jaeger-Operator steht eine ­Kubernetes-Automatisierung zur Verfügung, die das Setup und den Betrieb einer Produktivumgebung nahezu vollständig automatisiert. Zusammen mit dem Elasticsearch-Operator, der dasselbe für Elasticsearch übernimmt, können Entwickler schnell und unkompliziert mit Jaeger arbeiten.

Mehr zu diesem Thema
Fast fertig!

Bitte klicke auf den Link in der Bestätigungsmail, um deine Anmeldung abzuschließen.

Du willst noch weitere Infos zum Newsletter? Jetzt mehr erfahren

Anzeige
Anzeige
Schreib den ersten Kommentar!
Bitte beachte unsere Community-Richtlinien

Wir freuen uns über kontroverse Diskussionen, die gerne auch mal hitzig geführt werden dürfen. Beleidigende, grob anstößige, rassistische und strafrechtlich relevante Äußerungen und Beiträge tolerieren wir nicht. Bitte achte darauf, dass du keine Texte veröffentlichst, für die du keine ausdrückliche Erlaubnis des Urhebers hast. Ebenfalls nicht erlaubt ist der Missbrauch der Webangebote unter t3n.de als Werbeplattform. Die Nennung von Produktnamen, Herstellern, Dienstleistern und Websites ist nur dann zulässig, wenn damit nicht vorrangig der Zweck der Werbung verfolgt wird. Wir behalten uns vor, Beiträge, die diese Regeln verletzen, zu löschen und Accounts zeitweilig oder auf Dauer zu sperren.

Trotz all dieser notwendigen Regeln: Diskutiere kontrovers, sage anderen deine Meinung, trage mit weiterführenden Informationen zum Wissensaustausch bei, aber bleibe dabei fair und respektiere die Meinung anderer. Wir wünschen Dir viel Spaß mit den Webangeboten von t3n und freuen uns auf spannende Beiträge.

Dein t3n-Team

Melde dich mit deinem t3n Account an oder fülle die unteren Felder aus.

Bitte schalte deinen Adblocker für t3n.de aus!
Hallo und herzlich willkommen bei t3n!

Bitte schalte deinen Adblocker für t3n.de aus, um diesen Artikel zu lesen.

Wir sind ein unabhängiger Publisher mit einem Team von mehr als 75 fantastischen Menschen, aber ohne riesigen Konzern im Rücken. Banner und ähnliche Werbemittel sind für unsere Finanzierung sehr wichtig.

Schon jetzt und im Namen der gesamten t3n-Crew: vielen Dank für deine Unterstützung! 🙌

Deine t3n-Crew

Anleitung zur Deaktivierung
Artikel merken

Bitte melde dich an, um diesen Artikel in deiner persönlichen Merkliste auf t3n zu speichern.

Jetzt registrieren und merken

Du hast schon einen t3n-Account? Hier anmelden

oder
Auf Mastodon teilen

Gib die URL deiner Mastodon-Instanz ein, um den Artikel zu teilen.

Anzeige
Anzeige