Sprint 3 Report — Déploiement EKS Production-Grade
Période : 2026-05-13 à 2026-06-09
Release : merge develop vers main le 2026-06-10 (MR !137)
Équipe : 1 ingénieur DevOps (Youssef Kadi)
Repo : gitlab.com/yk-devops/fastapi-eks-project
Executive Summary
À l'entrée du Sprint 3, l'infra EKS tournait mais le déploiement applicatif était naïf : fichier par fichier, hardening jamais enforced sur le cluster, secrets en clair, exposition manuelle. Ce sprint a livré une stack production-grade de bout en bout : pipeline CI/CD durci (ECR immutable, promote par digest, SAST), HTTPS automatisé avec DNS managé, secrets depuis AWS Secrets Manager, et six contrôles de sécurité Kubernetes activement enforced. L'API est accessible à api.devopsyouss.com, la documentation à doc.devopsyouss.com ; dernière validation end-to-end : 2026-06-09, curl /healthz/ready → 200.
Stories livrées
Workload et Pipeline CI/CD
| # | Description | Taille | Statut |
|---|---|---|---|
| #52 | Deploy kubectl apply -k (kustomize) : le hardening atteint enfin EKS |
L | Livré |
| #65 | ECR IMMUTABLE + promote par digest (image scannée == image déployée) | L | Livré |
| #32 / #30 / #12 | Dockerfile multi-stage Python 3.12-slim pinnée par digest, split requirements prod/dev, 0 CVE CRITICAL/HIGH Trivy | L | Livré |
| #67 | Dockerfile venv (python -m venv), gunicorn retiré, .trivyignore purgé |
M | Livré |
| #69 | trivy-fs-scan format SARIF : GitLab SAST Security Dashboard activé |
S | Livré |
| #71 | Patch CVE-2026-45447 OpenSSL HIGH (libssl3t64 / openssl / openssl-provider-legacy) | S | Livré |
| #51 | Gate deploy derrière input DEPLOY=true |
S | Livré |
| #54 | Refactoring trigger deploy (spec:inputs dropdown, saisie manuelle supprimée) | S | Livré |
Sécurité
| # | Description | Taille | Statut |
|---|---|---|---|
| #41 | Mot de passe DB purgé de la documentation publique (Critical, SEC-001) | XS | Livré |
| #42 | CORS corrigé : ALLOWED_ORIGINS via Pydantic settings (SEC-002) |
S | Livré |
| #43 | securityContext container complet : allowPrivilegeEscalation, readOnlyRootFilesystem, capabilities, seccompProfile (SEC-003) |
S | Livré |
| #44 | replicas: 2 + PodDisruptionBudget minAvailable: 1 (SEC-004) |
S | Livré |
| #45 | ServiceAccount dédié fastapi, token monté automatique désactivé (SEC-005) |
S | Livré |
| #49 | tfsec + kube-linter intégrés en CI (stage security) |
M | Livré |
| #50 | 4 CRITICAL tfsec corrigés (ECR mutability, IAM scopés à l'ARN) | M | Livré |
| #55 | NetworkPolicy enfin enforced (VPC CNI déclaré addon managé + enableNetworkPolicy=true) |
M | Livré |
| #64 | Triage tfsec HIGH : 3 vrais fixes + 4 ignores justifiés + GIT_CLEAN_FLAGS: -ffdx |
M | Livré |
Infrastructure et Exposition
| # | Description | Taille | Statut |
|---|---|---|---|
| #28 | HTTPS public api.devopsyouss.com via cert-manager + Let's Encrypt DNS-01 + Cloudflare |
L | Livré |
| #33 | External Secrets Operator + IRSA : secrets depuis AWS Secrets Manager, zéro credential statique | L | Livré |
| #60 | Ownership exposition industrialisée : Gateway dans le bootstrap (cluster-admin), HTTPRoute dans le deploy CI (least-privilege) | L | Livré |
| #66 | ExternalDNS v1.15.0 : CNAME api.devopsyouss.com créé et autocorrigé en moins de 60s |
M | Livré |
| #46 | Lab local kind : overlay Kustomize, FastAPI + PostgreSQL testables sans AWS | M | Livré |
| #47 | Endpoints /healthz/live et /healthz/ready (probes K8s dédiées) |
M | Livré |
| #48 | NetworkPolicy posées : default-deny-all + allow-fastapi + allow-postgres | M | Livré |
Documentation et Observabilité
| # | Description | Taille | Statut |
|---|---|---|---|
| #62 / #63 | Dashboard interactif des incidents (53 incidents, drawer latéral, filtres par sévérité) | M | Livré |
| #56 | Runbook de validation EKS (7 catégories, commandes + résultats attendus + pièges) | M | Livré |
| #53 | Sync MkDocs : application.md, infra-eks-summary.md, ADR 007 backfill complet | M | Livré |
| / | 6 ADRs créés ou amendés (005, 007, 008, 009, 010, 011) | M | Livré |
| / | Diagramme d'architecture diagram-as-code (docs/diagrams/architecture.py) |
S | Livré |
| / | Doc publique doc.devopsyouss.com (Pages GitLab, domaine custom Cloudflare) |
S | Livré |
Stories non livrées / reportées Sprint 4
| # | Description | Raison du report | Priorité Sprint 4 |
|---|---|---|---|
| #72 | GitOps ArgoCD sur EKS | Périmètre identifié trop tard dans le sprint, scope non estimé | Medium |
| #70 | Migration ELB Classic vers NLB | Dépréciation AWS découverte en fin de sprint | Medium |
| #57 | CI consolidation (GIT_CLEAN_FLAGS global, pinning images par digest, anti-double-pipeline) | Scope élargi progressivement au fil du sprint | Low |
| #68 | SBOM CycloneDX | Non commencé | Low |
| #35 | Runner hardening | Non commencé | High |
| / | README mis à jour (Python 3.10 vers 3.12, stack réelle) | Non commencé | Low |
Vélocité
Ce rapport constitue la baseline Sprint 3. Pas de sprint précédent comparable sur ce projet.
| Taille | Nb éléments | Description |
|---|---|---|
| XS | 1 | Trivial, moins de 2 heures |
| S | 8 | Simple, demi-journée |
| M | 13 | Moyen, 1 à 2 jours |
| L | 6 | Complexe, 3 jours ou plus |
| Total | 28 |
Non livrés : 6 éléments, tous low ou medium, aucun critique pour la production.
Incidents majeurs et résolutions
Le sprint a capitalisé 13 incidents (INC-040 à INC-052). Quatre méritent d'être mis en avant pour leur valeur technique.
INC-044 : Régression lib Python kubernetes 36.0.0 (2026-05-23)
Le bootstrap Ansible tombait en 401 alors que kubectl passait avec les mêmes credentials. La cause : pip install kubernetes sans version pinnée tirait la v36, qui contient une régression d'authentification EKS. Diagnostic méthodique en couches (curl brut, puis client Python, puis Ansible) pour isoler la couche fautive. Fix : kubernetes==31.0.0 pinné dans le bootstrap et le teardown.
Leçon : Pinner les versions des libs critiques en CI. Une dépendance non pinnée peut casser l'infra sans aucun changement de ta part.
INC-045 et INC-046 : PSA et NetworkPolicy enforced en théorie, inertes en production (2026-05-24)
Les manifests étaient présents, lintés, appliqués. Test négatif : le pod sort quand même sur un port bloqué, et un pod non conforme est admis sous PSA. Causes : le namespace était owné par le deploy least-privilege (qui ne peut pas modifier un objet cluster-scoped), donc les labels PSA n'atteignaient jamais EKS ; le VPC CNI tournait en self-managed sans aucun addon managé déclaré.
Leçon : "Configuré n'est pas enforced." La seule preuve valable est le test négatif sur le cluster réel, jamais l'intention lue dans les manifests.
INC-050 : ECR IMMUTABLE révèle un couplage caché dans le pipeline (2026-06-03)
L'activation d'ECR immutable (durcissement sécurité) a cassé le pipeline app. L'investigation a révélé que le job promote-image faisait un rebuild kaniko plutôt qu'un retag : l'image déployée n'était donc pas l'image scannée par Trivy. Fix : promote réécrit en retag par digest (batch-get-image + put-image), aucune permission IAM supplémentaire requise.
Leçon : Un durcissement infra peut révéler des couplages cachés dans le pipeline. Tester avec l'infra réellement up, pas seulement en MR.
INC-049 : "MR verte mais merge rouge" (2026-06-01)
Le job tfsec échouait sur develop avec 1 CRITICAL absent de la MR avec le même code. Cause : runner self-hosted réutilisant son répertoire de build, contaminé par des fichiers Terraform non trackés laissés par un pipeline infra précédent. Fix : GIT_CLEAN_FLAGS: -ffdx sur le job tfsec.
Leçon : "MR verte mais merge rouge" pointe vers un problème environnemental, pas dans le code. Sur runner self-hosted, forcer le nettoyage du répertoire pour tout scan statique.
Décisions d'architecture (ADRs)
| ADR | Décision | Résumé |
|---|---|---|
| 005 | Pipeline CI/CD (amendé) | Promote par digest : image déployée == image scannée par Trivy |
| 008 | Security Hardening | 6 contrôles sécurité K8s activés (CORS, securityContext, SA dédié, PDB, PSA, NetworkPolicy) |
| 009 | External Secrets Operator + IRSA | ESO retenu vs Sealed Secrets. IRSA pour l'accès AWS Secrets Manager sans credentials statiques dans la CI |
| 010 | Ownership de l'exposition | Plateforme (bootstrap cluster-admin) possède GatewayClass, Gateway, Certificate. App (deploy CI) possède le HTTPRoute |
| 011 | Image de base multi-stage | python:3.12-slim retenu vs distroless (mesuré : distroless figé sur Python 3.11.2 avec 7 HIGH non patchables). Venv pour l'isolation complète des dépendances |
| 007 | Incidents et Lessons (amendé) | Backfill INC-041 à INC-052 |
Rétrospective
Ce qui a bien fonctionné
Discipline git tenue tout le sprint. Une branche, une MR, un Closes #N, la doc dans la même MR. Zéro branche orpheline en fin de sprint.
Test négatif méthodique. PSA et NetworkPolicy auraient pu rester inertes indéfiniment. C'est le test négatif (tenter le flux bloqué) qui a permis de les découvrir. Cette méthode a été appliquée systématiquement : ExternalDNS, ECR, probes K8s.
Reproduction locale avant de merger. tfsec via l'image Docker CI exacte, Trivy via la skill docker-image-lab. Itérations en secondes au lieu de "merge, attendre le pipeline, cluster qui tourne = argent".
Capitalisation au fil de l'eau. Chaque incident documenté dans sprint3.md et ADR 007 le jour même. Ce rapport sprint bénéficie directement de ce travail.
Décisions basées sur la mesure. ADR 011 (slim vs distroless) tranché par benchmark réel, pas par intuition. Résultat contre-intuitif : l'image "minimaliste" distroless avait plus de CVEs que slim.
Ce qui a bloqué ou coûté du temps
Marathon bootstrap 2026-05-23 (INC-041 à INC-044) : environ 8 heures. Quatre causes distinctes empilées. Cause aggravante : plusieurs changements non testés avaient été batchés la veille. La boucle "merge, pipeline, cluster qui tourne = argent" est très coûteuse quand le diagnostic est long.
AmazonEKSEditPolicy snapshot statique (INC-047 / INC-048) : une session perdue. Fausse piste d'agrégation RBAC Kubernetes. La documentation AWS ne distingue pas clairement une AWS Access Policy (snapshot statique) d'un ClusterRole Kubernetes vivant.
default_branch = main pendant tout le sprint. Closes #N ne fonctionnait pas, toutes les issues fermées à la main. Corrigé le 2026-06-10, en toute fin de sprint.
MkDocs non prévisualisable avant merge. Les jobs build_docs et pages ne tournent que sur develop. Impossible de valider le rendu avant de merger une PR documentaire.
3 actions concrètes pour Sprint 4
-
Tester chaque changement immédiatement, ne jamais batcher. Un cluster qui tourne coûte ; un diagnostic long sur causes empilées coûte encore plus. Une chose, testée, validée, puis la suivante.
-
Pinner toutes les images CI par digest (scope #57). Si
kubernetes==31.0.0avait été pinné dès le départ, INC-044 n'aurait pas existé. La même discipline s'applique aux images Docker des jobs CI. -
Démarrer ArgoCD (#72) tôt dans Sprint 4. C'était prévu dans le README initial du projet, jamais commencé. C'est un composant visible en entretien et dans le portfolio, ne pas le repousser une deuxième fois.
Validation end-to-end
Dernière validation complète : 2026-06-09 (infra up, pipeline deploy=true sur develop, image venv déployée).
curl https://api.devopsyouss.com/healthz/ready
→ {"status":"ok"}
Documentation publique : doc.devopsyouss.com
Sprint 3 fermé le 2026-06-09. Release mergée dans main le 2026-06-10 (MR !137).