ADR 016 — Alerting : Alertmanager + Slack (2026-06-14)
Statut
Accepté (2026-06-14). Issue #77. Clôt le milestone Observabilité du Sprint 4 (#73 métriques, #74 Prometheus/Grafana, #75 logs, #76 Grafana public, #77 alerting).
Contexte
L'observabilité expose les signaux (#74 métriques, #75 logs) mais reste passive : il faut regarder Grafana pour savoir que ça casse. L'alerting ferme la boucle en poussant une notification quand un seuil est franchi. Alertmanager est déjà déployé par le kube-prometheus-stack (#74) mais sans aucune configuration d'alerte.
Mêmes contraintes que le reste du milestone : cluster EKS éphémère mono-node, GitOps via ArgoCD, tout déclaratif et versionné, aucun secret en clair dans Git.
Décision 1 — Règles d'alerte applicatives, calées sur les métriques réelles
4 PrometheusRules dans k8s/platform/monitoring/prometheusrules-fastapi.yaml,
ownées par la plateforme monitoring (comme le ServiceMonitor), découvertes par
Prometheus sans label release (ruleSelectorNilUsesHelmValues: false, posé en #74).
| Alerte | Sévérité | Expression (résumé) |
|---|---|---|
FastAPITargetDown |
critical | up{namespace="fastapi", endpoint="http"} == 0 2 min |
FastAPIPodCrashLooping |
critical | increase(kube_pod_container_status_restarts_total{namespace="fastapi"}[10m]) > 3 |
FastAPIHighErrorRate |
warning | part de http_requests_total{status="5xx"} > 5% sur 5 min |
FastAPIHighLatency |
warning | histogram_quantile(0.99, ...http_request_duration_highr_seconds_bucket...) > 1s |
Les expressions reprennent exactement les métriques du dashboard FastAPI RED (#74) : pas de devinette, on alerte sur ce qu'on sait déjà mesurer. Les seuils sont un premier jet, volontairement simples, à affiner après observation réelle.
Décision 2 — Notification vers Slack
Le receiver est Slack (slack_configs natif d'Alertmanager), standard attendu
en entretien et capture vitrine propre. Écarté : un webhook générique (peu visuel),
Discord (pas de receiver natif, exigerait un pont), ou la seule UI Alertmanager
(pas de notification poussée).
Routing par sévérité (alertmanager.config dans les values) :
- Route par défaut →
slack-warning(couleurwarning,repeat_interval4h). severity = "critical"→slack-critical(couleurdanger, répété toutes les 1h).alertname = "Watchdog"→ receivernull: le Watchdog du chart est un deadman's switch qui alerte en permanence par design (il prouve que la chaîne d'alerting fonctionne). Le router versnullévite de spammer Slack.- Inhibition
critical → warning(mêmenamespace+alertname) : quand une alerte critique se déclenche, on tait le warning redondant.
Un incoming webhook Slack est lié à un seul channel : critical et warning partagent donc le même webhook/channel, différenciés par couleur et fréquence de répétition (pas par channel).
Décision 3 — URL du webhook via ESO, jamais en clair dans Git
L'URL du webhook Slack est un secret. Elle suit le même pattern que le mot de passe Grafana (#76) plutôt qu'une variable CI :
- Terraform crée un secret dédié
fastapi-eks/alertmanager-slackdans Secrets Manager (cléslack-webhook-url). Valeur externe (fournie par Slack, pas générée) → variable sensibleTF_VAR_slack_webhook_url, jamais commitée (commedb_password). - ESO (ns monitoring, SecretStore réutilisé de #76) matérialise un Secret K8s
alertmanager-slack. - Alertmanager monte ce secret via
alertmanagerSpec.secretsdans/etc/alertmanager/secrets/alertmanager-slack/et lit l'URL viaslack_configs.api_url_file(jamaisapi_urlinline).
Le rôle IRSA de l'ESO est étendu en least-privilege au seul ARN supplémentaire (3e secret : app + grafana + alertmanager-slack), pas de wildcard.
Pièges traités
- Watchdog spammeur : non routé → null (cf. Décision 2).
- Templates Go vs Helm : les
{{ .Status }}/{{ range .Alerts }}des champstitle/textsont des templates Alertmanager, pas Helm. Validé parhelm template: le chart sérialise la config (toYaml, pas detpl), les{{ }}sont préservés littéralement dans le Secretalertmanager.yaml. - Secret container vs valeur : Terraform crée le secret avec la valeur fournie
à l'
applypersistent ; sansTF_VAR_slack_webhook_url, l'apply réclame la valeur (variable requise, commedb_password).
Conséquences
- GitOps pur : règles + config dans le repo, réconciliées par ArgoCD
(Application
monitoring). Aucune modif Ansible/bootstrap. - Étape manuelle unique (live) : créer l'incoming webhook Slack et fournir son
URL via
TF_VAR_slack_webhook_urlau prochainapplypersistent. - Empreinte nulle : Alertmanager était déjà déployé (#74), on ne fait que le configurer. Pas d'ELB, teardown par cascade du root-app.
- Validable sans cluster :
promtool check rules(4 rules),helm template(config rendue),tfsec(0 HIGH). La validation live (alertes qui se déclenchent dans Slack) se fait au prochainaws-start.
Évolution possible
- Affiner les seuils après observation réelle (premier jet ici).
- Routes par équipe / silences planifiés si l'app grossit.
- Alertes infra (saturation node, PVC) si le cluster devenait permanent.
Date : 2026-06-14 Sprint : 4 Issue : #77