Skip to content

ADR 014 — Observabilité : kube-prometheus-stack via ArgoCD (2026-06-13)

Statut

Accepté (2026-06-13). Issue #74.

Contexte

Le Sprint 4 (« Day-2 operations ») introduit l'observabilité. L'application expose déjà ses métriques sur /metrics (#73, format Prometheus via prometheus-fastapi-instrumentator). Il manque la chaîne de collecte et de visualisation : un Prometheus qui scrape, un Grafana qui affiche.

Contraintes du projet :

  • Cluster EKS éphémère mono-node (t3.medium) monté/détruit à la demande pour le coût. Tout état non versionné meurt au teardown.
  • GitOps déjà en place (#72, ADR 013) : ArgoCD réconcilie le cluster depuis Git, pattern app-of-apps. Le root Application apps surveille k8s/platform/argocd-apps/.
  • /metrics ne doit pas être public (#81) : le scrape se fait en interne via le Service ClusterIP, la vitrine est Grafana.

Décision 1 — kube-prometheus-stack plutôt que des composants séparés

On déploie le chart kube-prometheus-stack (prometheus-community) plutôt que d'assembler Prometheus Operator, Grafana, Alertmanager, node-exporter et kube-state-metrics à la main.

Critère kube-prometheus-stack Composants séparés
Intégration Operator + Grafana + Alertmanager + exporters cohérents À câbler soi-même
Dashboards cluster Mixins K8s fournis (as-code) À importer un par un
Maintenance Un seul chart à bumper N charts désynchronisés
Standard marché De facto pour la stack Prometheus Rare en prod

La rétention de configuration manuelle n'apporte rien ici. Le chart est le standard et fournit gratuitement les dashboards cluster (kube-state-metrics + node-exporter), ce qui couvre le « dashboard cluster K8s » demandé sans JSON custom.

Décision 2 — Déploiement via ArgoCD (pas par le bootstrap Ansible)

L'addon est posé en GitOps (Application ArgoCD), pas par le bootstrap Ansible qui installe cert-manager / ESO / ExternalDNS. Raison : démontrer le pattern « ArgoCD gère les addons de plateforme » et éviter de regonfler le bootstrap. Conséquence : ajouter un addon = déposer une Application dans k8s/platform/argocd-apps/, le root-app la découvre.

Deux Applications, séparées par responsabilité :

  • kube-prometheus-stack (sync-wave 0) : le chart Helm. Source multi-source : le chart vient du repo Helm upstream, les values vivent dans notre repo Git ($values/k8s/platform/monitoring/values.yaml) pour être diffables en review.
  • monitoring (sync-wave 1) : kustomize, possède le ServiceMonitor fastapi et les dashboards as code. Sync-wave 1 car ces ressources consomment les CRDs (ServiceMonitor) posées par le chart en wave 0.

Le ServiceMonitor est owned par la plateforme monitoring, pas par l'app (k8s/base) : ainsi l'Application fastapi ne dépend pas d'un CRD externe à son premier sync (même logique d'ownership que l'exposition, ADR 010).

Décision 3 — Rétention courte, aucune persistence

retention: 24h, pas de PVC (Prometheus en emptyDir). Le cluster est éphémère : l'historique métriques meurt au teardown, c'est un choix coût assumé. La démonstration se fait en direct sous charge (fortio interne, cf. #55), pas sur de l'historique long terme. Un cluster permanent justifierait un PVC + rétention longue ; ce n'est pas le modèle du projet.

Décision 4 — Dashboards as code

Le dashboard FastAPI RED (Rate, Errors, Duration) est versionné en JSON dans le repo (k8s/platform/monitoring/dashboards/fastapi-red.json), matérialisé en ConfigMap labellisée grafana_dashboard: "1" et provisionné par le sidecar Grafana. Obligatoire vu le cluster éphémère : sinon un dashboard créé à la main dans Grafana serait perdu à chaque teardown.

Les requêtes du dashboard sont calées sur les vraies métriques de l'instrumentator (relevées sur /metrics en local, pas devinées) :

  • http_requests_total{handler,method,status} — le status est groupé en classes (2xx/3xx/4xx/5xx), d'où status="5xx" pour le taux d'erreur.
  • http_request_duration_highr_seconds_bucket{le} — histogramme global haute résolution, pour des percentiles précis (histogram_quantile).
  • http_request_duration_seconds_bucket{handler,le,method} — histogramme low-res par handler, pour la latence p95 par route.

Le datasource est référencé via une variable de template (${datasource}), comme les dashboards fournis par le chart : le dashboard reste portable.

Pièges traités

  • CRDs > limite d'annotation ArgoCD : les CRDs kube-prometheus-stack dépassent les 262144 octets de l'annotation last-applied-configuration du client-side apply. syncOptions: ServerSideApply=true sur l'Application contourne le piège.
  • Découverte des ServiceMonitor : par défaut le chart ne sélectionne que les objets portant son label release. serviceMonitorSelectorNilUsesHelmValues: false (idem pod/rule/probe/scrapeConfig) rend les sélecteurs {} (tout sélectionner), pour que notre ServiceMonitor fastapi soit scrapé.
  • Control-plane non scrapable sur EKS : kubeControllerManager, kubeScheduler, kubeEtcd, kubeProxy désactivés. EKS gère le control plane, ses endpoints ne sont pas exposés ; les laisser actifs produirait des cibles « down » permanentes dans Prometheus.

Conséquences

  • Pas de modification du bootstrap/teardown Ansible : l'addon est purement GitOps. Au teardown, la cascade du root Application apps supprime les Applications kube-prometheus-stack et monitoring (finalizer). Pas d'ELB créé (Grafana en ClusterIP, accès port-forward) : rien à ajouter côté anti-INC-016, contrairement à l'exposition.
  • Accès Grafana en port-forward dans un premier temps (kubectl -n monitoring port-forward svc/kps-grafana 3000:80). L'exposition publique grafana.devopsyouss.com est le périmètre de #76.
  • Mot de passe admin Grafana par défaut (prom-operator) : non exposé publiquement à ce stade, durcissement traité dans le bilan sécurité.
  • Empreinte ressources : sur un mono-node t3.medium déjà chargé (fastapi ×2, ArgoCD, ESO, cert-manager, Envoy), la stack ajoute Prometheus + Grafana + Alertmanager + exporters. Requests volontairement basses. À surveiller au prochain aws-start : si pression mémoire/scheduling, envisager un node plus grand (t3.large) ou réduire l'empreinte. Constat à valider live.

Évolution possible

  • Exposition Grafana publique (#76, livré) : https://grafana.devopsyouss.com en réutilisant la stack d'exposition du Sprint 3 (ADR 010). HTTPRoute (ns monitoring) attachée au Gateway partagé via allowedRoutes.from:All (cross-namespace), Certificate wildcard *.devopsyouss.com (DNS-01), CNAME auto ExternalDNS. Mot de passe admin géré via ESO/Secrets Manager (prérequis sécurité avant exposition).
  • Logs (#75) : Loki + Alloy, même pattern GitOps.
  • Alerting (#77) : PrometheusRules + Alertmanager (déjà déployé ici sans config custom) + webhook.
  • OpenTelemetry : écarté à ce stade. On pose d'abord les backends (Prometheus/Loki) ; la couche de collecte unifiée OTel (et le tracing, Tempo) viendrait après, repoussée Sprint 5. Mettre OTel avant d'avoir un backend serait prématuré.

Date : 2026-06-13 Sprint : 4 Issue : #74