Verteiltes Tracing für containerisierte Anwendungen mit OpenTelemetry
AI generated
FROM
RUN
Docker · OpenTelemetry · Jaeger · Tracing
Verteiltes Tracing für containerisierte Anwendungen mit OpenTelemetry
Anfragen über Container-Grenzen hinweg sichtbar machen

Wenn eine Anfrage durch fünf Container läuft und irgendwo 800 Millisekunden verliert, helfen Metriken und Logs allein nur bedingt weiter. Verteiltes Tracing mit OpenTelemetry verfolgt jede Anfrage als zusammenhängende Kette von Spans über Container-Grenzen hinweg und zeigt in Jaeger exakt, welcher Service, welche Datenbankabfrage oder welcher externe Aufruf für die Latenz verantwortlich ist.

18 Min. Lesezeit OpenTelemetry · Jaeger · Trace Context · Collector Docker Compose · Microservices

1. Warum Metriken und Logs verteiltes Tracing nicht ersetzen

Metriken zeigen, dass etwas langsam ist, Logs zeigen, was in einem einzelnen Container passiert ist, aber keines von beiden zeigt zuverlässig, wo entlang einer Anfrage durch mehrere Container die Zeit tatsächlich verloren geht. Genau diese Lücke schließt verteiltes Tracing: Statt isolierter Datenpunkte pro Container entsteht eine zusammenhängende Kette, die eine einzelne Anfrage von ihrem Eintritt am Reverse Proxy bis zur letzten Datenbankabfrage nachvollziehbar macht.

In einer typischen containerisierten Architektur mit Web-Container, API-Container, Cache und Datenbank ist eine langsame Anfrage ohne verteiltes Tracing ein Ratespiel: Liegt die Latenz an der Datenbank, am Cache-Miss, an einem externen API-Aufruf oder an der Netzwerkverbindung zwischen den Containern? OpenTelemetry, ein herstellerneutraler Standard für Instrumentierung, beantwortet genau diese Frage, indem jeder Schritt der Anfrage als Span mit Zeitstempel und Dauer erfasst und über alle beteiligten Container hinweg zu einem einzigen Trace zusammengeführt wird. Die folgenden Abschnitte zeigen den kompletten Weg von der Instrumentierung bis zur fertigen Trace-Ansicht in Jaeger.

2. Grundbegriffe: Trace, Span und Kontext

Ein Trace repräsentiert den gesamten Weg einer einzelnen Anfrage durch ein System, von der ersten Berührung bis zur Antwort an den Client. Ein Span ist ein einzelner benannter, zeitlich begrenzter Arbeitsschritt innerhalb dieses Traces, etwa ein HTTP-Request-Handler, eine Datenbankabfrage oder ein Aufruf an einen anderen Service. Jeder Span trägt eine eindeutige Span-ID, eine gemeinsame Trace-ID für alle Spans desselben Traces, und optional eine Parent-Span-ID, die die hierarchische Beziehung zwischen Spans abbildet.

Der Trace-Kontext ist der Mechanismus, der Trace-ID, Span-ID und weitere Metadaten von einem Container zum nächsten weiterreicht, meist über den standardisierten traceparent-HTTP-Header nach der W3C Trace Context Spezifikation. Ohne konsistente Weitergabe dieses Kontexts entstehen isolierte Traces pro Container statt eines zusammenhängenden Bildes über die gesamte Anfrage. Genau diese Kontext-Weitergabe zwischen Containern ist der technisch anspruchsvollste Teil beim Aufbau von verteiltem Tracing in containerisierten Umgebungen.

3. Anwendungen mit OpenTelemetry instrumentieren

OpenTelemetry bietet für die meisten verbreiteten Sprachen automatische Instrumentierung, die HTTP-Frameworks, Datenbank-Clients und Messaging-Bibliotheken ohne manuelle Code-Änderungen mit Spans versieht. Für PHP-Anwendungen etwa reicht die Installation der OpenTelemetry-Extension und des Auto-Instrumentation-Pakets, um eingehende HTTP-Requests, ausgehende cURL-Aufrufe und PDO-Datenbankabfragen automatisch zu erfassen, ohne jede einzelne Codezeile manuell anzupassen.

Für Geschäftslogik, die über automatische Instrumentierung hinausgeht, etwa einen kritischen Berechnungsschritt oder eine Warenkorb-Validierung, ergänzt man manuelle Spans über die OpenTelemetry-API. Diese Kombination aus automatischer und manueller Instrumentierung liefert das vollständigste Bild: Infrastruktur-Aufrufe werden ohne Zusatzaufwand erfasst, während geschäftskritische Schritte gezielt mit aussagekräftigen Namen und Attributen versehen werden.


# docker-compose.yml — application container with OpenTelemetry auto-instrumentation
services:
  checkout-api:
    build: ./checkout-api
    environment:
      OTEL_SERVICE_NAME: "checkout-api"
      OTEL_EXPORTER_OTLP_ENDPOINT: "http://otel-collector:4318"
      OTEL_TRACES_EXPORTER: "otlp"
      OTEL_PHP_AUTOLOAD_ENABLED: "true"
    depends_on:
      - otel-collector

  otel-collector:
    image: otel/opentelemetry-collector-contrib:0.104.0
    volumes:
      - ./otel-collector-config.yml:/etc/otelcol-contrib/config.yaml:ro
    ports:
      - "4318:4318"   # OTLP HTTP receiver
      - "4317:4317"   # OTLP gRPC receiver

4. Trace-Kontext zwischen Containern weitergeben

Automatische Instrumentierung erfasst einen Span für jeden eingehenden und ausgehenden HTTP-Request eines Containers, aber die Verbindung zwischen diesen Spans über Container-Grenzen hinweg funktioniert nur, wenn der traceparent-Header konsequent von einem Container zum nächsten weitergegeben wird. Bei HTTP-basierter Kommunikation übernehmen die meisten OpenTelemetry-Instrumentierungen diese Weitergabe automatisch, solange beide Seiten dieselbe Instrumentierungs-Bibliothek und dasselbe Propagations-Format verwenden.

Kritisch wird es bei asynchroner Kommunikation über Message Queues wie RabbitMQ oder Redis Streams, wo kein HTTP-Header automatisch mitgeführt wird. Hier muss der Trace-Kontext manuell in die Nachricht eingebettet werden, meist als zusätzliches Metadatenfeld, und beim Empfang explizit wieder extrahiert werden, um den Span-Baum korrekt fortzusetzen. Wird dieser Schritt vergessen, entstehen zwei getrennte Traces statt eines durchgehenden Bildes, was die eigentliche Stärke von verteiltem Tracing zunichtemacht.


<?php
// Manually propagating trace context through a message queue payload
use OpenTelemetry\API\Trace\Propagation\TraceContextPropagator;
use OpenTelemetry\Context\Context;

// Producer side: inject the current trace context into the message
$carrier = [];
TraceContextPropagator::getInstance()->inject($carrier, null, Context::getCurrent());

$message = [
    'payload' => $orderData,
    'trace_context' => $carrier, // e.g. ["traceparent" => "00-abc123...-def456...-01"]
];
$queue->publish(json_encode($message));

// Consumer side: extract the context and continue the same trace
$decoded = json_decode($rawMessage, true);
$extractedContext = TraceContextPropagator::getInstance()->extract(
    $decoded['trace_context'],
);
$scope = $extractedContext->activate();
// ... process the order within the same trace, then detach the scope
$scope->detach();

5. Den OpenTelemetry Collector in Docker betreiben

Der OpenTelemetry Collector ist eine eigenständige Komponente, die zwischen instrumentierten Anwendungen und dem Backend, etwa Jaeger, vermittelt. Statt dass jede Anwendung Traces direkt an Jaeger sendet, senden alle Container ihre Spans an einen zentralen Collector, der sie empfängt, batcht, gegebenenfalls filtert und an ein oder mehrere Backends weiterleitet. Diese Entkopplung erlaubt es, das Tracing-Backend auszutauschen, ohne jede einzelne Anwendung neu konfigurieren zu müssen.

In einer Docker-Umgebung läuft der Collector typischerweise als eigener Container, erreichbar über den internen Compose-Netzwerknamen, an den alle anderen Container ihre Traces über OTLP, das OpenTelemetry Protocol, senden. Die Collector-Konfiguration definiert Receiver für eingehende Daten, optionale Processors für Batching und Filterung, sowie Exporter für die Weiterleitung an Jaeger, Prometheus oder andere Backends, alles über eine einzige YAML-Datei.


# otel-collector-config.yml — receive OTLP, batch, export to Jaeger
receivers:
  otlp:
    protocols:
      grpc:
        endpoint: "0.0.0.0:4317"
      http:
        endpoint: "0.0.0.0:4318"

processors:
  batch:
    timeout: 5s
    send_batch_size: 1024

exporters:
  otlp/jaeger:
    endpoint: "jaeger:4317"
    tls:
      insecure: true

service:
  pipelines:
    traces:
      receivers: [otlp]
      processors: [batch]
      exporters: [otlp/jaeger]

6. Traces in Jaeger visualisieren und analysieren

Jaeger stellt jeden empfangenen Trace als Wasserfall-Diagramm dar, in dem jeder Span als horizontaler Balken erscheint, positioniert nach Startzeit und Dauer, verschachtelt nach der Eltern-Kind-Beziehung zwischen Spans. Diese Darstellung macht auf einen Blick sichtbar, welcher Abschnitt einer Anfrage den größten Anteil der Gesamtlatenz verursacht, ohne einzelne Log-Zeilen manuell korrelieren zu müssen.

Die Suchfunktion von Jaeger erlaubt das Filtern nach Service-Name, Operation, Dauer und benutzerdefinierten Span-Attributen, etwa einer Bestellnummer oder Kunden-ID, sofern diese als Attribut auf dem Span gesetzt wurden. Für die Fehlersuche nach einem konkreten Kundenvorfall lässt sich so gezielt der exakte Trace finden, der zu einer bestimmten Bestellung gehört, statt Logs mehrerer Container manuell nach einer Bestellnummer zu durchsuchen. Diese direkte Verknüpfung zwischen Geschäftskontext und technischem Trace ist einer der größten praktischen Vorteile von verteiltem Tracing gegenüber reinem Metriken- und Log-basiertem Monitoring.

7. Sampling-Strategien für produktive Umgebungen

Jede Anfrage vollständig zu tracen erzeugt in Hochlast-Umgebungen erhebliches Datenvolumen und entsprechenden Speicherbedarf im Tracing-Backend. Sampling reduziert diese Menge, indem nur ein Teil der Traces vollständig aufgezeichnet wird. Head-based Sampling entscheidet bereits beim Start einer Anfrage, etwa mit einer festen Wahrscheinlichkeit von 10 Prozent, ob der gesamte Trace aufgezeichnet wird, was einfach zu implementieren ist, aber riskiert, seltene, aber wichtige Fehlerfälle zu verpassen.

Tail-based Sampling trifft die Entscheidung stattdessen erst, nachdem der komplette Trace vorliegt, und kann so gezielt alle Traces mit Fehlern oder ungewöhnlich hoher Latenz vollständig behalten, während normale, schnelle Anfragen nur zu einem kleinen Prozentsatz gespeichert werden. Diese Strategie erfordert einen Collector, der den gesamten Trace zwischenspeichert, bevor die Sampling-Entscheidung fällt, ist aber für produktive Umgebungen mit hohem Traffic die deutlich aussagekräftigere Wahl, weil genau die Traces erhalten bleiben, die für die Fehlersuche am wertvollsten sind.

8. Typische Fehler bei der Einführung von Tracing

Der häufigste Fehler ist eine unvollständige Instrumentierung, bei der einzelne Container, oft ältere oder als nicht kritisch eingestufte Services, ausgelassen werden. Das Ergebnis sind Traces mit Lücken, in denen die Anfrage kurzzeitig verschwindet, bevor sie in einem instrumentierten Container wieder auftaucht, was die eigentliche Ursache einer Latenz verschleiert statt sie aufzudecken.


# Common mistakes when introducing distributed tracing

# WRONG: trace context not propagated through async messaging
# $queue->publish(json_encode($orderData));  # no trace_context field

# RIGHT: inject and extract trace context around the message boundary
# see PHP example in section 4 — inject on publish, extract on consume

# WRONG: 100% sampling in a high-traffic production service
# OTEL_TRACES_SAMPLER=always_on   # huge data volume, high backend cost

# RIGHT: tail-based sampling that always keeps errors and slow requests
# OTEL_TRACES_SAMPLER=parentbased_traceidratio
# OTEL_TRACES_SAMPLER_ARG=0.1     # 10% baseline, errors kept separately

# WRONG: missing service.name — all containers show up as "unknown_service"
# (no OTEL_SERVICE_NAME set)

# RIGHT: explicit, unique service name per container
# OTEL_SERVICE_NAME=checkout-api

Ein zweiter verbreiteter Fehler ist ein fehlender oder generischer service.name, wodurch alle Container in Jaeger unter demselben Namen erscheinen und die eigentliche Stärke von verteiltem Tracing, die klare Zuordnung von Latenz zu einem bestimmten Service, verloren geht. Ein dritter Fehler ist zu aggressives Sampling ohne Sonderbehandlung für Fehler, wodurch genau die Traces verworfen werden, die für die Fehlersuche am wichtigsten wären, während unauffällige, schnelle Anfragen überproportional oft erhalten bleiben.

9. Observability-Säulen im direkten Vergleich

Verteiltes Tracing ist eine von drei Säulen der Observability und ergänzt Metriken und Logs, statt sie zu ersetzen.

Säule Beantwortet Datenvolumen Einsatzfall
Metriken Wie stark, wie oft, über die Zeit Gering Alarmierung, Trend-Erkennung
Logs Was genau ist passiert Mittel bis hoch Detaillierte Fehlersuche pro Container
Verteiltes Tracing Wo entlang der Anfrage die Zeit verloren geht Hoch ohne Sampling Latenz-Analyse über Container-Grenzen

In der Praxis ergänzen sich diese drei Säulen: Ein Alertmanager-Alarm auf Basis von Metriken signalisiert erhöhte Latenz, Logs liefern den Fehlertext des betroffenen Containers, und verteiltes Tracing zeigt, welcher Schritt in der Kette tatsächlich die Zeit verbraucht hat. Keine dieser drei Säulen ersetzt die anderen vollständig, ihre Kombination liefert erst das vollständige Bild für Fehlersuche in containerisierten Microservice-Architekturen.

Für Teams, die bereits Prometheus und Loki einsetzen, fügt sich verteiltes Tracing nahtlos als dritte Säule in dieselbe Grafana-Oberfläche ein, sodass Metriken, Logs und Traces ohne Werkzeugwechsel gemeinsam ausgewertet werden können.

10. Zusammenfassung

Verteiltes Tracing für containerisierte Anwendungen mit OpenTelemetry schließt die Lücke, die Metriken und Logs bei Anfragen über mehrere Container hinweg offenlassen. Spans mit gemeinsamer Trace-ID bilden den Weg einer Anfrage nach, der Trace-Kontext wird über HTTP-Header oder manuell über Message-Queue-Grenzen weitergegeben, der OpenTelemetry Collector sammelt und exportiert die Daten, und Jaeger visualisiert sie als durchsuchbares Wasserfall-Diagramm.

Der entscheidende Erfolgsfaktor ist vollständige, konsistente Instrumentierung über alle beteiligten Container hinweg, kombiniert mit einer durchdachten Sampling-Strategie, die Fehler und langsame Anfragen bevorzugt behält. Wer diese Grundlagen umsetzt, bekommt mit verteiltem Tracing ein Werkzeug, das Latenz-Probleme in komplexen, containerisierten Architekturen in Minuten statt Stunden lokalisiert.

Verteiltes Tracing mit OpenTelemetry — Das Wichtigste auf einen Blick

Grundbegriffe

Ein Trace besteht aus Spans mit gemeinsamer Trace-ID, der Trace-Kontext verbindet sie über Container-Grenzen.

Instrumentierung

Automatische OpenTelemetry-Instrumentierung für HTTP und Datenbanken, manuelle Spans für Geschäftslogik.

Infrastruktur

Der OpenTelemetry Collector sammelt Spans zentral und exportiert sie an Jaeger oder andere Backends.

Sampling

Tail-based Sampling bewahrt gezielt Fehler und langsame Anfragen bei kontrolliertem Datenvolumen.

11. FAQ: Verteiltes Tracing für containerisierte Anwendungen mit OpenTelemetry

1Trace vs. Span?
Der Trace ist der gesamte Anfrageweg, der Span ein einzelner Arbeitsschritt darin, verbunden über eine gemeinsame Trace-ID.
2Kontext zwischen Containern?
Über den traceparent-HTTP-Header automatisch, bei Message Queues manuell in die Nachricht eingebettet.
3Eigene Instrumentierung pro Sprache?
Ja, sprachspezifische SDKs mit automatischer Instrumentierung für gängige Frameworks der jeweiligen Sprache.
4Wozu der Collector?
Entkoppelt Anwendungen vom Backend, sammelt und batcht Spans zentral vor dem Export.
5Head-based vs. Tail-based Sampling?
Head-based entscheidet beim Start, Tail-based erst nach Abschluss und bevorzugt Fehler und langsame Anfragen.
6Nur Fragmente in Jaeger?
Meist unvollständige Instrumentierung oder fehlende Kontext-Weitergabe bei asynchroner Kommunikation.
7Alle Container mit demselben Namen?
OTEL_SERVICE_NAME für jeden Container eindeutig setzen, statt den Standardwert zu verwenden.
8Tracing ohne Jaeger?
Ja, der Collector kann auch an Tempo, Zipkin oder kommerzielle APM-Lösungen exportieren.
9Wie viel Overhead?
Mit sinnvollem Sampling gering, im niedrigen einstelligen Millisekundenbereich pro Anfrage.
10Für kleine Setups sinnvoll?
Ab drei oder mehr beteiligten Containern liefert Tracing bereits deutlichen Mehrwert.