Tracing distribué
Spans, traces, OpenTelemetry, Jaeger et comment suivre les requêtes à travers plusieurs services.
Qu’est-ce que le tracing distribué ?
Lorsqu’une requête utilisateur traverse 5, 10 ou 20 services avant de renvoyer une réponse, comment savoir où le temps a été consommé ? Quel service a échoué ? Quel appel a été le plus lent ?
Le tracing distribué résout précisément ce problème : il permet de suivre le parcours complet d’une requête à travers tous les services qui y participent, en mesurant les temps et en capturant le contexte à chaque étape.
Concepts fondamentaux
Trace
Une trace représente le parcours complet d’une opération de bout en bout. Par exemple, une trace de « création de commande » couvre tout, depuis l’envoi de la requête par le frontend jusqu’à la réception de la confirmation par l’utilisateur.
Chaque trace possède un identifiant unique (traceId) qui se propage entre tous les services concernés.
Span
Un span représente une unité de travail au sein d’une trace. Chaque service ou composant qui participe à l’opération crée un ou plusieurs spans.
Un span contient :
- Nom : Description de l’opération (ex : « POST /api/orders »).
- Timestamps : Début et fin de l’opération.
- SpanId : Identifiant unique du span.
- ParentSpanId : Référence au span parent, formant un arbre.
- Tags/Attributes : Métadonnées supplémentaires (méthode HTTP, status code, etc.).
- Events/Logs : Événements ponctuels au sein du span.
- Status : OK, ERROR ou UNSET.
Contexte de propagation
Pour que le tracing fonctionne entre les services, le contexte (traceId, spanId) doit se propager à chaque appel. Les mécanismes les plus courants sont :
- En-têtes HTTP :
traceparent,tracestate(standard W3C Trace Context). - En-têtes de messagerie : Attributs dans les messages Kafka, RabbitMQ, SQS.
- gRPC metadata : Propagation automatique dans les appels gRPC.
OpenTelemetry
OpenTelemetry (OTel) est le standard ouvert pour l’instrumentation d’observabilité. Il unifie la collecte de traces, de métriques et de logs sous une seule API.
Pourquoi OpenTelemetry ?
Avant OTel, chaque éditeur avait sa propre API d’instrumentation. Si vous utilisiez Jaeger, vous instrumentiez avec l’API de Jaeger. Si vous passiez à Zipkin, il fallait tout réinstrumenter. OpenTelemetry résout ce problème avec une API vendor-neutral.
Composants d’OTel
- API : Interface pour instrumenter le code. Elle est stable et ne change pas.
- SDK : Implémentation de l’API avec la configuration des exportateurs, des samplers, etc.
- Exporters : Envoient les données vers des backends spécifiques (Jaeger, Zipkin, OTLP).
- Collector : Composant intermédiaire qui reçoit, traite et exporte la télémétrie.
- Auto-instrumentation : Bibliothèques qui instrumentent automatiquement les frameworks populaires.
Le Collector
Le OTel Collector est un composant clé dans les architectures de production :
Services → OTel Collector → Backend (Jaeger, Tempo, etc.)
Avantages de l’utilisation d’un Collector :
- Découplage : Les services n’ont pas besoin de savoir à quel backend envoyer les données.
- Traitement : Il peut filtrer, échantillonner et enrichir les traces avant de les exporter.
- Buffering : Il absorbe les pics de trafic sans perdre de données.
- Multi-backend : Il peut envoyer les données vers plusieurs destinations simultanément.
Jaeger
Jaeger est une plateforme de tracing distribué open-source, développée à l’origine par Uber.
Fonctionnalités principales
- Visualisation des traces : Interface web pour explorer les traces sous forme de diagrammes en cascade (waterfall).
- Recherche : Retrouver des traces par service, opération, tags, durée ou traceId.
- Comparaison : Comparer deux traces pour identifier les différences de performance.
- Graphes de dépendances : Visualiser les relations entre les services à partir des traces collectées.
Architecture de Jaeger
Jaeger peut être déployé de plusieurs façons :
- All-in-one : Un seul binaire pour le développement et les tests.
- Production : Collector, stockage (Elasticsearch, Cassandra) et service Query séparés.
- Avec OTel Collector : En utilisant le Collector comme récepteur à la place de l’agent Jaeger.
Alternatives à Jaeger
- Zipkin : Une autre plateforme de tracing open-source, plus simple.
- Grafana Tempo : Backend de traces optimisé pour le coût, intégré à Grafana.
- AWS X-Ray : Service managé de tracing sur AWS.
- Datadog APM : Plateforme SaaS avec tracing intégré.
Corrélation entre les signaux
La véritable puissance du tracing se révèle lorsqu’il est corrélé avec les logs et les métriques :
Trace → Logs
Incluez le traceId dans chaque ligne de log. Lorsque vous rencontrez une trace lente ou comportant des erreurs, vous pouvez retrouver tous les logs associés en filtrant sur ce traceId.
Trace → Métriques
Les traces peuvent générer des métriques automatiquement :
- Latence par service et par opération.
- Taux d’erreurs par endpoint.
- Distribution de la durée des spans.
C’est ce que l’on appelle les « métriques dérivées des traces », et des outils comme Grafana Tempo les prennent en charge nativement.
Exemplars
Les exemplars sont des liens depuis une métrique vers une trace spécifique. Lorsque vous observez un pic de latence sur un dashboard, vous pouvez cliquer et sauter directement vers la trace qui l’a provoqué.
Sampling
Dans les systèmes à fort trafic, capturer 100 % des traces peut s’avérer d’un coût prohibitif. Le sampling permet de ne capturer qu’un sous-ensemble représentatif.
Types de sampling
- Head-based sampling : La décision de capturer ou non est prise au début de la trace. Simple, mais peut faire perdre des traces intéressantes.
- Tail-based sampling : La décision est prise à la fin de la trace, lorsque le résultat est connu. Permet de capturer 100 % des erreurs et des traces lentes, mais nécessite du buffering.
- Rate limiting : Capturer au maximum N traces par seconde.
Stratégie recommandée
- Capturez 100 % des erreurs — toujours.
- Capturez 100 % des traces lentes (au-dessus d’un seuil).
- Appliquez un sampling probabiliste au reste (ex : 10 % des traces réussies).
Bonnes pratiques
Instrumentation pertinente
N’instrumentez pas chaque fonction — instrumentez les opérations qui franchissent des frontières :
- Appels HTTP/gRPC entre services.
- Requêtes vers les bases de données.
- Publication/consommation de messages.
- Appels vers des services externes.
Noms de spans descriptifs
Utilisez des noms qui identifient l’opération, et non l’implémentation :
- Bien :
POST /api/orders,OrderRepository.save - Mal :
handler,function1,doStuff
Attributs utiles
Ajoutez des attributs qui aident à diagnostiquer les problèmes :
http.method,http.status_code,http.urldb.system,db.statement(sans données sensibles)user.id,order.id(identifiants métier)
Propagation cohérente
Assurez-vous que le contexte se propage sur tous les canaux de communication — HTTP, messagerie, cron jobs, workers asynchrones. Une trace incomplète est pire que pas de trace du tout.
Résumé
Le tracing distribué est le troisième pilier de l’observabilité et probablement le plus précieux dans les architectures de microservices. Il permet de comprendre le flux complet d’une opération, d’identifier les goulots d’étranglement et de diagnostiquer les erreurs impliquant plusieurs services. Avec OpenTelemetry comme standard et des outils comme Jaeger, mettre en place du tracing est plus accessible que jamais.