ADR 007 â Incidents et Leçons Apprises
Contexte
Ce document recense tous les incidents rencontrés durant le projet fastapi-eks-devops, classés par sprint et ordre logique du pipeline. L'objectif est de capitaliser sur ces expériences pour améliorer les pratiques DevOps et DevSecOps.
SPRINT 0 â Fondations & CI/CD de base
INC-001 â Variable GitLab CI Protected sur feature branch
Contexte : Pipeline test stage SymptĂŽme :
pydantic_core.ValidationError: SECRET_KEY Field required
Cause :
Variable GitLab CI marquée "Protected"
Feature branch non protĂ©gĂ©e â variable non injectĂ©e
Fix :
Settings â CI/CD â Variables â SECRET_KEY
Protected : â â Masked : â
Leçon :
Protected = injecté uniquement sur branches protégées (main, develop)
Masked = cache la valeur dans les logs â suffisant pour la sĂ©curitĂ©
RĂšgle : CI/CD variables â Masked uniquement sauf secrets production
INC-002 â Pydantic BaseSettings prioritĂ© .env vs variables d'environnement
Contexte : Docker Compose networking SymptĂŽme :
Connection refused: localhost:5432
Cause :
Pydantic BaseSettings lit le fichier .env en priorité
DB_HOSTNAME=localhost dans .env écrase DB_HOSTNAME=db injecté par Docker
Fix :
# Créer un fichier .env.docker séparé
DB_HOSTNAME=db # nom du service Docker Compose
Leçon :
Pydantic BaseSettings priorité :
1. Variables d'environnement (haute)
2. Fichier .env (basse par défaut)
â En dev Docker : utiliser .env.docker
â En CI/CD : pas de fichier .env (variables injectĂ©es)
INC-003 â Migration Alembic dupliquĂ©e
Contexte : Démarrage de l'application SymptÎme :
sqlalchemy.exc.ProgrammingError: DuplicateTable relation "users" already exists
Cause :
Migration c9e6377cb51d_new_migration.py recréait des tables
déjà créées par les migrations précédentes
Fix :
# Identifier et supprimer la migration problématique
rm alembic/versions/c9e6377cb51d_new_migration.py
Leçon :
Toujours vérifier l'historique Alembic avant d'ajouter une migration
alembic history --verbose
Ne jamais créer une migration qui recrée des tables existantes
SPRINT 1 â DevSecOps & Security Scanning
INC-004 â CVEs en cascade depuis une dĂ©pendance inutilisĂ©e
Contexte : Trivy filesystem scan SymptĂŽme :
17 HIGH CVEs détectés dans la pipeline
Cause :
http-tools â mitmproxy (outil de debug rĂ©seau)
jamais utilisé par FastAPI mais dans requirements.in
GénÚre une cascade de 12 CVEs HIGH via ses dépendances
Fix :
# Supprimer http-tools de requirements.in
# pip-compile â requirements.txt regenerĂ©
# 12/17 CVEs éliminés automatiquement
Leçon :
Auditer réguliÚrement les dépendances
Principle of Least Dependency : n'installer que le nécessaire
Commande utile : pip-audit, pip list --outdated
INC-005 â python-jose CVE CRITICAL (CVE-2024-33663)
Contexte : Trivy scan â algorithme JWT SymptĂŽme :
CRITICAL: python-jose algorithm confusion with ECDSA keys
Cause :
python-jose 3.x vulnérable à l'algorithm confusion
Aucun patch disponible â projet peu maintenu
Fix :
# Migration python-jose â PyJWT
# requirements.in : python-jose â PyJWT>=2.12.0
# app/oauth2.py : from jose import JWTError â from jwt.exceptions import InvalidTokenError
Leçon :
Vérifier la santé des projets open source avant de les utiliser
CritĂšres : derniĂšre release, issues ouvertes, mainteneurs actifs
Outils : deps.dev, snyk advisor, PyPI stats
INC-006 â passlib incompatible bcrypt >= 4.0
Contexte : Tests pytest + pipeline CI SymptĂŽme :
AttributeError: module 'bcrypt' has no attribute '__about__'
ValueError: password cannot be longer than 72 bytes
Cause :
passlib 1.7.4 accĂšde Ă bcrypt.__about__.__version__
Supprimé dans bcrypt 4.0.0
passlib abandonnĂ© depuis 2023 â incompatible
Fix :
# Migration passlib â bcrypt direct
# requirements.in : supprimer passlib, garder bcrypt (sans restriction <4)
# app/utils.py : utiliser bcrypt.hashpw() / bcrypt.checkpw() directement
Leçon :
Surveiller l'activité des dépendances critiques (auth, crypto)
Un projet abandonné = risque de sécurité + incompatibilités futures
Outils : Dependabot, Renovate pour les alertes automatiques
INC-007 â CVEs OS image de base Docker (python:3.10-slim)
Contexte : Trivy image scan (aprĂšs build) SymptĂŽme :
14 CVEs OS Debian 13.4 â status "affected" (pas de fix)
wheel et jaraco.context : Trivy détecte 2 versions
Cause :
python:3.10-slim installe wheel/jaraco.context via apt
dans /usr/lib/python3/dist-packages/ (version ancienne)
pip installe une version plus récente dans /usr/local/lib/
Trivy remonte les 2 versions â CVEs sur l'ancienne
Fix court terme :
Ajouter les CVEs dans .trivyignore avec justification
â packages de build jamais utilisĂ©s Ă runtime par FastAPI
Fix long terme :
# Multi-stage build (Sprint 4)
FROM python:3.12-slim AS builder
# installer toutes les dépendances
FROM python:3.12-slim AS runtime
# copier uniquement /usr/local/lib/python3.12
# â Ă©limine tous les packages systĂšme de build
Leçon :
Les images Docker héritent des vulnérabilités de l'OS de base
Multi-stage build = meilleure pratique pour réduire la surface d'attaque
Alternatives : distroless, chainguard images
SPRINT 2 â Infrastructure Terraform AWS
INC-008 â Terraform state lock S3 non libĂ©rĂ©
Contexte : terraform plan/apply SymptĂŽme :
Error: Error acquiring the state lock
PreconditionFailed: At least one of the pre-conditions did not hold
Cause :
Terraform interrompu (Ctrl+C, redémarrage container)
â fichier .tflock reste dans S3
â prochaine opĂ©ration bloquĂ©e
Fix :
# Supprimer le lock manuellement
aws s3 rm s3://BUCKET/fastapi-eks/ephemeral/.tflock
# ou
terraform force-unlock LOCK_ID
Leçon :
Ne jamais interrompre un terraform apply/destroy
Utiliser tmux pour les longues opérations :
tmux new-session -d -s terraform 'terraform apply -auto-approve'
tmux attach -t terraform
INC-009 â Ressources AWS hors state Terraform (ECR, IAM)
Contexte : Restructuration modules â persistent/ephemeral SymptĂŽme :
RepositoryAlreadyExistsException: ECR repository already exists
EntityAlreadyExists: IAM Role already exists
Cause :
Ressources créées avec l'ancien state (terraform/main/)
Nouveau state (terraform/persistent/) ne les connaĂźt pas
Terraform essaie de les recrĂ©er â conflit AWS
Fix :
# En dev : supprimer et recréer
aws ecr delete-repository --repository-name fastapi-eks/fastapi --force
aws iam delete-role --role-name fastapi-eks-eks-cluster-role
# En prod : toujours terraform import
terraform import module.ecr.aws_ecr_repository.fastapi fastapi-eks/fastapi
Leçon :
Terraform ne connaĂźt que ce qui est dans SON state
RĂšgle d'or : jamais restructurer sans terraform import en production
En dev : terraform destroy â restructurer â terraform apply
INC-010 â EKS Node Group bloquĂ© sans NAT Gateway (27+ min)
Contexte : terraform apply â EKS module SymptĂŽme :
module.eks.aws_eks_node_group.main: Still creating... [27m36s elapsed]
Cause :
create_nat_gateway=false â nodes dans subnet privĂ©
â pas d'accĂšs internet
â nodes ne peuvent pas :
- s'enregistrer auprĂšs de l'API EKS
- puller les images systĂšme (kube-proxy, CoreDNS, aws-node)
- accéder aux APIs AWS (ECR, CloudWatch, S3)
Fix :
# terraform/ephemeral/terraform.tfvars
create_nat_gateway = true # TOUJOURS true avec EKS
Leçon :
EKS nodes nécessitent impérativement un accÚs internet sortant
â NAT Gateway obligatoire pour les nodes en subnet privĂ©
â Sans NAT : node group bloquĂ© indĂ©finiment en CREATING
Variable create_nat_gateway=false : uniquement pour tests VPC/RDS seuls
INC-011 â ENIs orphelins bloquant la suppression des subnets
Contexte : terraform destroy SymptĂŽme :
Error: deleting EC2 Subnet: DependencyViolation
The subnet has dependencies and cannot be deleted
Cause :
ENIs créés par EKS et RDS dans les subnets privés
â appartiennent aux services AWS
â pas supprimables directement par l'utilisateur
â AWS les libĂšre automatiquement mais cela prend 10-15 min
Fix :
# Attendre 10-15 min aprĂšs suppression EKS/RDS
# Vérifier les ENIs
aws ec2 describe-network-interfaces \
--filters "Name=vpc-id,Values=$VPC_ID" \
--query "NetworkInterfaces[].{ID:NetworkInterfaceId,Status:Status}" \
--output table
# Relancer terraform destroy quand ENIs libérés
Leçon :
Ne jamais interrompre terraform destroy
Si bloqué : attendre 10-15 min et réessayer
Solution long terme : vérifier ENIs avant destroy dans aws-stop.sh
INC-012 â ECR et IAM dĂ©truits quotidiennement avec ephemeral
Contexte : aws-stop.sh â terraform destroy SymptĂŽme :
Pipeline CI plantée le lendemain
AWS credentials invalides (IAM user supprimé)
ECR repository introuvable
Cause :
ECR et IAM dans le mĂȘme workspace que VPC/EKS/RDS
â terraform destroy tout supprime
â credentials GitLab CI invalides
â pipeline plante au stage build
Fix :
Restructuration en 2 workspaces :
terraform/persistent/ : ECR + IAM (jamais détruits)
terraform/ephemeral/ : VPC + EKS + RDS (daily destroy)
Leçon :
Séparer les ressources par cycle de vie :
- Persistantes (ECR, IAM, S3) : apply une fois
- ĂphĂ©mĂšres (EKS, RDS, VPC) : apply/destroy quotidien
Scripts aws-start.sh et aws-stop.sh pour automatiser
SPRINT 3 â DĂ©ploiement EKS & Pipeline CI/CD
INC-013 â AWS CLI v2 incompatible Alpine Linux (Docker CI)
Contexte : Pipeline CI â stage build SymptĂŽme :
Failed to load Python shared library libpython3.14.so.1.0:
dladdr1: symbol not found
Cause :
docker:24 image = Alpine Linux (musl libc)
AWS CLI v2 = compilé pour glibc
â incompatibilitĂ© fondamentale musl vs glibc
MĂȘme avec gcompat, Python 3.14 bundlĂ© ne fonctionne pas
Fix :
# Remplacer Docker-in-Docker par Kaniko
image:
name: gcr.io/kaniko-project/executor:v1.23.2-debug
entrypoint: [""]
# Kaniko authentifie ECR nativement via variables AWS
# Pas d'AWS CLI nécessaire
Leçon :
Docker-in-Docker + AWS CLI Alpine = incompatible
Kaniko = alternative moderne, pas de daemon Docker, pas de privileged mode
Avantages Kaniko : ECR auth native, cache layers, sécurisé
INC-014 â Variables GitLab CI Protected bloquant le build ECR
Contexte : Pipeline CI â Kaniko push ECR SymptĂŽme :
error: checking push permissions: 401 Unauthorized
Cause :
AWS_ACCESS_KEY_ID et AWS_SECRET_ACCESS_KEY marqués "Protected"
Feature branches non protĂ©gĂ©es â variables non injectĂ©es
â Kaniko sans credentials â 401 ECR
Fix :
Settings â CI/CD â Variables
AWS_ACCESS_KEY_ID : Protected â / Masked â
AWS_SECRET_ACCESS_KEY : Protected â / Masked â
Leçon :
Protected = branches protégées uniquement (main, develop)
Masked = cache dans les logs (suffisant pour sécurité CI)
RĂšgle : credentials AWS CI/CD â Masked uniquement
Credentials de production â Protected + Masked + environment scoped
INC-015 â Kaniko cache servant vieilles couches aprĂšs mise Ă jour deps
Contexte : Trivy image scan aprĂšs build SymptĂŽme :
CVE-2026-23949 jaraco.context 5.3.0 (fixed: 6.1.0)
CVE-2026-24049 wheel 0.45.1 (fixed: 0.46.2)
MĂȘme aprĂšs mise Ă jour de requirements.txt
Cause :
Kaniko --cache=true â sert les anciennes couches pip
mĂȘme si requirements.txt a changĂ©
â Trivy dĂ©tecte les vieilles versions dans l'image
Fix :
# Forcer rebuild complet
- /kaniko/executor
--cache=false # â pas --no-cache (flag Docker, pas Kaniko)
--destination $IMAGE_CANDIDATE
Leçon :
--no-cache n'existe pas dans Kaniko â utiliser --cache=false
AprÚs mise à jour de dépendances : toujours --cache=false
En production : remettre --cache=true pour les performances
INC-016 â Classic ELB créé par Envoy Gateway hors state Terraform
Contexte : aws-stop.sh â terraform destroy SymptĂŽme :
Error: deleting EC2 Subnet: DependencyViolation
ENI ELB af50eba3... in-use
Security Group k8s-elb-* bloquant le VPC
Cause :
kubectl apply -k k8s/overlays/gateway-api/
â Envoy Gateway Controller crĂ©e automatiquement :
- Classic ELB
- ENIs dans les subnets publics
- Security Group k8s-elb-*
Ces ressources sont hors du state Terraform
â terraform destroy ne les supprime pas
â bloquent la suppression VPC/subnets
Fix :
# Supprimer dans le bon ordre AVANT terraform destroy
kubectl delete gateway --all -n fastapi
kubectl delete httproute --all -n fastapi
sleep 60 # laisser AWS supprimer ELB et ENIs
# Si ENIs encore présents
aws elb delete-load-balancer --load-balancer-name NOM_ELB
aws ec2 delete-security-group --group-id sg-xxxxx
# Puis terraform destroy
tfd
Leçon :
Kubernetes peut créer des ressources AWS hors state Terraform :
â ELB (Envoy Gateway, Ingress ALB Controller)
â ENIs (EKS, RDS, ELB)
â Security Groups (k8s-elb-*)
â EBS Volumes (StorageClass)
â Route53 records (ExternalDNS)
RĂšgle : supprimer via kubectl AVANT terraform destroy
aws-stop.sh doit nettoyer les ressources Kubernetes en premier
INC-017 â POST /users/ requiert authentification JWT
Contexte : Test API FastAPI SymptĂŽme :
POST /users/ â 401 Not Authenticated
Impossible de créer le premier utilisateur sans token
Cause :
Bug de design dans app/routers/user.py
Depends(oauth2.get_current_user) sur create_user
L'inscription nĂ©cessite d'ĂȘtre dĂ©jĂ authentifiĂ© â paradoxe
Fix :
# Supprimer le Depends sur create_user
@router.post("/")
def create_user(
user: schemas.UserCreate,
db: Session = Depends(get_db)
# â supprimer user_id: int = Depends(oauth2.get_current_user)
):
Leçon :
Les endpoints publics (inscription, health check) ne doivent pas
requérir d'authentification
Code review obligatoire avant merge :
â vĂ©rifier que les endpoints ouverts sont intentionnels
â vĂ©rifier que les endpoints protĂ©gĂ©s le sont correctement
INC-018 â Kaniko --no-cache inexistant
SymptĂŽme :
Error: unknown flag: --no-cache
Cause : --no-cache est un flag Docker, pas Kaniko.
Fix : Utiliser --cache=false
Leçon : Kaniko a sa propre API â ne pas confondre avec Docker CLI.
INC-019 â Trivy docker login inutile sur ECR
SymptĂŽme :
/bin/sh: docker: not found
Cause : Image Trivy sans Docker â docker login impossible.
Fix : Supprimer le before_script docker login.
Trivy s'authentifie nativement avec ECR via AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY.
Leçon : Trivy supporte ECR nativement via variables AWS â pas besoin de docker login.
INC-020 â amazon/aws-cli entrypoint bloque les scripts CI
SymptĂŽme :
aws: [ERROR]: Found invalid choice 'sh'
Cause : amazon/aws-cli a entrypoint: ["aws"] â GitLab passe les scripts directement Ă la CLI AWS.
Fix :
image:
name: amazon/aws-cli:latest
entrypoint: [""]
Leçon : Toujours vérifier l'entrypoint des images non-standard. Ajouter entrypoint: [""] pour reset.
INC-021 â hashicorp/terraform entrypoint bloque les scripts CI
SymptĂŽme :
Terraform has no command named "sh"
Cause : hashicorp/terraform a entrypoint: ["terraform"].
Fix :
image:
name: hashicorp/terraform:1.15
entrypoint: [""]
Leçon : MĂȘme pattern qu'INC-020. Images officielles HashiCorp ont des entrypoints custom.
INC-022 â Permissions IAM manquantes pour Terraform EKS
SymptĂŽme :
AccessDenied: not authorized to perform: iam:TagRole
AccessDenied: not authorized to perform: iam:ListInstanceProfilesForRole
Cause : Policy terraform_ci trop restrictive â manque plusieurs actions IAM requises par Terraform lors de la crĂ©ation de rĂŽles EKS avec tags.
Fix : Ajouter dans la policy :
iam:TagRole, iam:UntagRole, iam:TagPolicy
iam:ListRolePolicies, iam:ListAttachedRolePolicies
iam:GetPolicyVersion, iam:ListPolicyVersions
iam:ListInstanceProfilesForRole
iam:ListRoleTags, iam:ListUserTags
iam:TagUser, iam:UntagUser
Leçon : Terraform nĂ©cessite plus de permissions IAM que prĂ©vu â dĂ©couverte itĂ©rative. En prod, utiliser IAM Access Analyzer pour identifier les permissions minimales.
INC-023 â CI_PIPELINE_SOURCE "web" vs include rules
SymptĂŽme : Pipeline vide lors d'un Run Pipeline manuel.
Cause : CI_PIPELINE_SOURCE = "web" quand déclenché depuis l'UI GitLab.
Include rules utilisant d'autres valeurs ne matchaient pas.
Fix :
include:
- local: .gitlab/ci/infra.yml
rules:
- if: '$CI_PIPELINE_SOURCE == "web"'
- if: '$CI_PIPELINE_SOURCE == "schedule"'
Leçon : Toujours ajouter un job debug temporaire pour vérifier la valeur réelle de CI_PIPELINE_SOURCE.
INC-024 â spec:inputs + sĂ©parateur --- obligatoire
SymptĂŽme : Inputs non visibles dans l'UI Run Pipeline.
Cause : spec: inputs: sans sĂ©parateur --- â GitLab ne peut pas parser la configuration.
Fix :
spec:
inputs:
action:
default: "status"
options:
- status
- start
- stop
--- # â obligatoire !
include:
...
Leçon : Le séparateur --- est mandatory avec spec: inputs: dans GitLab CI.
INC-025 â Variables projet CI/CD prioritĂ© > variables fichier YAML
SymptÎme : AWS_ACCESS_KEY_ID dans le fichier YAML écrasé par la variable projet.
Cause : Variables dĂ©finies dans Settings â CI/CD â Variables ont une prioritĂ© HAUTE et Ă©crasent les variables du fichier pipeline.
Fix : Utiliser before_script avec export pour forcer les credentials :
before_script:
- export AWS_ACCESS_KEY_ID=$AWS_INFRA_ACCESS_KEY_ID
- export AWS_SECRET_ACCESS_KEY=$AWS_INFRA_SECRET_ACCESS_KEY
Leçon :
PrioritĂ© variables GitLab CI (haute â basse) :
1. Trigger/schedule variables
2. Settings â CI/CD â Variables (projet)
3. Variables fichier YAML
4. Variables groupe
INC-026 â terraform.tfvars gitignored â TF_VAR_ en CI
SymptĂŽme :
Error: No value for required variable â var.db_password
Cause : terraform.tfvars est dans .gitignore â pas disponible en CI/CD.
Fix : Utiliser le préfixe TF_VAR_ dans les variables GitLab CI.
Terraform lit automatiquement les variables d'environnement TF_VAR_*.
GitLab CI Variables :
TF_VAR_db_password = <REDACTED> â Masked â
(valeur stockée uniquement dans GitLab CI)
Leçon : Ne jamais committer les tfvars (secrets). Utiliser TF_VAR_ en CI ou AWS Secrets Manager.
INC-027 â pip / pip3 introuvable dans alpine/ansible
Contexte : Job bootstrap CI â installation des dĂ©pendances Python Ansible SymptĂŽme :
/bin/bash: line 193: pip: command not found
/bin/bash: line 193: pip3: command not found
Cause :
alpine/ansible:latest inclut Python mais pas pip/pip3
pip n'est pas disponible par défaut sur Alpine
pip3 non plus sans installation explicite
Fix :
- apk add --no-cache ... py3-pip # â ajouter py3-pip
- pip3 install kubernetes pyyaml --break-system-packages
Leçon :
Sur Alpine : pip n'existe pas, pip3 doit ĂȘtre installĂ© via apk add py3-pip
Ne pas supposer que pip/pip3 est présent dans une image Alpine
MĂȘme rĂšgle pour python3 : toujours installer explicitement
INC-028 â Envoy Gateway Helm repo HTTP introuvable (404)
Contexte : Ansible bootstrap â installation Envoy Gateway via Helm SymptĂŽme :
Error: looks like "https://envoyproxy.io/charts" is not a valid chart repository
Error: looks like "https://gateway.envoyproxy.io/helm" is not a valid chart repository
failed to fetch .../index.yaml : 404 Not Found
Cause :
Envoy Gateway a migré vers OCI registry (Docker Hub)
Plus aucun repo Helm HTTP traditionnel disponible depuis v1.x
Les URLs https://envoyproxy.io/charts et https://gateway.envoyproxy.io/helm
ne servent plus d'index.yaml
Fix :
# Ansible bootstrap.yml â utiliser OCI directement
- name: Install Envoy Gateway
kubernetes.core.helm:
name: envoy-gateway
chart_ref: oci://docker.io/envoyproxy/gateway-helm
chart_version: v1.4.0
# plus besoin de helm repo add
Leçon :
De plus en plus de projets Helm migrent vers OCI (Docker Hub, GHCR)
Avant d'utiliser un chart : vérifier la doc officielle pour l'URL courante
OCI = pas de helm repo add nécessaire, utiliser chart_ref: oci://...
INC-029 â eks:DescribeCluster manquant pour le user CI
Contexte : Job bootstrap â aws eks update-kubeconfig SymptĂŽme :
AccessDeniedException: User arn:aws:iam::199167114788:user/ci/fastapi-eks-gitlab-ci
is not authorized to perform: eks:DescribeCluster
Cause :
aws eks update-kubeconfig requiert eks:DescribeCluster
La policy IAM du user CI ne l'incluait pas
Le job bootstrap utilisait les credentials CI (AWS_ACCESS_KEY_ID projet)
au lieu des credentials infra â user sans permission EKS
Fix :
Ajouter *infra_credentials dans le before_script du job bootstrap :
- export AWS_ACCESS_KEY_ID=$AWS_INFRA_ACCESS_KEY_ID
- export AWS_SECRET_ACCESS_KEY=$AWS_INFRA_SECRET_ACCESS_KEY
Variables GitLab CI â priority haute Ă©crase les variables YAML
â before_script export est obligatoire pour forcer les credentials infra
Leçon :
Les variables YAML (variables: section) sont écrasées par les variables projet
â toujours utiliser before_script export pour les credentials sensibles
Voir INC-025 (priorité variables GitLab CI)
INC-030 â needs DAG vs stages pour un flow destroy infra
Contexte : Conception pipeline infra â ordering teardown â destroy SymptĂŽme : (design, pas d'erreur runtime)
needs: - teardown sur infra-stop â risque de skip accidentel
Si teardown absent du pipeline â infra-stop peut dĂ©marrer sans cleanup
â terraform destroy sans avoir libĂ©rĂ© ELBs â DependencyViolation
Cause :
needs exécute en DAG (graph) : flexible mais contournable
- peut skip des jobs dépendants si optional ou absent du pipeline
- allow_failure: true sur le job référencé bypass la garantie
Stages = ordre strict séquentiel : stage N+1 ne démarre jamais avant N
Fix :
# â needs pour destroy = risque
infra-stop:
needs: [ teardown ] # contournable
# â
stages pour destroy = garanti
stages:
- infra # status + start
- bootstrap # aprĂšs start
- teardown # avant stop
- destroy # terraform destroy
# teardown (stage 3) TOUJOURS avant infra-stop (stage 4)
# rÚgle centrale, pas distribuée sur chaque job
Leçon :
needs = flexibilitĂ© DAG â pour des dĂ©pendances optionnelles ou parallĂšles
stages = ordre strict â pour les flows destructifs (destroy, drop, purge)
RĂšgle : ne jamais utiliser needs pour un flow infra destroy
SPRINT 3 â EKS Access Entries + Deploy End-to-End (2026-05-19)
INC-031 â AmazonEKSClusterPolicy attachĂ©e Ă un IAM user
Contexte : Configuration youss_admin pour accĂšs kubectl depuis devcontainer SymptĂŽme :
youss_admin ne pouvait pas accéder au cluster malgré la policy attachée
AmazonEKSClusterPolicy = droits insuffisants pour kubectl
Cause :
AmazonEKSClusterPolicy est la policy du rĂŽle IAM du control plane EKS
Elle est prévue pour eks.amazonaws.com (service), pas pour un user humain
Attachée à un user, elle ne donne aucun droit kubectl utile
Fix :
# â Mauvaise policy pour un user
policy_arn = "arn:aws:iam::aws:policy/AmazonEKSClusterPolicy"
# â
Policy minimale pour générer un kubeconfig
resource "aws_iam_policy" "youss_admin_eks_connect" {
policy = jsonencode({
Statement = [{
Action = ["eks:DescribeCluster", "eks:ListClusters"]
Effect = "Allow"
Resource = "*"
}]
})
}
Leçon :
Deux couches pour l'accĂšs EKS :
1. IAM (AWS) â eks:DescribeCluster pour gĂ©nĂ©rer le kubeconfig
2. EKS Access Entry â AmazonEKSClusterAdminPolicy pour les droits K8s
AmazonEKSClusterPolicy â permission kubectl â c'est pour le control plane
INC-032 â eks:DescribeCluster manquant pour gitlab-ci (deploy job)
Contexte : Job deploy dans .gitlab-ci.yml â aws eks update-kubeconfig SymptĂŽme :
User arn:aws:iam::199167114788:user/ci/fastapi-eks-gitlab-ci is not
authorized to perform: eks:DescribeCluster
Cause :
La policy ecr_push de gitlab-ci couvre uniquement ECR
aws eks update-kubeconfig nĂ©cessite eks:DescribeCluster mĂȘme si le user
a déjà un EKS Access Entry avec AmazonEKSEditPolicy
IAM et RBAC K8s sont deux couches indépendantes
Fix :
# Ajouter dans la policy ecr_push
{
Sid = "EKSConnect"
Effect = "Allow"
Action = ["eks:DescribeCluster"]
Resource = "*"
}
Leçon :
EKS Access Entry = droits dans Kubernetes
eks:DescribeCluster = droit AWS pour atteindre le endpoint EKS
Les deux sont nécessaires, ils ne se substituent pas
INC-033 â kubectl cluster-info forbidden (namespace kube-system)
Contexte : before_script du deploy job â vĂ©rification connexion cluster SymptĂŽme :
Error from server (Forbidden): services is forbidden:
User "fastapi-eks-gitlab-ci" cannot list resource "services"
in API group "" in the namespace "kube-system"
Cause :
kubectl cluster-info liste les services dans kube-system
gitlab-ci a AmazonEKSEditPolicy scopé namespace fastapi uniquement
Pas d'accĂšs aux namespaces systĂšme
Fix :
# â NĂ©cessite accĂšs kube-system
- kubectl cluster-info
# â
Vérifie les droits dans le bon namespace
- kubectl auth can-i create deployments -n fastapi
Leçon :
Toujours tester les commandes de vérification avec le user CI
kubectl cluster-info = opĂ©ration cluster-wide â incompatible avec least privilege
kubectl auth can-i = vérification ciblée par namespace
INC-034 â kubectl create namespace forbidden + doublon bootstrap
Contexte : script deploy job â crĂ©ation namespace fastapi SymptĂŽme :
Error from server (Forbidden): namespaces is forbidden:
User "fastapi-eks-gitlab-ci" cannot create resource "namespaces"
Cause :
Création de namespace = opération cluster-level
AmazonEKSEditPolicy (namespace scope) ne couvre pas la création de namespaces
De plus, le namespace fastapi est déjà créé par ansible/bootstrap.yml
Fix :
# Supprimer du deploy job :
# kubectl create namespace fastapi --dry-run=client -o yaml | kubectl apply -f -
# kubectl apply -f k8s/base/namespace.yaml
# Le namespace est créé par bootstrap Ansible (cluster-admin)
Leçon :
Séparer les responsabilités :
- bootstrap (cluster-admin) â crĂ©er namespaces, installer helm charts
- deploy (edit ns fastapi) â dĂ©ployer l'application uniquement
INC-035 â Init:InvalidImageName â ${ECR_IMAGE} non substituĂ©
Contexte : kubectl apply -f k8s/base/deployment.yaml sans envsubst SymptĂŽme :
Init:InvalidImageName
spec.initContainers{migrations}: Failed to apply default image tag
"${ECR_IMAGE}": invalid reference format
Cause :
deployment.yaml contient image: "${ECR_IMAGE}" (placeholder envsubst)
kubectl apply -f deployment.yaml applique le fichier tel quel
${ECR_IMAGE} littéral = nom d'image invalide pour Kubernetes
kubectl set image ne mettait Ă jour que le container principal,
pas l'initContainer migrations
Fix :
# before_script â installer gettext pour envsubst
- yum install -y gettext --quiet
# script
- export ECR_IMAGE="$ECR_REGISTRY/$ECR_REPOSITORY:$CI_COMMIT_SHA"
- envsubst < k8s/base/deployment.yaml | kubectl apply -f - -n fastapi
Leçon :
envsubst substitue toutes les variables shell dans un fichier texte
Nécessite gettext (yum install) sur Amazon Linux
Remplace kubectl set image â une seule commande, tous les containers mis Ă jour
INC-036 â DB_PASSWORD manquant dans fastapi-secrets
Contexte : initContainer migrations â validation Pydantic Settings SymptĂŽme :
pydantic_core._pydantic_core.ValidationError: 1 validation error for Settings
DB_PASSWORD
Field required [type=missing]
Cause :
kubectl create secret fastapi-secrets ne créait que SECRET_KEY
DB_PASSWORD non inclus â Pydantic Settings Ă©choue au dĂ©marrage
TF_VAR_db_password existe dans GitLab CI/CD mais n'était pas passé au secret
Fix :
kubectl create secret generic fastapi-secrets \
--from-literal=SECRET_KEY="$SECRET_KEY" \
--from-literal=DB_PASSWORD="$TF_VAR_db_password" \
-n fastapi \
--dry-run=client -o yaml | kubectl apply -f -
Leçon :
TF_VAR_db_password = variable Terraform ET variable deploy â mĂȘme source
Lister tous les champs requis par Pydantic Settings avant de créer le secret
INC-037 â terraform apply sur ancien pipeline â No changes (fix non appliquĂ©)
Contexte : Apply du persistent stack pour activer eks:DescribeCluster sur gitlab-ci SymptĂŽme :
terraform plan : No changes. Infrastructure is up-to-date.
Policy ecr_push non mise à jour malgré le fix mergé dans develop
Cause :
Relance d'un ancien pipeline GitLab (avant le merge du fix)
Le job terraform apply de cet ancien pipeline utilisait l'ancien code
git pull origin develop non effectué avant l'apply manuel
Fix :
aws-vault exec iamadmin -- bash
cd terraform/persistent
git pull origin develop # â obligatoire avant apply manuel
terraform apply -auto-approve
Leçon :
Relancer un ancien pipeline â appliquer le code actuel
Pour un apply manuel : toujours git pull avant terraform apply
"No changes" peut ĂȘtre trompeur si le code source n'est pas Ă jour
INC-038 â State lock avec mauvais credentials (youss_admin)
Contexte : Tentative terraform apply depuis devcontainer avec youss_admin SymptĂŽme :
Error acquiring the state lock
Cause :
youss_admin n'a que eks:DescribeCluster + eks:ListClusters
S3 (state) et DynamoDB (lock) non accessibles
L'erreur ressemble à un vrai lock mais c'est en réalité un AccessDenied
Fix :
# Terraform â toujours avec iamadmin
aws-vault exec iamadmin -- bash
# youss_admin = kubectl uniquement
awsyouss # â kubectl get nodes, kubectl logs...
Leçon :
"Error acquiring state lock" peut masquer un AccessDenied S3/DynamoDB
Deux profils, deux usages :
- iamadmin â Terraform, AWS Console, opĂ©rations admin
- youss_admin â kubectl uniquement
INC-039 â Hardening Sprint 3 jamais appliquĂ© sur EKS (deploy fichier par fichier)
Contexte : Job deploy de .gitlab-ci.yml, aprĂšs livraison du hardening Sprint 3 (PSA, ServiceAccount, PDB, NetworkPolicy)
SymptĂŽme :
Le hardening est dans les manifests, validé en local (kustomize build, kube-linter, kind),
mais sur EKS : namespace sans labels PSA, pas de SA fastapi, pas de PDB, pas de NetworkPolicy.
Le prochain deploy aurait cassé (deployment référence serviceAccountName: fastapi inexistant).
Cause :
Le deploy appliquait les manifests un par un :
- kubectl apply -f configmap.yaml / service.yaml / hpa.yaml
- envsubst < deployment.yaml | kubectl apply -f -
Soit 4 manifests sur 10. namespace.yaml (PSA), serviceaccount.yaml, pdb.yaml et les
3 networkpolicy-*.yaml n'Ă©taient JAMAIS appliquĂ©s â le durcissement n'atteignait pas le cluster.
Fix :
# before_script : installer kustomize (en plus de kubectl), retirer gettext/envsubst
curl -sSL ".../kustomize_v5.4.3_linux_amd64.tar.gz" | tar -xz && mv kustomize /usr/local/bin/
# script : injecter le nom+tag image puis appliquer TOUT le base d'un coup
cd k8s/base
kustomize edit set image fastapi="$ECR_IMAGE" # remplace envsubst, met Ă jour init + main container
cd "$CI_PROJECT_DIR"
kubectl apply -k k8s/base/ # applique les 10 ressources (kustomize)
Leçon :
Valider le hardening en local ne suffit pas s'il n'est pas DĂPLOYĂ par le mĂȘme chemin.
Un deploy qui cherry-picke les fichiers laisse une partie du durcissement hors du cluster.
DĂ©ployer le mĂȘme artefact que celui qu'on lint : `kustomize build` = source de vĂ©ritĂ© unique.
Toujours tester end-to-end sur l'environnement cible (EKS), pas seulement en local (kind).
Suite (infra UP) : ces points "Ă valider" se sont rĂ©vĂ©lĂ©s NON enforced â PSA (INC-045, corrigĂ©) et NetworkPolicy (INC-046, mitigation #55). « ConfigurĂ© n'est pas enforced » : valider par test nĂ©gatif sur le cluster.
INC-040 â terraform apply rejetĂ© : accent dans une description de security group
Contexte : Premier aws-start aprÚs le fix tfsec #50 (descriptions ajoutées aux rÚgles egress des SG RDS et EKS)
SymptĂŽme :
Error: "egress.0.description" doesn't comply with restrictions
("^[0-9A-Za-z_ .:/()#,@\[\]+=&;{}!$*-]*$"): "Réponses intra-VPC uniquement"
with module.rds.aws_security_group.rds
Cause :
AWS valide la description des rĂšgles de SG contre une regex strictement ASCII.
Le "Ă©" de "RĂ©ponses" (UTF-8) n'appartient pas Ă l'ensemble autorisĂ© â rejet cĂŽtĂ© API.
Ni tfsec (scan sécurité), ni terraform validate (syntaxe/types), ni terraform plan
ne le détectent : la contrainte est imposée par l'API AWS au moment de l'apply.
Fix :
# Avant : description = "Réponses intra-VPC uniquement"
# AprĂšs : description = "Intra-VPC responses only"
# SG EKS aussi : "Communication ... vers nodes (intra-VPC)" -> "Control-plane to nodes (intra-VPC)"
Leçon :
Convention : tout en anglais ASCII dans la conf (valeurs envoyées aux APIs cloud :
descriptions, tags, noms). Les linters statiques ne couvrent pas la conformité des
valeurs aux contraintes d'API. Garde-fou possible : check pre-commit/CI rejetant
tout caractÚre non-ASCII dans les .tf (viable une fois tout passé en anglais).
INC-041 â kubectl i/o timeout : jobs exĂ©cutĂ©s sur un runner GitLab partagĂ© (2026-05-23)
Contexte : Marathon bootstrap EKS. infra-start et bootstrap échouent avant toute authentification.
SymptĂŽme :
kubectl get nodes
dial tcp <api-eks>:443: i/o timeout
Cause :
.gitlab-ci-infra.yml n'avait pas de `default: tags`. Les jobs partaient sur un runner
GitLab partagé (GCP, IP 35.x) au lieu du runner self-hosted. L'IP du runner partagé
n'est pas dans cluster_public_access_cidrs (82.66.53.81/32) â EKS drop les paquets
silencieusement â i/o timeout (et non 401/403).
Fix :
default:
tags: [ubuntu] # force tous les jobs sur le runner self-hosted (IP autorisée)
Leçon :
Pinner le runner via `tags`. Un i/o timeout sur l'API EKS = problÚme réseau/CIDR,
pas un problĂšme d'auth (qui donnerait 401/403). Le code d'erreur oriente le diagnostic.
INC-042 â Variable GitLab "Protected" non injectĂ©e sur feature branch (2026-05-23)
Contexte : Test d'un fix de CIDR sur une feature branch pendant le marathon. SymptĂŽme :
TF_VAR_cluster_public_access_cidrs absente en CI â terraform retombe sur le placeholder
par défaut 203.0.113.0/24 (RFC 5737), le fix testé n'a aucun effet.
Cause :
La variable est "Protected" dans GitLab â injectĂ©e uniquement sur branches/tags protĂ©gĂ©s
(main, develop). Sur une feature branch non protégée, elle n'existe pas.
Fix :
Pour une valeur non secrÚte (une IP publique) : décocher "Protected".
Garder "Protected" pour les vrais secrets (clés AWS).
Leçon :
Distinguer secret (Protected) de non-secret. Une variable Protected casse la capacité
Ă valider un fix en MR avant merge. MĂȘme classe que INC-001.
INC-043 â Ansible : crash sur condition until + connexion SSH sur localhost (2026-05-23)
Contexte : Play bootstrap Ansible (kubernetes.core) attendant que les nodes soient prĂȘts.
SymptĂŽme :
(1) object of type 'dict' has no attribute 'resources'
(2) ssh: connect to host localhost port 22: Connection refused
Cause :
(1) until: nodes.resources | length > 0 plante quand k8s_info échoue (pas de clé resources)
(2) -i localhost, fait traiter localhost comme un hĂŽte distant joint en SSH
Fix :
until: (nodes.resources | default([])) | length > 0 # (1) default() la variable registered
connection: local # (2) play exécuté en local
Leçon :
Toujours `default()` une variable registered utilisée dans une condition `until`.
Pour les plays kubernetes.core qui tournent en local : connection: local.
INC-044 â Bootstrap 401 : rĂ©gression de la lib Python kubernetes 36.0.0 (2026-05-23)
Contexte : Boss final du marathon. kubernetes.core renvoie 401 alors que kubectl passe juste avant.
SymptĂŽme :
kubernetes.client.exceptions.UnauthorizedException: (401) Unauthorized
# alors que `kubectl get nodes` dans le before_script passe avec les MĂMES credentials
Cause :
RĂ©gression dans la lib Python kubernetes 36.0.0 cassant l'auth EKS. Le MĂME token est
accepté par kubectl et curl brut (HTTP 200) mais rejeté par le client v36 (401), sur
Alpine ET Ubuntu. `pip install kubernetes` (non pinné) tirait la 36 ; une version
antérieure marchait 4 jours avant.
Diagnostic (élimination méthodique) :
token â curl brut HTTP 200 â
(token valide)
identitĂ© â kubectl auth whoami = bon user â
OS â Ă©choue aussi sur Ubuntu â (pas un problĂšme Alpine)
version â kubernetes==31.0.0 â
/ 36.0.0 â â LA cause
Reproduit en local hors pipeline pour itérer en secondes.
Fix :
- pip3 install kubernetes pyyaml --break-system-packages
+ pip3 install kubernetes==31.0.0 pyyaml --break-system-packages # bootstrap + teardown
Leçon :
Pinner les versions des libs critiques en CI. `pip install <lib>` sans pin = une release
upstream peut casser l'infra du jour au lendemain, sans aucun changement de ton cÎté.
Méta-leçon du marathon : tester à chaque itération (batcher 4 changements non testés a
empilé 4 causes), descendre au niveau le plus bas pour isoler (curl > client Python >
Ansible), reproduire en local pour sortir de la boucle pipeline coûteuse.
INC-045 â PSA jamais enforced sur EKS : namespace ownĂ© par le bootstrap, pas par le deploy (2026-05-24)
Contexte : PremiĂšre validation end-to-end du deploy applicatif (#52) avec l'infra up. SymptĂŽme :
kubectl apply -k k8s/base/
Error from server (Forbidden): namespaces "fastapi" is forbidden (patch des labels PSA)
# le reste du hardening (SA, NetworkPolicy, PDB, deployment) s'applique quand mĂȘme
Cause :
Le Namespace est cluster-scoped. Le user CI deploy est en least-privilege (edit dans le
ns fastapi uniquement) â il ne peut pas modifier l'objet Namespace. Or namespace.yaml
Ă©tait dans k8s/base â le deploy tentait de poser les labels PSA â Forbidden. Le bootstrap
crĂ©ait le ns SANS labels â PSA enforce:restricted (cru actif depuis #48) n'a JAMAIS
atteint EKS.
Fix :
Ownership du namespace déplacé vers le bootstrap Ansible (cluster-admin) : il crée le ns
AVEC les labels PSA restricted. namespace.yaml retiré de k8s/base (le deploy ne gÚre plus
le ns). local-kind garde son ns baseline en resource propre.
Preuve :
Sous enforce restricted : pod fastapi (C3 compliant) admis, rollout 1/1.
Pod nginx non conforme rejeté à l'admission (4 violations restricted).
â le contrĂŽle bloque rĂ©ellement un dĂ©ploiement non durci.
Leçon :
Une ressource cluster-scoped (Namespace + labels PSA) doit ĂȘtre ownĂ©e par le bootstrap
cluster-admin, pas par le deploy least-privilege. Un hardening "validé en kind" peut ne
jamais atteindre la prod si le chemin d'application diffÚre. Vérifier l'état réel
(kubectl get ns --show-labels), pas l'intention dans les manifests.
INC-046 â NetworkPolicy appliquĂ©es mais non enforced sur EKS (VPC CNI self-managed) (2026-05-24)
Contexte : Dans la foulée du fix PSA (INC-045), test négatif méthodique sur les NetworkPolicy. SymptÎme :
# depuis le pod fastapi, sortie sur le port 80 (hors allowlist egress) :
curl -m 5 http://example.com â 200 # devrait ĂȘtre bloquĂ© par default-deny + allow-fastapi
kubectl get netpol -n fastapi # les 3 policies sont pourtant bien présentes
Cause :
Une NetworkPolicy n'est enforced que par un CNI qui l'implémente. Le cluster tourne avec
le VPC CNI en self-managed (défaut EKS) : aucun addon EKS managé déclaré, confirmé deux
fois â aucun aws_eks_addon dans le Terraform ET `aws eks list-addons` â []. Il n'existe
nulle part oĂč poser enableNetworkPolicy, et le VPC CNI par dĂ©faut ne l'active pas.
MĂȘme classe de problĂšme que PSA (INC-045).
Statut (mitigation #55) :
resource "aws_eks_addon" "vpc_cni" {
cluster_name = ...
addon_name = "vpc-cni" # version >= 1.14
configuration_values = jsonencode({ enableNetworkPolicy = "true" })
}
# puis re-valider par test négatif. Non encore livré à la date de cet incident.
Leçon :
« Configuré n'est pas enforced » (2e occurrence aprÚs PSA). Un manifest NetworkPolicy
valide, appliqué et linté ne protÚge rien si le CNI ne l'enforce pas. La seule preuve
fiable est le test nĂ©gatif sur le cluster rĂ©el (tenter un flux qui doit ĂȘtre bloquĂ©),
jamais l'intention lue dans les manifests. Sur EKS : NetworkPolicy â VPC CNI en addon
managé + enableNetworkPolicy=true.
INC-047 â Edit ClusterRole ne couvre pas les CRDs ESO (deploy app 403) (2026-05-29)
Contexte : Premier run du deploy app end-to-end aprÚs le merge de MR-D (#33), qui pose SecretStore + ExternalSecret dans k8s/base/. Le bootstrap (cluster-admin) avait installé ESO sans souci, mais le deploy (least-privilege) ne peut rien appliquer sur les CRDs.
SymptĂŽme :
externalsecrets.external-secrets.io "fastapi-secrets" is forbidden:
User "arn:aws:iam::199167114788:user/ci/fastapi-eks-gitlab-ci" cannot get
resource "externalsecrets" in API group "external-secrets.io" in the
namespace "fastapi"
# idem secretstores
# 403 sur le get server-side du 3-way merge, AVANT l'apply
Cause :
Le user CI est mappé via EKS Access Entry à AmazonEKSEditPolicy scopée au ns
fastapi (terraform/ephemeral/main.tf:115-125). Cette policy AWS-managée
correspond au ClusterRole built-in "edit", qui est AGRĂGĂ via le sĂ©lecteur
rbac.authorization.k8s.io/aggregate-to-edit: "true". Par défaut, edit couvre
Deployments / Services / ConfigMaps mais PAS les CRDs de tiers. Le chart Helm
external-secrets v2.5.0 n'installe pas de ClusterRole avec ce label.
MĂȘme classe que INC-045 (PSA / namespace) : ressources posĂ©es sans Ă©tendre
les permissions du compte qui doit les appliquer.
Fix tenté (MR !116) :
# Premier essai : ClusterRole d'agrĂ©gation â structurellement correcte
# mais sans effet sur les users mappés via AWS Access Policy (voir Cause réelle).
- name: Create aggregation ClusterRole to extend "edit" with ESO CRDs
kubernetes.core.k8s:
state: present
definition:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: aggregate-eso-to-edit
labels:
rbac.authorization.k8s.io/aggregate-to-edit: "true"
rules:
- apiGroups: ["external-secrets.io"]
resources: ["externalsecrets", "secretstores"]
verbs: ["get","list","watch","create","update","patch","delete"]
Preuve (résultat réel) :
kubectl get clusterrole edit -o yaml | grep -A2 external-secrets
# â external-secrets.io APPARAĂT dans edit.rules (agrĂ©gation structurelle OK)
kubectl auth can-i create externalsecrets -n fastapi \
--as=arn:aws:iam::199167114788:user/ci/fastapi-eks-gitlab-ci
# â no (rĂ©ponse fiable, mal interprĂ©tĂ©e comme faux nĂ©gatif sur le moment)
# Re-run pipeline DEPLOY=true : MĂME 403 sur SecretStore + ExternalSecret
Cause réelle (pivot vers INC-048) :
AmazonEKSEditPolicy n'est PAS un binding vers le ClusterRole built-in "edit"
agrĂ©gĂ©. C'est une policy AWS-managed, snapshot statique â elle ne suit PAS
l'agrĂ©gation RBAC Kubernetes. Ătendre "edit" via aggregate-to-edit n'a aucun
effet sur les users mappés via EKS Access Entry + AWS Access Policy.
â Vrai fix : binding RBAC explicite sur l'ARN. Voir INC-048.
Leçon :
AmazonEKSEditPolicy (et les AWS Access Policies) sont des snapshots statiques.
Le can-i --as=<ARN> était fiable dÚs le début.
ConservĂ© en doc : rĂ©cit honnĂȘte du premier essai, matĂ©riau d'entretien.
INC-048 â AmazonEKSEditPolicy snapshot statique : seul un binding RBAC explicite fonctionne (2026-05-30)
Contexte : Suite directe d'INC-047. Le premier fix (ClusterRole d'agrégation MR !116) n'a pas résolu le 403. Pivot vers un binding RBAC explicite sur l'ARN du user CI. SymptÎme :
# MĂȘmes 403 qu'INC-047 aprĂšs re-run du bootstrap avec aggregate-eso-to-edit
externalsecrets.external-secrets.io "fastapi-secrets" is forbidden: ...
# kubectl auth can-i create externalsecrets -n fastapi --as=<ARN> â no
Cause :
AmazonEKSEditPolicy est une policy AWS-managed, snapshot statique des
permissions view+edit publiées par AWS. Elle NE SUIT PAS l'agrégation RBAC
Kubernetes. Ătendre le ClusterRole "edit" via aggregate-to-edit n'a aucun
effet sur les users mappés via EKS Access Entry + AWS Access Policy.
Pour étendre les permissions d'un tel user : binding RBAC K8s EXPLICITE
(Role/RoleBinding) bindant directement son ARN.
Fix :
# ansible/bootstrap.yml â aprĂšs crĂ©ation du namespace fastapi
- name: Create Role for CI user on ESO CRDs in fastapi namespace
kubernetes.core.k8s:
state: present
definition:
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: fastapi-deploy-eso
namespace: fastapi
rules:
- apiGroups: ["external-secrets.io"]
resources: ["externalsecrets", "secretstores"]
verbs: ["get","list","watch","create","update","patch","delete"]
- name: Create RoleBinding for CI user on ESO CRDs in fastapi namespace
kubernetes.core.k8s:
state: present
definition:
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: fastapi-deploy-eso
namespace: fastapi
subjects:
- kind: User
name: "arn:aws:iam::{{ aws_account.stdout }}:user/ci/{{ project_name }}-gitlab-ci"
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: Role
name: fastapi-deploy-eso
apiGroup: rbac.authorization.k8s.io
# Symétrique dans teardown.yml : Delete RoleBinding + Delete Role avant ESO uninstall.
# ClusterRole aggregate-eso-to-edit (INC-047) retirée du bootstrap et du teardown.
Preuve :
kubectl auth can-i create externalsecrets -n fastapi \
--as=arn:aws:iam::199167114788:user/ci/fastapi-eks-gitlab-ci
# â yes
# Pipeline app DEPLOY=true : stage deploy vert
kubectl -n fastapi get secretstore,externalsecret
# â SecretStore: Valid, ExternalSecret: SecretSynced
kubectl -n fastapi get pods
# â fastapi-* 1/1 Ready
curl -sS https://api.devopsyouss.com/healthz/ready
# â HTTP 200
Leçon :
AmazonEKSEditPolicy (et les AWS-managed Access Policies en général) sont des
snapshots statiques â ils ne suivent PAS l'agrĂ©gation RBAC K8s.
Pour étendre les permissions d'un user mappé via EKS Access Entry :
â Binding RBAC EXPLICITE sur l'ARN du user (Role/RoleBinding dans le ns cible)
â Jamais via aggregate-to-edit si le user est mappĂ© par une AWS Access Policy
Méta-leçon sur le diagnostic :
- INC-047 conservĂ© en doc : honnĂȘtetĂ© technique, matĂ©riau d'entretien
- Le can-i --as=<ARN> était fiable dÚs le début
- Quand un test d'autorisation retourne no, c'est no â ne pas le rationaliser
comme faux négatif sans preuve
Recommandations Pipeline Infra
1. entrypoint: [""] obligatoire pour images non-standard
(aws-cli, terraform, kaniko, trivy...)
2. CI_PIPELINE_SOURCE :
push â git push
merge_request_event â MR
web â Run Pipeline UI
schedule â Schedule
3. spec: inputs: â sĂ©parateur --- obligatoire
4. Variables projet écrasent variables fichier
â utiliser before_script export pour forcer
5. Séparer pipeline app et infra :
.gitlab-ci.yml â app (push/MR)
.gitlab-ci-infra.yml â infra (web/schedule)
6. IAM Least Privilege :
gitlab_ci â ECR push uniquement
gitlab_ci_infra â Terraform + infra status
7. terraform.tfvars â gitignored â TF_VAR_ en CI
Références
- Sprint 3 â Pipeline CI/CD + Infra
- Date : 2026-05-18
Recommandations Générales
Pipeline CI/CD
1. Variables CI : Masked (pas Protected) pour les branches feature
2. Kaniko plutĂŽt que Docker-in-Docker sur Alpine
3. Pattern candidate â scan â promote (jamais push sans scan)
4. --cache=false aprÚs mise à jour de dépendances
5. Trivy image scan APRĂS build, AVANT push final
Terraform
1. Ne jamais interrompre apply/destroy (tmux obligatoire)
2. Séparer ressources persistantes et éphémÚres
3. terraform import avant toute restructuration en production
4. State lock : vérifier .tflock dans S3 si blocage
5. create_nat_gateway=true obligatoire avec EKS
Kubernetes / EKS
1. Supprimer ressources K8s AVANT terraform destroy
(ELB, Ingress, Gateway libĂšrent les ENIs)
2. ENIs orphelins : attendre 10-15 min ou chercher par VPC
3. EKS nodes : NAT Gateway obligatoire pour accĂšs internet
4. HPA : metrics-server obligatoire (pas installé par défaut sur EKS)
5. Alembic migrations : init container recommandé
Sécurité
1. DĂ©pendances abandonnĂ©es â migrer (passlib â bcrypt, python-jose â PyJWT)
2. Auditer réguliÚrement les dépendances (pip-audit, trivy)
3. Multi-stage build pour réduire CVEs image de base
4. .trivyignore : toujours commenter avec justification + date
5. Endpoints publics vs protégés : vérification systématique
Coûts AWS
1. ECR + IAM dans persistent/ â jamais dĂ©truits
2. EKS + RDS + VPC dans ephemeral/ â destroy chaque soir
3. aws-stop.sh : nettoyer ELB avant destroy (sinon blocage ENIs)
4. NAT Gateway : ~0.045$/h â destroy quand pas utilisĂ©
5. EKS Control Plane : ~0.10$/h â la ressource la plus coĂ»teuse
Références
- Sprint 0 : INC-001, INC-002, INC-003
- Sprint 1 : INC-004, INC-005, INC-006, INC-007
- Sprint 2 : INC-008, INC-009, INC-010, INC-011, INC-012
- Sprint 3 : INC-013, INC-014, INC-015, INC-016, INC-017
- Sprint 3 (suite) : INC-018, INC-019, INC-020, INC-021, INC-022, INC-023, INC-024, INC-025, INC-026
- Sprint 3 (2026-05-18) : INC-027, INC-028, INC-029, INC-030
- Sprint 3 (2026-05-19) : INC-031, INC-032, INC-033, INC-034, INC-035, INC-036, INC-037, INC-038
- Sprint 3 (2026-05-23) : INC-039, INC-040, INC-041, INC-042, INC-043, INC-044, INC-045, INC-046
- Sprint 3 (2026-05-29) : INC-047
- Sprint 3 (2026-05-30) : INC-048
- Date : 2026-05-18