Aller au contenu
Outils medium

Jaeger : le tracing distribué open source expliqué

14 min de lecture

Jaeger est une plateforme open source de tracing distribué qui suit une requête à travers tous les microservices qu'elle traverse, pour révéler où le temps se perd et où les erreurs naissent. Créé par Uber, c'est un projet CNCF Graduated, le plus haut niveau de maturité de la fondation cloud-native. Sa version 2, actuelle, est entièrement rebâtie sur l'OpenTelemetry Collector.

Ce guide s'adresse aux profils intermédiaires et avancés qui exploitent des microservices et veulent diagnostiquer les requêtes lentes. Vous allez comprendre les concepts du tracing, ce qui change entre Jaeger v1 et v2, démarrer Jaeger en local, instrumenter une application avec OpenTelemetry et configurer un stockage persistant.

  • Définir Jaeger et les concepts de span, trace et baggage.
  • Distinguer Jaeger v1 de la v2 fondée sur OpenTelemetry.
  • Démarrer Jaeger en local avec l'image all-in-one.
  • Instrumenter une application via le SDK OpenTelemetry.
  • Configurer un stockage persistant et le sampling.

Jaeger répond à une question que les logs et les métriques laissent sans réponse : par où est passée cette requête, et qui l'a ralentie ? Dans une architecture microservices, un simple clic déclenche une cascade d'appels entre services. Sans traçage, chaque service ne voit que sa propre part ; personne n'a la vue d'ensemble.

Le tracing distribué reconstitue ce trajet complet. Chaque service ajoute sa contribution à une trace commune, identifiée par un trace_id propagé d'appel en appel. Jaeger collecte ces fragments, les recompose et les affiche dans une interface web où l'on lit la chronologie exacte de la requête. C'est l'outil de référence pour ce besoin, aux côtés de Grafana Tempo et de Zipkin.

Trois notions suffisent à lire n'importe quelle trace. Les maîtriser rend l'interface immédiatement compréhensible.

  • Le span est l'unité de base : une opération avec un début, une fin et une durée. Un appel HTTP, une requête SQL ou un envoi de message sont autant de spans. Chaque span porte un nom, un statut et des attributs (code HTTP, table interrogée).
  • La trace est l'arbre de spans reliés par un même trace_id. Le span racine est souvent la requête entrante ; ses enfants sont les appels qu'elle a déclenchés. La trace montre lesquels se déroulent en parallèle et lesquels s'enchaînent.
  • Le baggage transporte des métadonnées le long de la trace, par exemple un user_id ou un tenant_id, pour retrouver toutes les traces d'un même client.

Concrètement, une trace frontend GET /order de 450 ms peut contenir un span order-service de 120 ms et un span stock-service de 80 ms, le premier appelant lui-même un span db-query de 45 ms. On voit d'un coup d'oeil quel maillon consomme le temps.

La version 2 de Jaeger est une refonte majeure, à connaître avant toute installation. Elle abandonne la constellation de binaires de la v1 (agent, collector, query) et la configuration par variables d'environnement, au profit d'une base commune : l'OpenTelemetry Collector.

AspectJaeger v1Jaeger v2
FondationBinaires propres à JaegerOpenTelemetry Collector
ConfigurationVariables d'environnement (SPAN_STORAGE_TYPE…)Fichier YAML unique
Composant agentSéparé (jaeger-agent)Supprimé, remplacé par OTLP
Format d'entréeJaeger, Zipkin, OTLPOTLP en priorité, Jaeger et Zipkin

Conséquence pratique : les tutoriels qui configurent Jaeger avec SPAN_STORAGE_TYPE=elasticsearch décrivent la v1. En v2, tout passe par un fichier de configuration au format Collector, comme montré plus bas. Ce guide cible la v2, la version maintenue.

Même unifiée dans un seul binaire, l'architecture logique reste lisible. Comprendre ces rôles aide à dimensionner un déploiement de production, où on les sépare à nouveau pour scaler.

ComposantRôle
Récepteurs (receivers)Acceptent les traces, surtout en OTLP (ports 4317/4318)
PipelineTraite et exporte les spans vers le stockage
StoragePersiste les traces (mémoire, Badger, Elasticsearch, Cassandra)
Query + UIInterroge le stockage et sert l'interface web (port 16686)

En mode all-in-one, ces rôles tournent dans un seul conteneur avec un stockage en mémoire : parfait pour découvrir ou développer. En production, on déploie collector et query séparément, adossés à un stockage externe.

L'image all-in-one lance Jaeger complet en une commande, récepteur OTLP et interface inclus. On épingle la version pour un résultat reproductible.

Fenêtre de terminal
docker run -d --name jaeger \
-p 16686:16686 \
-p 4317:4317 \
-p 4318:4318 \
jaegertracing/jaeger:2.19.0

Cette image expose trois ports utiles : l'interface web sur 16686, et les récepteurs OTLP gRPC (4317) et HTTP (4318), actifs par défaut sans configuration.

Vérification : ouvrez http://localhost:16686, l'interface Jaeger s'affiche. Pour confirmer l'ingestion sans instrumenter d'application, envoyez une trace de test en OTLP HTTP :

Fenêtre de terminal
curl -X POST http://localhost:4318/v1/traces \
-H "Content-Type: application/json" \
-d '{
"resourceSpans": [{
"resource": {"attributes": [{"key": "service.name", "value": {"stringValue": "checkout"}}]},
"scopeSpans": [{
"spans": [{
"traceId": "5b8efff798038103d269b633813fc60c",
"spanId": "eee19b7ec3c1b174",
"name": "GET /order",
"kind": 2,
"startTimeUnixNano": "'$(date +%s)'000000000",
"endTimeUnixNano": "'$(date +%s)'500000000"
}]
}]
}]
}'

La réponse {"partialSuccess":{}} confirme l'acceptation. Le service checkout apparaît alors dans la liste de l'interface, et sa trace est consultable. L'horodatage au présent (date +%s) garantit qu'elle tombe dans la fenêtre de recherche par défaut.

En pratique, ce sont vos applications qui émettent les traces, via le SDK OpenTelemetry. C'est la méthode recommandée : le code n'est plus lié à Jaeger, il parle OTLP, et pourrait pointer vers Tempo ou un autre backend sans rien changer.

from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
trace.set_tracer_provider(TracerProvider())
exporter = OTLPSpanExporter(endpoint="http://localhost:4317", insecure=True)
trace.get_tracer_provider().add_span_processor(BatchSpanProcessor(exporter))
tracer = trace.get_tracer(__name__)
with tracer.start_as_current_span("process_order") as span:
span.set_attribute("order.id", 12345)
# ... traitement métier

Pour de nombreux frameworks, l'auto-instrumentation évite d'écrire ce code. En Python, un seul wrapper suffit à tracer une application entière :

Fenêtre de terminal
pip install opentelemetry-distro opentelemetry-exporter-otlp
opentelemetry-bootstrap -a install
opentelemetry-instrument \
--traces_exporter otlp \
--exporter_otlp_endpoint http://localhost:4317 \
--service_name mon-service \
python app.py

Le mode all-in-one perd toutes les traces à l'arrêt du conteneur, puisqu'il stocke en mémoire. Pour conserver l'historique, on fournit un fichier de configuration v2 qui déclare un backend de stockage. L'exemple ci-dessous utilise Badger, une base de données locale sur fichier, idéale pour un poste ou un petit environnement.

service:
extensions: [jaeger_storage, jaeger_query]
pipelines:
traces:
receivers: [otlp]
exporters: [jaeger_storage_exporter]
extensions:
jaeger_query:
storage:
traces: main_storage
jaeger_storage:
backends:
main_storage:
badger:
directories:
keys: /tmp/jaeger/keys
values: /tmp/jaeger/values
ephemeral: false
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
http:
endpoint: 0.0.0.0:4318
exporters:
jaeger_storage_exporter:
trace_storage: main_storage

On lance ensuite Jaeger en montant ce fichier :

Fenêtre de terminal
docker run -d --name jaeger \
-p 16686:16686 -p 4317:4317 -p 4318:4318 \
-v "$(pwd)/config.yaml:/etc/jaeger/config.yaml" \
jaegertracing/jaeger:2.19.0 \
--config /etc/jaeger/config.yaml

Les traces survivent désormais aux redémarrages. Pour la production à fort volume, on remplace le bloc badger par un backend distribué (elasticsearch, opensearch ou cassandra) au même niveau backends, avec les paramètres de connexion correspondants ; la structure du pipeline, elle, ne change pas.

Tracer 100 % des requêtes est ruineux en stockage sur un service à fort trafic. Le sampling (échantillonnage) ne conserve qu'une fraction représentative des traces. Deux stratégies coexistent.

  • Head sampling : la décision est prise au début de la trace, côté application, via le SDK OpenTelemetry. Simple et peu coûteux, mais aveugle au contenu de la trace.
  • Tail sampling : la décision est prise après avoir vu toute la trace, dans un OpenTelemetry Collector. Plus intelligent, il garde par exemple toutes les traces en erreur et échantillonne le reste, au prix d'une mémoire tampon.
Type de décisionCompromis
Probabiliste (ex. 10 %)Head, SDKSimple, mais rate des erreurs rares
Rate limiting (N/s)Head, SDKProtège le backend d'un pic
Tail sampling (garde les erreurs)CollectorPrécis, coûte de la mémoire

Un réglage courant : probabiliste à 100 % sur une API critique peu sollicitée, 10 % par défaut ailleurs, et un tail sampling qui rattrape systématiquement les traces contenant une erreur.

Les deux stockent des traces et se pilotent en OTLP, mais leur philosophie diffère. Jaeger offre une interface de recherche riche et indexe les traces : on cherche facilement par service, opération ou tag. Grafana Tempo n'indexe que le trace ID et mise sur un stockage objet très bon marché, au prix d'une recherche par attribut plus limitée.

Le choix dépend du contexte. Si vous vivez déjà dans Grafana avec Loki et Prometheus, Tempo s'y fond naturellement. Si vous voulez une UI de traces autonome et puissante en recherche, Jaeger reste une valeur sûre. Les deux acceptant OTLP, migrer de l'un à l'autre ne touche pas votre instrumentation.

Les incidents Jaeger se concentrent sur la propagation et le stockage. Voici les symptômes les plus fréquents.

SymptômeCause probableSolution
Traces incomplètesContexte non propagé entre servicesVérifier la transmission des en-têtes traceparent
Aucun service dans l'UITrace hors fenêtre de rechercheContrôler l'horodatage et le lookback
Spans orphelinsHorloges désynchroniséesSynchroniser les serveurs en NTP
Conteneur qui redémarreChemin de stockage non inscriptibleUtiliser un dossier accessible en écriture
Fenêtre de terminal
# Lister les services vus par Jaeger
curl http://localhost:16686/api/services
# Rechercher les traces d'un service
curl "http://localhost:16686/api/traces?service=checkout&lookback=1h&limit=5"
  1. Jaeger trace les requêtes de bout en bout dans les microservices : span, trace et baggage sont les trois notions à connaître.
  2. La v2 repose sur l'OpenTelemetry Collector : configuration en YAML, format OTLP par défaut, plus de variables d'environnement à la v1.
  3. L'image all-in-one démarre Jaeger complet en une commande, récepteur OTLP et UI inclus.
  4. On instrumente avec le SDK OpenTelemetry, jamais avec une bibliothèque propre à Jaeger, pour rester libre du backend.
  5. En production, stockage distribué et sampling : Elasticsearch ou Cassandra pour le volume, échantillonnage pour le coût.

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens +700 guides gratuits, sans pub ni tracking. Un soutien, même symbolique, m'aide à couvrir l'hébergement et à garder ces ressources gratuites. Merci pour votre appui.

Le formulaire ne s'affiche pas ? Ouvrir Ko-fi dans un onglet.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn