1. Le problème à résoudre #
Relier des spans créés dans des processus différents.
Pourquoi propager ? #
Chaque service crée ses propres spans, mais pour obtenir une seule trace il faut transmettre l'identité de la trace (TraceID) et du span parent à chaque appel. Cette transmission s'appelle la propagation de contexte : l'appelant injecte le contexte dans le message, l'appelé l'extrait.
2. Le standard W3C Trace Context #
traceparent, tracestate et baggage.
Les en-têtes #
traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01
# | | | |
# | trace-id (16 octets, hex) span-id parent flags (01 = échantillonnée)
# version
tracestate: vendor1=valeur,vendor2=autre # informations propres à des fournisseurs
baggage: user.tier=gold,region=eu # paires clé/valeur métier propagées à tous les services| En-tête | Contenu | Attention |
|---|---|---|
traceparent | TraceID, SpanID parent, flags | Indispensable pour relier les spans |
tracestate | Données spécifiques à un fournisseur | Peu utilisé en pratique |
baggage | Contexte métier (client, région...) | Visible de tous les services et tiers : jamais de secret |
3. Configurer les propagateurs #
Variables d'environnement.
OTEL_PROPAGATORS #
export OTEL_PROPAGATORS=tracecontext,baggage # valeur par défaut : W3C
export OTEL_PROPAGATORS=tracecontext,baggage,b3multi # compatibilité avec des services Zipkin/B3
export OTEL_PROPAGATORS=jaeger,tracecontext # migration depuis JaegerTous les services d'une même chaîne doivent partager au moins un format commun : c'est la première chose à vérifier quand une trace est coupée en deux.
# Tester à la main : forcer un TraceID connu et le retrouver dans Tempo
curl -H 'traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01' http://localhost:8080/commande
curl -s http://localhost:3200/api/traces/4bf92f3577b34da6a3ce929d0e0e4736 | jq '.batches | length'4. Propager manuellement #
Messagerie, jobs et clients HTTP non instrumentés.
Injecter et extraire (Python) #
from opentelemetry import trace, propagate, context
tracer = trace.get_tracer("orders")
# Producteur : injecter le contexte dans les en-têtes du message
def publier(message):
with tracer.start_as_current_span("publier_commande", kind=trace.SpanKind.PRODUCER):
headers = {}
propagate.inject(headers) # ajoute traceparent / baggage
kafka_producer.send("commandes", value=message, headers=[(k, v.encode()) for k, v in headers.items()])
# Consommateur : extraire et continuer la trace
def consommer(record):
carrier = {k: v.decode() for k, v in record.headers}
ctx = propagate.extract(carrier)
with tracer.start_as_current_span("traiter_commande", context=ctx, kind=trace.SpanKind.CONSUMER) as span:
span.set_attribute("messaging.destination.name", "commandes")
traiter(record.value)Dépanner une trace coupée #
| Symptôme | Cause probable | Action |
|---|---|---|
| Deux traces au lieu d'une | Propagateurs différents entre services | Aligner OTEL_PROPAGATORS |
| En-tête absent côté appelé | Proxy / gateway qui supprime les en-têtes | Autoriser traceparent et baggage dans la configuration du proxy |
| Trace perdue après une file de messages | Contexte non injecté dans les en-têtes du message | Injecter/extraire manuellement ou activer l'instrumentation du client |
| Spans orphelins en tâche de fond | Contexte perdu entre threads/coroutines | Utiliser context.attach() / run_in_executor instrumenté |
| Une partie seulement des traces est complète | Échantillonnage head différent entre services | Utiliser parentbased_traceidratio partout |
# nginx : laisser passer / générer le contexte
proxy_set_header traceparent $http_traceparent;
proxy_set_header tracestate $http_tracestate;
proxy_set_header baggage $http_baggage;Erreur Courante : Ne placez jamais de données sensibles dans le baggage : il est propagé à tous les services en aval, y compris les services tiers.
