Les traces, une histoire d’observabilité#
Vous commencez à connaître le terme observabilité par cœur maintenant à force d’en parler à travers mes articles.
Dans l’article précédent, je vous ai parlé de logs avec Loki et Alloy. C’est donc la suite logique de continuer sur cette lancée en abordant les traces !
Oui mais qu’est-ce qu’une trace ?
Eh bien c’est ce qui permet de visualiser et de comprendre le flux d’exécution d’une requête, en particulier dans les architectures microservices où une seule action utilisateur peut déclencher des dizaines d’appels entre différents services.
Chaque trace est composée de plusieurs segments appelés spans. Ces segments représentent les différentes opérations entre les composants permettant d’établir toutes les relations entre ces derniers et comprendre les différentes dépendances.
Différences entre trace et spans
Si on parle de plus en plus de traces aujourd’hui c’est notamment parce que l’adoption de standards comme OpenTracing puis OpenTelemetry a considérablement démocratisé l’instrumentation des applications pour les collecter, rendant cette pratique plus accessible.
Laisser des traces dans votre cluster Kubernetes, ça peut vous aider !#
Kubernetes est devenu l’environnement que l’on mentionne le plus quand on parle d’applications Cloud Natives notamment quand elles sont décomposées en microservices. Tout simplement parce que les forces de cet orchestrateur reposent sur le fait de mettre à l’échelle rapidement chaque composant, permettant aussi d’améliorer leur résilience et leur disponibilité.
Voici quelques points qui résument, à mon sens, le rôle essentiel d’avoir des traces dans un monde conteneurisé :
Identifier les goulots d’étranglement : Les traces permettent d’identifier précisément quels services ou quels appels consomment le plus de temps dans une transaction complète ;
Comprendre et identifier les dépendances : Les traces révèlent les relations entre les différents services et composants, aidant à comprendre l’architecture globale d’une application ;
Optimisation des performances : Quand on parle de performance au sujet des traces, on identifie surtout la latence. Cela permet d’avoir une idée du composant qui met le plus de temps sur une requête au global et agir sur l’optimisation de ce dernier.
Sans oublier que dans l’écosystème Kubernetes, d’autres composants rentrent en jeu comme les Ingress Controllers, Gateway API, Service Mesh, Network Policy, etc. Ces traces sont donc un prérequis pour comprendre le cheminement d’un appel à travers une application mais aussi à travers les différents mécanismes de l’orchestrateur !
Tempo#
Tempo de Grafana est un outil open source présent au sein de la Cloud Native Computing Foundation (CNCF), qui s’intègre à la perfection avec le monde Prometheus et Grafana. Il représente en lui seul une solution de stockage pour les traces sous forme de backend.
L’observabilité au sein de la CNCF en mars 2025
Tempo peut s’interfacer avec du stockage objet comme S3, Google Cloud Storage, Azure Blob Storage voire Minio pour stocker les traces brutes.
De plus, il offre un large éventail de formats : Jaeger, Zipkin et OpenTelemetry, sous différents protocoles facilitant ainsi son adoption dans des environnements existants.
Comme dit plus haut, il se marie très bien avec Grafana permettant de faire correspondre facilement des métriques et des logs aux traces grâce à l’utilisation d’identifiants de trace (traceId).
La durée de conservation des traces est aussi configurable, grâce à la définition de politiques de rétention flexibles, adaptées aux besoins spécifiques de chaque organisation.
L’architecture de Tempo lui permet de s’adapter à des volumes massifs de données de trace sans perte de performance.
Enfin, Tempo se met à l’échelle en fonction de vos besoins grâce à une architecture distribuée, facilitant l’amélioration des performances lors de l’ingestion des traces.
Architecture#
Architecture de Tempo provenant de la documentation officielle
L’architecture de Tempo se décompose en plusieurs composants :
- Distributor va récupérer les traces sous différents formats ;
- Ingester sera chargé de créer des filtres et index en organisant les traces sous forme de blocs avant de les stocker ;
- Query Frontend permet de fournir une API pour récupérer les traces, c’est ce composant que l’on interrogera via Grafana ;
- Querier se situe derrière le Query Frontend permettant de venir piocher dans les traces en utilisant le stockage ou l’Ingester ;
- Compactor se charge de réduire la place du stockage ;
- Metrics generator est un composant optionnel permettant de générer des métriques à partir des traces pour alimenter, pourquoi pas, Prometheus.
Modes d’installation#
Tempo arrive avec deux modes d’installation :
Monolithic (Single Binary) : Rapide à mettre en place dans le cas d’un test, ou pour les petits volumes de données ;
Distributed : À considérer pour les déploiements de grande envergure, Tempo est décomposé en microservices (ingester, compactor, distributor, etc.), permettant une mise à l’échelle beaucoup plus granulaire que sur le premier mode.
À noter qu’un opérateur existe : Tempo Operator pour les amateurs de ce type de mécanisme. Il reprend en partie le mode microservices décrit plus haut.
Après la théorie, la pratique !#
Pour suivre les étapes, vous pouvez récupérer les configurations à travers ce dépôt de code :
Set up and configure Tempo with Alloy and Grafana to look for traces in your Kubernetes cluster.
N’hésitez pas à modifier les différentes valeurs si cela fait du sens par rapport à votre configuration.
Ces différents fichiers values permettront de configurer Tempo et les différents outils associés.
C’est parti !
Alloy#
Alloy permet d’aller beaucoup plus loin qu’un simple collecteur de logs. Il permet aussi de récupérer les traces via une configuration dédiée.
Pour cela, il est primordial d’injecter la bonne configuration, par rapport à ce cas d’utilisation, les points de terminaison http et grpc au format OpenTelemetry sont nécessaires.
Cette configuration est totalement modulable et dépend du format que votre application ou votre outil est capable de fournir dans le but de récupérer les traces.
alloy:
configMap:
# -- Create a new ConfigMap for the config file.
create: true
# -- Content to assign to the new ConfigMap. This is passed into `tpl` allowing for templating from values.
content: |-
logging {
level = "info"
}
otelcol.receiver.otlp "default" {
http {}
grpc {}
output {
traces = [otelcol.processor.batch.default.input]
}
}
otelcol.processor.batch "default" {
output {
traces = [otelcol.exporter.otlp.tempo.input]
}
}
otelcol.exporter.otlp "tempo" {
client {
endpoint = "tempo-distributor.observability.svc.cluster.local:4317"
tls {
insecure = true
}
}
}
Sans oublier de les faire suivre et donc de les exporter vers le service Tempo qui sera déployé juste après.
Par défaut, le collecteur d’Alloy expose un service qu’il est nécessaire de surcharger pour rendre disponible les ports associés au protocole d’OpenTelemetry (OTLP) aussi bien en HTTP qu’en GRPC.
# -- Extra ports to expose on the Alloy container.
extraPorts:
- name: otlp-http
port: 4318
targetPort: 4318
protocol: TCP
- name: otlp-grpc
port: 4317
targetPort: 4317
protocol: TCP
La configuration semble correcte, l’étape suivante consiste à le déployer.
Avant de commencer, n’oubliez pas de configurer la source pour récupérer les charts Helm de Grafana :
helm repo add grafana https://grafana.github.io/helm-charts
helm repo update
Et ensuite, de procéder à l’installation :
helm install alloy grafana/alloy --version 0.12.5 --namespace observability --create-namespace --values ./values-alloy.yaml
Alloy est maintenant prêt pour ingurgiter vos événements !
Tempo#
Pour l’installation de Tempo avec Helm, vous avez le choix entre deux configurations :
Un chart tempo en mode monolithique, un seul binaire à déployer ;
Un autre chart décomposé en microservices : tempo-distributed idéal pour gérer la charge de manière granulaire sur chacun des composants.
Pour tester le fonctionnement de Tempo dans des conditions proches de la réalité, j’ai choisi le chart tempo-distributed. Libre à vous de choisir l’installation qui vous convient le mieux.
Dans ce mode de configuration, un stockage objet est fortement recommandé. Pour ma part, j’utilise Minio qui est directement inclus sous forme de sous chart Helm en lui associant un bucket pour stocker les traces :
# Minio
minio:
enabled: true
mode: standalone
rootUser: grafana-tempo
rootPassword: supersecret
buckets:
# Default Tempo storage bucket.
- name: tempo-traces
policy: none
purge: false
Ensuite, les protocoles OTLP doivent être activés pour que Tempo soit en mesure de recevoir les traces provenant d’Alloy :
traces:
otlp:
http:
# -- Enable Tempo to ingest Open Telemetry HTTP traces
enabled: true
grpc:
# -- Enable Tempo to ingest Open Telemetry GRPC traces
enabled: true
Enfin, on termine avec la configuration du stockage en mode S3 avec les paramètres du Minio déployé :
# To configure a different storage backend instead of local storage:
storage:
trace:
# -- The supported storage backends are gcs, s3 and azure, as specified in https://grafana.com/docs/tempo/latest/configuration/#storage
backend: s3
wal:
path: /tmp/tempo/wal
s3:
bucket: tempo-traces
endpoint: tempo-minio:9000
access_key: grafana-tempo
secret_key: supersecret
insecure: true
tls_insecure_skip_verify: true
Pour déployer le tout, la commande helm install
que vous connaissez (presque) par cœur :
helm install tempo grafana/tempo-distributed --version 1.32.7 --namespace observability --create-namespace --values ./values-tempo.yaml
Grafana#
Grafana, l’outil de visualisation par excellence bénéficie d’une configuration allégée, demandant d’initialiser une datasource afin de manipuler les données de Tempo :
datasources:
datasources.yaml:
apiVersion: 1
datasources:
# Tempo DataSource
- name: Tempo
uid: tempo
type: tempo
url: http://tempo-query-frontend:3100/
access: proxy
orgId: 1
Là aussi, même punition pour déployer :
helm install grafana grafana/grafana --version 8.10.4 --namespace observability --create-namespace --values ./values-grafana.yaml
Traefik pour tester#
Il ne reste plus qu’à venir brancher une application sur laquelle on souhaite visualiser les traces. De mon côté j’utilise Traefik qui peut générer des traces au format OTLP et qui sera capable de les déverser dans un collecteur comme Alloy.
Pourquoi Traefik ?
Traefik est un Ingress Controller, en d’autres termes, la porte d’entrée pour exposer mes services vers l’extérieur. Je trouve intéressant de pouvoir comprendre les routes et middlewares appelés pour chacune des requêtes. Cela m’aide à comprendre si ma configuration est correcte ou non.
Pour cela, la configuration de Traefik est légère. L’objectif étant de pouvoir lire des traces basiques fournies par ce dernier.
Pour déverser les traces au format OTLP, voici un extrait du paramétrage à adopter :
## Tracing
# -- https://doc.traefik.io/traefik/observability/tracing/overview/
tracing: # @schema additionalProperties: false
# -- Enables tracing for internal resources. Default: false.
addInternals: true
otlp:
# -- See https://doc.traefik.io/traefik/v3.0/observability/tracing/opentelemetry/
enabled: true
http:
# -- Set to true in order to send metrics to the OpenTelemetry Collector using HTTP.
enabled: true
# -- Format: <scheme>://<host>:<port><path>. Default: http://localhost:4318/v1/metrics
endpoint: "http://alloy.observability:4318/v1/traces"
Sans surprise, on vient renseigner le endpoint fourni par Alloy associé au protocole HTTP.
De plus, le addInternals: true
permet de tracer l’ensemble des couches internes de Traefik, très utile quand on configure une suite de Middleware
.
Enfin, dans l’objectif de générer du trafic et avoir des traces, on peut utiliser ici un service en mode NodePort
pour simplifier le cas d’utilisation :
service:
enabled: true
## -- Single service is using `MixedProtocolLBService` feature gate.
## -- When set to false, it will create two Service, one for TCP and one for UDP.
type: NodePort
Là aussi, pour déployer le chart Traefik, Helm est de la partie :
helm install traefik traefik/traefik --version 34.4.1 --namespace ingress --create-namespace --values ./values-traefik.yaml
Les outils sont maintenant correctement déployés et surtout opérationnels ! Comme vous pouvez le constater :
$ kubectl -n observability get po
NAME READY STATUS RESTARTS AGE
alloy-d7649 2/2 Running 0 72s
grafana-5b5dd98f75-dpdlm 1/1 Running 0 67s
tempo-compactor-5856cfc4b6-vf9sm 1/1 Running 3 (94s ago) 2m1s
tempo-distributor-776dd495cc-k2vdl 1/1 Running 3 (97s ago) 2m1s
tempo-ingester-0 1/1 Running 3 (86s ago) 2m1s
tempo-ingester-1 1/1 Running 3 (88s ago) 2m1s
tempo-ingester-2 1/1 Running 3 (89s ago) 2m1s
tempo-memcached-0 1/1 Running 0 2m1s
tempo-minio-568d558987-rvvjp 1/1 Running 0 2m1s
tempo-querier-6b7fb8f848-2ztqs 1/1 Running 3 (88s ago) 2m1s
tempo-query-frontend-685fcd8fb-fl8bg 1/1 Running 3 (97s ago) 2m1s
$ kubectl -n ingress get po
NAME READY STATUS RESTARTS AGE
traefik-59c8dbcb57-9gmpt 1/1 Running 0 78s
Consulter les traces#
Allez dans Grafana grâce à un port-forward :
kubectl -n observability port-forward svc/grafana 8080:80
Générez quelques traces en accédant au service Traefik et sans plus attendre, allez dans la section Explore de Grafana en sélectionnant Tempo comme DataSource.
Voici un exemple de ma configuration personnelle, bien évidemment pour obtenir ce résultat, j’ai configuré quelques IngressRoute
et Middleware
au sein de Traefik :
Visualisation de traces de Traefik avec Tempo et Grafana
Quelques mots pour conclure#
Tempo se révèle comme l’outil idéal pour collecter les traces de vos applications. À travers son architecture en mode microservices, il permet de bénéficier de la mise à l’échelle offerte par Kubernetes tout en s’intégrant à merveille dans l’écosystème Grafana.
Les différents formats et protocoles permettent à Tempo de stocker un très large éventail de données sans avoir à utiliser d’autres solutions tierces.
Enfin, sa mise en place est assez rapide grâce à une documentation et des charts Helm prêt à l’emploi.