Introduction — De la théorie à la pratique
Tu connais maintenant l’architecture OpenTelemetry : SDK, Collector, backends. Mais une architecture sans instrumentation, c’est comme un réseau de caméras sans enregistrement — l’infrastructure est là, mais tu ne vois rien.
L’instrumentation, c’est l’acte de rendre ton code observable. C’est ajouter les capteurs qui vont générer les traces, métriques et logs exploitables. Et la bonne nouvelle, c’est qu’avec OTel, 80% du travail est automatique.
Ce cours couvre les deux approches (auto et manuelle), les stratégies de sampling pour contrôler le volume en production, et les conventions qui rendent tes données exploitables par n’importe quel backend.
🔥 Cas réel : Une fintech a instrumenté ses 12 microservices en une journée grâce à l’auto-instrumentation OTel. Résultat immédiat : ils ont découvert que 40% des requêtes faisaient 3 allers-retours vers le cache Redis au lieu d’un seul — un bug invisible sans tracing distribué.
Auto-instrumentation : le 80/20 de l’observabilité
L’auto-instrumentation intercepte automatiquement les appels aux frameworks et bibliothèques populaires (Express, Flask, Spring Boot, gRPC, clients HTTP, drivers SQL…) pour générer des spans sans toucher au code applicatif.
Chaque langage a son mécanisme. En Python, un wrapper CLI charge les hooks avant l’application. En Java, un agent JVM injecte le bytecode à chaud. En Node.js, un fichier de setup s’exécute avant l’app.
Voici la mise en place pour les trois langages les plus courants :
# Python — installation + lancement
pip install opentelemetry-distro opentelemetry-exporter-otlp
opentelemetry-bootstrap -a install
opentelemetry-instrument \
--service_name order-service \
--traces_exporter otlp \
--metrics_exporter otlp \
--exporter_otlp_endpoint http://otel-collector:4317 \
python app.py
# Java — agent JVM, zéro modification de code
java -javaagent:opentelemetry-javaagent.jar \
-Dotel.service.name=payment-service \
-Dotel.exporter.otlp.endpoint=http://otel-collector:4317 \
-jar payment-service.jar
// Node.js — tracing.js à charger AVANT tout import
const { NodeSDK } = require('@opentelemetry/sdk-node');
const { OTLPTraceExporter } = require('@opentelemetry/exporter-trace-otlp-grpc');
const { getNodeAutoInstrumentations } = require('@opentelemetry/auto-instrumentations-node');
const sdk = new NodeSDK({
serviceName: 'frontend-service',
traceExporter: new OTLPTraceExporter({
url: 'http://otel-collector:4317',
}),
instrumentations: [getNodeAutoInstrumentations()],
});
sdk.start();
// Lancement : node --require ./tracing.js app.js
Sur Kubernetes, l’Operator OTel va encore plus loin : une simple annotation sur le Deployment injecte l’auto-instrumentation sans même modifier l’image Docker :
apiVersion: opentelemetry.io/v1alpha1
kind: Instrumentation
metadata:
name: otel-instrumentation
spec:
exporter:
endpoint: http://otel-collector.monitoring:4317
propagators: [tracecontext, baggage]
sampler:
type: parentbased_traceidratio
argument: "0.25"
---
# Annotation sur le Deployment — c'est tout
# inject-python: "true" / inject-java / inject-nodejs
apiVersion: apps/v1
kind: Deployment
metadata:
name: order-service
spec:
template:
metadata:
annotations:
instrumentation.opentelemetry.io/inject-python: "true"
💡 Tip DevOps : L’Operator OTel est la manière la plus propre d’instrumenter sur Kubernetes. Les devs n’ont rien à changer dans leur code ni leur Dockerfile — l’équipe plateforme gère l’instrumentation via des annotations. C’est la séparation des responsabilités idéale.
⚠️ Attention : L’auto-instrumentation ajoute une latence de ~1-5ms par span. Sur un chemin critique avec 20 spans, ça peut représenter 20-100ms. Mesure toujours l’impact en staging avant de déployer en production.
Instrumentation manuelle : la logique métier
L’auto-instrumentation capture les entrées/sorties techniques (HTTP, SQL, gRPC). Mais elle ne sait pas que ta requête traite une commande VIP, que le paiement a échoué pour insuffisance de fonds, ou que le stock est tombé à zéro. Pour ça, il faut l’instrumentation manuelle.
Le pattern est simple : tu crées un span autour de chaque opération métier significative, et tu y attaches des attributs qui donnent du contexte exploitable :
from opentelemetry import trace, metrics
from opentelemetry.trace import StatusCode
tracer = trace.get_tracer("order-service", "1.0.0")
meter = metrics.get_meter("order-service", "1.0.0")
# Métriques custom
order_counter = meter.create_counter("orders.created", unit="1")
order_duration = meter.create_histogram("orders.processing_duration", unit="ms")
def process_order(order_id: str, user_id: str):
with tracer.start_as_current_span(
"process_order",
attributes={"order.id": order_id, "user.id": user_id}
) as span:
try:
with tracer.start_as_current_span("validate_order") as v_span:
validated = validate(order_id)
v_span.set_attribute("order.valid", validated)
if not validated:
span.set_status(StatusCode.ERROR, "Validation failed")
span.add_event("validation_failed", {"reason": "insufficient_stock"})
return
with tracer.start_as_current_span("process_payment") as p_span:
result = charge_payment(order_id)
p_span.set_attribute("payment.method", result.method)
order_counter.add(1, {"order.type": "standard"})
span.set_status(StatusCode.OK)
except Exception as e:
span.set_status(StatusCode.ERROR, str(e))
span.record_exception(e)
raise
🧠 À retenir : La méthode record_exception(e) est précieuse — elle capture automatiquement le type d’exception, le message et la stack trace comme un span event. En cas d’incident, tu as le contexte complet dans la trace sans aller chercher dans les logs.
La propagation de contexte est ce qui relie les spans entre services. Quand le service A appelle le service B en HTTP, le SDK ajoute automatiquement un header traceparent (format W3C Trace Context) qui contient le traceId et le spanId courant. Le SDK du service B le lit et crée un span enfant — la trace reste cohérente d’un bout à l’autre.
Sampling : contrôler le volume sans perdre la visibilité
Un service à 5 000 req/s qui génère 5 spans par requête produit 25 000 spans/seconde — soit ~2 milliards par jour. Stocker tout est économiquement absurde. Le sampling est la discipline qui consiste à ne garder qu’une fraction représentative des données.
Deux approches existent, et la meilleure stratégie les combine :
Head-based sampling (au SDK) — La décision est prise au début de la trace, avant même son exécution. Simple et léger, mais aveugle : tu ne sais pas encore si la trace sera intéressante. Typiquement, on garde 25-50% des traces.
Tail-based sampling (au Collector) — La décision est prise à la fin, quand tous les spans sont collectés. Beaucoup plus intelligent : tu peux garder 100% des erreurs et des traces lentes, et ne sampler que le trafic normal. Mais ça consomme de la mémoire au Collector.
La stratégie production recommandée combine les deux pour un contrôle fin :
# Tail sampling au Collector Gateway
processors:
tail_sampling:
decision_wait: 10s
num_traces: 100000
policies:
# Garder 100% des erreurs
- name: errors
type: status_code
status_code: { status_codes: [ERROR] }
# Garder 100% des traces > 1s
- name: slow-traces
type: latency
latency: { threshold_ms: 1000 }
# Garder 100% des endpoints critiques
- name: critical-endpoints
type: string_attribute
string_attribute:
key: http.route
values: ["/api/payments", "/api/orders"]
# 10% du reste
- name: default
type: probabilistic
probabilistic: { sampling_percentage: 10 }
🔥 Cas réel : Un SaaS B2B traitait 50 000 traces/s. Sans sampling : ~150 TB/mois de stockage de traces, coût Grafana Cloud ~45 000€/mois. Avec tail sampling (100% erreurs, 100% lentes, 5% reste) : ~8 TB/mois, coût ~2 500€/mois. Zéro perte de visibilité sur les problèmes réels.
⚠️ Attention : Le tail sampling doit tourner dans un Collector Gateway centralisé, pas dans les agents DaemonSet. Le Collector doit voir tous les spans d’une trace pour prendre une décision cohérente. Si les spans sont répartis entre plusieurs agents, chacun ne voit qu’un fragment.
Semantic Conventions : le langage commun
Les semantic conventions OTel standardisent les noms d’attributs. C’est ce qui permet à Grafana, Jaeger ou Datadog de proposer des dashboards automatiques — parce que tout le monde appelle la méthode HTTP http.method et pas httpMethod ou METHOD.
Voici les conventions essentielles à connaître :
# Identité du service (resource attributes)
service.name: "order-service"
service.version: "2.1.0"
deployment.environment: "production"
k8s.namespace.name: "production"
# HTTP
http.method: "POST"
http.route: "/api/orders"
http.status_code: 201
# Base de données
db.system: "postgresql"
db.statement: "SELECT * FROM orders WHERE id = $1"
db.operation: "SELECT"
# Messaging
messaging.system: "kafka"
messaging.destination: "orders-topic"
messaging.operation: "publish"
💡 Tip DevOps : Ajoute systématiquement deployment.environment et service.version comme resource attributes. En cas d’incident, tu peux immédiatement filtrer par environnement et identifier si le problème est apparu avec une nouvelle version.
Stack de démo complète
Pour mettre tout en pratique, voici un Docker Compose qui lance une stack d’observabilité complète — Collector, Tempo (traces), Prometheus (métriques), Loki (logs) et Grafana (visualisation) :
# docker-compose.yaml
version: "3.8"
services:
otel-collector:
image: otel/opentelemetry-collector-contrib:latest
command: ["--config=/etc/otel-collector-config.yaml"]
volumes:
- ./otel-collector-config.yaml:/etc/otel-collector-config.yaml
ports:
- "4317:4317"
- "4318:4318"
tempo:
image: grafana/tempo:latest
command: ["-config.file=/etc/tempo.yaml"]
volumes:
- ./tempo.yaml:/etc/tempo.yaml
prometheus:
image: prom/prometheus:latest
volumes:
- ./prometheus.yaml:/etc/prometheus/prometheus.yml
ports:
- "9090:9090"
grafana:
image: grafana/grafana:latest
ports:
- "3000:3000"
environment:
GF_AUTH_ANONYMOUS_ENABLED: "true"
GF_AUTH_ANONYMOUS_ORG_ROLE: Admin
Lance docker compose up, instrumente ton app avec l’auto-instrumentation OTel pointant vers localhost:4317, et ouvre Grafana sur localhost:3000. Tu verras tes traces dans Tempo, tes métriques dans Prometheus, le tout corrélé dans les dashboards Grafana.
Résumé
L’instrumentation OTel se décline en deux niveaux complémentaires. L’auto-instrumentation capture le trafic technique (HTTP, SQL, gRPC, messaging) sans modifier le code — c’est le point de départ obligatoire. L’instrumentation manuelle ajoute le contexte métier (identifiants de commande, méthodes de paiement, flags utilisateur) qui rend les traces réellement exploitables.
Le sampling est indispensable en production : combine head-based (réduction brute au SDK) et tail-based (intelligence au Collector) pour garder 100% des problèmes et une fraction statistique du trafic normal. Les semantic conventions sont le ciment qui rend tes données interopérables entre tous les backends.
🧠 À retenir : L’objectif n’est pas de tout instrumenter, mais d’instrumenter ce qui compte. Les chemins critiques, les opérations métier clés, les points d’intégration entre services. Qualité de l’instrumentation > quantité de spans.
⚠️ Attention : Le Collector Gateway en tail sampling est un point de passage obligatoire et potentiel SPOF. Déploie-le en haute disponibilité (3+ replicas) avec un load balancer qui route par traceId pour que tous les spans d’une trace arrivent au même replica.
Contenu réservé aux abonnés
Ce chapitre fait partie de la formation complète. Abonne-toi pour débloquer tous les contenus.
Débloquer pour 29 CHF/moisLe chapitre 1 de chaque formation est gratuit.
Série pas encore débloquée
Termine la série prérequise d'abord pour accéder à ce contenu.
Aller à la série prérequiseSérie : Observabilité Moderne
2 / 6Sur cette page
Articles liés
OpenTelemetry : architecture et Collector
Les 3 piliers de l'observabilité (traces, métriques, logs), l'architecture OpenTelemetry SDK → Collector → Backend, et la configuration du Collector en détail.
Prometheus : architecture et PromQL
Architecture interne de Prometheus (TSDB, scraping, service discovery), PromQL avancé et recording/alerting rules pour le monitoring production.
Alertmanager et Grafana dashboards
Alertmanager avec routing et escalade (PagerDuty, Slack), Grafana dashboards efficaces, et stratégies de scaling Prometheus (Thanos, Cortex).