Aller au contenu

Bonnes pratiques

Haute disponibilité (HA)

Prometheus / Mimir

Composant Stratégie HA Configuration
Prometheus 2 replicas identiques (scraping parallele) --storage.tsdb.retention.time=7d sur chaque replica
Mimir Ingester 3 replicas minimum, réplication factor 3 Zone-aware réplication si multi-AZ
Mimir Querier 2+ replicas (stateless) Load balancer devant les queriers
Mimir Store Gateway 2+ replicas Sharding par bloc
# Mimir — configuration HA
ingester:
  ring:
    replication_factor: 3
    zone_awareness_enabled: true
    kvstore:
      store: etcd

Loki

Mode Quand l'utiliser HA
Monolithique < 50 Go/jour de logs Non (single point of failure)
Simple Scalable 50-500 Go/jour Oui (3 replicas read + 3 write)
Micro-services > 500 Go/jour Oui (composants indépendants)
# Loki Simple Scalable — 3 replicas par tier
write:
  replicas: 3
  persistence:
    size: 50Gi

read:
  replicas: 3

backend:
  replicas: 2

Grafana

# Grafana HA avec base de donnees partagee
replicas: 2

database:
  type: postgres
  host: postgresql.observability:5432
  name: grafana
  user: grafana
  password: ${GRAFANA_DB_PASSWORD}

# Partage des sessions entre replicas
session:
  provider: redis
  provider_config: addr=redis.observability:6379

Alerting

Principes fondamentaux

Principe Description
Actionnable Chaque alerte doit correspondre a une action concrète
Runbook Chaque alerte a un lien vers une procedure de résolution
Sévérité claire Critical = intervention immédiate, Warning = investigation sous 4h
Routage Les alertes arrivent a la bonne équipe (pas de broadcast)
Anti-fatigue Grouper, dedupliquer, silencer pendant les maintenances

Structure d'une regle d'alerte

# Regle d'alerte Prometheus / Alertmanager
groups:
  - name: service-slo
    rules:
      - alert: HighErrorRate
        expr: |
          sum(rate(http_requests_total{status=~"5.."}[5m])) by (service)
          /
          sum(rate(http_requests_total[5m])) by (service)
          > 0.01
        for: 5m
        labels:
          severity: critical
          team: platform
        annotations:
          summary: "Taux d'erreur > 1% pour {{ $labels.service }}"
          description: "Le service {{ $labels.service }} a un taux d'erreur de {{ $value | humanizePercentage }} sur les 5 dernieres minutes."
          runbook_url: "https://wiki.internal/runbooks/high-error-rate"
          dashboard_url: "https://grafana.internal/d/service-overview?var-service={{ $labels.service }}"

Routage Alertmanager

# alertmanager.yml
route:
  group_by: ['alertname', 'service']
  group_wait: 30s
  group_interval: 5m
  repeat_interval: 4h
  receiver: default

  routes:
    # Alertes critiques → PagerDuty + Slack
    - match:
        severity: critical
      receiver: pagerduty-critical
      continue: true

    - match:
        severity: critical
      receiver: slack-critical

    # Alertes warning → Slack uniquement
    - match:
        severity: warning
      receiver: slack-warning

    # Alertes securite → equipe securite
    - match:
        team: security
      receiver: slack-security

receivers:
  - name: default
    slack_configs:
      - channel: '#alerts-default'

  - name: pagerduty-critical
    pagerduty_configs:
      - routing_key: ${PAGERDUTY_KEY}

  - name: slack-critical
    slack_configs:
      - channel: '#alerts-critical'
        send_resolved: true

  - name: slack-warning
    slack_configs:
      - channel: '#alerts-warning'
        send_resolved: true

  - name: slack-security
    slack_configs:
      - channel: '#security-alerts'
        send_resolved: true

Lutter contre la fatigue d'alerte

Technique Description
Groupage Regrouper les alertes similaires (group_by)
Deduplication Alertmanager deduplique automatiquement
Silences Silencer pendant les maintenances planifiees
Inhibition Si le cluster est down, ne pas alerter sur chaque service
Burn rate Alerter sur la vitesse de consommation de l'error budget, pas sur des seuils fixes
Revue régulière Supprimer les alertes jamais actionnees (review trimestrielle)

Burn rate alerting

Au lieu d'alerter sur "taux d'erreur > 1%", alerter sur "au rythme actuel, le SLO sera depasse dans X heures". Cela reduit les faux positifs et donne du contexte.

# Burn rate : consommation de l'error budget sur 1h vs 30 jours
(
  1 - (sum(rate(http_requests_total{status!~"5.."}[1h])) / sum(rate(http_requests_total[1h])))
)
/
(1 - 0.999)  # SLO 99.9%
> 14.4  # Burn rate critique (budget consomme en ~1h)

Design de dashboards

Golden signals par service

Chaque service doit avoir un dashboard avec 4 panels principaux :

graph TD
    subgraph Dashboard["Service: service_name"]
        subgraph Row1[" "]
            Rate["Request Rate (req/s)"]
            Error["Error Rate (%)"]
        end
        subgraph Row2[" "]
            Latency["Latency p50/p95/p99"]
            Saturation["Saturation (CPU/Mem)"]
        end
        Logs["Recent Logs (errors + warnings)"]
        Traces["Active Traces (erreurs) — Lien vers Tempo"]
    end

Conventions de nommage

Élément Convention Exemple
Dashboard [Zone] Service - Vue [Production] Keycloak - Overview
Variable $service, $namespace, $instance Template variables Grafana
Panel titre Action + objet "Taux de requêtes", "Latence p99"
Unites Toujours specifier l'unite req/s, ms, %, octets
Seuils Rouge > critique, Orange > warning Coherent avec les alertes

Anti-patterns dashboards

A eviter

  • Dashboard mural : > 20 panels sur un seul dashboard (illisible)
  • Metriques sans contexte : un graphe sans titre, sans unite, sans seuil
  • Dashboards manuels : crees dans l'interface sans etre versionnes en Git
  • Copier-coller : dupliquer un dashboard au lieu d'utiliser des variables
  • Tout sur un seul dashboard : séparer infrastructure, application, business

Maîtrise des coûts

Cardinalite des metriques

La cardinalite (nombre de combinaisons uniques de labels) est le premier facteur de coût :

Action Impact
Supprimer les labels inutiles Reduire la cardinalite de 10-50%
Eviter les labels a valeurs infinies (request_id, user_id) Eviter l'explosion des series
Utiliser metric_relabel_configs pour filtrer Reduire a la source
Aggreger les metriques internes non nécessaires Moins de series stockees
# Prometheus — filtrer les metriques inutiles a la source
scrape_configs:
  - job_name: "keycloak"
    metric_relabel_configs:
      # Garder uniquement les metriques utiles
      - source_labels: [__name__]
        regex: "keycloak_(logins|login_errors|registrations|request_duration|active_sessions).*"
        action: keep
      # Supprimer les labels a haute cardinalite
      - regex: "instance"
        action: labeldrop

Volume de logs

Action Impact
Filtrer les logs DEBUG en production Reduire le volume de 50-80%
Echantillonner les logs d'accès normaux Garder 10% du trafic normal, 100% des erreurs
Compresser les logs (Loki compresse nativement) ~10x de compression
Retention differenciee (voir chapitre Confidentialite) Reduire le stockage longue duree
// Alloy — filtrer les logs DEBUG avant envoi a Loki
loki.process "drop_debug" {
  stage.match {
    selector = '{level="debug"}'
    action   = "drop"
  }
  forward_to = [loki.write.default.receiver]
}

Échantillonnage des traces

// Alloy — tail-based sampling
// Garder 100% des traces en erreur, 10% des traces normales
otelcol.processor.tail_sampling "default" {
  decision_wait = 10s

  policy {
    name = "errors"
    type = "status_code"
    status_code {
      status_codes = ["ERROR"]
    }
  }

  policy {
    name = "slow"
    type = "latency"
    latency {
      threshold_ms = 1000
    }
  }

  policy {
    name = "default"
    type = "probabilistic"
    probabilistic {
      sampling_percentage = 10
    }
  }

  output {
    traces = [otelcol.exporter.otlp.tempo.input]
  }
}

Troubleshooting

Metriques manquantes

Symptome Cause probable Diagnostic
Metrique absente dans Grafana Target non scrape Vérifier http://prometheus:9090/targets
Metrique presente puis disparait Pod redeploy, label change Vérifier les relabel configs
Valeurs a 0 Compteur reinitialise (restart) Utiliser rate() ou increase()
"No data" dans un panel Mauvaise requête ou mauvaise datasource Tester dans Prometheus UI d'abord
# Verifier qu'un endpoint expose des metriques
curl -s http://service:8080/metrics | head -20

# Verifier les targets Prometheus
curl -s http://prometheus:9090/api/v1/targets | \
  jq '.data.activeTargets[] | {job: .labels.job, health: .health, lastError: .lastError}'

# Verifier la cardinalite
curl -s http://prometheus:9090/api/v1/status/tsdb | \
  jq '.data.seriesCountByMetricName[:10]'

Performances Loki

Symptome Cause probable Solution
Requête LogQL lente (> 30s) Scan sur trop de données Ajouter des labels pour restreindre, reduire la fenêtre temporelle
"context deadline exceeded" Timeout de requête Augmenter limits_config.max_query_length
Ingestion rejetee (429) Rate limit atteint Augmenter limits_config.ingestion_rate_mb
Labels manquants Pipeline Alloy mal configure Vérifier les stages labels dans la config Alloy
# Bonne pratique : toujours commencer par un filtre de labels
# BIEN : filtre label d'abord, puis contenu
{service="catalog", level="error"} |= "timeout"

# MAL : scan de tous les logs puis filtre
{job=~".+"} |= "timeout"

Permissions Grafana

Symptome Cause Solution
"Access denied" sur un dashboard Rôle insuffisant Vérifier les permissions du dossier Grafana
Datasource invisible Pas de permission sur la datasource Ajouter le rôle dans les datasource permissions
Alerte non routee Mauvais contact point Vérifier la configuration Alertmanager / Grafana alerting
Dashboard vide apres login SSO Mauvais mapping de rôles OIDC → Grafana Vérifier role_attribute_path dans la config OIDC
# Verifier les permissions d'un utilisateur
curl -s http://grafana:3000/api/user/orgs \
  -H "Authorization: Bearer ${USER_TOKEN}" | jq .

# Lister les permissions d'un dossier
curl -s http://grafana:3000/api/folders/${FOLDER_UID}/permissions \
  -H "Authorization: Bearer ${ADMIN_TOKEN}" | jq .