Pourquoi adopter OpenTelemetry plutôt qu'un agent propriétaire
OpenTelemetry est un projet CNCF graduaté qui unifie les trois piliers de l'observabilité — traces, métriques et logs — sous un standard vendor-neutral. Contrairement aux agents Datadog ou New Relic qui vous enferment dans un contrat et un format propriétaire, l'OTel Collector reçoit, transforme et exporte la télémétrie vers n'importe quel backend compatible. Vous changez de stockage sans retoucher une ligne de votre code applicatif.
Les SDKs couvrent Node.js, Python, Go, Java, PHP, Ruby et la plupart des langages modernes. Les propagateurs de contexte W3C TraceContext et B3 sont inclus : ils transmettent automatiquement le trace-id entre vos microservices via les headers HTTP, sans configuration supplémentaire. Enfin, l'OTel Collector peut tourner en tant qu'agent local sur chaque VM ou en mode gateway centralisé — vous choisissez la topologie qui convient à votre infrastructure.
Ce que la stack OTel + Grafana Tempo vous donne
- Tracing distribué sans index externe — Grafana Tempo v2.x stocke les traces en blocs compressés (parquet) sur disque local ou objet storage, sans Cassandra ni Elasticsearch.
- Requêtes TraceQL — le langage dédié de Tempo permet de filtrer les traces par durée, service, statut d'erreur ou attributs personnalisés en quelques secondes.
- Compatibilité multi-protocoles — Tempo accepte OTLP, Zipkin et Jaeger : vos applications existantes s'y connectent sans ré-instrumentation complète.
- Stack légère sur VPS — 2 Go de RAM suffisent pour un environnement de dev ou staging ; en production avec charge, 4 Go assurent une marge confortable.
- Coût maîtrisé — un VPS à 99 DH/mois self-hosted remplace un abonnement APM Datadog facturé environ 23 $ par hôte et par mois.
- Corrélation traces–logs–métriques — en liant Tempo à Loki et Prometheus dans Grafana, vous passez d'une trace à ses logs et ses métriques en un seul clic.
- Pas de vendor lock-in — si vous migrez vers Jaeger demain, vous reconfigurez un exporter dans le Collector ; vos SDKs et votre code ne bougent pas.
Prérequis VPS
Votre VPS doit disposer d'au moins 2 Go de RAM (4 Go recommandés en production) et d'un accès root ou sudo. Docker Engine ≥ 24 et Docker Compose v2 doivent être installés.
Prévoyez un sous-domaine dédié — par exemple grafana.votre-domaine.com — pour exposer l'interface Grafana derrière un reverse proxy TLS (nginx ou Caddy). Les ports 4317 (OTLP/gRPC) et 4318 (OTLP/HTTP) doivent être ouverts dans votre pare-feu côté privé pour recevoir la télémétrie depuis vos applications ; ne les exposez pas sur l'interface publique. Le port 3200 de Tempo et le port 3000 de Grafana restent internes au réseau Docker.
Côté stockage, prévoyez au minimum 10 Go libres pour les blocs de traces. La valeur montée par défaut dans /tmp/tempo ne survit pas aux redémarrages : remplacez-la dès la mise en production par un volume Docker ou un montage sur un disque dédié.
Architecture de la stack : du span à la visualisation
Le flux de données suit quatre étapes :
1. Votre application instrumente ses requêtes avec un SDK OTel et exporte des spans vers l'OTel Collector (OTLP gRPC port 4317 ou HTTP port 4318).
2. L'OTel Collector reçoit, met en tampon par batch processor, puis exporte vers Tempo via OTLP gRPC interne. Il peut simultanément envoyer les métriques vers Prometheus et les logs vers Loki.
3. Grafana Tempo stocke les blocs de traces compressés sur disque (ou S3). Il expose une API TraceQL sur le port 3200 pour les requêtes de Grafana.
4. Grafana interroge Tempo avec TraceQL, affiche les flamegraphs, les service graphs et les span metrics. Si Tempo est lié à Loki, un clic sur un span ouvre les logs du service concerné sur la même fenêtre temporelle.
TraceQL est le langage de requête natif de Tempo. Une requête typique ressemble à : {service.name="api" && status=error && duration>500ms}. Vous filtrez par attribut de ressource, attribut de span, statut ou durée — et vous obtenez immédiatement les traces qui franchissent un seuil de latence.
Les Span Metrics sont un pipeline Tempo qui dérive automatiquement des métriques RED (Rate, Error, Duration) à partir des traces entrantes, sans que vous n'ajoutiez un seul compteur dans votre code. Ces métriques peuvent être scrappées par Prometheus et affichées dans des tableaux de bord Grafana standard.
Déployer la stack OTel + Tempo en sept étapes
Créer la structure de répertoires
Organisez un dossier
otel-stack/sur votre VPS :mkdir -p otel-stack/config cd otel-stackVous y placerez trois fichiers de configuration :
tempo.yaml,otel-collector-config.yamletdocker-compose.yml.Écrire la configuration Tempo (tempo.yaml)
Créez
config/tempo.yamlavec le backend de stockage local et le support de TraceQL :server: http_listen_port: 3200 distributor: receivers: otlp: protocols: grpc: endpoint: 0.0.0.0:4317 ingester: max_block_bytes: 1_000_000 max_block_duration: 5m compactor: compaction: block_retention: 168h # 7 jours storage: trace: backend: local wal: path: /tmp/tempo/wal local: path: /tmp/tempo/blocksAjustez
block_retentionselon la durée de rétention souhaitée (voir la section dédiée plus bas).Écrire la configuration de l'OTel Collector
Créez
config/otel-collector-config.yaml:receivers: otlp: protocols: grpc: endpoint: 0.0.0.0:4317 http: endpoint: 0.0.0.0:4318 processors: batch: timeout: 5s send_batch_size: 1000 exporters: otlp: endpoint: tempo:4317 tls: insecure: true service: pipelines: traces: receivers: [otlp] processors: [batch] exporters: [otlp]Pour ajouter les métriques vers Prometheus et les logs vers Loki, ajoutez les receivers et exporters correspondants dans les pipelines
metricsetlogs.Écrire le docker-compose.yml
Créez
docker-compose.ymlà la racine deotel-stack/:services: otel-collector: image: otel/opentelemetry-collector-contrib:latest volumes: - ./config/otel-collector-config.yaml:/etc/otelcol-contrib/config.yaml ports: - "4317:4317" - "4318:4318" networks: - observability depends_on: - tempo tempo: image: grafana/tempo:latest command: ["-config.file=/etc/tempo.yaml"] volumes: - ./config/tempo.yaml:/etc/tempo.yaml - tempo-data:/tmp/tempo ports: - "3200:3200" networks: - observability grafana: image: grafana/grafana:latest ports: - "3000:3000" environment: - GF_AUTH_ANONYMOUS_ENABLED=true - GF_AUTH_ANONYMOUS_ORG_ROLE=Admin networks: - observability depends_on: - tempo volumes: tempo-data: networks: observability:Lancez la stack :
docker compose up -dpuis vérifiez que les trois services sonthealthyavecdocker compose ps.Configurer Grafana Tempo comme datasource
Ouvrez Grafana sur
http://votre-vps:3000. Naviguez vers Connections → Data sources → Add data source et sélectionnez Tempo. Renseignez l'URLhttp://tempo:3200. Activez Service graph et Span metrics pour enrichir la visualisation avec des graphiques de dépendances et des métriques RED calculées automatiquement. Si votre stack inclut Loki, liez les deux datasources dans le champ Linked data sources : vous pourrez naviguer d'un span à ses logs en un clic.Envoyer votre première trace de test
Testez que la stack reçoit bien des traces avec l'outil
telemetrygen:docker run --rm --network otel-stack_observability \ ghcr.io/open-telemetry/opentelemetry-collector-contrib/telemetrygen:latest \ traces \ --otlp-endpoint otel-collector:4317 \ --otlp-insecure \ --duration 5s \ --rate 10Dans Grafana, ouvrez Explore, sélectionnez la datasource Tempo et lancez la requête
{}pour voir toutes les traces reçues. Si aucune trace n'apparaît, vérifiez les logs du Collector :docker compose logs otel-collector.Requêter vos traces avec TraceQL
Dans le panneau Explore de Grafana (datasource Tempo), saisissez une requête TraceQL pour filtrer vos traces. Exemples utiles :
-
{service.name="mon-api"}— toutes les traces du servicemon-api
-{status=error}— toutes les traces en erreur
-{duration>1s && service.name="mon-api"}— traces lentes de plus d'une seconde
-{span.http.route="/api/orders" && status=error}— erreurs sur une route préciseCliquez sur une trace pour ouvrir le flamegraph détaillé : chaque span affiche sa durée, ses attributs et les erreurs éventuelles.
Configurer la rétention et le stockage des traces
Par défaut, Tempo stocke les blocs localement dans /tmp/tempo. Pour une installation durable, deux options s'offrent à vous.
Stockage local sur volume Docker : c'est la configuration recommandée pour un VPS standalone. Le volume tempo-data défini dans le docker-compose.yml ci-dessus persiste les données entre les redémarrages. Ajustez block_retention dans tempo.yaml selon votre besoin : 72 h pour un environnement de staging, 168 h (7 jours) à 720 h (30 jours) pour la production. Chaque heure de trace compressée occupe environ 100–300 Mo selon le volume de spans.
Stockage objet S3-compatible : pour une stack multi-nœuds ou un stockage sans plafond, remplacez le backend local par un backend s3 dans tempo.yaml :
storage:
trace:
backend: s3
s3:
bucket: mon-bucket-traces
endpoint: s3.amazonaws.com
region: eu-west-1
access_key: ${S3_ACCESS_KEY}
secret_key: ${S3_SECRET_KEY}
wal:
path: /tmp/tempo/walTempo est compatible avec AWS S3, MinIO, Scaleway Object Storage et tout backend qui implémente l'API S3. La WAL (Write-Ahead Log) reste toujours sur disque local pour garantir la durabilité des traces en cours d'ingestion.
Le compacteur de Tempo fusionne les petits blocs et applique la politique de rétention. Il tourne en arrière-plan et ne nécessite aucune intervention manuelle. Surveillez la taille du dossier blocks/ avec docker exec tempo du -sh /tmp/tempo/blocks pour anticiper un éventuel manque d'espace.
Instrumenter une application Node.js
L'auto-instrumentation OTel pour Node.js capture automatiquement les appels HTTP sortants, les requêtes Express/Fastify, les requêtes de base de données et bien d'autres bibliothèques sans modifier votre code métier.
Installez les packages nécessaires :
npm install @opentelemetry/sdk-node \
@opentelemetry/auto-instrumentations-node \
@opentelemetry/exporter-trace-otlp-grpcCréez un fichier tracing.js à charger en premier :
const { NodeSDK } = require('@opentelemetry/sdk-node');
const { getNodeAutoInstrumentations } = require('@opentelemetry/auto-instrumentations-node');
const { OTLPTraceExporter } = require('@opentelemetry/exporter-trace-otlp-grpc');
const sdk = new NodeSDK({
traceExporter: new OTLPTraceExporter({
url: process.env.OTEL_EXPORTER_OTLP_ENDPOINT || 'grpc://localhost:4317',
}),
instrumentations: [getNodeAutoInstrumentations()],
});
sdk.start();Démarrez votre application en chargeant ce fichier en premier :
export OTEL_SERVICE_NAME=mon-api
export OTEL_EXPORTER_OTLP_ENDPOINT=http://votre-vps:4317
node --require ./tracing.js app.jsPour les environnements Docker, passez les variables OTEL_SERVICE_NAME et OTEL_EXPORTER_OTLP_ENDPOINT dans votre docker-compose.yml ou votre manifest Kubernetes. Tous les appels HTTP entrants et sortants seront automatiquement tracés et corrélés par le propagateur W3C TraceContext.
Instrumenter une application Python
Le package opentelemetry-distro fournit un mécanisme de bootstrap qui installe automatiquement tous les packages d'instrumentation pour les bibliothèques détectées dans votre environnement.
Installez et bootstrappez :
pip install opentelemetry-distro opentelemetry-exporter-otlp
opentelemetry-bootstrap -a installLancez votre application en la préfixant avec opentelemetry-instrument :
export OTEL_SERVICE_NAME=mon-service-python
export OTEL_EXPORTER_OTLP_ENDPOINT=http://votre-vps:4317
export OTEL_EXPORTER_OTLP_PROTOCOL=grpc
opentelemetry-instrument python app.pyLes frameworks Django, Flask, FastAPI, SQLAlchemy et les clients HTTP (requests, httpx, aiohttp) sont instrumentés automatiquement. Pour un déploiement en conteneur, définissez les variables OTEL_* dans votre fichier d'environnement et assurez-vous que le port 4317 de l'OTel Collector est accessible depuis le réseau de vos conteneurs.
Si vous préférez une instrumentation manuelle pour un contrôle plus fin, utilisez directement l'API OTel :
from opentelemetry import trace
tracer = trace.get_tracer(__name__)
with tracer.start_as_current_span("traitement-commande") as span:
span.set_attribute("commande.id", commande_id)
# votre logique métierOTel + Tempo vs Jaeger vs Zipkin vs Datadog APM
Faites défiler le tableau
| Critère | OTel + Grafana Tempo | Jaeger | Datadog APM |
|---|---|---|---|
| Coût mensuel | Hébergement seul (99 DH/mois VPS) | Hébergement seul (similaire) | ~23 $ par hôte par mois |
| Stockage backend | Local, S3, GCS, Azure Blob | Cassandra, Elasticsearch, Badger | SaaS managé (opaque) |
| Langage de requête | TraceQL (natif, puissant) | Jaeger Query UI (basique) | APM Query Language (propriétaire) |
| Auto-instrumentation | OTel SDK universel | Bibliothèques Jaeger ou OTel | Agent Datadog propriétaire |
| Corrélation traces–logs | Oui (Loki + Grafana) | Limitée | Oui (intégré, payant) |
| Span Metrics (RED) | Oui (pipeline Tempo natif) | Non natif | Oui (calculé côté Datadog) |
| Vendor lock-in | Aucun (standard CNCF) | Faible (compatible OTel) | Fort (agent et format propriétaires) |
| Complexité d'installation | Moyenne (3 conteneurs) | Faible à moyenne | Faible (agent auto) |
Alertes et SLOs dans Grafana
Grafana 10+ permet de définir des alertes directement sur les métriques dérivées des traces. Les Span Metrics de Tempo génèrent automatiquement trois métriques par service : traces_spanmetrics_calls_total (taux de requêtes), traces_spanmetrics_latency_bucket (histogramme de latence) et leur compteur d'erreurs associé.
Pour créer un SLO sur le taux d'erreur de votre API :
1. Dans Grafana, allez dans Alerting → Alert rules → New alert rule.
2. Choisissez la datasource Prometheus (ou Tempo via Span Metrics) et saisissez une requête comme rate(traces_spanmetrics_calls_total{status_code="STATUS_CODE_ERROR", service="mon-api"}[5m]) / rate(traces_spanmetrics_calls_total{service="mon-api"}[5m]).
3. Définissez le seuil d'alerte (par exemple > 0.05 pour un taux d'erreur supérieur à 5 %) et la fenêtre d'évaluation.
4. Configurez un contact point : Alertmanager, PagerDuty, Slack ou webhook — tout s'intègre depuis Alerting → Contact points.
Pour le P99 de latence, interrogez histogram_quantile(0.99, rate(traces_spanmetrics_latency_bucket{service="mon-api"}[5m])) et déclenchez une alerte si la valeur dépasse votre budget d'erreur (par exemple 2 secondes). Ces alertes basées sur des traces sont bien plus précises que des alertes sur des métriques synthétiques, car elles reflètent le vécu réel de chaque requête.
Combiner traces, logs et métriques dans un seul Collector
L'OTel Collector n'est pas réservé aux traces. En ajoutant un receiver filelog dans votre configuration, vous collectez les logs de vos conteneurs Docker et les expédiez vers Loki. Un receiver prometheus scrape vos endpoints /metrics et les exporte vers Mimir ou Prometheus. Un seul processus gère ainsi l'intégralité de la télémétrie de votre VPS.
Dans Grafana, liez Tempo et Loki via des derived fields : quand un log contient un trace_id, un lien cliquable vous amène directement au flamegraph correspondant. À l'inverse, depuis une trace Tempo, un lien vous ouvre les logs Loki du service et de la fenêtre temporelle associés. Cette navigation croisée réduit considérablement le temps de diagnostic : vous n'avez plus à corréler manuellement des timestamps entre trois onglets distincts.
Résoudre les problèmes courants
Aucun span n'arrive dans Tempo. Vérifiez d'abord que l'OTel Collector est joignable depuis votre application (telnet votre-vps 4317). Consultez les logs du Collector (docker compose logs otel-collector) : une erreur connection refused sur tempo:4317 indique que Tempo n'est pas encore démarré ou que son port interne n'est pas exposé sur le réseau Docker. Assurez-vous que les deux services sont sur le même réseau (observability).
Connexion refusée depuis l'application vers le Collector. Sur un pare-feu UFW ou firewalld, autorisez les ports 4317 et 4318 uniquement pour les IP de vos applications, pas pour l'ensemble d'Internet. En production, les applications et le Collector doivent être sur le même réseau privé ; n'exposez jamais le Collector sur l'interface publique sans authentification.
Traces tronquées ou spans manquants. Le batch processor du Collector regroupe les spans avant de les envoyer. Si votre application envoie peu de spans, les traces peuvent mettre jusqu'à timeout secondes (5 s par défaut) pour apparaître dans Grafana. Pour le développement, réduisez timeout: 1s dans le batch processor. Si des spans sont systématiquement absents, vérifiez que le propagateur de contexte est activé dans votre SDK (W3CTraceContextPropagator) — sans lui, les appels entre services génèrent des traces orphelines.
Tempo consomme trop d'espace disque. Réduisez block_retention dans tempo.yaml et relancez Tempo. Le compacteur appliquera la nouvelle politique au prochain cycle (toutes les 5 minutes par défaut). En cas d'urgence, supprimez manuellement les anciens blocs dans /tmp/tempo/blocks tout en laissant la WAL intacte.
Grafana ne trouve pas les traces depuis les logs Loki. Vérifiez que vos applications injectent bien le trace_id dans leurs logs (format structuré JSON recommandé) et que le champ Derived fields dans la datasource Loki pointe vers le bon pattern ("traceId": "(\w+)"). L'URL du lien doit pointer vers la datasource Tempo avec le bon identifiant de datasource Grafana.