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
appssurveillek8s/platform/argocd-apps/. /metricsne 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 ServiceMonitorfastapiet 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}— lestatusest 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-configurationdu client-side apply.syncOptions: ServerSideApply=truesur 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 ServiceMonitorfastapisoit scrapé. - Control-plane non scrapable sur EKS :
kubeControllerManager,kubeScheduler,kubeEtcd,kubeProxydé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
appssupprime les Applicationskube-prometheus-stacketmonitoring(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 publiquegrafana.devopsyouss.comest 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.comen réutilisant la stack d'exposition du Sprint 3 (ADR 010). HTTPRoute (ns monitoring) attachée au Gateway partagé viaallowedRoutes.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