Correlation-IDs fuer verteiltes Tracing in Shell-Pipelines
AI generated
$_
#!/
Bash · Tracing · Observability · Logging
Correlation-IDs in Shell-Pipelines
Eine ID durch mehrere Skripte und Programme hinweg durchreichen

Ein Deployment, das aus fuenf nacheinander aufgerufenen Bash-Skripten besteht, hinterlaesst ohne gemeinsame Kennung fuenf isolierte Log-Fragmente, die sich im Nachhinein kaum wieder zu einem zusammenhaengenden Ablauf zusammensetzen lassen. Eine Correlation-ID, einmal am Anfang erzeugt und konsequent per Umgebungsvariable an jedes aufgerufene Skript und jeden aufgerufenen Prozess weitergereicht, macht aus diesen Fragmenten einen durchsuchbaren, zusammenhaengenden Trace.

17 Min. Lesezeit export · uuidgen · X-Correlation-ID Bash · Logging · Observability

1. Was eine Correlation-ID ist und warum Shell-Pipelines sie brauchen

Eine Correlation-ID ist ein eindeutiger Bezeichner, meist eine UUID, der einen einzelnen logischen Vorgang von Anfang bis Ende begleitet, unabhaengig davon, wie viele einzelne Prozesse, Skripte oder Programme an diesem Vorgang beteiligt sind. Anders als eine Prozess-ID, die mit jedem neuen Skriptaufruf wechselt, bleibt die Correlation-ID ueber die gesamte Kette hinweg konstant und wird explizit weitergereicht, statt sich implizit aus der Systemumgebung zu ergeben.

In einer typischen Bash-Deployment-Pipeline, die etwa Migrationen ausfuehrt, Assets baut und einen Health-Check durchfuehrt, laeuft jeder dieser Schritte als eigener Prozessaufruf mit eigener Prozess-ID und eigenem Log-Fenster. Ohne eine gemeinsame Correlation-ID muss ein Entwickler bei der Fehlersuche zeitlich anhand von Zeitstempeln muehsam rekonstruieren, welche Log-Zeilen zu welchem Deployment-Lauf gehoeren, was bei mehreren parallel laufenden Deployments praktisch unmoeglich wird.

2. Die Correlation-ID am Pipeline-Anfang einmal erzeugen

Die Correlation-ID muss an genau einer Stelle entstehen, dem Einstiegspunkt der Pipeline, und darf danach niemals von einem der nachgelagerten Skripte neu generiert werden. Das Skript, das die Pipeline startet, prueft dafuer zunaechst, ob eine Correlation-ID bereits von aussen uebergeben wurde, etwa weil es selbst von einem uebergeordneten Prozess wie einem CI-Job aufgerufen wird, und generiert nur dann eine neue, wenn keine vorhanden ist.

Fuer die Generierung selbst reicht uuidgen auf den meisten Linux-Distributionen, alternativ laesst sich mit $(date +%s%N)-$$ auch ohne dieses Zusatzpaket eine ausreichend eindeutige Kennung erzeugen, wenn uuidgen nicht verfuegbar ist. Wichtig ist nur, dass die Erzeugung idempotent gegenueber einer bereits vorhandenen ID ist, damit verschachtelte Aufrufe der Pipeline nicht versehentlich mehrere unabhaengige IDs fuer denselben logischen Vorgang erzeugen.


#!/usr/bin/env bash
set -euo pipefail

# Reuse an inherited correlation ID, or generate a new one if this is the entry point
export CORRELATION_ID="${CORRELATION_ID:-$(uuidgen 2>/dev/null || echo "$(date +%s%N)-$$")}"

echo "Starting deployment pipeline, correlation_id=${CORRELATION_ID}"

3. Weitergabe per Umgebungsvariable an aufgerufene Skripte

Eine mit export gesetzte Umgebungsvariable vererbt sich automatisch an jeden Kindprozess, den ein Bash-Skript startet, egal ob es sich um ein weiteres Bash-Skript, ein Python-Programm oder ein beliebiges anderes Kommando handelt. Genau dieses Verhalten macht die Umgebungsvariable zum idealen Traeger fuer eine Correlation-ID: Solange jedes Skript in der Kette die Variable am Anfang uebernimmt, statt sie zu ueberschreiben, bleibt sie ohne zusaetzlichen Verdrahtungsaufwand ueber beliebig viele Ebenen erhalten.

Ein haeufiger Fehler ist, die Correlation-ID nur als lokale Shell-Variable zu setzen statt sie zu exportieren. Ohne export ist die Variable zwar im aktuellen Skript sichtbar, aber fuer jeden Kindprozess unsichtbar, sodass die Kette genau an der Stelle abreisst, an der ein neues Skript oder Programm gestartet wird, ohne dass dieser Fehler beim Testen im interaktiven Terminal sofort auffaellt.


#!/usr/bin/env bash
set -euo pipefail
# entrypoint.sh

export CORRELATION_ID="${CORRELATION_ID:-$(uuidgen)}"

./bin/run-migrations.sh    # inherits CORRELATION_ID
./bin/build-assets.sh      # inherits CORRELATION_ID
./bin/health-check.sh      # inherits CORRELATION_ID

echo "Pipeline finished, correlation_id=${CORRELATION_ID}"

4. Prozessgrenzen ueberwinden: SSH, Cron und Hintergrundprozesse

Die einfache Vererbung ueber export funktioniert nur innerhalb eines zusammenhaengenden Prozessbaums. Sobald die Pipeline ueber ssh einen Befehl auf einem anderen Host ausfuehrt, muss die Correlation-ID explizit als Argument oder Umgebungsvariable an den Remote-Befehl uebergeben werden, weil SSH standardmaessig keine lokalen Umgebungsvariablen an die Remote-Session weiterreicht, sofern der SSH-Server nicht ausdruecklich dafuer konfiguriert wurde.

Bei einem per cron gestarteten Skript existiert erst gar keine uebergeordnete Shell-Sitzung, deren Umgebungsvariablen vererbt werden koennten, sodass ein solches Skript grundsaetzlich immer eine neue Correlation-ID am Einstiegspunkt generieren muss. Bei Hintergrundprozessen, die mit & gestartet werden, funktioniert die Vererbung dagegen normal, solange die Variable vor dem & bereits exportiert wurde, was in der Praxis leicht uebersehen wird, wenn die Reihenfolge im Skript vertauscht ist.


#!/usr/bin/env bash
set -euo pipefail

export CORRELATION_ID="${CORRELATION_ID:-$(uuidgen)}"

# SSH does not forward local env vars by default -- pass explicitly
ssh deploy@remote-host "CORRELATION_ID='${CORRELATION_ID}' /opt/scripts/restart-service.sh"

# Background job started AFTER export still inherits the variable correctly
long_running_cleanup_task &

5. Integration mit strukturiertem Logging

Eine Correlation-ID entfaltet ihren vollen Nutzen erst in Kombination mit strukturiertem Logging, weil sich strukturierte Felder in einem Log-Aggregator direkt filtern lassen. Jede Log-Funktion einer Pipeline sollte deshalb automatisch das Feld correlation_id aus der gleichnamigen Umgebungsvariable in jede geloggte Zeile einfuegen, ohne dass der Aufrufer diesen Wert bei jedem Log-Aufruf manuell mitgeben muss.

In der Praxis reicht dafuer eine kleine jq-basierte Log-Funktion, die die Umgebungsvariable direkt liest, weil Umgebungsvariablen innerhalb eines Bash-Prozesses ohnehin global sichtbar sind. Ein Log-Aggregator wie ELK oder Loki kann anschliessend mit einer einzigen Abfrage nach correlation_id="..." alle Zeilen quer durch alle beteiligten Skripte und Hosts eines einzelnen Deployment-Laufs anzeigen, in korrekter zeitlicher Reihenfolge.


#!/usr/bin/env bash
set -euo pipefail

log_json() {
  local level="$1" message="$2"
  jq -nc \
    --arg ts "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
    --arg level "$level" \
    --arg msg "$message" \
    --arg correlation_id "${CORRELATION_ID:-unknown}" \
    '{timestamp: $ts, level: $level, message: $msg, correlation_id: $correlation_id}' >&2
}

log_json "info" "Running database migrations"

6. Correlation-IDs an HTTP-Aufrufe weiterreichen

Ruft ein Bash-Skript im Rahmen der Pipeline eine HTTP-API auf, etwa um einen Deployment-Status an ein externes System zu melden, sollte die Correlation-ID als benutzerdefinierter Header wie X-Correlation-ID mitgesendet werden. Das erlaubt es, den Trace ueber die Grenze zwischen Shell-Skript und Webanwendung hinweg fortzusetzen, sofern die empfangende Anwendung diesen Header ebenfalls in ihr eigenes strukturiertes Logging uebernimmt.

Diese Praxis folgt demselben Prinzip, das in verteilten Webanwendungen laengst etabliert ist, wo Correlation-IDs oder Trace-IDs von Reverse Proxies wie nginx gesetzt und durch alle nachgelagerten Microservices durchgereicht werden. Ein Bash-Skript, das per curl mit einem solchen Header kommuniziert, reiht sich damit nahtlos in eine bestehende Observability-Kette ein, statt eine isolierte Insel ohne Anbindung an das uebrige System zu bleiben.


#!/usr/bin/env bash
set -euo pipefail

curl -sS -X POST "https://status.internal/api/deployments" \
  -H "X-Correlation-ID: ${CORRELATION_ID}" \
  -H "Content-Type: application/json" \
  -d "{\"status\": \"in_progress\"}"

7. Correlation-ID versus Trace-ID und Span-ID aus OpenTelemetry

Moderne Tracing-Standards wie OpenTelemetry unterscheiden zwischen einer Trace-ID, die einen gesamten verteilten Vorgang identifiziert, und einer Span-ID, die einen einzelnen Teilschritt innerhalb dieses Vorgangs markiert, zusammen mit einer Parent-Span-ID fuer die hierarchische Struktur. Eine einfache Correlation-ID in einem Bash-Skript deckt in der Praxis meist nur die Rolle der Trace-ID ab, ohne die feingranulare Span-Hierarchie eines vollstaendigen Tracing-Systems nachzubilden.

Fuer die meisten Bash-Automatisierungsaufgaben reicht diese vereinfachte Variante voellig aus, weil das Ziel meist nicht eine detaillierte Latenzanalyse einzelner Teilschritte ist, sondern schlicht die Moeglichkeit, alle zusammengehoerigen Log-Zeilen eines Vorgangs zu finden. Wer echtes verteiltes Tracing mit Span-Hierarchie und Latenzmessung braucht, sollte pruefen, ob ein OpenTelemetry-kompatibles Shell-Instrumentierungswerkzeug die bessere Wahl ist, statt eine eigene, vereinfachte Loesung von Grund auf nachzubauen.

8. Fallstricke: verschachtelte IDs, ueberschriebene Variablen und Sub-Shells

Der haeufigste Fehler ist, dass ein aufgerufenes Skript versehentlich eine eigene, neue Correlation-ID generiert, statt die geerbte zu pruefen und wiederzuverwenden, meist weil das Skript auch eigenstaendig ausserhalb der Pipeline lauffaehig sein soll. Die Loesung ist konsequent das Muster CORRELATION_ID="${CORRELATION_ID:-$(uuidgen)}", das eine bereits vorhandene ID immer bevorzugt und nur im Alleinlauf eine neue erzeugt.

Ein zweiter Fallstrick betrifft Sub-Shells, die durch Klammern ( ... ) oder Pipes entstehen: Variablen, die innerhalb einer Sub-Shell veraendert werden, wirken sich nicht auf die Elternumgebung aus, was bei einer versehentlichen Neuzuweisung der Correlation-ID innerhalb einer solchen Sub-Shell zu verwirrenden, scheinbar zufaelligen Log-Ausfaellen fuehrt, die schwer zu reproduzieren sind, weil sie vom genauen Aufbau der Pipe-Kette abhaengen.

9. Correlation-IDs im Vergleich zu anderen Tracing-Ansaetzen

Die Wahl des richtigen Tracing-Ansatzes fuer Shell-Automatisierung haengt von der Komplexitaet der beteiligten Systeme ab: Fuer eine einzelne Deployment-Pipeline aus Bash-Skripten reicht eine simple Correlation-ID meist voellig aus, waehrend ein System mit vielen Microservices und komplexer Anfragen-Fan-out-Struktur von einem vollstaendigen OpenTelemetry-Setup mit Span-Hierarchie deutlich mehr profitiert.

Ansatz Aufwand Detailtiefe Typischer Einsatz
Correlation-ID per env var Gering, wenige Zeilen Code Vorgang als Ganzes Bash-Pipelines, Deployment-Skripte
Trace-ID + Span-ID (OpenTelemetry) Hoch, Instrumentierung noetig Einzelne Teilschritte mit Latenz Verteilte Microservice-Systeme
Reine Zeitstempel-Korrelation Sehr gering, kein Zusatzcode Ungenau, manuelle Zuordnung Kleine, einmalige Debugging-Sessions
Zentrale Request-ID vom Reverse Proxy Mittel, Proxy-Konfiguration Vorgang ab dem ersten HTTP-Request Webanwendungen mit vorgeschaltetem Proxy

Mironsoft

Shell-Automatisierung, DevOps-Tooling und Deployment-Infrastruktur

Shell-Skripte, die in der Produktion zuverlässig laufen?

Wir analysieren bestehende Bash-Skripte, erkennen fragile Muster und ersetzen sie durch robuste Bash-Patterns: mit vollständiger Fehlerbehandlung, Logging und sicherer Parallelisierung für euren Deployment-Stack.

Code-Review

ShellCheck-Analyse und manuelle Prüfung auf kritische Bash-Pattern-Verstöße.

Refactoring

Fehlerbehandlung, Logging und sichere Dateioperationen nachrüsten.

CI-Integration

ShellCheck und BATS in Pipelines integrieren und Regressionstests aufbauen.

10. Zusammenfassung

Correlation-IDs fuer verteiltes Tracing: Das Wichtigste auf einen Blick

Grundprinzip

Eine Correlation-ID einmal am Pipeline-Anfang erzeugen und per exportierter Umgebungsvariable an alle Kindprozesse weiterreichen.

Prozessgrenzen

SSH und Cron vererben keine Umgebungsvariablen automatisch, die ID muss dort explizit uebergeben oder neu erzeugt werden.

Logging-Integration

Jede Log-Zeile sollte das Feld correlation_id automatisch aus der Umgebungsvariable uebernehmen, ohne manuellen Zusatzaufwand pro Aufruf.

Fallstricke

Idempotentes Pattern CORRELATION_ID=${CORRELATION_ID:-$(uuidgen)} verhindert versehentliche Neugenerierung in nachgelagerten Skripten.

11. FAQ: Correlation-IDs fuer verteiltes Tracing: Das Wichtigste auf einen Blick

1Was ist eine Correlation-ID in Bash-Skripten?
Ein eindeutiger Bezeichner, meist eine UUID, der einen logischen Vorgang ueber mehrere Skripte und aufgerufene Programme hinweg begleitet und in jeder geloggten Zeile erscheint, um zusammengehoerige Ereignisse spaeter wiederzufinden.
2Wie erzeuge ich eine Correlation-ID in Bash?
Mit uuidgen, oder falls dieses Paket nicht verfuegbar ist, als Fallback mit einer Kombination aus $(date +%s%N) und der Prozess-ID $$ fuer eine ausreichend eindeutige Kennung.
3Warum reicht eine lokale Variable nicht fuer die Weitergabe?
Weil eine nicht exportierte Variable nur im aktuellen Skript sichtbar ist. Ohne export vererbt sie sich nicht an Kindprozesse, und die Correlation-Kette reisst beim naechsten aufgerufenen Skript ab.
4Vererbt SSH lokale Umgebungsvariablen automatisch?
Nein, standardmaessig nicht. Die Correlation-ID muss explizit als Teil des Remote-Befehls oder als Argument uebergeben werden, damit sie auf dem entfernten Host ankommt.
5Wie gehe ich mit Cronjobs um, die keine geerbte ID haben?
Ein per Cron gestartetes Skript hat keine uebergeordnete Sitzung und muss deshalb immer eine neue Correlation-ID am Einstiegspunkt generieren, statt eine Vererbung vorauszusetzen.
6Wie kombiniere ich Correlation-IDs mit strukturiertem Logging?
Die Log-Funktion liest die Umgebungsvariable CORRELATION_ID direkt aus und fuegt sie automatisch als Feld in jede geloggte JSON-Zeile ein, ohne dass der Aufrufer sie manuell mitgeben muss.
7Kann ich eine Correlation-ID auch an eine HTTP-API weitergeben?
Ja, ueblicherweise als benutzerdefinierter Header wie X-Correlation-ID bei einem curl-Aufruf, sodass der Trace ueber die Grenze zwischen Shell-Skript und Webanwendung hinweg fortgesetzt wird.
8Was ist der Unterschied zu einer OpenTelemetry Trace-ID?
Eine einfache Correlation-ID deckt meist nur die Rolle der Trace-ID ab, ohne die feingranulare Span-Hierarchie mit Latenzmessung, die ein vollstaendiges OpenTelemetry-Setup bietet.
9Warum generiert ein aufgerufenes Skript manchmal versehentlich eine neue ID?
Meist weil es auch eigenstaendig ausserhalb der Pipeline lauffaehig sein soll und die Pruefung auf eine bereits vorhandene ID vergessen wurde. Das Pattern ${CORRELATION_ID:-$(uuidgen)} verhindert das.
10Wirken sich Aenderungen an der Correlation-ID in einer Sub-Shell auf das Hauptskript aus?
Nein. Variablen, die innerhalb einer Sub-Shell durch Klammern oder Pipes veraendert werden, wirken sich nicht auf die Elternumgebung aus, was zu verwirrenden Luecken in der Log-Kette fuehren kann.