RĂ©sumĂ© Infra EKS â fastapi-eks-project
Contexte projet
Portfolio DevOps â FastAPI + PostgreSQL sur AWS EKS.
GitLab : gitlab.com/yk-devops/fastapi-eks-project
Dev Container : VS Code avec AWS CLI, Terraform, kubectl, Helm.
Stack technique
App : FastAPI + PostgreSQL + Alembic + PyJWT + bcrypt
Cloud : AWS eu-west-3 (Paris)
IaC : Terraform 1.15
Container : Docker â ECR â EKS
CI/CD : GitLab CI/CD (runner self-hosted Ubuntu 22.04 ESXi)
Routing : Envoy Gateway (Gateway API)
TLS : cert-manager + Let's Encrypt (DNS-01 via Cloudflare)
DNS : ExternalDNS (provider Cloudflare, source gateway-httproute, policy sync)
Domaine : devopsyouss.com (Cloudflare)
Architecture AWS
Internet (HTTPS â api.devopsyouss.com)
â
NLB (créé par Envoy Gateway â ports 80 + 443, #70 ADR 017)
â
Envoy Proxy Pod (TLS terminĂ© ici â cert rempli par cert-manager + Let's Encrypt)
â
Service ClusterIP (fastapi)
â
FastAPI Pod (namespace: fastapi)
â
RDS PostgreSQL (subnet privé)
Terraform â 2 workspaces sĂ©parĂ©s
persistent/ (jamais détruit)
ECR : fastapi-eks/fastapi
IAM : gitlab_ci (ECR push) + gitlab_ci_infra (terraform)
Secrets Manager : fastapi-eks/app (DB_PASSWORD + SECRET_KEY) â consommĂ© par ESO (#33)
State S3 : yk-devops-terraform-state/fastapi-eks/persistent/
ephemeral/ (apply matin, destroy soir)
VPC : 10.0.0.0/16, 2 subnets publics + 2 privés
NAT Gateway : obligatoire pour nodes EKS
EKS : fastapi-eks-cluster, version 1.32, node t3.medium
RDS : PostgreSQL 16, db.t3.micro, subnet privé
IRSA ESO (#33) : provider OIDC du cluster + rĂŽle fastapi-eks-eso-irsa (lecture du secret fastapi-eks/app)
State S3 : yk-devops-terraform-state/fastapi-eks/ephemeral/
Scripts quotidiens
./aws-start.sh # matin â terraform apply + kubectl config
./aws-stop.sh # soir â kubectl delete gateway + terraform destroy
Pipeline CI/CD GitLab
.gitlab-ci.yml (app pipeline)
Stages : test â security â build â scan-image â promote â deploy (write-back GitOps)
test : pytest + PostgreSQL service
security : SAST, Secret Detection, Trivy fs, tfsec, kube-linter
build : Kaniko build le Dockerfile multi-stage (python:3.12-slim pinnĂ© par digest, deps prod only) â ECR (tag: candidate-SHA-PIPELINEID, unique car repo IMMUTABLE). Cf ADR 011
scan-image : Trivy image scan depuis ECR (sur le candidate)
promote : retag par digest aws-cli (batch-get-image + put-image â tag SHA). Pas de rebuild, pas de latest. Image dĂ©ployĂ©e == image scannĂ©e (INC-050)
update-image-tag : GitOps write-back (#72, ADR 013 ; recible env dev #139). kustomize edit set image dans k8s/overlays/{fastapi,frontend}/dev/kustomization.yaml + git commit [skip ci] + push sur develop. ArgoCD reconcilie l'env dev (pull-based). staging/prod n'avancent PAS ici. develop only.
Déclenché sur : push, MR, develop, main
Déploiement : pull-based via ArgoCD (write-back automatique sur develop, pas d'input manuel)
Promotion multi-env (dev â staging â prod, #139 ADR 029 D2)
Le tag d'image vit par overlay (k8s/overlays/<workload>/<env>/kustomization.yaml,
champ images[].newTag), plus dans base. Chemin de promotion :
- dev = automatique. Ă chaque merge sur
develop,update-image-tagbumpeoverlays/{fastapi,frontend}/dev. ArgoCD déploie dev seul. Un merge ne touche QUE dev. - staging puis prod = MR de promotion. Promouvoir = recopier le
newTagvalidĂ© de l'overlay amont vers l'aval (devâstaging, puisstagingâprod), sur une branche, puis MR versdevelop. Rien d'autre ne change (mĂȘme image, dĂ©jĂ scannĂ©e).
# Exemple : promouvoir le back de dev vers staging
TAG=$(grep newTag k8s/overlays/fastapi/dev/kustomization.yaml | awk '{print $2}')
cd k8s/overlays/fastapi/staging && kustomize edit set image \
fastapi=199167114788.dkr.ecr.eu-west-3.amazonaws.com/fastapi-eks/fastapi:$TAG
# puis: branche + MR vers develop (staging->prod idem, review CODEOWNERS requise)
Gate prod : .gitlab/CODEOWNERS exige 1 approbation sur toute MR modifiant
overlays/*/prod/. Effectif seulement si develop (protégée) a "Require approval
from code owners" activé (réglage projet GitLab). E2E staging vert = condition It.4 (#140).
.gitlab-ci-infra.yml (infra pipeline)
Stages : infra â bootstrap â teardown â destroy
infra-status : aws eks list-clusters, rds, ecr...
infra-start : terraform apply + kubectl get nodes
bootstrap : Ansible â Envoy Gateway + metrics-server + cert-manager + ClusterIssuers + External Secrets Operator + ExternalDNS (Cloudflare, gateway-httproute) + ArgoCD v3.4.3 (chart 9.5.20, app-of-apps root Application) + exposition (GatewayClass + Gateway + Certificate) + RBAC user CI (ESO + HTTPRoute) (action=start)
infra-stop : aws-stop.sh (cleanup K8s + terraform destroy)
Déclenché sur : Run Pipeline (web) + Schedule 20h
Inputs : action = status | start | stop
Sérialisation : les jobs mutants (infra-start, bootstrap, infra-stop,
teardown) partagent resource_group: infra â un pipeline
start et le teardown programmé ne tournent jamais en
parallÚle (évite la corruption mid-bootstrap). #132
Variables GitLab CI/CD
APP PIPELINE :
AWS_ACCESS_KEY_ID â gitlab_ci (ECR push)
AWS_SECRET_ACCESS_KEY â gitlab_ci (ECR push)
AWS_DEFAULT_REGION â eu-west-3
ECR_REGISTRY â 199167114788.dkr.ecr.eu-west-3.amazonaws.com
ECR_REPOSITORY â fastapi-eks/fastapi
SECRET_KEY â JWT secret
INFRA PIPELINE :
AWS_INFRA_ACCESS_KEY_ID â gitlab_ci_infra
AWS_INFRA_SECRET_ACCESS_KEY â gitlab_ci_infra
TF_VAR_db_password â <REDACTED - stored in GitLab CI as Masked variable>
CLOUDFLARE_API_TOKEN â token API Cloudflare (Masked + Protected, Zone > Zone Read + DNS Edit)
Note ESO (#33) :
TF_VAR_db_passwordalimente aussi le secret Secrets Manager persistent (mĂȘme valeur que RDS).SECRET_KEYest gĂ©nĂ©rĂ© par Terraform (random_password) et stockĂ© dans Secrets Manager. Depuis MR-D, le job deploy ne crĂ©e plus le Secret (ESO le synchronise) : la variable CISECRET_KEYest devenue inutile cĂŽtĂ© deploy et peut ĂȘtre supprimĂ©e des settings GitLab.
Kubernetes manifests (k8s/)
k8s/base/ (réconcilié par ArgoCD via kustomize, #72 ; le tag image est commité dans kustomization.yaml par le write-back CI)
âââ serviceaccount.yaml â SA dĂ©diĂ© fastapi, automountServiceAccountToken false
âââ configmap.yaml â DB_HOSTNAME, DB_PORT, DB_NAME...
âââ deployment.yaml â FastAPI pods, probes /healthz, resources, securityContext
âââ service.yaml â ClusterIP port 80â8080
âââ hpa.yaml â min:1, max:5, cpu:70%, mem:80%
âââ pdb.yaml â PodDisruptionBudget minAvailable 1
âââ networkpolicy-default-deny.yaml â deny-all ingress + egress
âââ networkpolicy-fastapi.yaml â ingress 8080 ; egress 5432 (RDS), 443 (AWS), 53 (DNS)
âââ networkpolicy-postgres.yaml â rĂšgles pour le pod postgres (prĂ©sent en local-kind)
âââ secretstore.yaml â ESO SecretStore (AWS Secrets Manager, auth IRSA contrĂŽleur)
âââ externalsecret.yaml â ESO ExternalSecret â Secret fastapi-secrets (DB_PASSWORD, SECRET_KEY)
âââ httproute.yaml â Gateway API HTTPRoute (app : route api.devopsyouss.com, ownĂ© par le deploy CI, #60 ; rule /metrics â 404 directResponse, #81)
âââ httproutefilter-deny-metrics.yaml â HTTPRouteFilter Envoy GW : directResponse 404 sur /metrics (coupe l'expo publique, scrape interne intact, #81 ADR 018)
k8s/overlays/ (multi-env, #134 ADR 029 D1 â un overlay par workload et par env)
âââ fastapi/{dev,staging,prod}/kustomization.yaml â resources:[../../../base] + patchs : namespace fastapi-<env>, host <env>.api.devopsyouss.com, bornes HPA, parentRef Gateway partagĂ© (ns fastapi)
âââ frontend/{dev,staging,prod}/kustomization.yaml â resources:[../../../frontend] + patchs : namespace frontend-<env>, host <env>.app.devopsyouss.com, replicas
(instanciĂ©s par les ApplicationSet fastapi/frontend ; tag image encore hĂ©ritĂ© du base, reciblage write-back â overlays/dev = #139)
Le namespace fastapi héberge désormais le Gateway partagé + le RBAC user CI (ns
plateforme, un seul NLB pour les 3 envs) ; les workloads applicatifs vivent dans
fastapi-<env>/frontend-<env>. Ces namespaces (+ labels PSA enforce restricted) ne
sont PAS dans les overlays :
ils sont créés par le bootstrap Ansible (cluster-admin). Le user CI deploy en
least-privilege ne peut pas patcher un objet cluster-scoped (INC-045).
â
Les NetworkPolicy sont enforced sur EKS depuis #55 : vpc-cni déclaré en addon
EKS managé avec enableNetworkPolicy=true (résout INC-046). Validé par test négatif
(egress port 80 â timeout) + test positif (/healthz/ready â 200). Voir validation-runbook.md.
â
L'exposition (GatewayClass + Gateway + Certificate) est posée par le bootstrap
Ansible, pas par le deploy CI (#60, ADR 010). Raison : GatewayClass cluster-scoped +
CRDs Gateway API / cert-manager non couverts par "edit" (mĂȘmes classes qu'INC-045/047/048).
Le HTTPRoute reste owné par l'app (k8s/base) avec son binding RBAC explicite
fastapi-deploy-httproute, pour faire évoluer les routes (canary, blueprints) sans re-bootstrap.
k8s/platform/ (exposition + GitOps â appliquĂ©s par le bootstrap Ansible, cluster-admin)
âââ gateway.yaml â EnvoyProxy nlb-config (annotation NLB, #70 ADR 017) + GatewayClass envoy (parametersRef) + Gateway fastapi-gateway (listeners 80/443, TLS Terminate, #60 ; allowedRoutes from:All pour routes cross-ns, #76)
âââ certificate.yaml â Certificate wildcard *.devopsyouss.com (cert-manager DNS-01, secret wildcard-devopsyouss-tls, #76)
âââ argocd-apps/ â App-of-apps ArgoCD (#72, ADR 013)
â âââ root-app.yaml â Application "apps" (root, surveille argocd-apps/, auto-sync)
â âââ fastapi-appset.yaml â ApplicationSet "fastapi" (gĂ©nĂ©rateur liste dev/staging/prod â 3 Applications sur k8s/overlays/fastapi/<env>, #134 ADR 029)
â âââ frontend-appset.yaml â ApplicationSet "frontend" (idem â k8s/overlays/frontend/<env>, #134)
â âââ kube-prometheus-stack.yaml â Application Helm multi-source, ServerSideApply, sync-wave 0 (#74, ADR 014)
â âââ monitoring.yaml â Application kustomize (ServiceMonitor + dashboards + datasource Loki), sync-wave 1 (#74/#75)
â âââ loki.yaml â Application Helm Loki SingleBinary, ns logging, sync-wave 1 (#75, ADR 015)
â âââ alloy.yaml â Application Helm Grafana Alloy DaemonSet, ns logging, sync-wave 2 (#75, ADR 015)
âââ monitoring/ â ObservabilitĂ© mĂ©triques as-code (ns monitoring, #74, ADR 014)
â âââ values.yaml â values kube-prometheus-stack (rĂ©tention 24h, no persistence, control-plane off)
â âââ servicemonitor-fastapi.yaml â scrape /metrics du Service fastapi (interne ClusterIP, #81)
â âââ datasource-loki.yaml â datasource Loki pour Grafana (sidecar datasources, #75)
â âââ secretstore.yaml â ESO SecretStore ns monitoring (#76)
â âââ externalsecret-grafana.yaml â Secret K8s grafana-admin depuis Secrets Manager (#76)
â âââ httproute-grafana.yaml â expose grafana.devopsyouss.com via le Gateway partagĂ© (#76)
â âââ externalsecret-alertmanager-slack.yaml â Secret K8s alertmanager-slack (webhook Slack) depuis Secrets Manager (#77)
â âââ prometheusrules-fastapi.yaml â 4 rĂšgles d'alerte FastAPI (target down, crashloop, 5xx, latence p99) (#77)
â âââ kustomization.yaml â ServiceMonitor + datasource + ESO + HTTPRoute + dashboards + alerting
â âââ dashboards/fastapi-red.json â dashboard FastAPI RED, provisionnĂ© par le sidecar Grafana
âââ logging/ â Logs as-code (ns logging, #75, ADR 015)
âââ loki-values.yaml â values Loki SingleBinary (filesystem, rĂ©tention 24h, no cache/gateway/minio)
âââ alloy-values.yaml â values Alloy (DaemonSet, tail /var/log/pods, push vers Loki, labels low-cardinality)
k8s/overlays/
âââ ingress-alb/ â ALB Ingress Controller (option A, Ă©cartĂ©e #34)
âââ local-kind/ â lab local (postgres en pod + ns baseline), dev only (#46)
â
L'observabilité (#74, ADR 014) est déployée en GitOps par ArgoCD, pas par le
bootstrap : kube-prometheus-stack (Prometheus Operator + Grafana + Alertmanager +
node-exporter + kube-state-metrics) via une Application Helm multi-source (chart
upstream + values dans le repo). Le ServiceMonitor scrape /metrics en interne
(Service ClusterIP, jamais public, #81). Dashboards as-code (FastAPI RED + mixins
cluster du chart). Rétention 24h sans PVC (cluster éphémÚre). Grafana en
port-forward (exposition publique = #76). Pas d'ELB créé : teardown via cascade
du root-app, rien d'anti-INC-016 Ă ajouter.
â
Les logs (#75, ADR 015) sont centralisés via Loki (SingleBinary, filesystem,
rétention 24h, ns logging) + Grafana Alloy (DaemonSet, tail /var/log/pods, push
vers Loki, labels low-cardinality namespace/pod/container). Alloy remplace
Promtail (déprécié). Datasource Loki déclarée cÎté Grafana via le sidecar
datasources (ConfigMap labellisĂ©e dans ns monitoring). CorrĂ©lation mĂ©triques â
logs depuis le dashboard RED via Grafana Explore. Déployé en GitOps (2
Applications ArgoCD), pas par le bootstrap.
â
Mot de passe admin Grafana géré via ESO (#76) : secret dédié
fastapi-eks/grafana dans Secrets Manager (généré par Terraform), lu par un
ExternalSecret (ns monitoring) â Secret K8s grafana-admin, consommĂ© par
grafana.admin.existingSecret. Remplace le mot de passe par défaut du chart
(prérequis avant l'exposition publique). Le rÎle IRSA de l'ESO est étendu en
least-privilege au seul ARN supplémentaire.
â
Grafana exposé sur https://grafana.devopsyouss.com (#76) en réutilisant TOUTE
la stack d'exposition du Sprint 3 (preuve de réutilisabilité, ADR 010) : une
HTTPRoute (ns monitoring) attachée au Gateway partagé via allowedRoutes.from:All
(routage cross-namespace), un Certificate wildcard *.devopsyouss.com
(cert-manager DNS-01 Cloudflare, couvre api + grafana + futurs sous-domaines),
ExternalDNS qui crée le CNAME automatiquement. La route est owned par les
manifests (ADR 010), le Gateway/Certificate par le bootstrap. Teardown :
HTTPRoute supprimée avant l'uninstall d'Envoy (anti-INC-016).
â
Alerting (#77, ADR 016) : 4 PrometheusRules FastAPI (target down, CrashLoop,
taux de 5xx, latence p99) découvertes par Prometheus, routées par Alertmanager
vers Slack selon la sévérité (critical répété plus souvent, inhibition
critical â warning, Watchdog vers un receiver null pour ne pas spammer). L'URL
du webhook Slack n'est jamais en clair dans Git : secret dédié
fastapi-eks/alertmanager-slack (Secrets Manager) â ESO â Secret K8s
alertmanager-slack monté dans Alertmanager, lu via api_url_file. RÎle IRSA de
l'ESO étendu en least-privilege au seul ARN supplémentaire (3e secret). Tout en
GitOps (rules + values), zéro modif bootstrap.
IAM â Least Privilege
gitlab_ci :
â ECR push uniquement (app pipeline)
â ecr:GetAuthorizationToken, BatchCheck, InitiateUpload...
gitlab_ci_infra :
â terraform_ci policy (ec2:*, eks:*, rds:*, iam:limited + OIDC provider + secretsmanager read pour IRSA #33)
â infra_status policy (read-only EKS, EC2, RDS, ECR, ELB)
Coûts AWS (ephemeral 4h/jour)
NAT Gateway : 0.045$/h Ă 4 = 0.18$/jour
EKS Control : 0.10$/h Ă 4 = 0.40$/jour
t3.medium : 0.047$/h Ă 4 = 0.19$/jour
RDS micro : 0.02$/h Ă 4 = 0.08$/jour
Total : ~0.85$/jour â ~17$/mois (20 jours)
Persistent (toujours) :
ECR : ~0.01$/mois
IAM : gratuit
Ătat Sprint 3
â
Terraform persistent/ephemeral
â
Kubernetes manifests (base + overlays)
â
Envoy Gateway opérationnel
â
FastAPI + RDS validé sur EKS
â
Pipeline build/scan/promote
â
Pipeline infra start/stop/status
â
IAM Least Privilege (2 users)
â
Schedule nightly destroy
â
Stage deploy CI/CD validé end-to-end via kustomize (#52)
â
PSA enforce restricted effectif sur EKS (#48, INC-045)
â
NetworkPolicy enforced via VPC CNI managé (#55, INC-046)
â ALB Ingress Controller (#34) â Ă©cartĂ©, direction gateway-api actĂ©e pour portabilitĂ© multi-cloud
â
HTTPS public api.devopsyouss.com â cert-manager + Let's Encrypt DNS-01 Cloudflare (#28)
đ§ ExternalDNS Cloudflare (#66) â code livrĂ©, validation au prochain aws-start (CNAME api.devopsyouss.com automatique)
đ§ Secrets Manager + ESO (#33) â MR-AâD livrĂ©es (secret persistent fastapi-eks/app, provider OIDC + rĂŽle IRSA ephemeral, install ESO via bootstrap, manifests SecretStore/ExternalSecret, deploy sans Secret impĂ©ratif). Sync ESO de fastapi-secrets validĂ©e live le 2026-05-29. Reste : valider le deploy app end-to-end via le pipeline develop, puis fermer #33
Commandes utiles
# AWS
awslogin # ouvrir session aws-vault
aws sts get-caller-identity # vérifier l'identité
aws eks list-clusters # lister clusters EKS
aws ecr list-images --repository-name fastapi-eks/fastapi
# Kubernetes
kubectl get nodes
kubectl get all -n fastapi
kubectl logs -n fastapi -l app=fastapi -f
kubectl port-forward -n fastapi deployment/fastapi 8080:8080
# Terraform
cd terraform/ephemeral && terraform output # voir outputs
cd terraform/persistent && terraform state list
# Pipeline infra
./aws-start.sh # démarrer l'infra
./aws-stop.sh # arrĂȘter l'infra