Distributed Tracing
1 - Concepts
1.1 Pourquoi le tracing ?
Problème : Une requête lente... mais où exactement ?
1.2 Anatomie d'une Trace
1.3 Terminologie
| Terme | Description |
|---|---|
| Trace | Parcours complet d'une requête |
| Span | Unité de travail dans une trace |
| Context | Métadonnées propagées |
| Baggage | Données custom propagées |
| Sampling | % de traces capturées |
2 - OpenTelemetry
2.1 Architecture
2.2 Installation (Node.js)
// tracing.js
const { NodeSDK } = require('@opentelemetry/sdk-node');
const { getNodeAutoInstrumentations } = require('@opentelemetry/auto-instrumentations-node');
const { OTLPTraceExporter } = require('@opentelemetry/exporter-trace-otlp-http');
const { Resource } = require('@opentelemetry/resources');
const { SemanticResourceAttributes } = require('@opentelemetry/semantic-conventions');
const sdk = new NodeSDK({
resource: new Resource({
[SemanticResourceAttributes.SERVICE_NAME]: 'my-service',
[SemanticResourceAttributes.SERVICE_VERSION]: '1.0.0',
}),
traceExporter: new OTLPTraceExporter({
url: 'http://otel-collector:4318/v1/traces',
}),
instrumentations: [getNodeAutoInstrumentations()],
});
sdk.start();
// index.js
require('./tracing'); // Avant tout import
const express = require('express');
const app = express();
// ...
2.3 Manual Instrumentation
const { trace } = require('@opentelemetry/api');
const tracer = trace.getTracer('my-service');
async function processOrder(orderId) {
// Créer un span
return tracer.startActiveSpan('process-order', async (span) => {
try {
span.setAttribute('order.id', orderId);
// Span enfant
const result = await tracer.startActiveSpan('validate-order', async (childSpan) => {
// Validation logic
childSpan.setAttribute('validation.passed', true);
childSpan.end();
return validated;
});
// Event
span.addEvent('order-validated', { orderId });
return result;
} catch (error) {
span.recordException(error);
span.setStatus({ code: SpanStatusCode.ERROR });
throw error;
} finally {
span.end();
}
});
}
3 - Jaeger
3.1 Installation
# docker-compose.yml
version: '3.8'
services:
jaeger:
image: jaegertracing/all-in-one:1.52
ports:
- "16686:16686" # UI
- "4317:4317" # OTLP gRPC
- "4318:4318" # OTLP HTTP
- "14268:14268" # Jaeger HTTP
environment:
- COLLECTOR_OTLP_ENABLED=true
3.2 Kubernetes
helm repo add jaegertracing https://jaegertracing.github.io/helm-charts
helm install jaeger jaegertracing/jaeger \
--namespace monitoring \
--set provisionDataStore.cassandra=false \
--set storage.type=memory
3.3 Configuration Collector
# otel-collector-config.yaml
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
http:
endpoint: 0.0.0.0:4318
processors:
batch:
timeout: 1s
send_batch_size: 1024
exporters:
jaeger:
endpoint: jaeger:14250
tls:
insecure: true
service:
pipelines:
traces:
receivers: [otlp]
processors: [batch]
exporters: [jaeger]
4 - Grafana Tempo
4.1 Architecture
4.2 Installation
# docker-compose.yml
tempo:
image: grafana/tempo:2.3.0
ports:
- "3200:3200" # Tempo API
- "4317:4317" # OTLP gRPC
- "4318:4318" # OTLP HTTP
volumes:
- ./tempo-config.yaml:/etc/tempo/tempo.yaml
command: ["-config.file=/etc/tempo/tempo.yaml"]
# tempo-config.yaml
server:
http_listen_port: 3200
distributor:
receivers:
otlp:
protocols:
grpc:
http:
storage:
trace:
backend: local
local:
path: /tmp/tempo/blocks
wal:
path: /tmp/tempo/wal
compactor:
compaction:
block_retention: 48h
metrics_generator:
registry:
external_labels:
source: tempo
storage:
path: /tmp/tempo/generator/wal
remote_write:
- url: http://prometheus:9090/api/v1/write
send_exemplars: true
4.3 Grafana Integration
# datasources.yaml
datasources:
- name: Tempo
type: tempo
url: http://tempo:3200
jsonData:
tracesToLogsV2:
datasourceUid: loki
spanEndTimeShift: '1h'
tags: [{ key: 'service.name', value: 'service' }]
tracesToMetrics:
datasourceUid: prometheus
spanEndTimeShift: '1h'
tags: [{ key: 'service.name', value: 'service' }]
serviceMap:
datasourceUid: prometheus
5 - Context Propagation
5.1 W3C Trace Context
traceparent: 00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01
tracestate: congo=t61rcWkgMzE
Format: version-trace_id-parent_id-flags
5.2 Configuration
const { W3CTraceContextPropagator } = require('@opentelemetry/core');
const { registerInstrumentations } = require('@opentelemetry/instrumentation');
// Propagator par défaut
const propagator = new W3CTraceContextPropagator();
// HTTP headers automatiques
const { HttpInstrumentation } = require('@opentelemetry/instrumentation-http');
registerInstrumentations({
instrumentations: [
new HttpInstrumentation({
// Headers propagés automatiquement
}),
],
});
5.3 Cross-service Example
// Service A
app.get('/api/orders', async (req, res) => {
const span = trace.getActiveSpan();
span.setAttribute('request.path', '/api/orders');
// Appel vers Service B - context propagé automatiquement
const response = await axios.get('http://service-b/validate');
res.json(response.data);
});
// Service B - reçoit le context
app.get('/validate', async (req, res) => {
// Ce span est automatiquement enfant du span de Service A
const span = trace.getActiveSpan();
span.setAttribute('validation.type', 'order');
res.json({ valid: true });
});
6 - Sampling
6.1 Stratégies
| Strategy | Description | Usage |
|---|---|---|
| Always On | 100% des traces | Dev/Debug |
| Always Off | 0% des traces | N/A |
| Probabilistic | X% des traces | Production |
| Rate Limiting | N traces/sec | High traffic |
| Parent-based | Suit la décision parent | Distributed |
6.2 Configuration
const { TraceIdRatioBasedSampler, ParentBasedSampler } = require('@opentelemetry/sdk-trace-base');
// 10% des traces
const sampler = new ParentBasedSampler({
root: new TraceIdRatioBasedSampler(0.1),
});
const sdk = new NodeSDK({
sampler: sampler,
// ...
});
6.3 Tail-based Sampling (Collector)
# otel-collector-config.yaml
processors:
tail_sampling:
decision_wait: 10s
num_traces: 100
policies:
- name: errors-policy
type: status_code
status_code:
status_codes: [ERROR]
- name: slow-traces
type: latency
latency:
threshold_ms: 1000
- name: probabilistic-sample
type: probabilistic
probabilistic:
sampling_percentage: 10
7 - Service Map
Grafana Tempo génère automatiquement une Service Map à partir des traces.
Résumé
Dans ce chapitre, nous avons appris :
- Les concepts du distributed tracing
- OpenTelemetry SDK et instrumentation
- Jaeger pour le stockage des traces
- Grafana Tempo cloud-native
- Le Context Propagation
- Les stratégies de Sampling
- La génération de Service Maps
Prochaine étape
Dans le prochain chapitre, nous verrons Application Performance Monitoring (APM).