06 — Alerting : symptômes vs causes, pager fatigue, runbooks
Ce que tu vas apprendre
- La distinction fondamentale symptôme / cause — et pourquoi elle change tout
- Les anti-patterns d'alerting qui créent du bruit inutile
- Comment structurer une alerte actionnable
- Les runbooks : documenter la réponse avant l'incident
- Configuration Prometheus Alertmanager et Grafana Alerts
Prérequis
Symptôme vs cause : la règle fondamentale
Alerter sur les symptômes, pas sur les causes.
Un symptôme est ce que l'utilisateur ressent : latence élevée, erreurs, données manquantes. Une cause est ce qui produit le symptôme : CPU élevé, mémoire pleine, disque saturé.
Le problème des alertes sur les causes : un CPU à 90% n'impacte pas forcément les utilisateurs. La mémoire peut être à 95% et le service répond parfaitement (cache chaud). À l'inverse, un CPU à 30% peut accompagner un taux d'erreur de 50% si le bottleneck est ailleurs.
❌ Alertes sur des causes — beaucoup de bruit, peu d'impact
"CPU > 80%"
"Mémoire > 90%"
"Disque > 85%"
"Connexions BDD > 80"
✓ Alertes sur des symptômes — impact direct sur les utilisateurs
"Taux d'erreur HTTP > 5% depuis 5 minutes"
"p99 latence > 1s depuis 10 minutes"
"Error budget consommé à 50% en 6 heures"
"Aucune commande traitée depuis 15 minutes"
La règle de Google SRE :
"Every page should be actionable and there should be a clear response to every alert. If you can't act on an alert, why are you sending it?" — Beyer et al., Site Reliability Engineering (2016), Chapitre 6
Les 4 questions d'une bonne alerte
Avant d'ajouter une alerte, répondre aux 4 questions :
- Est-ce que l'utilisateur est impacté ? Si non → log ou métrique, pas une alerte
- Est-ce actionnable ? Si on ne peut rien faire → pas d'alerte
- Est-ce urgent ? Si ça peut attendre le matin → ticket, pas un page de nuit
- Est-ce que l'alerte indique clairement quoi faire ? Si non → écrire un runbook d'abord
Configuration Prometheus Alertmanager
Alertes PromQL
yaml# rules/alerts.yml
groups:
- name: api-alerts
rules:
# SLO — symptôme : taux d'erreur utilisateur
- alert: HighErrorRate
expr: |
sum(rate(http_requests_total{status=~"5.."}[5m]))
/
sum(rate(http_requests_total[5m])) > 0.05
for: 5m
labels:
severity: critical
team: backend
annotations:
summary: "Taux d'erreur HTTP > 5% depuis 5 minutes"
description: "Taux actuel : {{ $value | humanizePercentage }} sur {{ $labels.route }}"
runbook: "https://wiki.exemple.fr/runbooks/high-error-rate"
dashboard: "https://grafana.exemple.fr/d/api-overview"
# SLO — symptôme : latence élevée
- alert: HighLatencyP99
expr: |
histogram_quantile(0.99, rate(http_request_duration_seconds_bucket[5m])) > 1.0
for: 10m
labels:
severity: warning
team: backend
annotations:
summary: "p99 latence > 1s depuis 10 minutes"
description: "p99 actuel : {{ $value | humanizeDuration }} sur {{ $labels.route }}"
runbook: "https://wiki.exemple.fr/runbooks/high-latency"
# Symptôme métier : aucune commande traitée
- alert: NoOrdersProcessed
expr: |
increase(orders_processed_total[15m]) == 0
AND
hour() >= 8 AND hour() <= 22
for: 0m
labels:
severity: critical
team: backend
annotations:
summary: "Aucune commande traitée depuis 15 minutes (heures ouvrées)"
runbook: "https://wiki.exemple.fr/runbooks/no-orders"
# Error budget — burn rate critique
- alert: SLOBurnRateCritical
expr: |
(1 - job:sli_availability:ratio_rate5m) / (1 - 0.999) > 14.4
AND
(1 - job:sli_availability:ratio_rate1h) / (1 - 0.999) > 14.4
labels:
severity: critical
annotations:
summary: "Burn rate SLO critique — error budget épuisé en < 1h"
runbook: "https://wiki.exemple.fr/runbooks/slo-burn-rate"
Alertmanager : routing et déduplication
yaml# alertmanager.yml
global:
resolve_timeout: 5m
route:
group_by: ["alertname", "team"]
group_wait: 30s # Attendre 30s avant d'envoyer le premier groupe
group_interval: 5m # Délai entre les notifications du même groupe
repeat_interval: 4h # Renvoi si l'alerte persiste
routes:
# Critical → PagerDuty (page immédiat)
- match:
severity: critical
receiver: pagerduty
continue: false
# Warning → Slack (notification silencieuse)
- match:
severity: warning
receiver: slack-warnings
receivers:
- name: pagerduty
pagerduty_configs:
- service_key: "${PAGERDUTY_KEY}"
description: "{{ .CommonAnnotations.summary }}"
- name: slack-warnings
slack_configs:
- api_url: "${SLACK_WEBHOOK}"
channel: "#alerts-backend"
title: "{{ .CommonAnnotations.summary }}"
text: "{{ .CommonAnnotations.description }}\nRunbook: {{ .CommonAnnotations.runbook }}"
# Inhibitions — supprimer les alertes de cause si le symptôme est déjà alerté
inhibit_rules:
- source_match:
alertname: HighErrorRate
severity: critical
target_match:
severity: warning
equal: ["team"]
La pager fatigue : comment l'éviter
La pager fatigue survient quand les alertes sont trop nombreuses, trop bruyantes ou non actionnables. Résultat : les ingénieurs ignorent les alertes ou les snooze sans investiguer.
Symptômes de la pager fatigue :
- Plus de 5 pages par semaine par ingénieur
- Les ingénieurs snooze les alertes sans regarder
- Les mêmes alertes reviennent toutes les nuits
- Personne ne sait pourquoi une alerte a été créée
Remèdes :
yaml# 1. Inhibitions — ne pas alerter sur les causes si le symptôme est déjà alerté
inhibit_rules:
- source_match:
alertname: HighErrorRate
target_match_re:
alertname: (HighCPU|HighMemory|HighDiskIO)
# 2. Silences temporaires (via UI Alertmanager ou API)
# Silence automatique hors heures ouvrées pour les alertes non-critiques
- match:
severity: warning
time_intervals:
- weekdays: ["saturday", "sunday"]
- times: [{ start_time: "22:00", end_time: "08:00" }]
# 3. for: suffisamment long pour éviter les faux positifs
- alert: HighMemory
expr: process_resident_memory_bytes > 1e9
for: 15m # Pas 0m — attendre 15 minutes de mémoire élevée continue
Écrire un runbook utile
Un runbook est la procédure à suivre quand une alerte se déclenche. Sans runbook, l'ingénieur d'astreinte passe 10 minutes à chercher quoi faire — souvent de nuit.
Structure d'un runbook :
markdown# Runbook : HighErrorRate
## Contexte
Cette alerte se déclenche quand le taux d'erreur HTTP 5xx dépasse 5% pendant 5 minutes.
## Impact
Les utilisateurs voient des erreurs sur les pages qui appellent l'API Order Service.
## Étapes de diagnostic
### 1. Vérifier les logs d'erreur (2 minutes)
kibana.exemple.fr → filtre : level:error AND service:order-api
### 2. Vérifier les métriques (2 minutes)
Dashboard : grafana.exemple.fr/d/api-overview
- Quelle route a le plus d'erreurs ?
- Quelle est la distribution des codes d'erreur (500 vs 503 vs 504) ?
### 3. Vérifier la BDD (2 minutes)
- Connexions actives : grafana.exemple.fr/d/postgres
- Latence des requêtes > 500ms ?
### 4. Vérifier les dépendances
- Payment Service health : curl http://payment-service/health
- Redis : redis-cli ping
## Actions correctives
### Si erreurs 503 (service unavailable)
→ Vérifier le nombre de pods : kubectl get pods -n production
→ Si pods en CrashLoopBackOff : kubectl logs <pod> -n production
### Si erreurs 500 avec trace "connection refused" vers la BDD
→ Vérifier le pool de connexions : grafana.exemple.fr/d/postgres
→ Si pool saturé : redémarrer le service (kubectl rollout restart)
### Si pic d'erreurs après un déploiement
→ Rollback immédiat : kubectl rollout undo deployment/order-api -n production
## Escalade
Si non résolu en 30 minutes → contacter [Lead Backend] sur Slack #incidents
## Post-mortem
Après résolution → créer un ticket et planifier un post-mortem si impact > 15 minutes
Grafana Alerts (alternative à Alertmanager)
Si tu utilises Grafana Cloud ou Grafana 9+, les alertes peuvent être gérées directement dans Grafana sans Alertmanager :
yaml# Grafana alert rule (format JSON/YAML via API ou UI)
apiVersion: 1
groups:
- name: API Alerts
rules:
- uid: high-error-rate
title: High Error Rate
condition: C
data:
- refId: A
datasourceUid: prometheus
model:
expr: |
sum(rate(http_requests_total{status=~"5.."}[5m]))
/
sum(rate(http_requests_total[5m]))
- refId: C
datasourceUid: __expr__
model:
type: threshold
conditions:
- evaluator:
type: gt
params: [0.05]
for: 5m
labels:
severity: critical
annotations:
summary: "Taux d'erreur > 5%"
runbook: "https://wiki.exemple.fr/runbooks/high-error-rate"
noDataState: NoData
execErrState: Error
Résumé de la série
| Article | Contenu |
|---|---|
| 00 — Introduction | Les 3 piliers, monitoring vs observabilité |
| 01 — Logs structurés | pino, structlog, correlation ID |
| 02 — Métriques | Types Prometheus, RED, USE, PromQL |
| 03 — Traces distribuées | Spans, W3C TraceContext, sampling |
| 04 — OpenTelemetry | SDK TypeScript + Python, auto-instrumentation |
| 05 — SLO / SLI / SLA | Error budgets, burn rate, PromQL |
| 06 — Alerting | Symptômes vs causes, runbooks, Alertmanager |
Un système observable avec de bonnes alertes te permet de dormir sereinement — et surtout de te réveiller uniquement quand c'est vraiment nécessaire.
Sources
- Beyer, B., et al. (2016). Site Reliability Engineering, Chapitre 6 "Monitoring Distributed Systems". O'Reilly. sre.google/sre-book/monitoring-distributed-systems/
- Beyer, B., et al. (2018). The Site Reliability Workbook, Chapitre 5 "Alerting on SLOs". O'Reilly. sre.google/workbook/alerting-on-slos/
- Prometheus Authors. Alerting Rules. prometheus.io/docs/prometheus/latest/configuration/alerting_rules/
- Prometheus Authors. Alertmanager Configuration. prometheus.io/docs/alerting/latest/configuration/
- Grafana Labs. Grafana Alerting. grafana.com/docs/grafana/latest/alerting/
- Kim, G., et al. (2016). The DevOps Handbook, Partie IV "Technical Practices of Feedback". IT Revolution Press.