ADR 026 â Segmentation des node groups : core (on-demand) / observability (Spot) (2026-06-26)
Statut
ValidĂ© live (2026-06-27) â validation from-scratch complĂšte : les deux node groups montent, la stack obs atterrit sur le nĆud Spot taintĂ©, prometheus passe enfin Running, le trafic reste stable sur core. ADR compagnon : ADR 025 (gouvernance des ressources, #112) traite le volet « discipline des ressources » ; le prĂ©sent ADR traite le volet « capacitĂ© matĂ©rielle ». Issue #114 (nĂ©e de #112 / INC-060). #112 clos avec #114.
Contexte
La validation from-scratch de la gouvernance (#112, ADR 025, 2026-06-25) a mesurĂ© le mono-node t3.medium Ă 99 % de requests mĂ©moire (3286Mi / ~3319 allouables), avec prometheus-kps-prometheus-0 non planifiable (FailedScheduling: Insufficient memory, MemoryPressure: False â sur-rĂ©servation, pas manque de RAM rĂ©elle). DĂ©cision assumĂ©e (ADR 025) : garder des requests honnĂȘtes plutĂŽt que de baisser un plancher-jeton. ConsĂ©quence directe : sans capacitĂ© supplĂ©mentaire, la stack gouvernĂ©e ne planifie pas entiĂšrement.
S'ajoutent deux faiblesses structurelles révélées par INC-060 (le pic Grafana qui a coupé l'ingress ~10 min) :
- ObservabilitĂ© co-localisĂ©e au chemin de trafic : le pic mĂ©moire de Grafana a tuĂ© le nĆud qui portait aussi Envoy/NLB â api + argocd + grafana Ă 000. L'obs et le trafic critique partageaient le mĂȘme destin.
- Arrivée de Tempo (#108) : l'ingestion de traces va alourdir encore la stack obs, déjà la plus gourmande.
DĂ©cision â Deux managed node groups sĂ©parĂ©s par taint / tolerations / nodeSelector
| Node group | Capacité | Type de départ | Porte | Pourquoi |
|---|---|---|---|---|
| core | on-demand | t3.medium |
API FastAPI, Envoy/NLB, ArgoCD, cert-manager, ESO, ExternalDNS | stabilitĂ© du trafic + plateforme : pas d'Ă©viction Spot sur le chemin critique. Une fois l'obs partie, core â 2Gi sur 3.3 â marge ample pour l'HPA de l'app |
| observability | Spot (tainté) | t3.large diversifié (t3.large / t3a.large / m5.large) |
Prometheus, Loki, Tempo (#108), Grafana, Alloy | gourmand et croissant (Tempo), mais jetable (pas de persistance, perdu au teardown 20h) â on met le gros nĆud sur le moins cher (Spot ~-70 %) |
Mécanisme d'isolation :
- Le node group observability porte le taint
workload=observability:NoSchedule+ le labelworkload=observability. - CÎté k8s (MR2), les workloads obs portent une toleration de ce taint + un
nodeSelectorworkload=observability: la toleration autorise l'atterrissage sur le nĆud taintĂ©, le nodeSelector l'y force (sinon ils pourraient retomber sur core). - Les workloads core ne portent rien : le taint les repousse automatiquement du nĆud obs.
- PiĂšge DaemonSets :
node-exporter(mĂ©triques nĆud) etalloy(logs) doivent tourner sur tous les nĆuds â toleration OUI, nodeSelector NON (sinon on perd la tĂ©lĂ©mĂ©trie du nĆud core). Cilium tolĂšre dĂ©jĂ tout nativement.
Pourquoi ce dimensionnement, anti-intuition assumĂ©e : l'instinct prod « mettre l'app sur le gros nĆud » ne s'applique pas ici. L'API est volontairement lĂ©gĂšre (128Mi req, HPA plafonnĂ© Ă 5 â 1.3Gi pic) et sans trafic rĂ©el (portfolio) : la dimensionner gros, c'est provisionner pour une charge qui ne viendra pas. Le vrai gourmand mĂ©moire est l'observabilitĂ© (Prometheus/Grafana + Tempo) â c'est elle qui a saturĂ© le nĆud, donc elle qui va sur le gros nĆud Spot. Le principe « trafic sur on-demand stable » reste respectĂ© : core est on-demand et l'app y a la place de scaler. Les types sont un point de dĂ©part Ă confirmer en live (vraies mesures par pod).
Spot â prĂ©cautions
- Interruption AWS avec 2 min de prĂ©avis â Spot pour le jetable uniquement, on-demand pour le critique.
- Pas de stateful in-cluster (la DB est sur RDS) â la contrainte Spot est souple :
corereste on-demand pour ne pas couper le trafic à l'éviction, pas pour de la persistance. - Diversifier les instance types du node group Spot (
t3.large/t3a.large/m5.large) â meilleure disponibilitĂ©, moins d'interruptions. - Sans autoscaler, une interruption = pods obs
Pendingjusqu'au prochain start. Acceptable pour un lab éphémÚre ; comblé par Karpenter (Sprint 6) qui reprovisionnerait sur un autre type Spot.
Conséquences
- #112 devient structurellement impossible : un pic obs ne peut plus tuer le nĆud du trafic (nĆuds distincts). L'isolation des pannes est matĂ©rielle, pas seulement logique.
- La stack gouvernĂ©e (requests honnĂȘtes, ADR 025) planifie enfin : la capacitĂ© dĂ©bloque
prometheus(et la place pour Tempo). - Le nĆud le plus cher (celui qui absorbe Tempo) passe Ă ~-70 % via Spot.
- CoĂ»t : deux nĆuds au lieu d'un, mais infra Ă©phĂ©mĂšre (teardown 20h) et le gros nĆud est Spot â surcoĂ»t marginal.
- Renommage Terraform : l'ancien node group unique
maindevientcoreâ destroy/create cĂŽtĂ© Terraform, sans consĂ©quence sur infra Ă©phĂ©mĂšre (rien Ă migrer, recréée Ă chaque start).
Alternatives écartées
- Un seul nĆud plus gros (ex.
t3.xlarge) : rĂšgle la planification mais ne traite pas l'isolation (un pic obs tuerait toujours le trafic co-localisĂ©) et coĂ»te plein pot en on-demand. - Tout en Spot : exposerait le trafic aux interruptions (api Ă 000 le temps du reschedule) â inacceptable pour le chemin critique.
- Karpenter tout de suite : trĂšs fort en entretien mais gros morceau (IAM, NodePool, consolidation) et inutile sans requests honnĂȘtes (il scale sur le
Pending, donc sur les requests). Séquencé aprÚs. Sprint 6 dédié.
Validation live (â 2026-06-27, from-scratch)
Les six points cochés sur un cluster monté de zéro :
- â
kubectl get nodes -L workloadâ deux nĆuds, labelscore(ip-10-0-4-89) etobservability(ip-10-0-3-32). - â
Taints â
workload=observability:NoScheduleprĂ©sent sur le nĆud obs uniquement (core<none>). - â
prometheus-kps-prometheus-02/2 Running, plusgrafana,loki-0,alertmanager,kube-state-metrics, operator â tous sur le nĆud obs. Le verdict de l'ADR : le 2026-06-25, ce mĂȘmeprometheusrestaitPending(Insufficient memory) sur le mono-node ; ici il planifie. - â
fastapi,argocd-server,envoyâ sur le nĆud core ;curl https://api.devopsyouss.com/healthz/readyâ200, trafic stable. - â
node-exporter(2) etalloy(2) prĂ©sents sur les deux nĆuds (toleration jokeroperator: Exists).
Capacité mesurée aprÚs segmentation
| NĆud | Type | requests mĂ©moire |
requests CPU |
|---|---|---|---|
| core | t3.medium on-demand | 75 % (2504Mi) | 65 % |
| observability | t3.large Spot | 21 % (1492Mi) | 31 % |
| mono-node (2026-06-25, pour mĂ©moire) | t3.medium seul | 99 % â prometheus Pending |
â |
Deux nĆuds qui respirent lĂ oĂč le mono-node saturait Ă 99 % de requests mĂ©moire. La marge Ă 21 % sur le t3.large Spot valide le dimensionnement « large Ă dessein » : Tempo (#108) y rentrera sans nouvelle capacitĂ©. (Rappel pĂ©dagogique : les limits cumulĂ©es peuvent dĂ©passer 100 % â overcommit volontaire et sain ; ce qui gouverne la planification, ce sont les requests, pas les limits.)
- Facultatif (non fait) : simuler une interruption Spot et observer le reschedule (Pending sans autoscaler). Reporté à Karpenter / Sprint 6.
Date : 2026-06-26 Sprint : 5 Issue : #114 (capacité, né de #112 / INC-060)