Sprint 5 â Frontend & Polish
Incidents du Sprint 5 : accĂšs plateforme (SSO), capacitĂ© du nĆud, tracing, frontend.
INC-060 : saturation du nĆud unique â le redĂ©ploiement Grafana fait tomber tout l'ingress
Sévérité : High
Statut : â
RĂ©solu â dĂ©clencheur corrigĂ© (!195) + gouvernance validĂ©e from-scratch (2026-06-25) + capacitĂ© #114 (2 node groups core/observability) validĂ©e live le 2026-06-27 : la stack obs isolĂ©e sur le nĆud Spot taintĂ©, prometheus enfin Running, le nĆud core redescend Ă 75 % de requests mĂ©moire. Un pic obs ne peut plus tuer le chemin de trafic (nĆuds distincts). #112 et #114 clos ensemble (ADR 026)
Contexte : Validation live des accĂšs plateforme (#105/#106) le 2026-06-23. Cluster mono-nĆud t3.medium (2 vCPU / ~3931 Mi) portant toute la pile : app FastAPI, Envoy/NLB, ArgoCD, cert-manager, ESO, ExternalDNS, Cilium + Hubble, kube-prometheus-stack, Loki + Alloy. ArgoCD redĂ©ploie Grafana aprĂšs le correctif d'env de #111.
SymptĂŽme : Grafana met ~6 min Ă devenir Ready, puis les 3 hĂŽtes publics deviennent injoignables (api / argocd / grafana â connection timeout) pendant ~10 min. kubectl get events montre une rafale simultanĂ©e de Killing ... failed liveness probe sur ~11 pods (cilium-operator, metrics-server, argocd-server, argocd-repo-server, hubble-ui, node-exporter, fastapi, envoy-gateway, plan de donnĂ©es Envoy, prometheus, external-dns). Le plan de donnĂ©es Envoy (backend du NLB, partagĂ© par tous les hĂŽtes) redĂ©marre â plus aucune cible saine â coupure totale de l'ingress.
Cause : Le nĆud est sur-engagĂ© : limits.memory cumulĂ©es = 168 % de la capacitĂ© du nĆud (CPU 103 %). Il tient ~100 min, mais le redĂ©ploiement de Grafana dĂ©clenche un pic mĂ©moire + CPU : Grafana 11+/13 prĂ©installe plusieurs plugins au boot (lokiexplore, pyroscope, exploretraces, metricsdrilldown) et les re-tĂ©lĂ©charge Ă chaque dĂ©marrage car le stockage est en emptyDir (persistence off). Ce pic fait basculer le nĆud sur-souscrit â kubelet qui ne rĂ©pond plus â NodeNotReady qui flappe â Ă©checs de liveness en masse. Ni HPA, ni Karpenter, ni Cluster Autoscaler ne l'auraient Ă©vitĂ© : ils rĂ©agissent aux pods Pending, pas Ă l'overcommit au runtime.
RĂ©cupĂ©ration : Le nĆud s'est stabilisĂ© seul une fois Grafana bootĂ© (~11 min) ; supprimer le pod de plan de donnĂ©es Envoy coincĂ© a donnĂ© un backend NLB sain â accĂšs rĂ©tabli.
Fix :
1. DĂ©clencheur (!195) â
validé live 2026-06-25 : GF_PLUGINS_PREINSTALL_DISABLED=true sur Grafana (supprime le pic de boot, démarrage rapide et sobre) + liveness probe assouplie (initialDelaySeconds 90, failureThreshold 12). Boot from-scratch propre, api 200, aucune cascade. C'est le gain anti-incident immédiat.
2. Discipline des ressources (!196/!197/!199) â
validĂ©e from-scratch : requests/limits mesurĂ©s (!196) + LimitRange (defaults par namespace). Constat live #1 : le LimitRange livrĂ© en GitOps (sync-wave 2) arrivait aprĂšs les composants dĂ©jĂ dĂ©marrĂ©s â ils restaient BestEffort (un LimitRange n'agit qu'Ă l'admission). CorrigĂ© (!199) : dĂ©placĂ© au bootstrap Ansible, créé avant tout composant â 2e boot from-scratch = plus aucun BestEffort. ResourceQuota reportĂ© Sprint 6. Nuance honnĂȘte : la somme des limits.memory monte (168â235 %) car borner les pods jusque-lĂ non capĂ©s la fait croĂźtre tout en rĂ©duisant le risque (aucun runaway possible) ; ce n'est pas l'indicateur de risque.
3. CapacitĂ© (#114, ADR 026) â le vrai fix, â
validĂ© live le 2026-06-27 : avec des requests honnĂȘtes, le t3.medium montait Ă 99 % de requests mĂ©moire et prometheus ne se planifiait plus (Insufficient memory, pas de MemoryPressure : sur-rĂ©servation, pas sur-usage). Choix assumĂ© : garder les requests honnĂȘtes (pas de plancher-jeton Ă remonter ensuite). Le 2e node group observability (Spot taintĂ©, t3.large) isole les workloads gourmands ET fournit la capacitĂ© qui manque. Validation from-scratch (2026-06-27) : prometheus enfin Running sur le nĆud Spot, core Ă 75 % de requests et observability Ă 21 % (marge pour Tempo), api 200. Ăconomie Spot mesurĂ©e ~-70 %. DĂ©tails : ADR 026 (capacitĂ©) + ADR 025 (gouvernance).
Limite d'observabilitĂ© (leçon dans la leçon) : Prometheus / Loki / Alloy sont co-localisĂ©s sur le mĂȘme nĆud et sans persistance â ils ont redĂ©marrĂ© aussi et perdu leur TSDB/donnĂ©es, laissant un trou exactement pendant l'incident. Une stack de monitoring ne peut pas s'observer elle-mĂȘme quand son nĆud tombe. La chronologie vit dans kubectl get events, pas dans Loki (Alloy expĂ©die les logs de pods, pas les Events K8s).
Leçon : Un nĆud unique sur-engagĂ© est une bombe Ă retardement : tant que rien ne pique, ça tient ; le premier pic transitoire (boot, migration, redĂ©ploiement) fait tout tomber. Trois rĂ©flexes : (1) dimensionner sur le pic, borner l'overcommit (requests honnĂȘtes + quota sur les limits) ; (2) isoler les workloads gourmands et jetables (observabilitĂ©) du chemin de trafic critique (un pic obs ne doit jamais pouvoir tuer le pod qui sert le trafic) ; (3) ne pas attendre d'un autoscaler qu'il rattrape un overcommit runtime, il ne voit que le Pending. Voir le guide CapacitĂ© et ressources.
INC-061 : metrics-server injoignable sous Cilium â kubectl top et le HPA cassĂ©s en silence
Sévérité : Medium
Statut : â
RĂ©solu â fix hostNetwork validĂ© live le 2026-06-25 (#115)
Contexte : DĂ©couvert le 2026-06-24 pendant la campagne de mesure de capacitĂ© du #112. Pour relever le working set par pod, on tente kubectl top â Ă©chec. La pile tourne sous overlay Cilium (#80) depuis la migration VPC CNI â Cilium.
SymptĂŽme : kubectl top nodes / kubectl top pods renvoient error: Metrics API not available. L'APIService agrĂ©gĂ© v1beta1.metrics.k8s.io est Unavailable, avec dans son message Address is not allowed vers l'IP overlay 10.42.0.208 (le pod metrics-server). Les mesures ont dĂ» ĂȘtre prises autrement (Prometheus / cAdvisor via port-forward).
Cause : Jumeau exact d'INC-058, transposĂ© d'un webhook Ă un APIService agrĂ©gĂ©. metrics-server est servi par un pod ; l'API server EKS managĂ© dĂ©lĂšgue les requĂȘtes metrics.k8s.io Ă ce pod (c'est l'API server qui appelle le pod). Sous overlay, le pod a une IP 10.42.x hors VPC â le control plane managĂ© (qui ne participe pas Ă l'overlay VXLAN) ne sait pas la router â Address is not allowed. Effet de bord vicieux : le HPA fastapi (#82) consomme metrics.k8s.io â privĂ© de mĂ©triques, il a cessĂ© de scaler en silence dĂšs la migration #80, sans erreur visible (un HPA sans mĂ©triques n'alerte pas, il s'arrĂȘte juste de rĂ©agir). Bug latent ~6 jours.
Fix (#115, MR fix/115-metrics-server-cilium) : hostNetwork: true sur metrics-server (bootstrap Ansible) â le pod prend l'IP du node (10.0.x, VPC), de nouveau routable par le control plane. containerPort dĂ©calĂ© sur 10262 (donc --secure-port=10262) pour ne pas heurter le 10250 du kubelet, ni 10260 (cert-manager) ni 10261 (ESO) dĂ©jĂ pris par les webhooks hostNetwork d'INC-058 sur ce cluster mono-nĆud.
ValidĂ© live (2026-06-25, boot from-scratch) : pod metrics-server sur l'IP du node 10.0.4.38 (plus d'IP overlay), APIService metrics.k8s.io Available=True, kubectl top nodes/pods rĂ©pondent, et kubectl get hpa -n fastapi affiche des TARGETS chiffrĂ©es (cpu: 3%/70%, memory: 58%/80%, plus de <unknown>) â le HPA fastapi est de nouveau alimentĂ©.
Leçon : La rĂšgle d'INC-058 ne se limite pas aux webhooks : tout composant que l'API server doit appeler (webhook d'admission/conversion ou APIService agrĂ©gĂ©) doit ĂȘtre joignable depuis le VPC â sous overlay, hostNetwork. Et un HPA sans mĂ©triques Ă©choue en silence : il faut surveiller activement l'Ă©tat de la metrics API (kubectl top ou une alerte sur l'APIService), sinon une dĂ©pendance cassĂ©e passe inaperçue tant qu'aucune charge ne rĂ©clame de scale. Voir le guide Cilium.
#109 â Frontend : la base « minimale » n'est pas Ă jour
Type : leçon (build / supply chain), pas un incident de prod Statut : chaßne CI livrée (MR3), image à 0 CVE
Constat : le tag nginxinc/nginx-unprivileged:1.27-alpine, réputé minimal, embarque un alpine figé à sa publication. Premier scan Trivy de l'image frontend : 31 CVE HIGH/CRITICAL fixables (openssl, musl, zlib, libxml2, libpng, nghttp2...). Aucune due à notre code : paquets OS dont le correctif existe déjà en amont mais que la base n'a pas intégré.
Fix : apk upgrade --no-cache au runtime (bref USER root puis retour USER 101) â 0 CVE fixable, 0 au total. MĂȘme philosophie que le back (#67, #71).
Leçon : « image minimale » â « image Ă jour ». Un pin par digest est reproductible mais gĂšle aussi l'Ă©tat de patch de la base. Le pin (reproductibilitĂ©) et l'apk upgrade (fraĂźcheur des correctifs) sont complĂ©mentaires, pas contradictoires.
ChaĂźne CI : build-frontend-candidate + trivy-frontend-image-scan (double-gate + SBOM), mĂȘme politique que le back, sans exceptions hĂ©ritĂ©es.
#110 â Frontend : vertical slice validĂ©e live
Type : validation live (deploy GitOps + routing + intĂ©gration), pas un incident Statut : â ValidĂ© live le 2026-07-02 â #110 fermĂ©
Ce qui est prouvé (bout en bout, boot from-scratch) :
app.devopsyouss.comsert la SPA React, servie par le Gateway partagĂ© (HTTPRoute cross-nsAccepted=True, CNAME ExternalDNS â NLB). L'ApplicationArgoCDfrontendestSynced/Healthy, 2 replicasRunning.- La SPA fait un
fetchcross-origin versapi.devopsyouss.com/posts/public(endpoint public lecture seule, #110 MR1). - CORS strict validé :
Origin: https://app.devopsyouss.comâ rĂ©ponse avecaccess-control-allow-origin: https://app.devopsyouss.com; toute autre origine (test avecevil.com) â aucunaccess-control-allow-origin(moindre privilĂšge, pas de wildcard). Leaccess-control-allow-credentials: truerenvoyĂ© sur les deux est un en-tĂȘte statique qui n'autorise rien seul : ce qui gouverne l'accĂšs cĂŽtĂ© navigateur, c'estallow-origin. - Le feed (#124) s'affiche avec les posts. Base RDS vide au dĂ©part â seed de 2-3 posts via l'API publique (
POST /users/âPOST /loginen form-encoded âPOST /posts/Bearer). - Image servie =
deployed-<sha>(pascandidate-, blindage expiration ECR #104).
Deux findings annexes, non bloquants (issues de suivi créées) :
- #125 â
tempo-0OOMKilled une fois dans la fenĂȘtre de boot (exit 137) sous le burst de spans de la validation (sampling 100 %, ADR 027), puis steady-state stable Ă ~46Mi. Pistes : exclure les probes de l'auto-instrumentation (OTEL_PYTHON_EXCLUDED_URLS), sampling cĂŽtĂ© OTel Collector, ou bump modeste de la limite Tempo. - #126 â les 2 replicas
frontendsont co-localisĂ©s sur le nĆudcore(seul nĆud Ă©ligible : le nĆudobservabilityest taintĂ© et le front n'a pas de toleration).topologySpreadConstraintsĂ ajouter pour la HA quand le node groupcorescale â„2.
Leçon : une vertical slice frontend ne se prouve que live de bout en bout. Le rendu de la page dans le navigateur atteste d'un coup le service statique (Gateway â nginx), le fetch cross-origin, le CORS prod et le contrat /posts/public. La validation locale (compose miroir :3000â:8080) couvre le CORS, mais pas le routing Gateway ni la rĂ©solution DNS : ces deux-lĂ ne se voient qu'en live.
#128 â Frontend V2 validĂ©e live
Type : validation live (parcours utilisateur complet), pas un incident Statut : â ValidĂ© live le 2026-07-04 â #128 fermĂ© (code mergĂ© le 2026-07-03, !233â!235)
Ce qui est prouvé sur app.devopsyouss.com (boot from-scratch) :
- Cycle complet
register â login â JWTen conditions rĂ©elles. - CrĂ©ation de post (visible en tĂȘte de feed sans re-fetch) et vote (bouton reflĂšte l'Ă©tat serveur via les codes
201/409). - Recherche par mot-clé et pagination (« Charger plus ») sur un jeu de 7 posts.
- Panneau détail (
GET /posts/{id}Bearer) affiche les bonnes données.
Finding annexe (non bloquant, backlog) : le panneau dĂ©tail s'affiche dans un bloc fixe en haut de page plutĂŽt qu'au fil du post cliquĂ© â correct fonctionnellement, mais peu lisible avec plusieurs posts. TraitĂ© avec le CRUD complet (issue dĂ©diĂ©e, voir plus bas).
#117 + #125 â Service graph Tempo + fix OOM boot validĂ©s live
Type : validation live (observabilitĂ©), pas un incident Statut : â ValidĂ© live le 2026-07-04 â #117 et #125 fermĂ©s (MR1 hors infra !236 mergĂ©e le 2026-07-03)
Ce qui est prouvé (boot from-scratch) :
tempo-0etotel-collectorRunning,RESTARTS: 0depuis le boot â le correctifOTEL_PYTHON_EXCLUDED_URLS=healthz,metrics(ADR 027 addendum) tient : plus d'OOM au boot constatĂ© sur #125.- Service Graph visible dans l'onglet Node Graph de Grafana (datasource Tempo) : arĂȘte
fastapi â fastapi_db, cohĂ©rente avec le seul service applicatif instrumentĂ© (#107, FastAPI + SQLAlchemy â ni Envoy Gateway ni la SPA frontend, dont lefetchpart du navigateur, ne gĂ©nĂšrent de spans serveur).
Leçon : un graphe de service « minimal » n'est pas un dĂ©faut de configuration quand le pĂ©rimĂštre d'instrumentation l'est aussi â vĂ©rifier le scope avant de chercher un bug.
#130 â Frontend V3 validĂ©e live
Type : validation live (CRUD + polish), pas un incident Statut : â ValidĂ© live le 2026-07-04 â #130 fermĂ© (code mergĂ© le 2026-07-04, !238/!239)
Ce qui est prouvĂ© sur app.devopsyouss.com (mĂȘme boot from-scratch que #128 et #117/#125) :
- Ădition et suppression de ses propres posts depuis la SPA : boutons visibles uniquement sur ses posts (check
owner.emailcÎté UI), l'API restant l'autorité (PUT/DELETE /posts/{id}owner-only, 403 sinon). Confirmation avant suppression, curseur de pagination décalé aprÚs un delete. - Détail inline : le panneau détail s'affiche désormais sous le post cliqué (toggle), correction du finding de la validation #128.
- Footer stack DevOps : logos de la stack en SVG inline (simple-icons CC0, aucun chargement externe au runtime), liens vers les livrables live (repo, ArgoCD, Grafana, API docs, doc). Badge texte pour AWS EKS (logo AWS retiré de simple-icons à la demande d'AWS).
Leçon : livrer la V3 dans la foulĂ©e de la validation V2 (findings du matin â issue #130 â 2 MR â validation live le soir mĂȘme) n'a Ă©tĂ© possible que parce que les endpoints PUT/DELETE existaient dĂ©jĂ cĂŽtĂ© API â le coĂ»t marginal d'une itĂ©ration frontend est faible tant que le contrat back tient la promesse.