AWS : EKS
EKS est Kubernetes hébergé sur AWS. Le plan de contrôle (etcd, kube-apiserver, kube-controller-manager, kube-scheduler) est géré par AWS dans un compte isolé, non accessible. Ce qui reste à la charge de l'utilisateur : les nœuds workers, le réseau, le stockage, et les intégrations IAM.
EKS Auto Mode, disponible depuis fin 2024, étend ce périmètre géré. En plus du control plane, AWS prend en charge le provisionnement des nœuds (via Karpenter intégré), le driver de stockage EBS, et le contrôleur de load balancing. Pour quelqu'un qui vient de kubeadm on-premise, c'est l'équivalent d'avoir Karpenter, le EBS CSI Driver et le AWS Load Balancer Controller préconfigurés et maintenus par AWS.
Ce qu'AWS gère en Auto Mode
AWS gère Tu gères
──────────────────────────────── ─────────────────────────────
Control plane (API server, etcd...) Nœuds workers (via node pools)
Provisionnement des nœuds (Karpenter) NodeClass (critères d'instance)
OS des nœuds (Bottlerocket) Workloads applicatifs
EBS CSI Driver IRSA (credentials IAM pour les pods)
AWS Load Balancer Controller Ingress / Services
Certificats internes du cluster Logging, monitoring
Le control plane est facturé 0,10 $/heure (~73 $/mois). Les nœuds EC2 provisionnés par Karpenter sont facturés séparément selon leur type et leur durée de vie réelle.
Prérequis : deux rôles IAM distincts
EKS met en jeu deux entités qui ont besoin d'appeler l'API AWS, mais qui ne sont pas la même entité et n'ont pas les mêmes besoins.
Le service EKS doit pouvoir agir dans le compte AWS : créer des ENIs dans le VPC, modifier des security groups, gérer les ressources réseau des nœuds. C'est eks.amazonaws.com qui fait ces appels, pas l'utilisateur. Il faut lui déléguer des permissions via un rôle qu'il peut assumer.
AmazonEKSAutoClusterRole
Trusted entity : eks.amazonaws.com
Policies : AmazonEKSClusterPolicy + droits Auto Mode (gestion Karpenter, EBS, LB)
Les nœuds EC2 ont eux aussi besoin d'appeler l'API AWS, pour plusieurs raisons : le kubelet doit s'authentifier auprès du kube-apiserver, le VPC CNI doit allouer des IPs secondaires sur les ENIs du nœud, et containerd doit puller des images depuis ECR.
AmazonEKSAutoNodeRole
Trusted entity : ec2.amazonaws.com
Policies : AmazonEKSWorkerNodeMinimalPolicy + droits VPC CNI + ECR read
En Auto Mode, ces deux rôles peuvent être créés automatiquement par la console EKS lors de la création du cluster.
Créer le cluster (console AWS)
EKS → Create cluster
La console propose deux chemins. Quick configuration active automatiquement l'Auto Mode avec des valeurs par défaut. Custom configuration permet de contrôler chaque paramètre.
Dans les deux cas, les paramètres clés :
| Paramètre | Valeur |
|---|---|
| Kubernetes version | 1.36 (dernière disponible) |
| Region | eu-west-3 |
| Cluster IAM role | AmazonEKSAutoClusterRole |
| EKS Auto Mode | Activé |
| VPC | VPC existant avec 3 subnets (une par AZ) |
| Endpoint access | Public and private |
Public and private : l'API server est accessible depuis Internet (pour kubectl depuis ta machine) et depuis le VPC (pour les nœuds). En production, Private force à passer par un bastion ou un VPN.
Le logging est activé sur tous les composants par défaut en Auto Mode (API server, audit, authenticator, controller manager, scheduler), envoyés vers CloudWatch Logs.
Accéder au cluster
En local
aws eks update-kubeconfig --region eu-west-3 --name funny-bluegrass-orca
kubectl get nodes
# NAME STATUS ROLES AGE VERSION
# i-049f735198572cdc8 Ready <none> 71s v1.36.0-eks-75e0327
# i-05473ae22d581030d Ready <none> 71s v1.36.0-eks-75e0327
aws eks update-kubeconfig écrit un contexte dans ~/.kube/config. Ce contexte ne contient pas de token statique : à chaque appel kubectl, l'AWS CLI génère un token temporaire via aws eks get-token (expiration 15 minutes), vérifié par l'API server via IAM.
L'authentification ne passe plus par la ConfigMap aws-auth (méthode historique). EKS Auto Mode utilise le mode API access entries : les identités IAM autorisées sont gérées directement via l'API EKS.
# Lister les accès configurés
aws eks list-access-entries --cluster-name funny-bluegrass-orca --region eu-west-3
# Donner accès à un rôle IAM
aws eks create-access-entry \
--cluster-name funny-bluegrass-orca \
--principal-arn arn:aws:iam::123456789:role/dev-team \
--region eu-west-3
aws eks associate-access-policy \
--cluster-name funny-bluegrass-orca \
--principal-arn arn:aws:iam::123456789:role/dev-team \
--policy-arn arn:aws:eks::aws:cluster-access-policy/AmazonEKSViewPolicy \
--access-scope type=cluster \
--region eu-west-3
Les access policies prédéfinies (AmazonEKSAdminPolicy, AmazonEKSEditPolicy, AmazonEKSViewPolicy) mappent sur des ensembles de règles RBAC Kubernetes gérés par AWS.
Depuis GitHub Actions
Pour qu'un workflow GitHub Actions puisse piloter le cluster, il faut lui donner une identité AWS. La bonne approche est OIDC : GitHub Actions expose lui aussi un endpoint OIDC, et AWS IAM peut lui faire confiance. Aucune clé d'accès longue durée à stocker dans les secrets GitHub.
Côté AWS : créer un rôle IAM avec une trust policy qui autorise GitHub Actions à l'assumer via OIDC.
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::933103158736:oidc-provider/token.actions.githubusercontent.com"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
},
"StringLike": {
"token.actions.githubusercontent.com:sub": "repo:mon-org/mon-repo:*"
}
}
}
Ce rôle doit avoir les permissions pour lire le cluster EKS (eks:DescribeCluster) et, si le workflow déploie des ressources, les droits correspondants. Il faut aussi lui créer une access entry sur le cluster.
Côté GitHub Actions :
jobs:
deploy:
runs-on: ubuntu-latest
permissions:
id-token: write # nécessaire pour que GitHub génère le token OIDC
contents: read
steps:
- uses: actions/checkout@v4
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::933103158736:role/github-actions-eks
aws-region: eu-west-3
- name: Update kubeconfig
run: aws eks update-kubeconfig --region eu-west-3 --name funny-bluegrass-orca
- name: Deploy
run: kubectl apply -f k8s/
configure-aws-credentials échange le token OIDC de GitHub contre des credentials AWS temporaires via sts:AssumeRoleWithWebIdentity. Les étapes suivantes utilisent ces credentials comme si elles tournaient sur une machine avec le rôle IAM configuré.
Depuis Terraform
Le provider Terraform kubernetes a besoin de l'endpoint et d'un token pour se connecter au cluster. Plutôt que de les coder en dur, on les récupère dynamiquement depuis le provider aws :
data "aws_eks_cluster" "cluster" {
name = "funny-bluegrass-orca"
}
data "aws_eks_cluster_auth" "cluster" {
name = "funny-bluegrass-orca"
}
provider "kubernetes" {
host = data.aws_eks_cluster.cluster.endpoint
cluster_ca_certificate = base64decode(data.aws_eks_cluster.cluster.certificate_authority[0].data)
token = data.aws_eks_cluster_auth.cluster.token
}
provider "helm" {
kubernetes {
host = data.aws_eks_cluster.cluster.endpoint
cluster_ca_certificate = base64decode(data.aws_eks_cluster.cluster.certificate_authority[0].data)
token = data.aws_eks_cluster_auth.cluster.token
}
}
aws_eks_cluster_auth appelle aws eks get-token pour générer un token de courte durée. L'identité utilisée est celle du runner Terraform au moment de l'exécution : un rôle IAM en CI, les credentials locales en développement. Ce rôle doit avoir une access entry sur le cluster avec les permissions nécessaires pour appliquer les ressources Kubernetes.
Le token expire après 15 minutes. Pour des terraform apply longs qui modifient beaucoup de ressources Kubernetes, utiliser l'exec plugin pour regénérer le token à la demande :
provider "kubernetes" {
host = data.aws_eks_cluster.cluster.endpoint
cluster_ca_certificate = base64decode(data.aws_eks_cluster.cluster.certificate_authority[0].data)
exec {
api_version = "client.authentication.k8s.io/v1beta1"
command = "aws"
args = ["eks", "get-token", "--cluster-name", "funny-bluegrass-orca", "--region", "eu-west-3"]
}
}
Avec exec, Terraform appelle aws eks get-token à chaque requête vers l'API Kubernetes, ce qui évite l'expiration du token en cours d'exécution.
Node Pools et Karpenter
En Auto Mode, il n'y a pas de node groups à créer manuellement. Karpenter est intégré et provisionne des nœuds à la demande quand des pods sont en état Pending faute de capacité.
Deux NodePools sont créés automatiquement :
kubectl get nodepools
# NAME NODECLASS NODES READY
# general-purpose default 0 True
# system default 2 True
system : réservé aux pods du namespace kube-system. Il porte un taint CriticalAddonsOnly:NoSchedule qui empêche les pods utilisateur d'y atterrir. Ce pool tourne en permanence (2 nœuds minimum).
general-purpose : pour les workloads applicatifs. Démarre à 0 nœuds et scale à la demande. Se consolide automatiquement 30 secondes après que les nœuds deviennent vides ou sous-utilisés.
Les deux pools partagent une NodeClass qui définit les critères de sélection des instances :
# kubectl describe nodepool system (extrait)
Requirements:
- key: eks.amazonaws.com/instance-category
operator: In
values: [c, m, r] # compute, general purpose, memory
- key: eks.amazonaws.com/instance-generation
operator: Gt
values: ["4"] # instances de 5e génération ou plus
- key: karpenter.sh/capacity-type
operator: In
values: [on-demand]
Karpenter choisit lui-même le type d'instance dans ces critères selon les ressources demandées par les pods. Le premier nœud provisionné ici était un c7i-flex.large (2 vCPU, 4 Go RAM).
Les nœuds ont une durée de vie maximale de 336 heures (14 jours), après quoi ils sont remplacés par des nœuds frais. Ce mécanisme garantit que les nœuds tournent toujours sur la dernière version de Bottlerocket avec les derniers patches de sécurité.
OS : Bottlerocket
En Auto Mode, tous les nœuds tournent sur Bottlerocket, une distribution Linux minimaliste développée par AWS spécifiquement pour les workloads conteneurisés.
OS-IMAGE : Bottlerocket (EKS Auto, Standard) 2026.6.19 (aws-k8s-1.36-standard)
KERNEL : 6.18.33 (amd64)
CRI : containerd 2.1.8+bottlerocket
Bottlerocket n'a pas de shell interactif par défaut, pas de gestionnaire de paquets, et son système de fichiers racine est en lecture seule. Les mises à jour se font par remplacement atomique de l'OS entier (A/B partitions), pas par apt upgrade. Cette architecture réduit drastiquement la surface d'attaque par rapport à Amazon Linux 2.
Networking : le VPC CNI
Identique au mode classique. Chaque pod reçoit une IP directement dans le sous-réseau VPC, sans overlay ni encapsulation.
VPC 10.0.0.0/16
└── Subnet eu-west-3b (172.31.16.0/20)
├── Nœud EC2 172.31.27.34
│ ├── Pod A : 172.31.27.x ← IP VPC directe
│ └── Pod B : 172.31.27.x
└── Nœud EC2 172.31.41.122
├── Pod C : 172.31.41.x
└── Pod D : 172.31.41.x
Le VPC CNI pré-alloue des IPs secondaires sur les ENIs du nœud. Chaque type d'instance a une limite de pods liée au nombre d'ENIs supportées et d'IPs par ENI. Pour voir la limite d'un nœud :
kubectl describe node i-049f735198572cdc8 | grep "max-pods"
Stockage : EBS intégré
En Auto Mode, le EBS CSI Driver est géré par AWS et utilise le provisioner ebs.csi.eks.amazonaws.com (distinct du driver add-on classique ebs.csi.aws.com). Une StorageClass gp2 est disponible dès la création du cluster.
kubectl get sc
# NAME PROVISIONER VOLUMEBINDINGMODE
# gp2 kubernetes.io/aws-ebs WaitForFirstConsumer
WaitForFirstConsumer est le réglage critique. Sans lui, Kubernetes crée le volume EBS avant de savoir où le pod sera schedulé. Le volume peut alors se créer dans une AZ différente de celle du pod, rendant l'attachement impossible. Ce mode retarde la création jusqu'au scheduling du pod et crée le volume dans la même AZ.
Pour créer une StorageClass gp3 avec chiffrement :
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: gp3
provisioner: ebs.csi.eks.amazonaws.com
parameters:
type: gp3
encrypted: "true"
volumeBindingMode: WaitForFirstConsumer
reclaimPolicy: Delete
EBS est limité à un seul pod en écriture simultanée (ReadWriteOnce) et ancré dans une AZ. Pour du stockage partagé entre plusieurs pods ou plusieurs AZs, EFS (NFS managé) via son propre CSI driver est la solution.
Load Balancing intégré
En Auto Mode, elasticLoadBalancing.enabled: true est activé dans la configuration du cluster. AWS déploie et maintient le Load Balancer Controller. Il crée des NLB (L4) et des ALB (L7) à partir des ressources Kubernetes.
Service LoadBalancer → NLB :
apiVersion: v1
kind: Service
metadata:
name: api
annotations:
service.beta.kubernetes.io/aws-load-balancer-type: "external"
service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: "ip"
service.beta.kubernetes.io/aws-load-balancer-scheme: "internet-facing"
spec:
type: LoadBalancer
selector:
app: api
ports:
- port: 80
targetPort: 8000
nlb-target-type: ip route directement vers les IPs des pods, sans passer par kube-proxy. C'est possible parce que le VPC CNI donne des IPs VPC réelles aux pods.
Ingress → ALB :
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: api
annotations:
kubernetes.io/ingress.class: alb
alb.ingress.kubernetes.io/scheme: internet-facing
alb.ingress.kubernetes.io/target-type: ip
alb.ingress.kubernetes.io/certificate-arn: arn:aws:acm:eu-west-3:123456789:certificate/abc
spec:
rules:
- host: api.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: api
port:
number: 8000
Exposer un service sur Internet
Déployer un pod et le rendre accessible depuis Internet implique trois couches distinctes : le security group du cluster, les tags des subnets, et l'annotation du Service Kubernetes. Chaque couche est indépendante, et l'oubli de l'une d'elles laisse le service en état Pending indéfiniment.
1. Ouvrir le security group du cluster
EKS Auto Mode attache un security group au cluster (clusterSecurityGroupId dans la configuration). Par défaut, ce groupe n'autorise aucun trafic entrant depuis Internet. Il faut y ajouter explicitement la règle pour le port souhaité.
aws ec2 authorize-security-group-ingress \
--group-id sg-0436888777b09da32 \
--protocol tcp --port 80 --cidr 0.0.0.0/0
Sans cette règle, le load balancer est créé et reçoit le trafic, mais les paquets sont bloqués avant d'atteindre les pods.
2. Taguer les subnets
Le Load Balancer Controller intégré découvre les subnets disponibles en interrogeant l'API EC2 avec des filtres sur les tags. Il ne connaît pas la configuration du VPC, il ne sait pas quels subnets sont publics ou privés. Les tags sont le seul moyen de lui indiquer où créer le load balancer.
for subnet in subnet-0e37a4f92e2f3eb32 subnet-02568af2a95869569 subnet-078c6ce8eb8657500; do
aws ec2 create-tags --resources $subnet \
--tags Key=kubernetes.io/role/elb,Value=1 \
Key=kubernetes.io/cluster/funny-bluegrass-orca,Value=shared
done
kubernetes.io/role/elb=1 signale que ce subnet peut accueillir des load balancers Internet-facing. Sans ce tag, le service reste indéfiniment en état Pending : le controller ne trouve aucun subnet éligible et ne crée pas le LB.
3. Déployer et exposer
kubectl create deployment demo --image=nginx --replicas=2
kubectl expose deployment demo --type=LoadBalancer --port=80
# Forcer le scheme internet-facing (sinon le LB est interne par défaut)
kubectl annotate svc demo \
service.beta.kubernetes.io/aws-load-balancer-scheme=internet-facing --overwrite
Le choix entre internet-facing et internal ne se déduit pas automatiquement du type de subnet. Sans l'annotation, le controller crée un LB interne même si les subnets sont publics. L'annotation est obligatoire pour tout service destiné à être accessible depuis Internet.
Résultat
kubectl get svc demo
# NAME TYPE CLUSTER-IP EXTERNAL-IP
# demo LoadBalancer 10.100.x.x k8s-default-demo-36317854b0-89a4dd3b9a900cf5.elb.eu-west-3.amazonaws.com
curl k8s-default-demo-36317854b0-89a4dd3b9a900cf5.elb.eu-west-3.amazonaws.com
# HTTP 200 - nginx accessible depuis Internet
Le DNS du NLB est attribué par AWS lors de la création. La propagation DNS prend quelques secondes à quelques minutes. Le NLB pointe directement sur les IPs des pods (target type ip), sans passer par kube-proxy.
Récapitulatif des prérequis
| Étape | Sans elle |
|---|---|
Tag kubernetes.io/role/elb=1 sur les subnets | Service reste en Pending |
| Règle entrante dans le security group | LB créé mais trafic bloqué (timeout) |
Annotation aws-load-balancer-scheme=internet-facing | LB interne créé, inaccessible depuis Internet |
IRSA : credentials IAM pour les pods
C'est la partie qui reste entièrement manuelle, même en Auto Mode. IRSA permet à un pod d'obtenir des credentials IAM temporaires sans secret statique, en associant un rôle IAM à un ServiceAccount Kubernetes.
Le mécanisme repose sur OIDC. EKS expose un endpoint OIDC que AWS IAM reconnaît comme identity provider de confiance. Quand un pod démarre avec un ServiceAccount annoté, le kubelet injecte un token JWT signé par EKS. Ce token est échangé contre des credentials IAM temporaires via sts:AssumeRoleWithWebIdentity.
Pod → ServiceAccount annoté → token JWT injecté
→ appel AWS API avec ce token
→ STS vérifie le token auprès de l'endpoint OIDC du cluster
→ credentials temporaires émises pour le rôle IAM associé
Mise en place
- Enregistrer l'OIDC provider dans IAM (une fois par cluster) :
# L'issuer OIDC du cluster est visible ici :
aws eks describe-cluster --name funny-bluegrass-orca --region eu-west-3 \
--query 'cluster.identity.oidc.issuer'
# "https://oidc.eks.eu-west-3.amazonaws.com/id/DDD18D1C5646ADCBA937A2C7FDF24504"
eksctl utils associate-iam-oidc-provider \
--cluster funny-bluegrass-orca --region eu-west-3 --approve
- Créer un rôle IAM avec une trust policy qui restreint l'accès à un ServiceAccount précis. La condition
StringEqualsgarantit que seul le ServiceAccountmy-appdans le namespacedefaultpeut assumer le rôle :
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::933103158736:oidc-provider/oidc.eks.eu-west-3.amazonaws.com/id/DDD18D1C5646ADCBA937A2C7FDF24504"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"oidc.eks.eu-west-3.amazonaws.com/id/DDD18D1C5646ADCBA937A2C7FDF24504:sub": "system:serviceaccount:default:my-app"
}
}
}
- Annoter le ServiceAccount avec l'ARN du rôle :
apiVersion: v1
kind: ServiceAccount
metadata:
name: my-app
namespace: default
annotations:
eks.amazonaws.com/role-arn: arn:aws:iam::933103158736:role/my-app-role
Les SDKs AWS (boto3, SDK Go, SDK Java) détectent automatiquement les credentials montées dans /var/run/secrets/eks.amazonaws.com/serviceaccount/token et les utilisent sans configuration supplémentaire.
Coûts
| Composant | Coût approximatif |
|---|---|
| Control plane | 0,10 $/heure (~73 $/mois) |
| Nœud c7i-flex.large (2vCPU/4Go) | ~0,036 $/heure (~26 $/mois) |
| Nœud m5.xlarge (4vCPU/16Go) | ~0,22 $/heure (~160 $/mois) |
| NAT Gateway | 0,045 $/heure (~33 $/mois) + données |
| EBS gp3 20 Go | ~1,60 $/mois |
| ALB | 0,008 $/heure (~6 $/mois) + LCU |
Karpenter consolide les nœuds automatiquement : un nœud vide depuis 30 secondes est terminé. Le pool general-purpose scale à 0 quand il n'y a pas de workloads utilisateur, ce qui évite de payer des nœuds inutilisés en dehors des heures de travail.