Aller au contenu principal
ObservabilitéPrometheusGrafanaMonitoringSRE

Alertmanager et Grafana dashboards

30 min de lecture Observabilité Moderne — Chapitre 4

Alertmanager avec routing et escalade (PagerDuty, Slack), Grafana dashboards efficaces, et stratégies de scaling Prometheus (Thanos, Cortex).

Introduction — Des métriques aux actions

Collecter des métriques avec Prometheus, c’est la moitié du travail. L’autre moitié, c’est transformer ces métriques en alertes actionnables et en dashboards lisibles. Sans Alertmanager, tes alerting rules envoient des notifications brutes sans regroupement ni escalade. Sans Grafana bien configuré, tu te noies dans des graphes illisibles.

Ce cours couvre la configuration production d’Alertmanager (routing intelligent, inhibition, escalade PagerDuty), la construction de dashboards Grafana efficaces, et les stratégies de scaling quand un seul Prometheus ne suffit plus.

🔥 Cas réel : Une équipe SRE recevait 200+ alertes Slack par jour. Résultat : tout le monde ignorait le canal. Après reconfiguration d’Alertmanager — grouping par service, inhibition des alertes enfant, routing par severity — ils sont passés à ~15 notifications pertinentes par jour. Le MTTR a chuté de 40 minutes à 8 minutes parce que chaque alerte méritait l’attention.

🧠 À retenir : Le but n’est pas de recevoir plus d’alertes, mais de recevoir les bonnes alertes au bon moment par le bon canal. Une alerte qui ne déclenche pas d’action est du bruit — et le bruit tue l’attention.


Alertmanager : routing et escalade

Alertmanager est le composant qui reçoit les alertes de Prometheus et les distribue intelligemment. Son pipeline en quatre étapes transforme un flux brut d’alertes en notifications organisées.

Le grouping regroupe les alertes similaires — si 10 pods du même service crashent, tu reçois une notification, pas dix. L’inhibition supprime les alertes redondantes — si le cluster est down, inutile de notifier chaque service individuellement. Le silencing mute temporairement pendant les maintenances. Le routing envoie chaque alerte au bon canal selon sa severity et son équipe.

Voici une configuration production complète avec routing multi-canal et escalade :

# alertmanager.yaml
global:
  resolve_timeout: 5m
  slack_api_url: 'https://hooks.slack.com/services/xxx/yyy/zzz'

inhibit_rules:
  # Cluster down → inhibe les alertes individuelles
  - source_matchers: [alertname = ClusterDown]
    target_matchers: [severity =~ "warning|info"]
    equal: ['cluster']
  # Node down → inhibe les alertes des pods sur ce node
  - source_matchers: [alertname = NodeDown]
    target_matchers: [alertname = PodCrashLooping]
    equal: ['instance']

route:
  receiver: default-slack
  group_by: ['alertname', 'cluster', 'service']
  group_wait: 30s
  group_interval: 5m
  repeat_interval: 4h
  routes:
    # Critical → PagerDuty + Slack
    - matchers: [severity = critical]
      receiver: pagerduty-critical
      group_wait: 10s
      repeat_interval: 1h
      continue: true
    - matchers: [severity = critical]
      receiver: slack-critical
    # Warning → Slack dédié
    - matchers: [severity = warning]
      receiver: slack-warnings
      repeat_interval: 12h
    # Routing par équipe
    - matchers: [team = infra]
      receiver: slack-infra
    - matchers: [team = backend]
      receiver: slack-backend

receivers:
  - name: pagerduty-critical
    pagerduty_configs:
      - routing_key: 'YOUR_ROUTING_KEY'
        severity: '{{ .CommonLabels.severity }}'
        description: '{{ .CommonAnnotations.summary }}'
  - name: slack-critical
    slack_configs:
      - channel: '#alerts-critical'
        send_resolved: true
        color: '{{ if eq .Status "firing" }}danger{{ else }}good{{ end }}'
        title: '🔴 {{ .CommonLabels.alertname }}'
        text: >-
          {{ range .Alerts }}
          *Service:* {{ .Labels.service }}
          *Summary:* {{ .Annotations.summary }}
          *Runbook:* {{ .Annotations.runbook_url }}
          {{ end }}
        actions:
          - type: button
            text: 'Runbook 📖'
            url: '{{ (index .Alerts 0).Annotations.runbook_url }}'
  - name: default-slack
    slack_configs:
      - channel: '#alerts-default'
        send_resolved: true
  - name: slack-warnings
    slack_configs:
      - channel: '#alerts-warnings'
        send_resolved: true
  - name: slack-infra
    slack_configs:
      - channel: '#team-infra-alerts'
        send_resolved: true
  - name: slack-backend
    slack_configs:
      - channel: '#team-backend-alerts'
        send_resolved: true

💡 Tip DevOps : Le flag continue: true est puissant — il permet à une alerte de matcher plusieurs routes. Ici, les alertes critical vont sur PagerDuty ET Slack. Sans continue, le routing s’arrête au premier match.

⚠️ Attention : Le group_wait contrôle combien de temps Alertmanager attend pour regrouper les alertes avant d’envoyer. Trop court (5s) = flood de notifications individuelles. Trop long (5min) = tu es notifié trop tard. Pour les critical, 10-30s est un bon compromis. Pour les warning, 1-2min.


Grafana : dashboards efficaces

Un bon dashboard raconte une histoire en 5 secondes. Tu l’ouvres pendant un incident et tu vois immédiatement ce qui va et ce qui ne va pas. Deux frameworks guident la conception :

USE (pour l’infrastructure) : Utilization (CPU, mémoire, disque), Saturation (queues, load), Errors (OOM, disk errors). Un dashboard par cluster/node.

RED (pour les services) : Rate (requêtes/seconde), Errors (taux d’erreur %), Duration (latence p50/p99). Un dashboard par service.

L’approche Dashboard as Code avec Grafonnet (Jsonnet) garantit la reproductibilité et le versioning :

local grafana = import 'grafonnet/grafana.libsonnet';
local dashboard = grafana.dashboard;
local prometheus = grafana.prometheus;
local graphPanel = grafana.graphPanel;
local statPanel = grafana.statPanel;

dashboard.new(
  'Service Overview',
  tags=['production'],
  time_from='now-6h',
  refresh='30s',
)
.addTemplate(
  grafana.template.new(
    'service', 'Prometheus',
    'label_values(http_requests_total, service)')
)
.addPanel(
  statPanel.new('Request Rate', datasource='Prometheus')
  .addTarget(prometheus.target(
    'sum(rate(http_requests_total{service=~"$service"}[5m]))',
    legendFormat='RPS'
  )),
  gridPos={ h: 4, w: 6, x: 0, y: 0 }
)
.addPanel(
  graphPanel.new('Latency Distribution', datasource='Prometheus')
  .addTarget(prometheus.target(
    'histogram_quantile(0.99, sum(rate(http_request_duration_seconds_bucket{service=~"$service"}[5m])) by (le))',
    legendFormat='p99'
  ))
  .addTarget(prometheus.target(
    'histogram_quantile(0.50, sum(rate(http_request_duration_seconds_bucket{service=~"$service"}[5m])) by (le))',
    legendFormat='p50'
  )),
  gridPos={ h: 8, w: 12, x: 0, y: 4 }
)

Pour du JSON provisioning classique (sans Jsonnet), voici la structure des panels essentiels :

{
  "dashboard": {
    "title": "Service Overview",
    "templating": {
      "list": [{
        "name": "service",
        "type": "query",
        "query": "label_values(http_requests_total, service)",
        "multi": true,
        "includeAll": true
      }]
    },
    "panels": [
      {
        "title": "Error Rate",
        "type": "stat",
        "targets": [{
          "expr": "sum(service:http_errors:ratio_rate5m{service=~\"$service\"}) * 100"
        }],
        "fieldConfig": {
          "defaults": {
            "unit": "percent",
            "thresholds": {
              "steps": [
                {"color": "green", "value": null},
                {"color": "yellow", "value": 1},
                {"color": "red", "value": 5}
              ]
            }
          }
        }
      }
    ]
  }
}

🔥 Cas réel : Une équipe avait 47 dashboards Grafana créés à la main. Quand ils ont migré de cluster, tout a été perdu. Avec Grafonnet + Git, les dashboards sont versionnés, reviewés en PR, et déployés automatiquement via CI/CD. La migration s’est faite en un helm upgrade.

🧠 À retenir : Les variables template ($service, $namespace) sont indispensables. Un seul dashboard avec des variables remplace 20 dashboards hardcodés. Utilise includeAll: true pour voir l’agrégé et pouvoir drill-down sur un service spécifique.


Scaling Prometheus

Prometheus est conçu pour un seul nœud. Avec 1 million+ de time series ou un besoin de rétention longue, il atteint ses limites. Trois stratégies existent :

Sharding fonctionnel — Le plus simple : un Prometheus par domaine (infra, apps, platform). Chacun scrape ses targets, Grafana interroge les trois via des datasources séparées. Ça marche bien jusqu’à ~5 Prometheus.

Thanos — Ajoute un sidecar à chaque Prometheus pour uploader les blocs vers un object storage (S3, GCS). Thanos Query donne une vue unifiée sur toutes les instances. Idéal pour la rétention longue (mois/années) à coût minimal.

Grafana Mimir — La solution moderne : Prometheus envoie les métriques via remote write, Mimir les stocke en multi-tenant avec scaling horizontal. Compatible PromQL, pas besoin de changer tes dashboards.

La configuration remote write vers Mimir est minimaliste :

# prometheus.yaml — ajout remote_write
remote_write:
  - url: http://mimir.monitoring:9009/api/v1/push
    queue_config:
      capacity: 10000
      max_shards: 30
      max_samples_per_send: 5000
    write_relabel_configs:
      # Exclure les métriques internes inutiles
      - source_labels: [__name__]
        regex: 'go_.*|process_.*'
        action: drop

💡 Tip DevOps : Commence avec un seul Prometheus. Ajoute le sharding fonctionnel quand tu dépasses 500K séries. Passe à Thanos/Mimir quand tu as besoin de rétention > 30 jours ou de vue cross-cluster. Ne sur-architecte pas dès le départ.


Bonnes pratiques et pièges à éviter

Bonnes pratiques :

  • Chaque alerte doit avoir une runbook_url — une alerte sans procédure de résolution est inutile
  • Utilise l’inhibition pour éviter les cascades d’alertes pendant les pannes majeures
  • Dashboards : un panel de statut global en haut (vert/rouge), les détails en dessous
  • Versionnez vos dashboards dans Git (Grafonnet ou JSON exporté)
  • Testez vos routes Alertmanager avec amtool avant de déployer

Pièges courants :

  • repeat_interval trop court → notification toutes les heures pour la même alerte pendant des jours
  • Pas de send_resolved: true → l’équipe ne sait jamais quand le problème est résolu
  • Des dashboards avec 50 panels → personne ne les lit. Visez 8-12 panels max par dashboard
  • Ignorer le silencing → des alertes de maintenance réveillent l’astreinte à 3h du matin
  • Un seul canal Slack pour toutes les alertes → tout le monde mute le canal

⚠️ Attention : L’erreur la plus coûteuse en alerting n’est pas de manquer une alerte — c’est la fatigue d’alerte. Quand l’équipe reçoit trop de notifications non pertinentes, elle arrête de les regarder. Et le jour où une vraie alerte critique arrive, personne ne réagit. Qualité > quantité, toujours.


Résumé

Alertmanager transforme les alertes Prometheus en notifications intelligentes grâce au grouping (regroupement), à l’inhibition (suppression des cascades), au silencing (maintenance) et au routing (bon canal, bonne équipe). Grafana visualise les métriques avec des dashboards structurés selon les frameworks USE (infrastructure) et RED (services), idéalement gérés as code via Grafonnet. Pour le scaling, Thanos et Mimir étendent Prometheus avec du stockage distribué et une vue cross-cluster.

🧠 À retenir : La stack Prometheus + Alertmanager + Grafana est le trio gagnant du monitoring Cloud Native. La clé du succès n’est pas technique — c’est la discipline : des alertes actionnables avec runbooks, des dashboards lisibles en 5 secondes, et du code versionné dans Git.

Articles liés