Skip to content

Sprint 4 Report — ObservabilitĂ© & Day-2 Operations

PĂ©riode : 2026-06-10 Ă  2026-06-20 (clĂŽturĂ© en avance, due initiale 2026-07-11) Release : merge develop vers main (fin de sprint) Équipe : 1 ingĂ©nieur DevOps (Youssef Kadi) Repo : gitlab.com/yk-devops/fastapi-eks-project


Executive Summary

À l'entrĂ©e du Sprint 4, l'application Ă©tait dĂ©ployĂ©e en production sur EKS (Sprint 3) mais aveugle : aucune mĂ©trique, aucun log centralisĂ©, aucune alerte, et un dĂ©ploiement encore push-based (kubectl apply depuis la CI). Ce sprint a transformĂ© l'exploitation : passage en GitOps pull-based (ArgoCD, write-back du tag d'image), stack d'observabilitĂ© complĂšte (Prometheus / Grafana / Loki / Alloy / Alertmanager → Slack), et deux chantiers d'infrastructure de fond non prĂ©vus au cadrage initial mais livrĂ©s dans la foulĂ©e : la migration du rĂ©seau vers Cilium (eBPF, overlay, Hubble) et le durcissement du runner GitLab self-hosted (le dernier SPOF non traitĂ©).

Le sprint a aussi amorcé un projet transverse de gestion continue des CVE (vuln management), né du constat répété que les vulnérabilités frappent des images déjà construites sans nouveau commit.

Trois URLs vitrines live pour les entretiens : api.devopsyouss.com, grafana.devopsyouss.com, doc.devopsyouss.com. DerniÚre validation end-to-end from-scratch : 2026-06-18 (migration Cilium, api 200, ArgoCD 6/6 Synced, observabilité debout).


Stories livrées

GitOps & déploiement

# Description Taille Statut
#72 GitOps ArgoCD : passage push-based → pull-based, write-back du tag d'image (kustomize edit set image + commit [skip ci]), kubectl apply retirĂ© de la CI L LivrĂ©
#82 ArgoCD selfHeal vs HPA : retrait de replicas du Git + ignoreDifferences sur /spec/replicas (INC-054) M Livré
#83 Régression PDB/kube-linter post-#82 : PDB maxUnavailable: 1 + HPA minReplicas: 2 (zéro downtime) S Livré

Observabilité (milestone « Observabilité », clos le 2026-06-14)

# Description Taille Statut
#73 Endpoint /metrics Prometheus via prometheus-fastapi-instrumentator (probes exclues du comptage) M Livré
#74 kube-prometheus-stack déployé via ArgoCD (multi-source Helm, dashboard FastAPI RED as-code) L Livré
#75 Logs centralisés Loki (SingleBinary) + Alloy (DaemonSet, remplace Promtail) L Livré
#76 Grafana public grafana.devopsyouss.com : mot de passe via ESO + exposition cert wildcard (2 MR) L Livré
#77 Alerting Alertmanager → Slack : 4 PrometheusRules FastAPI, routing par sĂ©vĂ©ritĂ©, webhook via ESO (ADR 016) L LivrĂ©
#93 RĂšgle FastAPITargetMissing (absent(up{...})) couvrant le scale-Ă -0 (sĂ©rie absente ≠ sĂ©rie Ă  0) S LivrĂ©
#94 InfoInhibitor routé vers receiver null (comme le Watchdog) XS Livré
#88 Grafana OOMKilled au boot : limits.memory 200→768Mi (INC-056) S LivrĂ©
#85 loki-0 CrashLoop : emptyDir sur /var/loki en readOnlyRootFilesystem (INC-055) S Livré

Réseau & densité de pods

# Description Taille Statut
#80 Migration VPC CNI → Cilium : overlay VXLAN, kubeProxyReplacement eBPF, Hubble + UI (ADR 019) L LivrĂ©
#79 Prefix Delegation VPC CNI (max-pods 17→110) — rendu obsolĂšte par #80, l'overlay affranchit du plafond ENI M LivrĂ©
#70 NLB au lieu du Classic ELB pour Envoy Gateway (EnvoyProxy + parametersRef, ADR 017) M Livré

CI/CD & supply chain

# Description Taille Statut
#57 Consolidation CI : anti-doublon workflow:rules, 7 images pinnées par digest, DRY via ancres, main = branche d'archive L Livré
#95 Patch 4 CVE HIGH Python (PyJWT, python-multipart) + arbitrage starlette/instrumentator (2 CVE acceptées aprÚs analyse) M Livré
#78 DRY + pin digest du pipeline infra (.gitlab-ci-infra.yml), équivalence prouvée par merged_yaml M Livré
#68 SBOM CycloneDX de l'image, intégré au job trivy-image-scan (artifact reports:cyclonedx) S Livré
#92 kube-linter pinné v0.8.3 + curl -fsSL --retry (download latest flaky, INC-057) S Livré
#71 Patch CVE-2026-45447 OpenSSL HIGH (1re issue fermée auto via Closes #N) S Livré

Sécurité

# Description Taille Statut
#35 Durcissement du runner self-hosted (SPOF) : SSH clé-only / UFW / fail2ban, executor privileged=false + caps réduites, DOCKER-USER cloisonnement LAN, token tourné (3 phases) L Livré
#101 Vuln management Phase 0 : VEX not_affected + risk acceptance datée des 2 CVE starlette, --vex sur les 3 gates Trivy (ADR 020) M Livré
#81 /metrics retirĂ© de l'exposition publique : HTTPRoute dĂ©diĂ©e → directResponse 404 au niveau Gateway (ADR 018) S LivrĂ©

Documentation

# Description Taille Statut
#84 Rubrique pĂ©dagogique « 🎓 Comprendre » + 1er guide observabilite.md M LivrĂ©
#89 Runbook de validation enrichi (ESO/IRSA, GitOps, observabilité, alerting) + guide alerting.md M Livré
#91 Guide architecture.md (archi end-to-end, 2 flux, le pourquoi de chaque brique) M Livré
#98 Page runner transformée en runbook reproductible (commandes exactes par couche) M Livré
#99 Guide validation-hors-infra.md (validation statique avant push par type d'artefact) M Livré
#90 Relecture FR des guides comprendre/ (anglicismes, terminologie) S Livré
#97 Lever les « à valider live » aprÚs les validations du 16/06 (ADR 017/018) S Livré
#100 Dashboard incidents interactif cĂąblĂ© sur le Sprint 4 (Ă©tait figĂ© Ă  Sprint 0→3) S LivrĂ©

Stories non livrées / reportées

# Description Raison Suite
#38 Knowledge base searchable Faible priorité, non démarré Reporté Sprint 5
#96 Bump starlette ≄ 1.3.1 BloquĂ© par un tiers : prometheus-fastapi-instrumentator ne supporte pas starlette ≄ 1.3 (perte de /metrics). 2 CVE traitĂ©es par VEX + risk acceptance (#101) Backlog (se dĂ©bloque quand l'upstream bouge)
/ Multi-environnements dev/staging/prod Reporté volontairement Sprint 5
/ Tracing distribué Reporté volontairement Sprint 5

Vélocité

Baseline Sprint 3 = 28 éléments livrés. Sprint 4 = 32 éléments livrés.

Taille Nb éléments Description
XS 1 Trivial, moins de 2 heures
S 11 Simple, demi-journée
M 12 Moyen, 1 Ă  2 jours
L 8 Complexe, 3 jours ou plus
Total 32

Le sprint a dĂ©passĂ© son pĂ©rimĂštre initial (« ObservabilitĂ© ») : le milestone ObservabilitĂ© (5 issues : #73→#77) a Ă©tĂ© clos dĂšs le 2026-06-14, puis le sprint a absorbĂ© deux chantiers infra majeurs (Cilium #80, runner hardening #35) et amorcĂ© le projet vuln management (#101).


Incidents majeurs et résolutions

Le sprint a capitalisĂ© 7 incidents (INC-053 Ă  INC-059). Quatre mĂ©ritent d'ĂȘtre mis en avant.

INC-053 : « Too many pods » — densitĂ© limitĂ©e par le rĂ©seau, pas le CPU (2026-06-11)

L'installation d'ArgoCD a bloquĂ© 4 pods en Pending (Too many pods) alors que CPU/RAM Ă©taient Ă  34 %. Sur EKS, le VPC CNI alloue une IP secondaire d'ENI par pod : max-pods est plafonnĂ© par le rĂ©seau (t3.medium = 17), pas par les ressources. Fix : Prefix Delegation + launch template maxPods: 110, avec un piĂšge d'ordre (l'addon CNI doit ĂȘtre configurĂ© avant le boot des nodes, sinon ils restent en mode IP classique).

Leçon : sur EKS, max-pods est une limite rĂ©seau (ENI/IP). À anticiper avant toute montĂ©e en densitĂ©. Solution AWS-native ici, remplacĂ©e ensuite par Cilium (#80) qui affranchit la densitĂ© du plafond ENI.


INC-054 : ArgoCD selfHeal vs HPA — deux contrîleurs se disputent spec.replicas (2026-06-12)

Pods fastapi créés puis dĂ©truits en boucle, Application jamais Healthy. Cause : le Git fixait replicas: 2, le HPA descendait Ă  1, ArgoCD jugeait OutOfSync et selfHeal réécrivait 2, le HPA retuait le pod
 boucle infinie. Fix : retirer replicas du Git et ajouter ignoreDifferences sur /spec/replicas.

Leçon : en GitOps, un champ ne peut avoir qu'une seule source de vérité. Si un contrÎleur dynamique (HPA, VPA) gÚre un champ, le retirer de Git et dire à ArgoCD de l'ignorer. SymptÎme à reconnaßtre : une ressource qui ne se stabilise jamais en Healthy + un churn de pods sans cause applicative.


INC-058 : webhooks d'admission injoignables sous overlay Cilium (2026-06-18)

À la migration Cilium, l'amorce a Ă©chouĂ© sur cert-manager et ESO (Address is not allowed). Cause racine : sous overlay (pods en 10.42.x, hors VPC), le control plane EKS managĂ© ne participe pas au VXLAN et ne sait pas router vers une IP de pod overlay. Tout webhook d'admission self-hosted en failurePolicy: Fail devient injoignable depuis l'API server. Fix : hostNetwork: true sur les webhooks impactĂ©s (le pod prend l'IP du node, dans le VPC), avec deux ports distincts (mono-node).

Leçon : un overlay non-natif sur un control plane managĂ© qu'on ne contrĂŽle pas rompt le routage control-plane → pod. Tout composant que l'API server doit joindre en retour (webhooks, conversion, agrĂ©gation) doit ĂȘtre en hostNetwork ou exposĂ© par un Service routable cĂŽtĂ© VPC. Ce n'est pas une question de version K8s, c'est du routage rĂ©seau.


INC-055 / INC-056 : « rendu ≠ runtime », et lire le bon signal (2026-06-13 / 06-14)

Deux crashs au dĂ©marrage qu'aucune relecture de manifeste n'aurait montrĂ©s : Loki en CrashLoop (/var/loki read-only sans PVC → emptyDir), puis Grafana OOMKilled (Exit Code 137, limite 200Mi trop basse pour le pic de boot → 768Mi). Sur INC-056, un log d'erreur de plugin (permission denied) a d'abord dĂ©tournĂ© le diagnostic : le vrai tueur Ă©tait le 137 (OOM).

Leçon : helm template valide le rendu, pas le comportement runtime. Et lire le bon signal : Exit Code 137 = OOM, pas un problÚme de plugin. La preuve, c'est le comportement live, pas l'intention dans le YAML.


Décisions d'architecture (ADRs)

ADR Décision Résumé
012 Prefix Delegation VPC CNI max-pods 17→110 (launch template maxPods). RemplacĂ© par ADR 019.
013 GitOps ArgoCD Déploiement pull-based, write-back du tag, kubectl apply retiré de la CI
014 kube-prometheus-stack via ArgoCD Métriques + dashboards as-code, rétention 24h, no persistence
015 Logs Loki + Alloy SingleBinary filesystem, Alloy DaemonSet (remplace Promtail déprécié)
016 Alerting Alertmanager → Slack Routing par sĂ©vĂ©ritĂ©, webhook via ESO, Watchdog/InfoInhibitor → null
017 NLB pour Envoy Gateway NLB (L4) vs Classic ELB déprécié vs ALB redondant avec le L7 Envoy
018 /metrics retirĂ© du public directResponse 404 au niveau Gateway (404 ≠ 403 : ne divulgue pas l'endpoint)
019 Migration Cilium Overlay VXLAN + kubeProxyReplacement eBPF + Hubble ; remplace ADR 012
020 VEX & risk acceptance VEX not_affected (inexploitabilitĂ© prouvĂ©e) ≠ risk acceptance datĂ©e (atteignable mais assumĂ©)

Rétrospective

Ce qui a bien fonctionné

Validation hors infra systématique. tfsec, helm template, kustomize, ansible syntax-check, kube-linter, Trivy en local via les skills docker-image-lab et valider-hors-infra (créée ce sprint). Itérations en secondes, cluster up réduit au strict minimum (« cluster qui tourne = argent »). Les guides #99 + la skill jumelle ont industrialisé cette pratique.

La preuve par le comportement, pas par l'intention. Le test live a rĂ©vĂ©lĂ© des angles morts qu'aucune relecture de YAML n'aurait montrĂ©s : up == 0 qui ne couvre pas le scale-Ă -0 (#93), les webhooks injoignables sous overlay (INC-058), /metrics → 404 rĂ©ellement servi (#81), la NetworkPolicy enforced par Cilium sans addon managĂ© (test nĂ©gatif Network unreachable).

Discipline GitOps assumée. Le pattern HPA + ArgoCD (INC-054), le découplage déploiement/CI (un fix Grafana déployé par ArgoCD sans pipeline app, INC-056), le write-back non récursif ([skip ci]) : autant de comportements compris et documentés, pas subis.

Capitalisation au fil de l'eau. Chaque incident documentĂ© dans sprint4.md et l'index ADR 007 le jour mĂȘme. La rubrique « Comprendre » (#84) et les guides (#89/#91/#98/#99) font de ce sprint un atout portfolio directement rĂ©utilisable en entretien.

Ce qui a bloqué ou coûté du temps

Trou de cadrage sous overlay Cilium (INC-058). La migration a demandé deux sessions live : la premiÚre a découvert que les webhooks d'admission self-hosted deviennent injoignables sous overlay. Diagnostic non trivial (cartographie webhook par webhook), fix non patchable en live (Ansible --reset-values).

Conflit transitif instrumentator ↔ starlette (#95). Impossible de patcher 2 CVE starlette sans casser /metrics (lib d'observabilitĂ© non maintenue au rythme de starlette). TraitĂ© proprement (analyse d'exploitabilitĂ© + VEX + risk acceptance, #101) mais reste une dette ouverte (#96).

Le scheduler 20h détruit l'infra en pleine session. Le destroy programmé a tué le cluster pendant une session live du soir (cause du Unauthorized kubectl, pas un défaut RBAC). Follow-up : resource_group sur les jobs infra + mise en pause du schedule pour les sessions du soir.

Trou de process kube-linter (#82→#83). Une MR touchant k8s/ mergĂ©e sous ci_config_path infra n'exĂ©cute pas kube-linter-scan (dĂ©fini cĂŽtĂ© app) → rĂ©gression non dĂ©tectĂ©e. À garder en tĂȘte : valider les manifestes sous config app.

3 actions concrĂštes pour Sprint 5

  1. Lancer le multi-environnements dev/staging/prod (sĂ©paration par namespace + overlays Kustomize sur le mĂȘme cluster, pour le coĂ»t). C'Ă©tait l'Ă©volution prĂ©vue, Ă  cadrer par un ADR. ThĂšme central du Sprint 5.

  2. Poursuivre le projet vuln management (Phase 1 : cron Trivy ECR scheduled). La Phase 0 (#101) a posé les fondations VEX ; industrialiser le scan registry pour attraper les CVE sur images déjà construites.

  3. Robustifier les jobs infra (resource_group anti-chevauchement start/destroy) et finaliser le schĂ©ma d'architecture drawio (cahier des charges prĂȘt dans comprendre/architecture.md).


Validation end-to-end

DerniÚre validation complÚte from-scratch : 2026-06-18 (aws-start avec migration Cilium, tous critÚres ADR 019 cochés live).

curl https://api.devopsyouss.com/healthz/ready
→ {"status":"ok"}
  • ArgoCD : 6/6 Applications Synced/Healthy
  • Cilium : kubeProxyReplacement: True, plus d'aws-node/kube-proxy, NetworkPolicy enforced (test nĂ©gatif Network unreachable + positif 200)
  • ObservabilitĂ© : Prometheus scrape OK, dashboard FastAPI RED, logs {namespace="fastapi"} dans Grafana, alertes Slack bout-en-bout
  • Exposition : api + grafana en HTTPS (cert wildcard), /metrics → 404 public

Documentation publique : doc.devopsyouss.com


Sprint 4 clÎturé le 2026-06-20. Milestone « Observabilité » clos le 2026-06-14. Release mergée dans main en fin de sprint.