Aller au contenu principal

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

TermeDescription
TraceParcours complet d'une requête
SpanUnité de travail dans une trace
ContextMétadonnées propagées
BaggageDonné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

StrategyDescriptionUsage
Always On100% des tracesDev/Debug
Always Off0% des tracesN/A
ProbabilisticX% des tracesProduction
Rate LimitingN traces/secHigh traffic
Parent-basedSuit la décision parentDistributed

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).

→ Chapitre suivant : APM


← Retour à la table des matières