Une organisation avec 50+ microservices et 500+ collaborateurs produit téraoctets de logs par jour. Ajouter un dashboard Grafana de plus ne résout pas le problème. En fait, ça l'aggrave : fatigue d'alertes, rapport signal/bruit monstrueux, et une équipe ops noyée.
L'observabilité moderne = trois piliers (métriques, traces, logs) intégrés intelligemment. Pas juste "afficher plus de données."
Les trois piliers : métriques, traces, logs
Métriques : la vue d'ensemble
Agrégées, rapides, peu de bruit.
CPU utilization: 62%
Memory used: 4.2 GB
Request latency p95: 234ms
Error rate: 0.8%
Avantages : compactes, indexables, efficaces pour l'alerting Inconvénients : agrégées = contexte perdu
Traces : le récit
Suivi d'une requête entière à travers tous les services.
User request → API gateway (12ms)
→ Auth service (8ms)
→ Order service (156ms)
→ Database query (143ms)
→ Cache miss (2ms)
→ Payment service (78ms)
→ Return response (22ms)
Total: 284ms
Avantages : contexte complet, localisation du goulot d'étranglement Inconvénients : lourd, coûteux à stocker/analyser
Logs : les détails
Non structurés ou semi-structurés, très verbeux.
{
"timestamp": "2025-03-16T14:23:45Z",
"level": "ERROR",
"service": "order-api",
"trace_id": "abc123def456",
"message": "Database connection timeout",
"context": {
"user_id": 12345,
"order_id": 67890,
"retry_count": 3
}
}
Avantages : détail complet, recherche de patterns Inconvénients : volume énorme, coûteux
OpenTelemetry : l'intégration standard
OpenTelemetry (OTel) est l'approche moderne : instrumentez une fois, exportez partout.
import (
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
"go.opentelemetry.io/otel/sdk/trace"
"go.opentelemetry.io/otel/sdk/resource"
"go.opentelemetry.io/otel/semconv/v1.21.0"
)
// Initialize tracer provider
func initTracer() (*trace.TracerProvider, error) {
exporter, err := otlptracegrpc.New(ctx, otlptracegrpc.WithEndpoint("localhost:4317"))
if err != nil {
return nil, err
}
tp := trace.NewTracerProvider(
trace.WithBatcher(exporter),
trace.WithResource(resource.NewWithAttributes(
context.Background(),
semconv.ServiceNameKey.String("order-api"),
semconv.ServiceVersionKey.String("2.0.0"),
)),
)
return tp, nil
}
// Use in handler
func handleOrder(w http.ResponseWriter, r *http.Request) {
ctx, span := tracer.Start(r.Context(), "handleOrder")
defer span.End()
// Span attributs
span.SetAttributes(
attribute.String("order.id", orderID),
attribute.Int64("user.id", userID),
)
// Appel DB
dbCtx, dbSpan := tracer.Start(ctx, "database.query")
order := db.GetOrder(dbCtx, orderID)
dbSpan.End()
// Appel cache
cacheCtx, cacheSpan := tracer.Start(ctx, "cache.get")
cached := cache.Get(cacheCtx, key)
cacheSpan.End()
// ...
}
Une seule instrumentation, puis export vers :
- Jaeger (tracing)
- Prometheus (métriques)
- ELK (logs)
- Datadog, New Relic, etc.
Même code, plusieurs destinations.
Distributed tracing : localiser les goulots d'étranglement
Avec Jaeger ou Zipkin, vous voyez le flux complet.
POST /orders
├─ API Gateway (12ms)
├─ Auth Service (8ms)
├─ Order Service (156ms)
│ ├─ Parse JSON (1ms)
│ ├─ Validate (3ms)
│ ├─ Database.Query (143ms) ← GOULOT D'ÉTRANGLEMENT
│ │ ├─ Connection pool (2ms)
│ │ ├─ Query execution (140ms) ← requête lente
│ │ └─ Result fetch (1ms)
│ └─ Cache miss (2ms)
├─ Payment Service (78ms)
└─ Response (22ms)
Total: 284ms (15% base de données, 27% paiement, le reste est OK)
Installation de Jaeger :
docker run -d \
-p 16686:16686 \
-p 4317:4317 \
jaegertracing/all-in-one
# Access: http://localhost:16686
Maintenant vous visualisez :
- La carte des services (quel service appelle quoi)
- La latence par service
- Les taux d'erreur
- Les dépendances
Agrégation de logs : structuré et indexé
Logs massifs = incompréhensible. Solution : logs structurés + agrégation.
// Structured logging with zap
package main
import "go.uber.org/zap"
func main() {
logger, _ := zap.NewProduction() // JSON output
defer logger.Sync()
logger.Info("order created",
zap.String("order_id", "order-123"),
zap.String("user_id", "user-456"),
zap.Float64("amount", 199.99),
zap.String("currency", "CHF"),
zap.String("status", "pending"),
)
// Output:
// {"level":"info","ts":1710606000,"logger":"","msg":"order created",
// "order_id":"order-123","user_id":"user-456","amount":199.99,
// "currency":"CHF","status":"pending"}
}
Envoi vers ELK/Loki :
apiVersion: v1
kind: ConfigMap
metadata:
name: fluent-bit-config
data:
fluent-bit.conf: |
[SERVICE]
Flush 5
Log_Level info
Daemon off
[INPUT]
Name tail
Path /var/log/containers/*/*.log
Parser docker
Tag kube.*
Refresh_Interval 5
[FILTER]
Name kubernetes
Match kube.*
Kube_URL https://kubernetes.default.svc:443
Kube_CA_File /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
Kube_Token_File /var/run/secrets/kubernetes.io/serviceaccount/token
[OUTPUT]
Name loki
Match *
Host loki.default.svc.cluster.local
Port 3100
Interrogeable :
# Trouver les erreurs du service order
{job="order-api"} | json | level="ERROR"
# Erreurs avec latence > 1s
{job="*"} | json | duration > 1000 | level="ERROR"
# Compter les requêtes par statut
sum by (status) (rate({job="api"}[5m]))
Fatigue d'alertes : l'ennemi silencieux
Trop d'alertes = aucune alerte. 70 % des organisations ont > 100 alertes/jour actives. Résultat : abus d'astreinte, burnout.
Solution : alertes intelligentes
Mauvaises alertes :
alert: HighCPU
expr: cpu > 80%
for: 1m
# → Se déclenche toutes les heures pendant le scaling, inutile
Bonnes alertes :
alert: PodCrashLooping
expr: rate(kube_pod_container_status_restarts_total[15m]) > 0.1
for: 5m
# → Vrai problème, actionnable, rare
alert: SLOBreachInProgress
expr: (rate(http_requests_total{status=~"5.."}[5m]) > 0.01)
AND (timestamp() - max by (job) (timestamp(container_up_time))) < 600
for: 2m
# → SLA non respecté + pas un nouveau déploiement = à investiguer
Pattern : utiliser les error budgets
# Calculer le budget d'erreur restant
1 - (sum(rate(http_requests_total{status=~"2..|3.."}[30d])) /
sum(rate(http_requests_total[30d])))
Si le budget d'erreur est < 10 %, alerte. Sinon, silence.
Routage d'alertes : le contexte compte
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: app-alerts
spec:
groups:
- name: critical
rules:
- alert: DatabaseDown
expr: up{job="postgres"} == 0
for: 1m
annotations:
severity: critical
runbook: https://wiki/postgres-down
labels:
page: "true" # PagerDuty immédiatement
channel: "#p1-oncall" # Slack critique
- name: warning
rules:
- alert: DiskSpaceLow
expr: node_filesystem_avail_bytes{mountpoint="/"} < 10gb
for: 10m
annotations:
severity: warning
labels:
page: "false" # Ne pas pager
channel: "#alerts" # Slack normal
Automatisation des runbooks : pas d'héroïsme manuel
Une alerte se déclenche, et l'investigation commence sur le champ. Mieux : un runbook automatisé.
#!/bin/bash
# runbook: high-latency-api-troubleshoot
SERVICE=$1
THRESHOLD_MS=${2:-1000}
echo "Investigating latency > ${THRESHOLD_MS}ms in ${SERVICE}..."
# 1. Check pod health
echo "Pod status:"
kubectl get pods -l app=$SERVICE -o wide
# 2. Check logs for errors
echo "Recent errors:"
kubectl logs -l app=$SERVICE --tail=100 | grep ERROR | tail -20
# 3. Check database connections
echo "Database connections:"
psql -h postgres.default -U admin -c "SELECT count(*) FROM pg_stat_activity;"
# 4. Check cache hit rate
echo "Cache metrics:"
curl -s http://prometheus:9090/api/v1/query?query='rate(cache_miss_total[5m])' | jq '.data.result[0].value'
# 5. Auto-remediation: restart if in bad state
RESTART_THRESHOLD=2000
if [ $(curl -s http://$SERVICE/metrics | grep 'latency' | awk '{print $2}') -gt $RESTART_THRESHOLD ]; then
echo "Latency critical, attempting restart..."
kubectl rollout restart deployment/$SERVICE
echo "Waiting for pods to be ready..."
kubectl rollout status deployment/$SERVICE --timeout=2m
fi
echo "Investigation complete. Check dashboard at https://grafana.example.com"
Déclenchement automatique sur alerte :
rule: HighLatency
annotations:
action: "kubectl exec -n kube-system -- /scripts/latency-troubleshoot.sh api"
Coût de l'observabilité : le coût caché
Les logs coûtent cher. 1 milliard de logs = ~$5k-10k/mois en cloud.
Optimisation des coûts :
- Échantillonnage : Loggez 10 %, pas 100 %
if rand.Intn(10) == 0 { // 10% sampling
logger.Debug("request", zap.String("path", r.URL.Path))
}
- Gestion des niveaux de log : ERROR et WARN en prod, DEBUG en dev
// Production
logger, _ := zap.NewProduction() // JSON, ERROR level
// Development
logger, _ := zap.NewDevelopment() // Colored, DEBUG level
- Rétention Loki : Logs détaillés 7 jours, résumés 30 jours
- name: loki
args:
- -config.file=/etc/loki/loki-config.yaml
# loki-config.yaml
schema_config:
configs:
- from: 2020-05-15
store: boltdb
object_store: filesystem
schema: v11
index:
prefix: index_
period: 24h
retention_config:
enabled: true
retention_deletes_enabled: true
retention_period: 7d # Conserver 7 jours uniquement
- Échantillonnage de traces : Échantillonner 1 % des transactions
option.WithTraceSampler(sdktrace.ProbabilitySampler(0.01))
Cardinalité des métriques : le piège du passage à l'échelle
Un problème que peu d'organisations anticipent : l'explosion de la cardinalité. Quand vous passez de 10 à 200 microservices, le nombre de séries temporelles Prometheus peut exploser de 50 000 à 5 000 000+. Chaque label ajouté (user_id, request_id, endpoint) multiplie le nombre de séries.
Conséquences concrètes :
- Prometheus consomme 50+ GB de RAM au lieu de 4 GB
- Les requêtes PromQL qui prenaient 200ms prennent maintenant 30 secondes
- Les coûts de stockage (Thanos, Cortex) explosent de CHF 500/mois à CHF 15 000/mois
- Les dashboards Grafana deviennent inutilisables (timeouts)
Comment gérer la cardinalité à l'échelle :
-
Éviter les labels à haute cardinalité : ne jamais mettre user_id ou request_id comme label Prometheus. Utilisez les logs ou traces pour ce niveau de détail.
-
Recording rules : pré-agréger les métriques fréquemment utilisées pour réduire la charge au moment des requêtes.
# Recording rule : pré-agrégation
record: job:http_requests_total:rate5m
expr: sum by (job) (rate(http_requests_total[5m]))
-
Cycle de vie des métriques : supprimer les métriques qui ne sont plus utilisées. Auditer régulièrement avec promtool ou Grafana Mimirtool.
-
Rétention à plusieurs niveaux : métriques brutes 7 jours, agrégées 90 jours, tendances 1 an. Thanos compaction gère ça nativement.
-
Limiter les labels côté instrumentation : définir une convention d'équipe sur les labels autorisés. Maximum 5-7 labels par métrique.
Chez Hidora, nous avons vu des organisations réduire leur facture observabilité de 60% simplement en maîtrisant la cardinalité, sans perdre en visibilité.
Checklist d'observabilité
-
- OpenTelemetry instrumenté (métriques + traces + logs)
-
- Traces centralisées (Jaeger/Zipkin)
-
- Logs structurés en JSON
-
- Agrégation de logs (ELK/Loki)
-
- Métriques exposées (Prometheus)
-
- Alertes intelligentes (pas du spam)
-
- Routage d'alertes par sévérité
-
- Runbooks pour les alertes critiques
-
- SLO/SLI définis
-
- Contrôle des coûts (échantillonnage, rétention)
Maturité de l'observabilité
Niveau 1 : Dashboard Grafana
- Création manuelle de dashboards
- Beaucoup de bruit
- Fatigue d'alertes
Niveau 2 : Logging structuré + métriques
- Logs en JSON
- Règles d'alertes définies
- Monitoring SLO basique
Niveau 3 : Distributed tracing + alertes intelligentes
- OpenTelemetry implémenté
- Routage d'alertes (pager vs notification)
- Runbooks automatisés
Niveau 4 : Observabilité autonome
- AIOps (détection d'anomalies)
- Auto-remédiation (self-healing)
- Échantillonnage basé sur les coûts
- Alertes prédictives
Chez Hidora, nous aidons les organisations à progresser du niveau 1 au niveau 3 en 6 mois.
Conclusion
L'observabilité n'est pas un "bonus appréciable". C'est fondamental pour opérer en production avec confiance.
Le changement de mentalité :
- De : "que puis-je voir ?" (réactif)
- À : "que dois-je savoir ?" (proactif)
Les trois piliers (métriques, traces, logs) combinés intelligemment donnent une vision claire de l'état de votre système. Et ce sans fatigue d'alertes ni coûts explosifs.
À lire aussi :
Cet article vous a été utile ? Découvrez comment Hidora peut vous accompagner : Professional Services · Managed Services · SLA Expert

Responsable Produit Cloud
Responsable Produit Cloud chez Hidora. Spécialiste Kubernetes, IaC et observabilité.



