Skip to content

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 (couleur warning, repeat_interval 4h).
  • severity = "critical"slack-critical (couleur danger, répété toutes les 1h).
  • alertname = "Watchdog" → receiver null : 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 vers null évite de spammer Slack.
  • Inhibition critical → warning (même namespace + 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 :

  1. Terraform crée un secret dédié fastapi-eks/alertmanager-slack dans Secrets Manager (clé slack-webhook-url). Valeur externe (fournie par Slack, pas générée) → variable sensible TF_VAR_slack_webhook_url, jamais commitée (comme db_password).
  2. ESO (ns monitoring, SecretStore réutilisé de #76) matérialise un Secret K8s alertmanager-slack.
  3. Alertmanager monte ce secret via alertmanagerSpec.secrets dans /etc/alertmanager/secrets/alertmanager-slack/ et lit l'URL via slack_configs.api_url_file (jamais api_url inline).

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 champs title/text sont des templates Alertmanager, pas Helm. Validé par helm template : le chart sérialise la config (toYaml, pas de tpl), les {{ }} sont préservés littéralement dans le Secret alertmanager.yaml.
  • Secret container vs valeur : Terraform crée le secret avec la valeur fournie à l'apply persistent ; sans TF_VAR_slack_webhook_url, l'apply réclame la valeur (variable requise, comme db_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_url au prochain apply persistent.
  • 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 prochain aws-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