Aller au contenu
Observabilité
Blog
Observabilité12 min

Observabilité à l'échelle : au-delà des dashboards Grafana

Mattia Eleuteri16 octobre 2025

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 :

  1. Échantillonnage : Loggez 10 %, pas 100 %
if rand.Intn(10) == 0 {  // 10% sampling
  logger.Debug("request", zap.String("path", r.URL.Path))
}
  1. 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
  1. 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
  1. É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 :

  1. É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.

  2. 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]))
  1. 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.

  2. Rétention à plusieurs niveaux : métriques brutes 7 jours, agrégées 90 jours, tendances 1 an. Thanos compaction gère ça nativement.

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

Mattia Eleuteri
Mattia Eleuteri

Responsable Produit Cloud

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

CKACKSElastic Certified

Cet article vous parle ?

Hidora peut vous accompagner sur ce sujet.

Besoin d'un accompagnement ?

Parlons de votre projet. 30 minutes, sans engagement.