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)
Budget filtre support etendu : alerte des le 1er centime facture sur le type d'usage
EUW3-AmazonEKS-Hours:extendedSupport (INC-072, #195). Ni le budget global ni la
detection d'anomalie ne voyaient ce cas : le premier mesure un montant, la seconde
est aveugle a un poste dont le profil est deja irregulier (EKS oscille entre 0 et
22 $/mois). Ici c'est l'EXISTENCE de la ligne qui est le signal, pas son montant.
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.35 (#186), 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) â Masked, NON protegee (assume, #184)
AWS_SECRET_ACCESS_KEY â gitlab_ci (ECR push) â Masked, NON protegee (assume, #184)
AWS_DEFAULT_REGION â eu-west-3 (deja protegee, et les pipelines de MR passent :
kaniko et trivy derivent la region du nom d'hote du registre)
ECR_REGISTRY â 199167114788.dkr.ecr.eu-west-3.amazonaws.com
ECR_REPOSITORY â fastapi-eks/fastapi
INFRA PIPELINE :
AWS_INFRA_ACCESS_KEY_ID â gitlab_ci_infra â Masked + PROTECTED (#184)
AWS_INFRA_SECRET_ACCESS_KEY â gitlab_ci_infra â Masked + PROTECTED (#184)
TF_VAR_db_password â <REDACTED - stored in GitLab CI as Masked variable> â PROTECTED (#184)
CLOUDFLARE_API_TOKEN â token API Cloudflare (Masked + Protected, Zone > Zone Read + DNS Edit)
SUPPRIMEE (#184) :
SECRET_KEY â n'etait lue que par run-tests. Valeur de test desormais
en clair dans le bloc variables: de .gitlab-ci.yml. La vraie
cle vit dans Secrets Manager et n'a jamais transite par la CI.
Protegee != masquee. Masquee cache la valeur dans les logs. Protegee decide a QUI elle est servie : uniquement aux branches et tags proteges (
develop,main), jamais a une branche de travail ni a un pipeline de merge request. Les deux repondent a des questions differentes (INC-001, ADR 032).
AWS_ACCESS_KEY_IDreste non protegee parce quebuild-candidatepousse une image pendant le pipeline de MR. La dedoubler en cle de MR + cle de deploiement a ete envisage puis ecarte : les deux policies auraient ete identiques (ADR 032, decision 2).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_KEYĂ©tait devenue inutile cĂŽtĂ© deploy. Elle est supprimĂ©e des settings GitLab depuis #184, sa seule valeur de test vivant dĂ©sormais en clair dans.gitlab-ci.yml.
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), pour faire évoluer les routes (canary,
blueprints) sans re-bootstrap. Depuis le GitOps, c'est ArgoCD qui l'applique : le binding
RBAC fastapi-deploy-httproute du user CI a été retiré (#223, audit EK-10).
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 sur les DEUX depots nommes : fastapi-eks/fastapi, fastapi-eks/frontend
(plus de joker fastapi-eks/*, qui couvrait tout depot cree demain â #184)
â ecr:GetAuthorizationToken, BatchCheck, InitiateUpload, PutImage...
â eks:DescribeCluster SUPPRIME (#184) : plus aucun job de .gitlab-ci.yml ne parle
au cluster depuis #139, le write-back n'ecrit que dans Git
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)
Durcissement Terraform (audit, #189)¶
Quatre réglages, tous classés « trÚs faible » en effort, aucun ne redéploie le cluster. Actions 4, 13, 14 et 17 du plan de remédiation.
| # | RĂ©glage | OĂč | Constat |
|---|---|---|---|
| 4 | Transport chiffré obligatoire sur le bucket de state | backend/ |
AW-5 |
| 13 | VPC Flow Logs vers CloudWatch | modules/vpc/ |
EK-5 |
| 14 | Ports d'administration refusés au niveau NACL | modules/vpc/ |
CIS 5.2 / 5.3 |
| 17 | Chiffrement EBS par défaut sur la région | persistent/ |
EK-2 |
Action 4 â HTTPS obligatoire sur le bucket de state¶
Le chiffrement au repos Ă©tait dĂ©jĂ posĂ© (SSE-S3). Il ne dit rien du transport : sans politique, une requĂȘte en HTTP simple Ă©tait servie et le state transitait en clair.
Une politique de bucket refuse dĂ©sormais toute requĂȘte dont aws:SecureTransport est faux.
Ce Deny ne peut pas verrouiller le bucket : la CLI et les SDK AWS parlent en HTTPS par défaut,
donc la condition n'est jamais remplie en usage normal. C'est la seule raison pour laquelle un
Deny sur Principal = "*" est acceptable ici â et il ne rend rien public, il retire un
accĂšs au lieu d'en accorder un.
Action 13 â VPC Flow Logs¶
Aucune trace n'existait sur le trafic réseau interne au VPC, ni autorisé ni bloqué.
traffic_type = "ALL" et non "REJECT" : le trafic accepté répond à « qui a parlé à qui », que
le trafic rejeté ne dit pas. Le rejeté seul montre les échecs, jamais les flux légitimes.
â ïž Ce que les flow logs ne disent pas. Ils enregistrent des en-tĂȘtes â IP, port, protocole, verdict â pas le contenu. Ils ne remplacent ni Hubble, qui voit l'identitĂ© Cilium derriĂšre l'adresse, ni Tempo, qui voit la requĂȘte applicative. Trois outils, trois altitudes.
RĂ©tention Ă 90 jours, la mĂȘme que le journal du control plane et que l'expiration du bucket CloudTrail.
Action 14 â NACL sur les ports d'administration¶
Un NACL filtre Ă la frontiĂšre du subnet, pas au niveau de la ressource. Celui-ci est donc associĂ© aux quatre subnets : publics (NAT Gateway, Network Load Balancer) et privĂ©s (nĆuds EKS, RDS).
ingress
100 DENY tcp 22 depuis 0.0.0.0/0
110 DENY tcp 3389 depuis 0.0.0.0/0
200 ALLOW tout depuis 0.0.0.0/0
Les rÚgles sont évaluées par numéro croissant et la premiÚre qui correspond gagne.
â ïž Un NACL est stateless, contrairement Ă un security group. Il n'a aucune mĂ©moire des connexions : le trafic de retour doit ĂȘtre autorisĂ© explicitement, sur les ports Ă©phĂ©mĂšres (1024-65535). C'est la façon habituelle de couper tout le rĂ©seau d'un VPC en croyant le durcir.
D'oĂč la forme retenue : deux deny Ă©troits, puis un allow gĂ©nĂ©ral. Une alternative « explicite »
existe â lister 80, 443, les ports Ă©phĂ©mĂšres et le trafic interne au lieu du allow gĂ©nĂ©ral â mais
chaque ligne oubliée casse un flux, et cela ne se découvre qu'au montage, sur un NACL qui jette les
paquets sans message.
Ce que ça change vraiment, sans enjoliver : les nĆuds sont en subnet privĂ©, sans IP publique, donc rien ne pouvait les joindre sur 22 de toute façon. La valeur est la dĂ©fense en profondeur, le jour oĂč un security group est Ă©largi par erreur. C'est une case de conformitĂ© honnĂȘte, pas une transformation de la posture.
Action 17 â Chiffrement EBS par dĂ©faut¶
Réglage de région, pas de ressource : il s'applique à tout volume EBS créé ensuite dans
eu-west-3. Sans lui, chaque disque de nĆud EKS naissait en clair.
Posé dans persistent/ et non ephemeral/ : le réglage doit valoir pour le prochain cluster.
Dans ephemeral/, le destroy du soir le retirerait et le montage suivant recréerait des volumes
en clair jusqu'Ă l'apply.
Il ne chiffre pas les volumes dĂ©jĂ créés. Les nĆuds Ă©tant recréés Ă chaque montage, la question ne se pose pas ici.
VĂ©rifications¶
# Action 17
aws ec2 get-ebs-encryption-by-default
# Action 13 â aprĂšs le prochain montage seulement
aws logs describe-log-groups --log-group-name-prefix /aws/vpc/fastapi-eks
Action 4 â un test nĂ©gatif et un test positif, pas un curl.
â ïž Un curl anonyme en http:// sur ce bucket renvoie 403, mais il le renvoyait dĂ©jĂ avant
la politique : une requĂȘte sans identitĂ© sur un bucket privĂ© est refusĂ©e de toute façon. Les
deux causes produisent le mĂȘme code, le test ne les sĂ©pare pas. MesurĂ© le 2026-09-01, et Ă©cartĂ©
pour cette raison.
Le contrĂŽle doit ĂȘtre jouĂ© avec des identifiants valides, pour que le transport soit le seul facteur qui change :
# Positif â doit RĂUSSIR (la CLI parle HTTPS par dĂ©faut)
aws s3 ls s3://<bucket>/
# NĂ©gatif â doit ĂCHOUER en AccessDenied
aws s3 ls s3://<bucket>/ --endpoint-url http://s3.eu-west-3.amazonaws.com
MĂȘme identitĂ©, mĂȘme bucket, mĂȘme action : si le premier passe et le second est refusĂ©, la
politique est la seule explication possible. C'est la forme qu'a déjà la preuve du MFA
(aws-setup.md, section 9).
Mesure du 2026-09-01. Le positif rend le contenu du bucket (PRE fastapi-eks/). Le négatif :
An error occurred (AccessDenied) when calling the ListObjectsV2 operation:
User: arn:aws:iam::<compte>:user/iamadmin is not authorized to perform:
s3:ListBucket on resource: "arn:aws:s3:::yk-devops-terraform-state"
with an explicit deny in a resource-based policy
with an explicit deny in a resource-based policy : le refus est nommé par sa cause, et cette
cause est la politique de bucket. Ce n'est pas une absence de droit, c'est un refus dĂ©libĂ©rĂ© â la
distinction que le curl anonyme était incapable de faire.
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