Amazon EKS, dalla teoria al runbook

Tutto letto attraverso il cluster che hai davvero in mano: a-4510-eks-mil-cluster-p-01, region eu-south-1 (Milano), account 122610509347. Ogni comando è incollabile così com'è.

Il tuo cluster, in 10 numeri

Dati letti dalla console il 2 settembre 2026. Il campo rosso è quello che ti sta costando soldi adesso.

kubernetes1.32
platformeks.51
regioneu-south-1
creato28 mag 2025
support periodextended → 23 mar 2027
upgrade policyEXTENDED
node group3 · 8 nodi
add-on6 (6 aggiornabili)
cluster health0
upgrade insights1 error

Traduzione economica dello stato attuale. Il supporto standard di 1.32 è finito il 23 marzo 2026: da quel giorno il control plane è passato in extended support. Il control plane costa 0,10 $/ora per cluster in standard support e 0,60 $/ora in extended support — cioè circa 73 $/mese contro 438 $/mese, per lo stesso identico cluster. Il passaggio è automatico, senza approvazione, e l'unico modo per uscirne è l'upgrade. Fine dell'extended support: 23 marzo 2027, dopo di che AWS aggiorna d'ufficio.

01 Chi gestisce cosa

Se tieni a mente una sola cosa di EKS, tieni questa: AWS gestisce il control plane come un servizio chiuso, tu gestisci tutto il resto — nodi, rete, identità, add-on, workload.

Su Mirantis MKE o su OpenShift self-managed tu vedi i master, fai il backup di etcd, rinnovi i certificati kubeadm. Su EKS niente di tutto questo esiste: non hai SSH sui master, non vedi i pod di kube-apiserver, non fai backup di etcd. In cambio non puoi nemmeno mettere le mani su quei componenti quando qualcosa non torna: puoi solo leggere i log che AWS espone su CloudWatch e aprire un caso.

Control plane — gestito da AWS tu paghi 0,10 $/h (0,60 in extended). Non ci entri. kube-apiserverendpoint HTTPS, autenticazione etcdbackup e restore fatti da AWS schedulernessuna config custom controller-manager+ cloud-controller-manager OIDC provider del clusteroidc.eks.eu-south-1.amazonaws.com/id/A07A06… upgrade del control planelo lanci tu, lo esegue AWS, non è reversibile Data plane e contorno — gestito da te EC2, add-on, IAM, rete, workload: responsabilità tua node groupAMI, scaling, drain, patch OS add-onCNI, kube-proxy, CoreDNS, CSI IAM role e policycluster role, node role, IRSA VPC, subnet, SGIP, NAT, endpoint, route RBAC e access entrychi entra e con che diritti workloadPDB, probe, limiti, ingress upgrade di nodi e add-ondopo il control plane, e nell'ordine giusto

Le cinque parole che ti servono, in una riga ciascuna

TermineChe cos'è, detto senza giriNel tuo cluster
ARNL'indirizzo completo e univoco di una risorsa AWS. Ovunque AWS ti chieda "quale risorsa", risponde un ARN.arn:aws:eks:eu-south-1:122610509347:cluster/a-4510-eks-mil-cluster-p-01
IAMIl sistema di identità e permessi di AWS. Vive fuori da Kubernetes e decide chi può parlare con le API AWS.Il ruolo del cluster: a-4510-iam-mil-clusterrole-p-01
Node groupUn gruppo di EC2 identiche che fanno da worker, creato e mantenuto da EKS tramite un Auto Scaling Group.3 gruppi: apigwworkers (2), dbworkers (3), mainworkers (3)
IRSAIl meccanismo che dà a un pod — non al nodo — credenziali AWS temporanee tramite il suo ServiceAccount.Il controller EBS CSI usa il ruolo …-EBSCSIController
Add-onUn componente di sistema del cluster (CNI, DNS, CSI…) installato e versionato da EKS invece che da te a mano.6 add-on, tutti con una versione più recente disponibile

Il confine mentale che vale per tutto il resto della pagina. IAM risponde alla domanda "questa identità può chiamare questa API AWS?". RBAC di Kubernetes risponde a "questa identità può fare questa azione sugli oggetti del cluster?". Sono due sistemi separati che si toccano in due soli punti: l'autenticazione al cluster (access entry / aws-auth) e IRSA. Quasi tutti i problemi di permessi su EKS nascono dal confondere i due piani.

02 ARN — l'indirizzo di ogni cosa

ARN sta per Amazon Resource Name. È una stringa a sei campi separati da : che identifica in modo univoco una risorsa in tutto AWS. Non è un identificativo interno: è il modo in cui scrivi "questa risorsa qui" dentro policy, trust, comandi e configurazioni.

arn:aws:eks:eu-south-1:122610509347:cluster/a-4510-eks-mil-cluster-p-01 arn prefissosempre così aws partizioneaws-cn, aws-us-gov eks servizioiam, ecr, ec2, s3… eu-south-1 regionevuota se il servizio è globale 122610509347 account idil tuo account, 12 cifre cluster/a-4510-eks-mil-cluster-p-01 tipo/nome della risorsail separatore può essere / oppure : a seconda del servizio IAM è un servizio globale: nei suoi ARN il campo regione è vuoto, e infatti vedi due due-punti di fila — arn:aws:iam::122610509347:role/a-4510-iam-mil-clusterrole-p-01

Decoder

Incolla un ARN qualsiasi e vedi come si scompone. Utile quando ne trovi uno in un errore e non capisci a cosa punta.

Gli ARN che incontri su EKS

RisorsaForma
Cluster EKSarn:aws:eks:eu-south-1:122610509347:cluster/a-4510-eks-mil-cluster-p-01
Node grouparn:aws:eks:eu-south-1:122610509347:nodegroup/a-4510-eks-mil-cluster-p-01/a-4510-eks-mil-mainworkers-p-01/<uuid>
Ruolo IAMarn:aws:iam::122610509347:role/a-4510-iam-mil-clusterrole-p-01
Policy gestita da AWSarn:aws:iam::aws:policy/AmazonEKSWorkerNodePolicy — l'account è la parola aws
Policy tuaarn:aws:iam::122610509347:policy/mia-policy-s3
OIDC providerarn:aws:iam::122610509347:oidc-provider/oidc.eks.eu-south-1.amazonaws.com/id/A07A065561AB58A4762449B95E2DC121
Repository ECRarn:aws:ecr:eu-south-1:122610509347:repository/myapp
Security grouparn:aws:ec2:eu-south-1:122610509347:security-group/sg-0abc123
Secret di Secrets Managerarn:aws:secretsmanager:eu-south-1:122610509347:secret:prod/db-AbCdEf — con suffisso random
Identità assunta via IRSAarn:aws:sts::122610509347:assumed-role/<ruolo>/<session-name> — questa vedi nei log CloudTrail

Il wildcard * è ammesso nelle policy, e lì nasce metà dei problemi di sicurezza: arn:aws:s3:::mio-bucket/* significa "tutti gli oggetti del bucket", mentre "Resource": "*" significa "qualunque risorsa dell'account". Il secondo in una policy di nodo è quello che fa scattare le review di sicurezza.

Recuperare gli ARN veri dalla CLI

chi sono io, adesso
aws sts get-caller-identity
ARN del cluster e del suo ruolo
aws eks describe-cluster --name a-4510-eks-mil-cluster-p-01 --region eu-south-1 \
  --query 'cluster.{arn:arn,role:roleArn,oidc:identity.oidc.issuer,version:version,platform:platformVersion}' --output table
ARN di tutti i ruoli che contengono 4510
aws iam list-roles --query 'Roles[?contains(RoleName,`4510`)].[RoleName,Arn]' --output table

03 IAM — identità e permessi

IAM è il portiere di AWS. Ogni chiamata alle API AWS — creare un volume EBS, scaricare un'immagine da ECR, leggere un secret — viene valutata da IAM prima di essere eseguita. Kubernetes qui non c'entra ancora nulla.

I quattro oggetti, e a cosa servono davvero

Policy

Un documento JSON che elenca azioni permesse o negate su certe risorse. Da sola non fa nulla: è un pezzo di carta finché non la attacchi a qualcuno.

Role

Un'identità senza password che qualcuno può assumere temporaneamente. È il mattone di EKS: lo assumono le EC2 dei nodi, il control plane, e i pod via IRSA.

User

Un'identità con credenziali di lunga durata (password o access key). Nel data plane non dovrebbe comparire mai: le access key statiche nei pod sono l'anti-pattern numero uno.

Group

Solo un contenitore di user per attaccare policy in blocco. Non ha nulla a che vedere con i gruppi RBAC di Kubernetes, malgrado il nome.

Il pezzo che confonde tutti: le due policy di un role

Ogni ruolo IAM ha due documenti, e fanno cose diverse. Se non funziona qualcosa, il 90% delle volte è il primo, non il secondo.

DocumentoRisponde alla domandaDove lo trovi
Trust policy
assume role policy
Chi può indossare questo ruolo. Un servizio EC2? Un altro account? Un ServiceAccount Kubernetes?Console → ruolo → scheda "Trust relationships"
Permission policy
una o più, allegate
Cosa può fare chi lo indossa: quali azioni su quali ARN.Console → ruolo → scheda "Permissions"
trust policy — "possono indossarmi le istanze EC2" (è quella del node role)
{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Principal": { "Service": "ec2.amazonaws.com" },
    "Action": "sts:AssumeRole"
  }]
}
permission policy — "posso leggere solo questo bucket"
{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Action": ["s3:GetObject", "s3:ListBucket"],
    "Resource": [
      "arn:aws:s3:::a-4510-report-prod",
      "arn:aws:s3:::a-4510-report-prod/*"
    ]
  }]
}

I ruoli che un cluster EKS ha per forza

RuoloChi lo assumePolicy tipiche
Cluster role
a-4510-iam-mil-clusterrole-p-01
Il servizio EKS (eks.amazonaws.com) per conto del control planeAmazonEKSClusterPolicy — gli serve per creare ENI, aggiornare load balancer, leggere le subnet
Node role
uno per node group
Le EC2 worker, tramite instance profileAmazonEKSWorkerNodePolicy, AmazonEC2ContainerRegistryReadOnly (o …PullOnly), AmazonEKS_CNI_Policy
Ruoli IRSA
uno per componente
I singoli pod tramite ServiceAccountSolo i permessi di quel componente: EBS CSI, Load Balancer Controller, external-secrets…

Perché AmazonEKS_CNI_Policy sul node role è discutibile. Quella policy permette di manipolare le ENI dell'istanza: se sta sul ruolo del nodo, ogni pod che riesce a raggiungere il metadata endpoint eredita quel potere. La pratica corretta è spostarla su un ruolo IRSA dedicato al ServiceAccount aws-node, e bloccare l'accesso a IMDS dai pod (hop limit a 1).

chiudere IMDSv2 ai pod su un'istanza — impedisce di rubare le credenziali del nodo
aws ec2 modify-instance-metadata-options --instance-id i-0abc123 \
  --http-tokens required --http-put-response-hop-limit 1 --region eu-south-1

Ispezione rapida di un ruolo

trust policy + policy allegate di un ruolo
aws iam get-role --role-name a-4510-iam-mil-clusterrole-p-01 --query 'Role.AssumeRolePolicyDocument'
policy gestite e inline
aws iam list-attached-role-policies --role-name a-4510-iam-mil-clusterrole-p-01 --output table
aws iam list-role-policies --role-name a-4510-iam-mil-clusterrole-p-01
simulare un permesso prima di scoprirlo in produzione
aws iam simulate-principal-policy \
  --policy-source-arn arn:aws:iam::122610509347:role/a-4510-eks-mil-cluster-p-01-EBSCSIController \
  --action-names ec2:CreateVolume ec2:AttachVolume \
  --query 'EvaluationResults[].{action:EvalActionName,decision:EvalDecision}' --output table

04 IRSA — dare credenziali AWS a un pod

IAM Roles for Service Accounts risolve un problema preciso: un pod deve chiamare un'API AWS (creare un volume, leggere un secret) senza che tu gli metta dentro una access key, e senza dare quel permesso a tutti i pod del nodo.

Il problema che risolve

Senza IRSA hai due sole strade, entrambe brutte. La prima: metti una access key statica in un Secret Kubernetes — credenziali eterne, che ruotano solo se ci pensi tu, leggibili da chiunque abbia get secret. La seconda: attacchi il permesso al node role — e allora tutti i pod di quel nodo, compreso quello scritto male del fornitore, possono fare la stessa cosa.

IRSA introduce un terzo modo: il cluster ha un suo OIDC provider, il pod riceve un token firmato che dice "sono il ServiceAccount X nel namespace Y", e AWS scambia quel token con credenziali temporanee del ruolo che si fida esattamente di quel ServiceAccount.

Pod ebs-csi-controller serviceAccountName: ebs-csi-controller-sa namespace: kube-system token proiettato in: /var/run/secrets/eks.amazonaws.com/…/token ServiceAccount annotations: eks.amazonaws.com/role-arn: arn:aws:iam::1226…:role/…EBSCSIController AWS STS AssumeRoleWithWebIdentity verifica firma del token contro l'OIDC provider registrato in IAM OIDC provider del cluster oidc.eks.eu-south-1.amazonaws.com /id/A07A065561AB58A47624… espone le chiavi pubbliche (JWKS) IAM Role trust: sub = system:serviceaccount: kube-system:ebs-csi-controller-sa aud = sts.amazonaws.com Credenziali temporanee AccessKeyId / SecretKey / Token durata 1h, rinnovate dall'SDK → ec2:CreateVolume, AttachVolume… 1 2 3 4 5 1. il pod presenta il token proiettato · 2. STS chiede le chiavi pubbliche all'issuer del cluster · 3. verifica sub e aud 4. il ruolo accetta e STS emette le credenziali · 5. l'SDK dentro il pod le usa e le rinnova da solo, senza segreti sul disco

Le tre cose che devono combaciare

IRSA fallisce sempre per uno di questi tre disallineamenti. Quando debugghi, controllali in quest'ordine.

  1. L'OIDC provider del cluster è registrato in IAM. Il cluster espone l'issuer comunque, ma se non esiste l'oggetto oidc-provider in IAM, STS non si fida di nessun token.
  2. La trust policy del ruolo cita esattamente quel ServiceAccount. Namespace e nome sono case-sensitive; un typo qui dà AccessDenied e nient'altro.
  3. Il ServiceAccount ha l'annotation con l'ARN giusto e il pod usa davvero quel ServiceAccount (non default).
trust policy IRSA reale — quella dietro il ruolo EBS CSI del tuo cluster
{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Principal": {
      "Federated": "arn:aws:iam::122610509347:oidc-provider/oidc.eks.eu-south-1.amazonaws.com/id/A07A065561AB58A4762449B95E2DC121"
    },
    "Action": "sts:AssumeRoleWithWebIdentity",
    "Condition": {
      "StringEquals": {
        "oidc.eks.eu-south-1.amazonaws.com/id/A07A065561AB58A4762449B95E2DC121:aud": "sts.amazonaws.com",
        "oidc.eks.eu-south-1.amazonaws.com/id/A07A065561AB58A4762449B95E2DC121:sub": "system:serviceaccount:kube-system:ebs-csi-controller-sa"
      }
    }
  }]
}

Usa StringEquals sul campo sub, non StringLike con *. Un system:serviceaccount:*:* in trust significa "qualunque pod di questo cluster può assumere il ruolo", e vanifica tutto IRSA. Se ti serve un ruolo per più SA, elencali in una lista dentro StringEquals.

Verificare l'OIDC provider

issuer del cluster e provider registrati in IAM: i due valori devono coincidere
aws eks describe-cluster --name a-4510-eks-mil-cluster-p-01 --region eu-south-1 \
  --query 'cluster.identity.oidc.issuer' --output text
aws iam list-open-id-connect-providers
crearlo se manca (eksctl fa da solo il thumbprint)
eksctl utils associate-iam-oidc-provider --cluster a-4510-eks-mil-cluster-p-01 --region eu-south-1 --approve

Creare un ruolo IRSA in un colpo solo

eksctl crea ruolo, trust policy e annotation sul ServiceAccount
eksctl create iamserviceaccount \
  --cluster a-4510-eks-mil-cluster-p-01 --region eu-south-1 \
  --namespace app-prod --name s3-reader \
  --attach-policy-arn arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess \
  --role-name a-4510-iam-mil-s3reader-p-01 \
  --approve
a mano: l'annotation è tutto ciò che serve lato Kubernetes
apiVersion: v1
kind: ServiceAccount
metadata:
  name: s3-reader
  namespace: app-prod
  annotations:
    eks.amazonaws.com/role-arn: arn:aws:iam::122610509347:role/a-4510-iam-mil-s3reader-p-01

Provare che funziona davvero, dal pod

le variabili iniettate dal webhook devono esserci
kubectl -n app-prod exec deploy/mia-app -- env | grep AWS_
atteso
AWS_ROLE_ARN=arn:aws:iam::122610509347:role/a-4510-iam-mil-s3reader-p-01
AWS_WEB_IDENTITY_TOKEN_FILE=/var/run/secrets/eks.amazonaws.com/serviceaccount/token
AWS_STS_REGIONAL_ENDPOINTS=regional
chi sono davvero, visto da AWS (pod usa e getta)
kubectl -n app-prod run awscli --rm -it --restart=Never \
  --overrides='{"spec":{"serviceAccountName":"s3-reader"}}' \
  --image=amazon/aws-cli:latest -- sts get-caller-identity

Se l'ARN che torna contiene assumed-role/a-4510-iam-mil-s3reader-p-01 IRSA funziona. Se contiene il nome del node role, l'annotation non è stata letta: il webhook inietta le variabili solo alla creazione del pod, quindi dopo aver annotato il SA devi far ripartire i pod.

IRSA o Pod Identity?

Dal 2023 esiste un secondo meccanismo, EKS Pod Identity, che fa la stessa cosa in modo più semplice: un add-on eks-pod-identity-agent sul cluster e un'associazione fra ServiceAccount e ruolo lato EKS, senza OIDC e senza trust policy con condizioni.

IRSAPod Identity
Trust policy del ruoloUna per cluster, con condizioni su sub/audUna sola, uguale sempre, verso pods.eks.amazonaws.com
Riuso dello stesso ruolo su più clusterVa aggiunta una condizione per ogni clusterImmediato
PrerequisitiOIDC provider registratoAdd-on agent installato sui nodi
FargateNo
Cosa hai tu oggiQuesto — la colonna "EKS Pod Identity: Not set" degli add-on lo confermaNon attivo

Non c'è fretta di migrare: convivono, e IRSA resta pienamente supportato. Ha senso valutare Pod Identity quando il numero di ruoli inizia a fare male, o quando lo stesso componente gira su molti cluster.

05 Node group — dove girano davvero i pod

Un managed node group è un insieme di EC2 identiche che EKS crea, registra al cluster e aggiorna per te. Sotto c'è un normale Auto Scaling Group; sopra ci sono nodi Kubernetes con label, taint e un ciclo di vita gestito.

Managed node group — a-4510-eks-mil-mainworkers-p-01 quello che dichiari tu: instance type, desired/min/max, subnet, AMI type, node role, label, taint, maxUnavailable AMI release version 1.32.1-20250403 · desired size 3 · launch template: nessuno (EKS ne genera uno interno) EKS lo traduce in ↓ Auto Scaling Group launch template + AMI EKS-optimized distribuito sulle subnet che hai indicato Istanze EC2 + instance profile userdata: bootstrap verso l'endpoint del cluster assumono il node role → permessi AWS del nodo Oggetto Node in Kubernetes kubelet registrato al control plane label eks.amazonaws.com/nodegroup=… Label che EKS mette da solo su ogni nodo — usale nei nodeSelector, sono gratis e stabili eks.amazonaws.com/nodegroup = a-4510-eks-mil-mainworkers-p-01 topology.kubernetes.io/zone = eu-south-1a · node.kubernetes.io/instance-type = m6i.xlarge eks.amazonaws.com/capacityType = ON_DEMAND | SPOT · kubernetes.io/arch = amd64 | arm64

Le tre famiglie di compute, e quando usarle

TipoChi fa cosaQuando ha senso
Managed node groupEKS crea l'ASG, aggiorna l'AMI, fa cordon e drain durante l'update rispettando i PDBDefault per il 90% dei casi. È quello che hai.
Self-managedTi crei tu ASG, AMI e bootstrap; EKS non sa nulla dell'aggiornamentoAMI custom con hardening pesante, GPU particolari, requisiti che il managed non copre
FargateNiente nodi: ogni pod è una micro-VM. Nessun DaemonSet, nessun volume EBSJob isolati, workload a burst. Raramente adatto a stack bancari con sidecar e agent
EKS Auto ModeAWS gestisce anche i nodi: provisioning, patch, consolidamento. Costo maggiorato sull'EC2Cluster nuovi dove non vuoi gestire capacity. Non retrofittabile a cuor leggero

Il tuo layout a tre gruppi

Tre node group separati con nomi apigwworkers, dbworkers, mainworkers è un pattern di segregazione: workload diversi su hardware diverso, blast radius separato, e soprattutto la possibilità di aggiornare un gruppo alla volta. È anche il motivo per cui su un cluster così un upgrade non è mai "un" upgrade: sono tre finestre, o tre onde della stessa finestra.

inventario dei gruppi
aws eks list-nodegroups --cluster-name a-4510-eks-mil-cluster-p-01 --region eu-south-1 --output table
dettaglio di un gruppo: versione, AMI, scaling, subnet, ruolo
aws eks describe-nodegroup --cluster-name a-4510-eks-mil-cluster-p-01 \
  --nodegroup-name a-4510-eks-mil-mainworkers-p-01 --region eu-south-1 \
  --query 'nodegroup.{k8s:version,ami:releaseVersion,amiType:amiType,type:instanceTypes,cap:capacityType,scaling:scalingConfig,subnets:subnets,role:nodeRole,health:health.issues}'
tutti e tre in una riga ciascuno — il check che fai prima di ogni upgrade
for ng in $(aws eks list-nodegroups --cluster-name a-4510-eks-mil-cluster-p-01 --region eu-south-1 --query 'nodegroups[]' --output text); do
  aws eks describe-nodegroup --cluster-name a-4510-eks-mil-cluster-p-01 --nodegroup-name $ng --region eu-south-1 \
    --query "nodegroup.[nodegroupName,version,releaseVersion,amiType,scalingConfig.desiredSize,status]" --output text
done

Vista dal lato Kubernetes

nodi con gruppo, zona, tipo istanza e versione kubelet
kubectl get nodes -L eks.amazonaws.com/nodegroup,topology.kubernetes.io/zone,node.kubernetes.io/instance-type -o wide
quanti pod per nodo rispetto al massimo consentito
kubectl get nodes -o custom-columns='NODE:.metadata.name,MAXPODS:.status.allocatable.pods,CPU:.status.allocatable.cpu,MEM:.status.allocatable.memory'
solo i nodi di un gruppo
kubectl get nodes -l eks.amazonaws.com/nodegroup=a-4510-eks-mil-dbworkers-p-01

Scaling e capacità

cambiare desired/min/max e la finestra di indisponibilità durante gli update
aws eks update-nodegroup-config --cluster-name a-4510-eks-mil-cluster-p-01 \
  --nodegroup-name a-4510-eks-mil-mainworkers-p-01 --region eu-south-1 \
  --scaling-config minSize=3,maxSize=6,desiredSize=4 \
  --update-config maxUnavailable=1

maxUnavailable vale sia per lo scaling sia per gli update dell'AMI: è il numero di nodi che EKS può togliere contemporaneamente. Su un gruppo da 3 nodi, 1 è l'unico valore ragionevole. Esiste anche maxUnavailablePercentage, più comodo sui gruppi grandi.

Label e taint gestiti da EKS

aggiungere una label a tutto il gruppo — persiste ai replacement dei nodi
aws eks update-nodegroup-config --cluster-name a-4510-eks-mil-cluster-p-01 \
  --nodegroup-name a-4510-eks-mil-dbworkers-p-01 --region eu-south-1 \
  --labels 'addOrUpdateLabels={workload=database,tier=data}'
taint dedicato: solo chi lo tollera atterra su questi nodi
aws eks update-nodegroup-config --cluster-name a-4510-eks-mil-cluster-p-01 \
  --nodegroup-name a-4510-eks-mil-dbworkers-p-01 --region eu-south-1 \
  --taints 'addOrUpdateTaints=[{key=workload,value=database,effect=NO_SCHEDULE}]'

Label e taint messi con kubectl label node spariscono al primo replacement del nodo, che durante un upgrade succede a tutti. Se devono sopravvivere, vanno dichiarati sul node group.

Il legame con l'ASG

Il gruppo è gestito, ma sotto c'è un ASG normale che a volte devi guardare — per esempio quando i nodi non nascono e vuoi vedere l'errore vero (subnet senza IP, capacità esaurita nella AZ, quota EC2).

trovare l'ASG dietro al node group e leggerne le attività recenti
ASG=$(aws eks describe-nodegroup --cluster-name a-4510-eks-mil-cluster-p-01 \
  --nodegroup-name a-4510-eks-mil-mainworkers-p-01 --region eu-south-1 \
  --query 'nodegroup.resources.autoScalingGroups[0].name' --output text)
aws autoscaling describe-scaling-activities --auto-scaling-group-name $ASG --region eu-south-1 \
  --max-items 10 --query 'Activities[].[StartTime,StatusCode,Cause]' --output text

06 Autoscaling

Tre livelli distinti che si confondono facilmente: HPA scala i pod, Cluster Autoscaler o Karpenter scalano i nodi, VPA cambia le richieste dei pod.

ComponenteCosa muoveCome decide
HPANumero di repliche di un DeploymentMetriche (CPU/memoria via metrics-server, o custom). Nessun legame con AWS.
Cluster AutoscalerdesiredSize dei node groupGuarda i pod Pending e alza l'ASG. Vuole i tag k8s.io/cluster-autoscaler/… sull'ASG e un ruolo IRSA.
KarpenterIstanze EC2 dirette, senza ASGCalcola l'istanza migliore per i pod pendenti, la avvia in ~40 s, e consolida spegnendo nodi sottoutilizzati.
VPArequests/limits dei podStorico dei consumi. Da usare in modalità Off per raccomandazioni, prima di lasciarlo agire.
metrics-server: prerequisito di HPA, non è installato di default
kubectl top nodes
kubectl top pods -A --sort-by=memory | head -20
tag richiesti sull'ASG per il Cluster Autoscaler
aws autoscaling create-or-update-tags --region eu-south-1 --tags \
  ResourceId=$ASG,ResourceType=auto-scaling-group,Key=k8s.io/cluster-autoscaler/enabled,Value=true,PropagateAtLaunch=false \
  ResourceId=$ASG,ResourceType=auto-scaling-group,Key=k8s.io/cluster-autoscaler/a-4510-eks-mil-cluster-p-01,Value=owned,PropagateAtLaunch=false

Cluster Autoscaler e Karpenter non vanno usati insieme sugli stessi workload. Su un cluster con node group tipizzati per ruolo come il tuo, il Cluster Autoscaler è la scelta coerente: Karpenter dà il meglio quando accetti che la forma della flotta la decida lui.

07 Rete: VPC, CNI e il conto degli IP

Su EKS ogni pod prende un IP reale della VPC. Niente overlay, niente NAT interno: è la differenza più grossa rispetto a un cluster on-prem con OVN, e ha una conseguenza pratica pesante — gli IP finiscono.

Il vincolo che sorprende: max pods per nodo

Il VPC CNI assegna a ogni pod un IP secondario di una ENI dell'istanza. Quante ENI e quanti IP per ENI dipende dal tipo di istanza, quindi il numero massimo di pod per nodo è una proprietà dell'hardware, non una scelta.

formula (senza prefix delegation)
max_pods = ( ENI_max × (IPv4_per_ENI − 1) ) + 2
IstanzaENIIP per ENImax pods
t3.medium3617
m5.large / m6i.large31029
m5.xlarge / m6i.xlarge41558
m5.2xlarge41558
m5.4xlarge830234

I sidecar contano. Un cluster con service mesh consuma un IP per pod applicativo comunque, ma raddoppia i container: è la CPU a finire prima, non gli IP. Senza mesh, su nodi piccoli, finiscono prima gli IP.

verificare il valore reale in uso su un nodo
kubectl get node <nodo> -o jsonpath='{.status.allocatable.pods}{"\n"}'
prefix delegation: alza drasticamente il tetto assegnando /28 invece di singoli IP
kubectl set env daemonset aws-node -n kube-system ENABLE_PREFIX_DELEGATION=true
kubectl set env daemonset aws-node -n kube-system WARM_PREFIX_TARGET=1

Va abilitata prima che le subnet siano frammentate: richiede blocchi /28 contigui liberi. Su una VPC vissuta a lungo può fallire proprio per frammentazione.

Subnet, AZ e il tag che serve ai load balancer

Tag sulla subnetServe a
kubernetes.io/role/elb = 1Subnet pubbliche → dove il Load Balancer Controller crea gli ALB/NLB internet-facing
kubernetes.io/role/internal-elb = 1Subnet private → load balancer interni
kubernetes.io/cluster/<nome-cluster> = shared|ownedDiscovery del cluster (storico, ancora usato da diversi controller)
IP liberi per subnet: il numero da guardare prima di scalare
aws ec2 describe-subnets --region eu-south-1 \
  --filters "Name=tag:kubernetes.io/cluster/a-4510-eks-mil-cluster-p-01,Values=owned,shared" \
  --query 'Subnets[].{id:SubnetId,az:AvailabilityZone,cidr:CidrBlock,free:AvailableIpAddressCount}' --output table

Endpoint del control plane

L'API server ha due endpoint, e la combinazione decide chi può fare kubectl e come i nodi parlano con il control plane.

ConfigurazioneEffetto
PublicAPI raggiungibile da Internet (filtrabile per CIDR). I nodi escono verso l'endpoint pubblico via NAT.
Public + PrivateConfigurazione più comune: dall'esterno via CIDR autorizzati, dai nodi via DNS privato dentro la VPC.
Private onlyNessun accesso da Internet: kubectl solo da dentro la VPC, da un bastion o via VPN/Direct Connect. È l'assetto tipico degli ambienti bancari.
stato attuale degli endpoint e delle CIDR autorizzate
aws eks describe-cluster --name a-4510-eks-mil-cluster-p-01 --region eu-south-1 \
  --query 'cluster.resourcesVpcConfig.{public:endpointPublicAccess,private:endpointPrivateAccess,cidrs:publicAccessCidrs,sg:clusterSecurityGroupId,subnets:subnetIds}'
restringere l'accesso pubblico a una lista di CIDR aziendali
aws eks update-cluster-config --name a-4510-eks-mil-cluster-p-01 --region eu-south-1 \
  --resources-vpc-config endpointPublicAccess=true,endpointPrivateAccess=true,publicAccessCidrs="203.0.113.0/24"

In private-only servono gli VPC endpoint per i servizi che i nodi usano, altrimenti il bootstrap fallisce in silenzio: ecr.api, ecr.dkr, s3 (gateway, per i layer delle immagini), ec2, sts, logs, elasticloadbalancing. Il sintomo classico è un nodo che resta fuori dal cluster o pod fermi in ImagePullBackOff con timeout, non con 403.

Security group

Tre livelli, spesso confusi tra loro:

  • Cluster security group — creato da EKS, applicato al control plane e ai nodi gestiti. Consente il traffico nodi↔control plane. Toccarlo a mano è quasi sempre un errore.
  • SG del node group — quelli che aggiungi tu (via launch template) per il traffico verso database, storage, sistemi esterni.
  • Security group per pod — con SecurityGroupPolicy assegni un SG a un singolo pod tramite ENI dedicata. Utile quando un solo microservizio deve raggiungere un DB con regole strette, ma richiede ENABLE_POD_ENI=true e non è supportato su tutti i tipi di istanza.
SecurityGroupPolicy — SG applicato ai soli pod di un ServiceAccount
apiVersion: vpcresources.k8s.aws/v1beta1
kind: SecurityGroupPolicy
metadata:
  name: db-access
  namespace: app-prod
spec:
  serviceAccountSelector:
    matchLabels:
      app: payments
  securityGroups:
    groupIds:
      - sg-0db1122334455

Esporre servizi

OggettoCosa crea in AWS
Service type=LoadBalancerCon il Load Balancer Controller: un NLB. Senza controller: il vecchio CLB dell'in-tree provider.
Ingress + classe albUn Application Load Balancer, con target group verso i pod (target-type ip) o verso i NodePort (instance).
Gateway APISupportata dal LBC recente; è la strada dove sta andando l'ecosistema dopo il ritiro di ingress-nginx upstream.
annotation ALB che usi davvero, interno e con TLS
metadata:
  annotations:
    alb.ingress.kubernetes.io/scheme: internal
    alb.ingress.kubernetes.io/target-type: ip
    alb.ingress.kubernetes.io/listen-ports: '[{"HTTPS":443}]'
    alb.ingress.kubernetes.io/certificate-arn: arn:aws:acm:eu-south-1:122610509347:certificate/xxxx-xxxx
    alb.ingress.kubernetes.io/healthcheck-path: /healthz
    alb.ingress.kubernetes.io/load-balancer-attributes: idle_timeout.timeout_seconds=120

08 Storage

EBS per i volumi a singolo nodo, EFS quando serve ReadWriteMany. Entrambi arrivano come driver CSI, ed entrambi hanno bisogno di un ruolo IRSA per poter creare volumi.

EBS CSIEFS CSI
Access modeReadWriteOnce — un solo nodo alla voltaReadWriteMany — condiviso fra pod e AZ
Vincolo AZIl volume è legato a una AZ: il pod ci torna sempreNessuno, è NFS gestito
PrestazioniBasse latenze, IOPS dichiarabili (gp3)Latenza NFS, throughput a burst o provisioned
Uso tipicoDatabase, code, storage di statoContenuti condivisi, file di appoggio fra pod

Nel tuo cluster l'EBS CSI Driver è già add-on gestito, in versione v1.41.0-eksbuild.1, con ruolo IRSA a-4510-eks-mil-cluster-p-01-EBSCSIController. Quel ruolo è ciò che permette al controller di chiamare ec2:CreateVolume e ec2:AttachVolume: se qualcuno lo tocca, i PVC restano Pending per sempre.

StorageClass gp3 con espansione — la default sensata
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: gp3
  annotations:
    storageclass.kubernetes.io/is-default-class: "true"
provisioner: ebs.csi.aws.com
volumeBindingMode: WaitForFirstConsumer
allowVolumeExpansion: true
reclaimPolicy: Delete
parameters:
  type: gp3
  iops: "3000"
  throughput: "125"
  encrypted: "true"
  kmsKeyId: arn:aws:kms:eu-south-1:122610509347:key/xxxxxxxx-xxxx

WaitForFirstConsumer non è un dettaglio: senza, il volume viene creato in una AZ a caso e il pod può restare Pending perché non c'è capacità in quella zona.

PVC che non lega: la causa vera è quasi sempre nei log del controller
kubectl describe pvc <pvc> -n <ns>
kubectl -n kube-system logs deploy/ebs-csi-controller -c csi-provisioner --tail=100
volumi EBS del cluster visti da AWS, con il PVC di origine
aws ec2 describe-volumes --region eu-south-1 \
  --filters "Name=tag:kubernetes.io/cluster/a-4510-eks-mil-cluster-p-01,Values=owned" \
  --query 'Volumes[].{id:VolumeId,size:Size,type:VolumeType,state:State,az:AvailabilityZone,pvc:Tags[?Key==`kubernetes.io/created-for/pvc/name`]|[0].Value}' --output table

Con reclaimPolicy: Delete il volume EBS sparisce insieme al PVC. Per gli stateful che contano usa una StorageClass con Retain: costa qualche volume orfano da ripulire, ma evita la telefonata brutta.

09 Add-on — i componenti di sistema, versionati da EKS

Un add-on EKS è un componente che in un cluster vanilla installeresti a mano con un manifest o un chart — CNI, kube-proxy, CoreDNS, driver CSI — e che qui viene installato, versionato e aggiornato dall'API di EKS.

La differenza pratica: senza add-on gestito, l'aggiornamento di quei componenti durante un upgrade Kubernetes è un tuo problema, con un manifest da scaricare dalla versione giusta. Con l'add-on gestito, chiedi una versione e EKS applica il manifest corretto, decidendo cosa fare se hai modificato i campi a mano (resolveConflicts).

I sei del tuo cluster

Add-onCosa faIRSA
vpc-cniAssegna gli IP della VPC ai pod. È il pezzo di rete: si aggiorna con cautela e mai in contemporanea ad altro.Consigliato (aws-node)
kube-proxyProgramma le regole iptables/IPVS per i Service. Deve seguire da vicino la versione del control plane.Non serve
corednsDNS interno del cluster. Due repliche di default: su cluster grandi vanno alzate.Non serve
aws-ebs-csi-driverCrea, attacca e monta i volumi EBS. v1.41.0-eksbuild.1 nel tuo cluster.Sì — …-EBSCSIController
eks-pod-identity-agentAlternativa a IRSA, se installato. La colonna "EKS Pod Identity: Not set" dice che non lo stai usando.
metrics-server / altriMetriche per HPA e kubectl top, oppure osservabilità.Dipende

Comandi

cosa ho installato e a che versione
aws eks list-addons --cluster-name a-4510-eks-mil-cluster-p-01 --region eu-south-1 --output table
stato e ruolo IRSA di un add-on
aws eks describe-addon --cluster-name a-4510-eks-mil-cluster-p-01 --addon-name aws-ebs-csi-driver --region eu-south-1 \
  --query 'addon.{v:addonVersion,status:status,role:serviceAccountRoleArn,issues:health.issues}'
quali versioni sono compatibili con la versione Kubernetes di destinazione
aws eks describe-addon-versions --addon-name vpc-cni --kubernetes-version 1.33 --region eu-south-1 \
  --query 'addons[].addonVersions[].{version:addonVersion,default:compatibilities[0].defaultVersion}' --output table
aggiornare un add-on preservando le modifiche fatte a mano
aws eks update-addon --cluster-name a-4510-eks-mil-cluster-p-01 --region eu-south-1 \
  --addon-name aws-ebs-csi-driver --addon-version v1.4x.x-eksbuild.x \
  --resolve-conflicts PRESERVE
--resolve-conflictsComportamento
NONESe hai modificato campi che l'add-on gestisce, l'update fallisce. Default prudente.
PRESERVETiene le tue modifiche e aggiorna il resto. È quello giusto se hai tunato risorse o variabili d'ambiente.
OVERWRITERiporta tutto al manifest AWS. Le tue personalizzazioni spariscono senza avviso.
passare un add-on esistente sotto gestione EKS senza reinstallarlo
aws eks create-addon --cluster-name a-4510-eks-mil-cluster-p-01 --region eu-south-1 \
  --addon-name coredns --resolve-conflicts OVERWRITE
personalizzare la configurazione senza uscire dall'add-on gestito
aws eks update-addon --cluster-name a-4510-eks-mil-cluster-p-01 --region eu-south-1 \
  --addon-name coredns \
  --configuration-values '{"replicaCount":3,"resources":{"requests":{"cpu":"200m","memory":"128Mi"}}}'

Lo schema accettato lo leggi con aws eks describe-addon-configuration --addon-name coredns --addon-version <v>: evita di tirare a indovinare le chiavi.

"New versions are available for 6 add-ons" nel tuo cluster non è un dettaglio cosmetico: gli add-on hanno finestre di compatibilità con la versione Kubernetes, e nell'upgrade a 1.33 alcuni di questi devono salire prima o insieme al control plane. Vedi l'ordine nella sezione upgrade.

10 Accessi al cluster — dove IAM incontra RBAC

Per entrare in un cluster EKS servono due permessi distinti: uno IAM per farsi autenticare, e uno RBAC per poter fare qualcosa. Storicamente il ponte era il ConfigMap aws-auth; oggi è l'API delle access entry.

Principal IAMrole/utente o role SSOtoken firmato da aws eks get-token Access entrymappa l'ARN IAM ausername e gruppi K8s RBAC del clusterClusterRoleBinding sul gruppoo access policy EKS Azione permessaget pods, delete deploy… Errore "You must be logged in to the server (Unauthorized)" → manca il passo 2: il tuo ARN non è mappato. Errore "forbidden: User ... cannot list resource" → il passo 2 c'è, manca il passo 3: sei dentro ma senza diritti.

aws-auth o access entry?

ConfigMap aws-authAccess entry (API)
Dove viveUn ConfigMap in kube-system: per modificarlo devi già essere dentroAPI AWS: gestibile con IAM, Terraform, CloudFormation
Errore tipicoUn YAML sbagliato ti chiude fuori dal cluster, senza rimedio sempliceNessun lock-out: correggi da AWS
AuditNessunoCloudTrail
Modalità del clusterCONFIG_MAPAPI oppure API_AND_CONFIG_MAP per la transizione
quale modalità è attiva ora
aws eks describe-cluster --name a-4510-eks-mil-cluster-p-01 --region eu-south-1 --query 'cluster.accessConfig'
abilitare le access entry senza rompere aws-auth (passo sicuro, reversibile in avanti)
aws eks update-cluster-config --name a-4510-eks-mil-cluster-p-01 --region eu-south-1 \
  --access-config authenticationMode=API_AND_CONFIG_MAP

La transizione è a senso unico: da CONFIG_MAP puoi andare a API_AND_CONFIG_MAP e poi a API, mai tornare indietro. Prima di passare a API puro, verifica che ogni riga di aws-auth abbia la sua access entry — nodi compresi.

dare accesso in lettura a un ruolo, con una policy EKS già pronta
aws eks create-access-entry --cluster-name a-4510-eks-mil-cluster-p-01 --region eu-south-1 \
  --principal-arn arn:aws:iam::122610509347:role/a-4510-ops-readonly

aws eks associate-access-policy --cluster-name a-4510-eks-mil-cluster-p-01 --region eu-south-1 \
  --principal-arn arn:aws:iam::122610509347:role/a-4510-ops-readonly \
  --policy-arn arn:aws:eks::aws:cluster-access-policy/AmazonEKSViewPolicy \
  --access-scope type=cluster
Access policyEquivalente RBAC
AmazonEKSClusterAdminPolicycluster-admin — da tenere per pochissimi
AmazonEKSAdminPolicyAdmin su namespace, senza toccare oggetti cluster-wide critici
AmazonEKSEditPolicyModifica dei workload
AmazonEKSViewPolicySola lettura

Con --access-scope type=namespace,namespaces=app-prod,app-coll limiti la stessa policy a specifici namespace: è il modo pulito per dare "edit solo sul mio namespace" a un team applicativo senza scrivere RBAC a mano.

mappatura legacy: chi c'è dentro aws-auth oggi
kubectl -n kube-system get configmap aws-auth -o yaml
kubeconfig per il cluster, con alias leggibile
aws eks update-kubeconfig --name a-4510-eks-mil-cluster-p-01 --region eu-south-1 --alias eks-mil-prod
verificare i propri diritti prima di provare un'azione
kubectl auth can-i --list --namespace app-prod
kubectl auth can-i delete deployments -n app-prod

Se usi un ruolo assunto (SSO, assume-role), l'ARN da mappare è quello del ruoloarn:aws:iam::…:role/NomeRuolo — non quello della sessione arn:aws:sts::…:assumed-role/NomeRuolo/utente. È la causa numero uno di "Unauthorized" con credenziali apparentemente valide.

11 ECR — il registry

ECR è il registry di AWS. L'autenticazione non è una password fissa: è un token di 12 ore che ti fai emettere da IAM. Per i nodi, invece, è trasparente: se il node role ha la policy giusta, il pull funziona senza imagePullSecrets.

login docker/podman al registry dell'account
aws ecr get-login-password --region eu-south-1 | docker login --username AWS --password-stdin 122610509347.dkr.ecr.eu-south-1.amazonaws.com
creare un repository con scan automatico e immutabilità dei tag
aws ecr create-repository --repository-name myapp --region eu-south-1 \
  --image-scanning-configuration scanOnPush=true --image-tag-mutability IMMUTABLE
build, tag, push
docker build -t myapp:1.4.0 .
docker tag myapp:1.4.0 122610509347.dkr.ecr.eu-south-1.amazonaws.com/myapp:1.4.0
docker push 122610509347.dkr.ecr.eu-south-1.amazonaws.com/myapp:1.4.0
cosa c'è dentro, ordinato per data
aws ecr describe-images --repository-name myapp --region eu-south-1 \
  --query 'sort_by(imageDetails,&imagePushedAt)[-10:].{tag:imageTags[0],pushed:imagePushedAt,mb:imageSizeInBytes}' --output table
lifecycle policy: tieni 20 immagini taggate, butta le untagged dopo 7 giorni
{
  "rules": [
    { "rulePriority": 1, "description": "keep last 20 tagged",
      "selection": { "tagStatus": "tagged", "tagPrefixList": ["v","release"], "countType": "imageCountMoreThan", "countNumber": 20 },
      "action": { "type": "expire" } },
    { "rulePriority": 2, "description": "expire untagged after 7 days",
      "selection": { "tagStatus": "untagged", "countType": "sinceImagePushed", "countUnit": "days", "countNumber": 7 },
      "action": { "type": "expire" } }
  ]
}

Applicala con aws ecr put-lifecycle-policy --repository-name myapp --lifecycle-policy-text file://policy.json --region eu-south-1.

Pull cross-account. Se il cluster sta in un account e il registry in un altro, serve una repository policy sul repo che autorizza il node role dell'altro account. Il fallimento tipico è 403 Forbidden in ImagePullBackOff: 403 significa "autenticato ma non autorizzato", quindi guardi la policy del repository, non il login.

12 Log e osservabilità

I log del control plane non esistono finché non li accendi, e quando li accendi cominciano a costare. Vanno attivati con criterio: audit e authenticator sono quelli che risolvono i casi veri.

Tipo di logA cosa serve
apiRichieste all'API server: latenze, errori, throttling
auditChi ha fatto cosa e quando. Indispensabile in contesto bancario
authenticatorTraduzione IAM → identità Kubernetes: è qui che si legge perché un accesso è stato rifiutato
controllerManager / schedulerPod che non partono, volumi che non si attaccano, scheduling anomalo
quali log sono attivi
aws eks describe-cluster --name a-4510-eks-mil-cluster-p-01 --region eu-south-1 \
  --query 'cluster.logging.clusterLogging[].{types:types,enabled:enabled}'
attivare audit e authenticator
aws eks update-cluster-config --name a-4510-eks-mil-cluster-p-01 --region eu-south-1 \
  --logging '{"clusterLogging":[{"types":["audit","authenticator"],"enabled":true}]}'
cercare nell'audit chi ha cancellato un deployment (CloudWatch Logs Insights via CLI)
aws logs start-query --region eu-south-1 \
  --log-group-name /aws/eks/a-4510-eks-mil-cluster-p-01/cluster \
  --start-time $(date -v-2H +%s) --end-time $(date +%s) \
  --query-string 'fields @timestamp, user.username, verb, objectRef.resource, objectRef.name | filter verb="delete" | sort @timestamp desc | limit 50'

Su Linux, date -d '2 hours ago' +%s al posto di date -v-2H.

eventi del cluster in ordine cronologico — il primo posto dove guardare, sempre
kubectl get events -A --sort-by=.lastTimestamp | tail -40

13 Creazione di un cluster, passo per passo

Un cluster EKS non nasce da un comando solo: prima esistono la rete e due ruoli IAM, poi il control plane, poi i nodi, poi gli add-on e l'OIDC. Qui la sequenza completa in due modi — eksctl con un file dichiarativo, e la CLI nativa passo per passo — con la nomenclatura del tuo ambiente.

Prerequisiti di rete

EKS non crea la VPC (a meno che non glielo chieda eksctl). In un ambiente bancario la rete esiste già ed è governata da altri: quello che devi verificare prima di partire è che sia adatta.

RequisitoPerché
Almeno 2 subnet in AZ diverseIl control plane crea ENI in due zone; con una sola la creazione fallisce
Subnet abbastanza capientiOgni pod consuma un IP della VPC: una /24 per i worker si satura in fretta. Meglio /22 o più
Uscita verso ECR e S3NAT Gateway, oppure VPC endpoint (ecr.api, ecr.dkr, s3 gateway, ec2, sts, logs, elasticloadbalancing) se private-only
Tag sulle subnetkubernetes.io/role/elb=1 sulle pubbliche, kubernetes.io/role/internal-elb=1 sulle private, per i load balancer
DNS hostname abilitati sulla VPCSenza enableDnsHostnames i nodi non risolvono l'endpoint privato dell'API
verifica della VPC candidata
aws ec2 describe-vpcs --region eu-south-1 --vpc-ids vpc-0abc123 \
  --query 'Vpcs[].{cidr:CidrBlock,dns:EnableDnsSupport,hostnames:EnableDnsHostnames}'
aws ec2 describe-subnets --region eu-south-1 --filters "Name=vpc-id,Values=vpc-0abc123" \
  --query 'Subnets[].{id:SubnetId,az:AvailabilityZone,cidr:CidrBlock,free:AvailableIpAddressCount,public:MapPublicIpOnLaunch}' --output table
tag mancanti sulle subnet
aws ec2 create-tags --region eu-south-1 --resources subnet-0aaa subnet-0bbb --tags Key=kubernetes.io/role/internal-elb,Value=1

Percorso A — eksctl con file dichiarativo

È il modo più leggibile e ripetibile senza arrivare a Terraform: un solo YAML descrive cluster, node group, add-on, IRSA e logging. Sotto, eksctl genera CloudFormation.

cluster.yaml completo — nomenclatura e topologia coerenti con il tuo cluster di Milano
apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig

metadata:
  name: a-4510-eks-mil-cluster-p-02
  region: eu-south-1
  version: "1.34"
  tags:
    Environment: production
    Owner: infraops
    CostCenter: "4510"

# rete gia esistente: si dichiarano le subnet, non si fa creare la VPC
vpc:
  id: vpc-0abc123
  subnets:
    private:
      eu-south-1a: { id: subnet-0aaa }
      eu-south-1b: { id: subnet-0bbb }
      eu-south-1c: { id: subnet-0ccc }
  clusterEndpoints:
    publicAccess: false
    privateAccess: true

# log del control plane attivi da subito: dopo costa fatica ricostruire lo storico
cloudWatch:
  clusterLogging:
    enableTypes: ["audit", "authenticator", "api", "controllerManager", "scheduler"]
    logRetentionInDays: 90

iam:
  withOIDC: true          # crea e registra l'OIDC provider: prerequisito di IRSA
  serviceAccounts:
    - metadata:
        name: ebs-csi-controller-sa
        namespace: kube-system
      wellKnownPolicies:
        ebsCSIController: true
    - metadata:
        name: aws-load-balancer-controller
        namespace: kube-system
      wellKnownPolicies:
        awsLoadBalancerController: true

managedNodeGroups:
  - name: a-4510-eks-mil-mainworkers-p-02
    instanceType: m6i.xlarge
    amiFamily: AmazonLinux2023
    minSize: 3
    maxSize: 6
    desiredCapacity: 3
    volumeSize: 100
    volumeType: gp3
    volumeEncrypted: true
    privateNetworking: true
    labels: { workload: general }
    updateConfig:
      maxUnavailable: 1
    iam:
      withAddonPolicies:
        ebs: true
        cloudWatch: true

  - name: a-4510-eks-mil-dbworkers-p-02
    instanceType: r6i.xlarge
    amiFamily: AmazonLinux2023
    minSize: 3
    maxSize: 3
    desiredCapacity: 3
    volumeSize: 200
    volumeType: gp3
    privateNetworking: true
    labels: { workload: database, tier: data }
    taints:
      - key: workload
        value: database
        effect: NoSchedule

  - name: a-4510-eks-mil-apigwworkers-p-02
    instanceType: m6i.large
    amiFamily: AmazonLinux2023
    minSize: 2
    maxSize: 4
    desiredCapacity: 2
    privateNetworking: true
    labels: { workload: apigw }

addons:
  - name: vpc-cni
    configurationValues: '{"env":{"ENABLE_PREFIX_DELEGATION":"true"}}'
  - name: kube-proxy
  - name: coredns
    configurationValues: '{"replicaCount":3}'
  - name: aws-ebs-csi-driver
    useDefaultPodIdentityAssociations: false
dry run: mostra cosa farebbe, senza creare nulla
eksctl create cluster -f cluster.yaml --dry-run
creazione (15–25 minuti; l'output resta agganciato agli stack CloudFormation)
eksctl create cluster -f cluster.yaml
se qualcosa va storto, l'errore vero sta negli eventi CloudFormation
eksctl utils describe-stacks --cluster a-4510-eks-mil-cluster-p-02 --region eu-south-1

Percorso B — CLI nativa, un pezzo alla volta

Più verboso, ma è quello che ti serve capire quando il cluster lo crea qualcun altro (Terraform, pipeline, il team cloud) e devi verificarne i pezzi.

1 · Ruolo del cluster

trust policy e creazione
cat > cluster-trust.json <<'EOF'
{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Principal": { "Service": "eks.amazonaws.com" },
    "Action": "sts:AssumeRole"
  }]
}
EOF

aws iam create-role --role-name a-4510-iam-mil-clusterrole-p-02 \
  --assume-role-policy-document file://cluster-trust.json

aws iam attach-role-policy --role-name a-4510-iam-mil-clusterrole-p-02 \
  --policy-arn arn:aws:iam::aws:policy/AmazonEKSClusterPolicy

2 · Ruolo dei nodi

trust EC2 e le tre policy minime
cat > node-trust.json <<'EOF'
{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Principal": { "Service": "ec2.amazonaws.com" },
    "Action": "sts:AssumeRole"
  }]
}
EOF

aws iam create-role --role-name a-4510-iam-mil-noderole-p-02 \
  --assume-role-policy-document file://node-trust.json

for p in AmazonEKSWorkerNodePolicy AmazonEC2ContainerRegistryPullOnly AmazonEKS_CNI_Policy; do
  aws iam attach-role-policy --role-name a-4510-iam-mil-noderole-p-02 --policy-arn arn:aws:iam::aws:policy/$p
done

Su un cluster nuovo conviene togliere AmazonEKS_CNI_Policy dal node role e assegnarla via IRSA al ServiceAccount aws-node: stesso risultato, senza dare a ogni pod la possibilità di manipolare le ENI.

3 · Control plane

creazione del cluster
aws eks create-cluster --name a-4510-eks-mil-cluster-p-02 --region eu-south-1 \
  --kubernetes-version 1.34 \
  --role-arn arn:aws:iam::122610509347:role/a-4510-iam-mil-clusterrole-p-02 \
  --resources-vpc-config subnetIds=subnet-0aaa,subnet-0bbb,subnet-0ccc,endpointPublicAccess=false,endpointPrivateAccess=true \
  --access-config authenticationMode=API_AND_CONFIG_MAP,bootstrapClusterCreatorAdminPermissions=true \
  --logging '{"clusterLogging":[{"types":["audit","authenticator","api"],"enabled":true}]}' \
  --tags Environment=production,CostCenter=4510
attendere che sia ACTIVE (10–15 minuti)
aws eks wait cluster-active --name a-4510-eks-mil-cluster-p-02 --region eu-south-1
aws eks describe-cluster --name a-4510-eks-mil-cluster-p-02 --region eu-south-1 --query 'cluster.[status,version,platformVersion]' --output text

4 · OIDC provider (senza, niente IRSA)

registrazione in IAM
eksctl utils associate-iam-oidc-provider --cluster a-4510-eks-mil-cluster-p-02 --region eu-south-1 --approve
aws iam list-open-id-connect-providers

5 · Node group

un gruppo, con label, taint e finestra di update
aws eks create-nodegroup --cluster-name a-4510-eks-mil-cluster-p-02 --region eu-south-1 \
  --nodegroup-name a-4510-eks-mil-dbworkers-p-02 \
  --node-role arn:aws:iam::122610509347:role/a-4510-iam-mil-noderole-p-02 \
  --subnets subnet-0aaa subnet-0bbb subnet-0ccc \
  --instance-types r6i.xlarge --ami-type AL2023_x86_64_STANDARD --capacity-type ON_DEMAND \
  --disk-size 200 \
  --scaling-config minSize=3,maxSize=3,desiredSize=3 \
  --update-config maxUnavailable=1 \
  --labels workload=database,tier=data \
  --taints 'key=workload,value=database,effect=NO_SCHEDULE' \
  --tags Environment=production,CostCenter=4510
attesa e verifica
aws eks wait nodegroup-active --cluster-name a-4510-eks-mil-cluster-p-02 --nodegroup-name a-4510-eks-mil-dbworkers-p-02 --region eu-south-1

6 · Add-on

i quattro di base, nell'ordine giusto
aws eks create-addon --cluster-name a-4510-eks-mil-cluster-p-02 --region eu-south-1 --addon-name vpc-cni
aws eks create-addon --cluster-name a-4510-eks-mil-cluster-p-02 --region eu-south-1 --addon-name kube-proxy
aws eks create-addon --cluster-name a-4510-eks-mil-cluster-p-02 --region eu-south-1 --addon-name coredns
aws eks create-addon --cluster-name a-4510-eks-mil-cluster-p-02 --region eu-south-1 --addon-name aws-ebs-csi-driver \
  --service-account-role-arn arn:aws:iam::122610509347:role/a-4510-eks-mil-cluster-p-02-EBSCSIController

CNI, kube-proxy e CoreDNS vengono installati comunque dal servizio alla creazione: crearli come add-on gestiti significa portarli sotto il controllo dell'API, che è ciò che rende semplice l'upgrade. Il driver EBS invece non c'è finché non lo aggiungi.

7 · Accesso e kubeconfig

kubeconfig e primo contatto
aws eks update-kubeconfig --name a-4510-eks-mil-cluster-p-02 --region eu-south-1 --alias eks-mil-p02
kubectl get nodes
kubectl get pods -A
dare accesso al resto del team, senza toccare aws-auth
aws eks create-access-entry --cluster-name a-4510-eks-mil-cluster-p-02 --region eu-south-1 \
  --principal-arn arn:aws:iam::122610509347:role/a-4510-infraops-admin

aws eks associate-access-policy --cluster-name a-4510-eks-mil-cluster-p-02 --region eu-south-1 \
  --principal-arn arn:aws:iam::122610509347:role/a-4510-infraops-admin \
  --policy-arn arn:aws:eks::aws:cluster-access-policy/AmazonEKSClusterAdminPolicy \
  --access-scope type=cluster

bootstrapClusterCreatorAdminPermissions=true rende amministratore l'identità che ha creato il cluster — spesso un ruolo di pipeline che nessuno usa più. Prima di considerare chiuso il provisioning, crea le access entry per gli umani e per i gruppi che devono lavorarci, altrimenti l'unico accesso amministrativo resta in mano a una pipeline.

Percorso C — Terraform

Quando il cluster è parte di un'infrastruttura versionata, il modulo terraform-aws-modules/eks/aws copre control plane, node group, add-on e IRSA con le stesse primitive viste sopra. Il valore non è nella sintassi, è nel fatto che la ricreazione è deterministica e la review passa da una merge request.

scheletro minimo, da adattare
module "eks" {
  source  = "terraform-aws-modules/eks/aws"
  version = "~> 20.0"

  cluster_name    = "a-4510-eks-mil-cluster-p-02"
  cluster_version = "1.34"

  vpc_id     = "vpc-0abc123"
  subnet_ids = ["subnet-0aaa", "subnet-0bbb", "subnet-0ccc"]

  cluster_endpoint_public_access  = false
  cluster_endpoint_private_access = true
  enable_irsa                     = true

  cluster_addons = {
    vpc-cni            = {}
    kube-proxy         = {}
    coredns            = {}
    aws-ebs-csi-driver = {}
  }

  eks_managed_node_groups = {
    mainworkers = {
      instance_types = ["m6i.xlarge"]
      ami_type       = "AL2023_x86_64_STANDARD"
      min_size       = 3
      max_size       = 6
      desired_size   = 3
    }
  }
}

Attenzione a un dettaglio ricorrente: desired_size gestito da Terraform e Cluster Autoscaler che modifica lo stesso valore entrano in conflitto a ogni apply. Si risolve con un lifecycle { ignore_changes = [scaling_config[0].desired_size] } o con l'equivalente del modulo.

Checklist di collaudo del cluster nuovo

tutto quello che si verifica prima di dichiararlo pronto
kubectl get nodes -o wide
kubectl get pods -A --field-selector=status.phase!=Running,status.phase!=Succeeded
kubectl run net-test --rm -it --restart=Never --image=busybox -- nslookup kubernetes.default
aws eks list-addons --cluster-name a-4510-eks-mil-cluster-p-02 --region eu-south-1 --output table
aws eks list-access-entries --cluster-name a-4510-eks-mil-cluster-p-02 --region eu-south-1
aws eks describe-cluster --name a-4510-eks-mil-cluster-p-02 --region eu-south-1 --query 'cluster.logging'
prova end-to-end dello storage: se questo PVC lega, EBS CSI e IRSA sono a posto
kubectl apply -f - <<'EOF'
apiVersion: v1
kind: PersistentVolumeClaim
metadata: { name: test-ebs }
spec:
  accessModes: [ReadWriteOnce]
  storageClassName: gp3
  resources: { requests: { storage: 1Gi } }
EOF
kubectl get pvc test-ebs -w

Distruggere quello che hai creato

ordine inverso: prima i workload con load balancer, poi i nodi, poi il cluster
kubectl delete ingress,svc --all -A          # altrimenti restano ALB e NLB orfani, che continuano a costare
aws eks delete-nodegroup --cluster-name a-4510-eks-mil-cluster-p-02 --nodegroup-name <ng> --region eu-south-1
aws eks wait nodegroup-deleted --cluster-name a-4510-eks-mil-cluster-p-02 --nodegroup-name <ng> --region eu-south-1
aws eks delete-cluster --name a-4510-eks-mil-cluster-p-02 --region eu-south-1
con eksctl, in un colpo solo
eksctl delete cluster -f cluster.yaml --disable-nodegroup-eviction

14 Lab in locale, senza account AWS

Si può esercitarsi molto senza spendere nulla, purché sia chiaro cosa si sta simulando: Kubernetes sì, l'integrazione con AWS quasi per niente. Sul Mac servono solo Docker e Homebrew.

Cosa è replicabile e cosa no

In locale funzionaIn locale non esiste
Manifest, Helm chart, operator, CRDNode group, ASG, AMI EKS-optimized
RBAC, NetworkPolicy, PDB, taint e tolerationIRSA con STS reale (serve un account)
Drain, cordon, rollout, eviction bloccate dai PDBAdd-on gestiti e loro versioning
Analisi API deprecate: pluto e kubent sui manifestALB/NLB, Ingress via Load Balancer Controller
Prove di upgrade Kubernetes minor per minorUpgrade Insights, extended support, fatturazione
Comportamento dei webhook durante un drainVPC CNI con IP reali della VPC

Tradotto: il lab locale è perfetto per la parte che ti rompe davvero durante un upgrade — chart, CRD, webhook, PDB — e inutile per la parte AWS. Che è comunque una buona notizia, perché la parte AWS la provi sul cluster di collaudo.

Opzione 1 — kind, il minimo indispensabile

installazione su macOS
brew install kind kubectl helm
kind-eks-lab.yaml — tre worker, per avere qualcosa da drenare
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
name: eks-lab
nodes:
  - role: control-plane
  - role: worker
    labels:
      workload: general
  - role: worker
    labels:
      workload: database
  - role: worker
    labels:
      workload: apigw
creazione, uso, distruzione
kind create cluster --config kind-eks-lab.yaml
kubectl get nodes -L workload
kind delete cluster --name eks-lab
provare un upgrade minor: si ricrea il cluster con un'immagine di nodo diversa
kind create cluster --name eks-lab-133 --image kindest/node:v1.33.0

Opzione 2 — kind con i binari di EKS Distro

EKS Distro (EKS-D) è la distribuzione open source con cui AWS costruisce EKS: stessi binari di API server, scheduler, kubelet. Esistono immagini di nodo kind costruite su EKS-D, quindi puoi avere in locale un cluster con esattamente i componenti che girano su EKS.

immagini disponibili su ECR pubblico (non serve account: è un registry pubblico)
aws ecr-public describe-image-tags --repository-name eks-distro/kubernetes-sigs/kind/node --region us-east-1 --no-sign-request --query 'imageTagDetails[-15:].imageTag' --output text
cluster con nodo EKS-D
kind create cluster --name eksd-lab --image public.ecr.aws/eks-distro/kubernetes-sigs/kind/node:<tag>
kubectl version
kubectl -n kube-system get pods

Se il comando aws non è installato, i tag si leggono anche dalla pagina della gallery pubblica di ECR. Su Apple Silicon verifica che esista il tag arm64: non tutte le release sono multi-architettura.

Opzione 3 — EKS Anywhere con provider Docker

È l'unica cosa che si chiama davvero "EKS" e gira sul portatile: EKS Anywhere crea un cluster locale in container, con Cilium come CNI e il ciclo di vita gestito da Cluster API, cioè con la stessa forma dichiarativa dei cluster EKS-A di produzione.

installazione
brew install aws/tap/eks-anywhere
eksctl anywhere version
generazione della configurazione e creazione
export CLUSTER_NAME=eksa-lab
eksctl anywhere generate clusterconfig $CLUSTER_NAME --provider docker > $CLUSTER_NAME.yaml
eksctl anywhere create cluster -f $CLUSTER_NAME.yaml
accesso al cluster creato
export KUBECONFIG=$PWD/$CLUSTER_NAME/$CLUSTER_NAME-eks-a-cluster.kubeconfig
kubectl get nodes
eksctl anywhere delete cluster -f $CLUSTER_NAME.yaml

Due limiti da conoscere prima di perderci un pomeriggio. EKS Anywhere richiede architettura x86/amd64: sui Mac con chip Apple Silicon non è supportato. E nel file generato conviene cambiare il CIDR di default 192.168.0.0/16, che va quasi sempre in conflitto con la rete di casa o con la VPN aziendale. Su Docker Desktop può inoltre chiedere deprecatedCgroupv1 nelle impostazioni.

Opzione 4 — emulare le API AWS, non il cluster

Per la parte che manca — IAM, ARN, policy, STS — esistono emulatori delle API AWS che girano in un container. Non ti danno un cluster EKS, ti danno un endpoint contro cui far parlare la CLI: sufficiente per prendere confidenza con ruoli, trust policy e simulazioni di permessi senza toccare un account vero.

moto server: mock open source delle API AWS
docker run --rm -p 5000:5000 motoserver/moto:latest
CLI puntata all'emulatore: credenziali finte, endpoint locale
export AWS_ACCESS_KEY_ID=test AWS_SECRET_ACCESS_KEY=test AWS_DEFAULT_REGION=eu-south-1
aws --endpoint-url http://localhost:5000 iam create-role --role-name lab-node-role --assume-role-policy-document file://node-trust.json
aws --endpoint-url http://localhost:5000 iam attach-role-policy --role-name lab-node-role --policy-arn arn:aws:iam::aws:policy/AmazonEKSWorkerNodePolicy
aws --endpoint-url http://localhost:5000 iam list-roles --query 'Roles[].Arn'

Esiste anche LocalStack, più completo e con una UI: nel piano gratuito copre i servizi di base, mentre l'emulazione di EKS ed ECR sta nel piano a pagamento. Per esercitarsi su IAM e ARN il piano gratuito basta; per simulare un cluster non serve, perché per quello ci sono le opzioni 1–3.

Opzione 5 — nodi finti per provare drain e scheduling su larga scala

Se quello che vuoi provare è il comportamento di un upgrade su decine di nodi — PDB che bloccano, workload che non trovano posto — puoi generare nodi simulati senza consumare risorse.

kwok: nodi e pod finti su un cluster kind
brew install kwok
kwokctl create cluster --name kwok-lab
kwokctl scale node --name kwok-lab --replicas 20

Il percorso di apprendimento che consiglierei

  1. kind per la meccanica Kubernetes pura: RBAC, PDB, drain, webhook, operator. È dove impari cosa si rompe durante un upgrade.
  2. moto o LocalStack per IAM e ARN: crea ruoli, scrivi trust policy sbagliate, guarda gli errori. Costa zero e sono gli errori che vedrai in produzione.
  3. Cluster di collaudo su AWS per tutto ciò che non è emulabile: IRSA vero, node group, add-on, ALB, upgrade insights. Un cluster acceso mezza giornata costa pochi euro di control plane più le istanze — l'unico costo da tenere d'occhio è il NAT Gateway, che va spento con il resto.

15 Upgrade runbook

Un upgrade EKS è sempre lo stesso ciclo ripetuto per un solo minor alla volta: verifiche, control plane, nodi, add-on. Non si salta una versione e non si torna indietro.

Dove sei, e quanto ti costa restarci 1.32 — oggi standard fino a 23 mar 2026 extended · 0,60 $/h fino a 23 mar 2027 tu sei qui 1.33 standard terminato ago 2026 → anche questa è extended extended 1.34 · 1.35 · 1.36 supporto standard · 0,10 $/h — è qui che devi arrivare Strada obbligata, un salto per volta 1.32 1.33 1.34 1.35 1.36 ogni salto: control plane, poi nodi, poi add-on. Nessun rollback.

Il vincolo che ti riguarda per primo: Amazon Linux 2 finisce con 1.32. Da 1.33 in avanti AWS non pubblica più AMI EKS-optimized basate su AL2, solo AL2023. Se i tuoi tre node group sono su AL2_x86_64, il salto a 1.33 non è solo un cambio di versione: è anche un cambio di sistema operativo, con bootstrap diverso (nodeadm al posto di bootstrap.sh), cgroup v2 e default di sicurezza diversi. Verificalo prima di pianificare qualsiasi finestra.

controllo AL2 / AL2023 sui tre gruppi — da fare subito
for ng in $(aws eks list-nodegroups --cluster-name a-4510-eks-mil-cluster-p-01 --region eu-south-1 --query 'nodegroups[]' --output text); do
  aws eks describe-nodegroup --cluster-name a-4510-eks-mil-cluster-p-01 --nodegroup-name $ng --region eu-south-1 \
    --query "nodegroup.[nodegroupName,amiType,releaseVersion]" --output text
done

Fase 0 — verifiche, giorni prima

Le Upgrade Insights fanno da sole gran parte del lavoro di analisi: API deprecate ancora usate, kubelet troppo indietro, incompatibilità note. Il tuo cluster ne ha una in errore: quella va letta e risolta prima di tutto il resto.

leggere le insight, partendo da quelle non conformi
aws eks list-insights --cluster-name a-4510-eks-mil-cluster-p-01 --region eu-south-1 \
  --query 'insights[].{id:id,name:name,status:insightStatus.status,reason:insightStatus.reason}' --output table
dettaglio: quali risorse e quali client stanno usando l'API deprecata
aws eks describe-insight --cluster-name a-4510-eks-mil-cluster-p-01 --region eu-south-1 --id <insight-id> \
  --query 'insight.{name:name,desc:description,rec:recommendation,details:categorySpecificSummary.deprecationDetails}'
quali versioni sono realmente disponibili come target
aws eks describe-cluster-versions --region eu-south-1 --output table
  • IP liberi: il control plane ha bisogno di indirizzi liberi nelle subnet per creare le nuove ENI durante l'aggiornamento. Subnet sature = upgrade che fallisce a metà.
  • PDB sensati: un PDB con minAvailable uguale alle repliche blocca ogni drain. Censiscili prima, non durante.
  • Capacità per il surge: durante l'update dei nodi EKS ne aggiunge di nuovi prima di togliere i vecchi. Servono quota EC2 e IP.
  • Compatibilità applicativa: chart Helm, operator, CRD e agent (Dynatrace e simili) hanno matrici di supporto proprie.
censimento PDB rischiosi
kubectl get pdb -A -o custom-columns='NS:.metadata.namespace,NAME:.metadata.name,MIN:.spec.minAvailable,MAX:.spec.maxUnavailable,ALLOWED:.status.disruptionsAllowed'

Fase 0-bis — API deprecate: trovarle davvero

Le Upgrade Insights coprono le API rimosse nella prossima versione, ma vedono solo il traffico recente registrato dall'API server: un job che gira una volta al mese, o un chart mai applicato dopo l'ultimo deploy, non compaiono. Per questo si incrociano tre fonti: cosa il cluster ha ricevuto, cosa è installato adesso, cosa c'è nei tuoi manifest e chart.

1 · Cosa l'API server ha davvero ricevuto

contatore nativo dell'API server: ogni chiamata a un'API deprecata incrementa questa metrica
kubectl get --raw /metrics | grep apiserver_requested_deprecated_apis
lettura del risultato
apiserver_requested_deprecated_apis{group="flowcontrol.apiserver.k8s.io",removed_release="1.32",resource="flowschemas",subresource="",version="v1beta3"} 1

# group/version/resource = cosa viene chiamato
# removed_release        = la versione in cui sparisce: se e' <= al tuo target, e' bloccante

La metrica dice cosa, non chi. Per il chiamante serve l'audit log: è l'unico posto dove compare lo userAgent.

incrocio con l'audit: quale client usa quale API deprecata (richiede il log audit attivo)
aws logs start-query --region eu-south-1 \
  --log-group-name /aws/eks/a-4510-eks-mil-cluster-p-01/cluster \
  --start-time $(date -d '30 days ago' +%s) --end-time $(date +%s) \
  --query-string 'fields @timestamp, userAgent, user.username, objectRef.apiGroup, objectRef.apiVersion, objectRef.resource
                  | filter objectRef.apiVersion like /beta/
                  | stats count(*) by userAgent, objectRef.apiGroup, objectRef.apiVersion, objectRef.resource
                  | sort count desc | limit 100'
recupero del risultato (la query è asincrona)
aws logs get-query-results --region eu-south-1 --query-id <query-id>

Trenta giorni è il minimo sensato: sotto quella finestra i job mensili sfuggono. Se l'audit non è attivo da abbastanza tempo, accendilo ora e pianifica l'upgrade dopo un mese di raccolta.

2 · Cosa è installato adesso nel cluster

pluto contro le release Helm e gli oggetti live
pluto detect-helm --target-versions k8s=v1.33 -A -o wide
pluto detect-api-resources --target-versions k8s=v1.33 -o wide
kube-no-trouble sul cluster corrente
kubent --target-version 1.33 --cluster --helm3 --exit-error

--exit-error restituisce exit code diverso da zero se trova qualcosa: è il modo per infilarlo in una pipeline e bloccare una promozione.

CRD e API group non standard presenti — l'inventario da cui parte l'analisi operator
kubectl api-resources --sort-by=name -o wide | grep -v -E '^(NAME|.*\.k8s\.io|.*kubernetes\.io)' | head -60
kubectl get crd -o custom-columns='CRD:.metadata.name,GROUP:.spec.group,VERSIONS:.spec.versions[*].name,STORED:.status.storedVersions'
CRD ancora serviti su versioni beta: quelli sono i candidati a rompersi
kubectl get crd -o json | jq -r '.items[] | select(.spec.versions[].name | test("v1beta|v1alpha")) | "\(.metadata.name)\t\(.spec.versions[].name)"' | sort -u

3 · Cosa c'è nei manifest e nei chart, prima che vengano applicati

scansione statica di una directory di manifest o di un repo GitOps
pluto detect-files -d ./manifests --target-versions k8s=v1.33 -o wide
kubent --target-version 1.33 --filename ./manifests
chart Helm renderizzato prima di essere installato
helm template mia-release ./chart -f values-prod.yaml | pluto detect - --target-versions k8s=v1.33
conversione automatica di un manifest a un'API più recente
kubectl convert -f vecchio.yaml --output-version apps/v1 > nuovo.yaml

Attenzione allo storedVersion dei CRD. Un CRD può servire v1 ma avere ancora v1beta1 in status.storedVersions: finché è lì, la vecchia versione non può essere rimossa dal CRD e l'operator che la fornisce non può essere aggiornato senza una migrazione degli oggetti esistenti. Si risolve rileggendo e riscrivendo ogni oggetto di quel tipo, poi ripulendo storedVersions — procedura del vendor dell'operator, non improvvisabile.

Fase 0-ter — operator, controller e agent

Il control plane si aggiorna sempre; è tutto quello che ci sta sopra a rompersi. Ogni operator dichiara una finestra di versioni Kubernetes supportate, e su un cluster bancario ce ne sono più di quanti se ne ricordino. La regola operativa: censisci, verifica la matrice, aggiorna quelli fuori finestra prima del control plane.

censimento: tutte le release Helm con chart e versione dell'app
helm list -A -o table
censimento: ogni Deployment e DaemonSet fuori dai namespace applicativi, con l'immagine esatta
kubectl get deploy,ds -A -o custom-columns='NS:.metadata.namespace,KIND:.kind,NAME:.metadata.name,IMAGE:.spec.template.spec.containers[*].image' \
  | grep -E 'kube-system|monitoring|istio|cert-manager|argocd|external-secrets|dynatrace|operators'
censimento: operator installati via OLM, se presente
kubectl get csv -A -o custom-columns='NS:.metadata.namespace,NAME:.metadata.name,VERSION:.spec.version,PHASE:.status.phase' 2>/dev/null
Componente installato sul clusterPerché è sensibile all'upgradeCome leggi la versione
Istio
istio-base, istio-system, via Helm
Matrice di supporto stretta verso Kubernetes e webhook di injection con failurePolicy critico. L'ordine corretto è quasi sempre mesh prima, cluster dopoistioctl version · helm list -n istio-system
cert-managerConversion webhook sui propri CRD: se i pod del webhook sono giù durante il drain, ogni operazione su Certificate e Issuer fallisce, e con essa i rinnovihelm list -A | grep cert-manager
EDB Postgres for Kubernetes
postgresql-operator-system
Il più delicato dei tuoi: il drain dei nodi dbworkers provoca switchover dei cluster Postgres. Vanno verificati prima ruolo primario, stato della replica e PDB dei cluster gestitikubectl -n postgresql-operator-system get deploy -o wide · kubectl get cluster.postgresql.k8s.enterprisedb.io -A
Dynatrace Operator
ns dynatrace
DaemonSet privilegiato che legge cgroup e container runtime: il salto AL2 → AL2023 (cgroup v2) lo tocca più della versione Kuberneteskubectl -n dynatrace get deploy,ds -o wide
AWS Load Balancer Controller
elbv2.k8s.aws
CRD TargetGroupBinding e webhook propri; le versioni vecchie non conoscono le API più recenti e possono rifiutare Ingress dopo l'upgradekubectl -n kube-system get deploy aws-load-balancer-controller -o jsonpath='{..image}'
Secrets Store CSI DriverMonta segreti come volumi tramite IRSA: dopo l'upgrade un pod che non riparte spesso è un mount fallito, non un problema applicativokubectl get ds -A | grep -i secrets-store
AWS Private CA issuerEstende cert-manager: va aggiornato in coppia con lui, mai da solokubectl get deploy -A | grep -i pca
CloudWatch Observability
add-on gestito
Essendo add-on EKS ha una versione minima per ogni release Kubernetes: rientra nella Fase 2, non va dimenticato fra i "componenti terzi"aws eks describe-addon --addon-name amazon-cloudwatch-observability --cluster-name a-4510-eks-mil-cluster-p-01 --region eu-south-1
VPC CNI network policy
policyendpoints, eniconfigs
Fa parte del CNI: si aggiorna con l'add-on vpc-cni, mai separatamente e mai insieme al drain dei nodiaws eks describe-addon --addon-name vpc-cni …

Nessun OLM, nessun Prometheus Operator, nessun Karpenter e nessuna Gateway API in questo cluster: l'inventario CRD è tutto qui, e semplifica parecchio l'analisi. I componenti senza CRD — cluster-autoscaler, metrics-server, agent vari — non compaiono in kubectl get crd: vanno cercati con l'inventario Deployment/DaemonSet qui sopra.

Il gruppo dbworkers merita una fase a sé. Aggiornare quei nodi significa far ruotare i primari Postgres gestiti dall'operator EDB. Prima della finestra: verifica che ogni cluster abbia repliche in stato healthy, che il primario non sia sull'ultimo nodo da drenare, e che esista un backup valido. Durante: un nodo alla volta, con verifica dello switchover fra uno e l'altro. Se possibile, fai lo switchover controllato tu prima del drain, invece di lasciarlo provocare dall'eviction.

stato dei cluster Postgres prima di toccare i nodi
kubectl get cluster.postgresql.k8s.enterprisedb.io -A -o custom-columns='NS:.metadata.namespace,NAME:.metadata.name,INSTANCES:.spec.instances,READY:.status.readyInstances,PRIMARY:.status.currentPrimary,PHASE:.status.phase'
su quale nodo sta ogni primario
kubectl get pods -A -l role=primary -o custom-columns='NS:.metadata.namespace,POD:.metadata.name,NODE:.spec.nodeName'

Webhook: la trappola che blocca l'upgrade a metà

Un webhook ammissione con failurePolicy: Fail il cui backend non è raggiungibile rifiuta ogni scrittura sugli oggetti che intercetta. Durante il drain dei nodi succede esattamente questo: il pod del webhook viene sfrattato, e da quel momento le creazioni di pod falliscono.

webhook bloccanti e loro backend
kubectl get validatingwebhookconfigurations -o custom-columns='NAME:.metadata.name,POLICY:.webhooks[*].failurePolicy,SVC:.webhooks[*].clientConfig.service.name'
kubectl get mutatingwebhookconfigurations -o custom-columns='NAME:.metadata.name,POLICY:.webhooks[*].failurePolicy,SVC:.webhooks[*].clientConfig.service.name'

Per ognuno con Fail: il servizio dietro deve avere più repliche, anti-affinity fra nodi e un PDB coerente, altrimenti l'upgrade dei nodi si auto-blocca. È il motivo tipico di un drain che resta appeso senza un errore evidente.

Prova generale su un cluster non produttivo

Il modo affidabile per validare operator e applicazioni resta ripetere l'intera sequenza su un ambiente gemello — collaudo o pre-produzione — con gli stessi chart e la stessa versione di partenza. Le insight e pluto trovano le API rotte; solo l'esecuzione trova i webhook lenti, i PDB assurdi e gli agent che non ripartono su AL2023.

Checklist di finestra

QuandoCosa
D-30Attiva i log audit e authenticator se spenti: servono trenta giorni di traffico per fidarsi dell'analisi delle API deprecate
D-14Insight risolte · pluto/kubent puliti su cluster, manifest e chart · matrice operator verificata · amiType dei tre gruppi noto (AL2 o AL2023)
D-7Stessa sequenza eseguita in collaudo, fino ai nodi · PDB e webhook rivisti · IP liberi nelle subnet e quota EC2 verificati · backup applicativo dei volumi che contano
D-1Freeze dei deploy · fotografia dello stato: kubectl get all -A, kubectl get pv,pvc -A, versioni add-on e node group salvate su file
D0Control plane → add-on minimi → un node group alla volta, dal meno critico · verifica funzionale fra un gruppo e l'altro
D+1Nessun pod fuori posto · insight nuovamente PASSING · agent di monitoraggio che riportano · costo del cluster tornato alla tariffa standard se sei uscito dall'extended

Fase 1 — control plane

Dura tipicamente 25–40 minuti, non ha impatto sui workload in esecuzione, ma non è annullabile. Durante l'operazione l'API server resta disponibile.

avvio
aws eks update-cluster-version --name a-4510-eks-mil-cluster-p-01 --region eu-south-1 --kubernetes-version 1.33
seguirlo
aws eks describe-update --name a-4510-eks-mil-cluster-p-01 --region eu-south-1 --update-id <update-id> \
  --query 'update.{status:status,type:type,errors:errors}'
confermare a fine corsa
aws eks describe-cluster --name a-4510-eks-mil-cluster-p-01 --region eu-south-1 --query 'cluster.[version,platformVersion,status]' --output text
kubectl version

Fase 2 — add-on che devono seguire subito

Alcuni add-on hanno un minimo compatibile con la nuova versione. kube-proxy in particolare non deve restare indietro rispetto al control plane, e il CNI va allineato prima di toccare i nodi.

versione di default per la nuova release, add-on per add-on
for a in vpc-cni kube-proxy coredns aws-ebs-csi-driver; do
  echo "== $a"
  aws eks describe-addon-versions --addon-name $a --kubernetes-version 1.33 --region eu-south-1 \
    --query 'addons[0].addonVersions[?compatibilities[0].defaultVersion==`true`].addonVersion' --output text
done
aggiornare, uno per volta, verificando i pod fra uno e l'altro
aws eks update-addon --cluster-name a-4510-eks-mil-cluster-p-01 --region eu-south-1 \
  --addon-name kube-proxy --addon-version <versione> --resolve-conflicts PRESERVE
kubectl -n kube-system rollout status ds/kube-proxy

Fase 3 — node group

Qui c'è l'impatto vero: ogni nodo viene sostituito, quindi ogni pod viene sfrattato almeno una volta. EKS fa cordon, drain e terminazione nel rispetto dei PDB.

un gruppo alla volta, partendo dal meno critico
aws eks update-nodegroup-version --cluster-name a-4510-eks-mil-cluster-p-01 --region eu-south-1 \
  --nodegroup-name a-4510-eks-mil-apigwworkers-p-01 --kubernetes-version 1.33
solo AMI più recente, senza cambiare versione Kubernetes (patch di sicurezza del SO)
aws eks update-nodegroup-version --cluster-name a-4510-eks-mil-cluster-p-01 --region eu-south-1 \
  --nodegroup-name a-4510-eks-mil-mainworkers-p-01
osservare la sostituzione mentre avviene
watch -n 20 "kubectl get nodes -L eks.amazonaws.com/nodegroup --sort-by=.metadata.creationTimestamp"

--force esiste e ignora i PDB pur di completare l'aggiornamento. Su un cluster di produzione bancaria è l'ultima risorsa, non l'opzione di default: se un drain si blocca, la risposta giusta è capire quale PDB lo blocca e perché, non forzare l'eviction di un quorum.

chi sta bloccando il drain
kubectl get pdb -A | awk '$5==0'
kubectl -n <ns> describe pdb <nome>

Alternativa blue/green, quando il rollback conta più della semplicità

L'update in-place non ha marcia indietro: se la nuova AMI ha un problema, l'unico rimedio è un altro update. La strategia con rollback vero è creare un nuovo node group già alla versione di destinazione, spostarci i workload, e tenere il vecchio spento ma esistente per qualche giorno.

1. nuovo gruppo, stessa forma, versione nuova
eksctl create nodegroup --cluster a-4510-eks-mil-cluster-p-01 --region eu-south-1 \
  --name a-4510-eks-mil-mainworkers-p-02 --node-type m6i.xlarge --nodes 3 --nodes-min 3 --nodes-max 6 \
  --node-ami-family AmazonLinux2023 --version 1.33
2. svuotare il vecchio, gradualmente
kubectl cordon -l eks.amazonaws.com/nodegroup=a-4510-eks-mil-mainworkers-p-01
kubectl drain <nodo> --ignore-daemonsets --delete-emptydir-data --timeout=600s
3. eliminare il vecchio, solo dopo il periodo di osservazione
aws eks delete-nodegroup --cluster-name a-4510-eks-mil-cluster-p-01 --region eu-south-1 \
  --nodegroup-name a-4510-eks-mil-mainworkers-p-01

Fase 4 — chiusura

nessun nodo rimasto indietro
kubectl get nodes -o custom-columns='NODE:.metadata.name,KUBELET:.status.nodeInfo.kubeletVersion,OS:.status.nodeInfo.osImage,RUNTIME:.status.nodeInfo.containerRuntimeVersion'
niente di rotto in giro
kubectl get pods -A --field-selector=status.phase!=Running,status.phase!=Succeeded
kubectl get events -A --sort-by=.lastTimestamp | tail -30
insight ripulite e supporto tornato standard
aws eks list-insights --cluster-name a-4510-eks-mil-cluster-p-01 --region eu-south-1 \
  --query 'insights[?insightStatus.status!=`PASSING`]' --output table

Politica di supporto, in una riga. 14 mesi di supporto standard per ogni minor, poi 12 mesi di extended a costo sestuplicato, poi upgrade automatico da parte di AWS. Con un rilascio ogni quattro mesi circa, una cadenza di upgrade trimestrale o semestrale è ciò che tiene il cluster dentro la finestra standard senza corse.

16 Troubleshooting — dal sintomo alla causa

Pod in ImagePullBackOff

MessaggioCausaVerifica
403 Forbidden da ECRIl node role non ha i permessi di pull, o manca la repository policy cross-accountaws iam list-attached-role-policies --role-name <node-role>
no basic auth credentialsRegistry esterno senza imagePullSecretkubectl get sa default -n <ns> -o yaml
Timeout / i/o timeoutNodi in subnet private senza NAT o senza VPC endpoint per ecr.dkr, ecr.api, s3aws ec2 describe-vpc-endpoints --region eu-south-1
manifest unknownTag inesistente, o architettura sbagliata (arm64 vs amd64)aws ecr describe-images --repository-name <repo>
l'errore vero sta negli eventi, non nello stato del pod
kubectl describe pod <pod> -n <ns> | sed -n '/Events/,$p'

Pod Pending

il motivo è sempre in fondo al describe
kubectl describe pod <pod> -n <ns> | grep -A15 Events
Messaggio dello schedulerSignificato su EKS
Insufficient cpu / memoryManca capacità: o scali il node group, o le requests sono sovradimensionate
Too many podsHai colpito il tetto max pods dell'istanza, non un limite di CPU. Prefix delegation o istanze più grandi
node(s) had untolerated taintTaint del node group senza toleration nel workload
had volume node affinity conflictIl volume EBS è in una AZ e i nodi liberi in un'altra
didn't find available persistent volumesPVC senza StorageClass valida, o EBS CSI senza permessi IRSA

Il pod non riesce a chiamare un'API AWS

Sequenza di verifica in tre comandi: prima le variabili iniettate, poi l'identità effettiva, poi il permesso.

1 · il webhook ha iniettato le variabili?
kubectl -n <ns> exec <pod> -- env | grep AWS_ROLE_ARN
2 · chi sono, per AWS?
kubectl -n <ns> exec <pod> -- aws sts get-caller-identity
3 · il ruolo ha il permesso?
aws iam simulate-principal-policy --policy-source-arn <role-arn> --action-names s3:GetObject --resource-arns arn:aws:s3:::bucket/key

Se al punto 1 non c'è nulla: annotation mancante sul SA, o pod creato prima dell'annotation (ricrealo). Se al punto 2 esce il node role: il pod non sta usando quel ServiceAccount. Se fallisce il 3: è un problema di permission policy, non di IRSA.

Nodo NotReady

condizioni del nodo e pressioni
kubectl describe node <nodo> | grep -A12 Conditions
stato di salute che EKS conosce già del node group
aws eks describe-nodegroup --cluster-name a-4510-eks-mil-cluster-p-01 --nodegroup-name <ng> --region eu-south-1 --query 'nodegroup.health'
sull'istanza, via SSM (niente SSH e niente bastion)
aws ssm start-session --target i-0abc123 --region eu-south-1
sudo journalctl -u kubelet -n 200 --no-pager
sudo journalctl -u containerd -n 100 --no-pager

Cause ricorrenti: disco pieno (DiskPressure per immagini mai ripulite), kubelet che non raggiunge l'endpoint dell'API in private-only, oppure il nodo che non ha permessi per registrarsi — access entry mancante per il node role dopo un cambio di autenticazione.

Ingress ALB non nasce

i log del controller dicono sempre il perché
kubectl -n kube-system logs deploy/aws-load-balancer-controller --tail=100 | grep -i error
  • Subnet senza tag kubernetes.io/role/elb o internal-elb → il controller non trova dove metterlo.
  • Ruolo IRSA del controller senza i permessi elasticloadbalancing:* necessari.
  • Meno di due subnet in AZ diverse: un ALB ne pretende almeno due.
  • ingressClassName non impostato e nessuna classe di default.

Kubectl non funziona

ErroreRimedio
You must be logged in to the server (Unauthorized)Il tuo ARN non è mappato: access entry o aws-auth. Verifica anche di stare usando il profilo AWS giusto.
error: You must be logged in… (401) dopo un cambio profiloaws eks update-kubeconfig di nuovo, il kubeconfig incapsula il profilo
Unable to connect to the server: dial tcp i/o timeoutEndpoint privato e tu sei fuori dalla VPC, oppure il tuo IP non è nelle publicAccessCidrs
forbidden: User "…" cannot list resourceAutenticato ma senza RBAC: manca l'access policy o il binding

17 Comandi rapidi

Ricognizione di un cluster che non conosci

i cinque comandi con cui apri qualsiasi cluster EKS sconosciuto
aws eks list-clusters --region eu-south-1 --output table
aws eks describe-cluster --name <cluster> --region eu-south-1 --query 'cluster.{v:version,pf:platformVersion,status:status,ep:resourcesVpcConfig.endpointPublicAccess,role:roleArn}'
aws eks list-nodegroups --cluster-name <cluster> --region eu-south-1 --output table
aws eks list-addons --cluster-name <cluster> --region eu-south-1 --output table
aws eks list-insights --cluster-name <cluster> --region eu-south-1 --query 'insights[?insightStatus.status!=`PASSING`]' --output table
ComandoCosa ti dice
aws sts get-caller-identityCon quale identità stai operando adesso
aws eks update-kubeconfig --name … --region … --alias …Aggiunge il contesto al kubeconfig
aws eks describe-update --name … --update-id …Stato di un upgrade in corso
aws eks list-access-entries --cluster-name …Chi può autenticarsi al cluster
eksctl get cluster --region eu-south-1Vista sintetica, più leggibile della CLI nativa
eksctl get nodegroup --cluster … --region …Node group con versioni e scaling in una tabella
eksctl utils describe-stacks --cluster …Stack CloudFormation dietro al cluster, se creato con eksctl
kubectl get nodes -L eks.amazonaws.com/nodegroupNodi con il gruppo di appartenenza
kubectl get events -A --sort-by=.lastTimestampCronologia di ciò che è successo davvero
kubectl top nodesConsumo reale contro allocato (serve metrics-server)

One-liner che tornano utili

pod non Running su tutto il cluster
kubectl get pods -A --field-selector=status.phase!=Running,status.phase!=Succeeded
top 20 pod per restart
kubectl get pods -A --sort-by=.status.containerStatuses[0].restartCount -o wide | tail -20
requests totali per nodo contro allocatable
kubectl describe nodes | grep -A5 "Allocated resources"
tutti i ServiceAccount con annotation IRSA
kubectl get sa -A -o json | jq -r '.items[] | select(.metadata.annotations."eks.amazonaws.com/role-arn") | "\(.metadata.namespace)\t\(.metadata.name)\t\(.metadata.annotations."eks.amazonaws.com/role-arn")"'
immagini in uso, per capacità di pull e inventario
kubectl get pods -A -o jsonpath='{range .items[*]}{range .spec.containers[*]}{.image}{"\n"}{end}{end}' | sort | uniq -c | sort -rn

18 Glossario

SiglaPer esteso, e cosa significa in pratica
EKSElastic Kubernetes Service — il control plane Kubernetes gestito da AWS
ECRElastic Container Registry — il registry immagini dell'account, con login temporaneo via IAM
VPCVirtual Private Cloud — la rete privata dove vivono nodi e pod
IAMIdentity and Access Management — identità e permessi sulle API AWS
ARNAmazon Resource Name — l'indirizzo univoco di una risorsa
IRSAIAM Roles for Service Accounts — credenziali AWS per singolo pod via OIDC
STSSecurity Token Service — il servizio che emette le credenziali temporanee
OIDCOpenID Connect — il protocollo con cui AWS si fida dei token emessi dal cluster
SGSecurity Group — firewall stateful a livello di ENI
ENIElastic Network Interface — la scheda di rete virtuale che porta gli IP dei pod
ASGAuto Scaling Group — il gruppo EC2 sotto ogni managed node group
AMIAmazon Machine Image — l'immagine del sistema operativo del nodo
IMDSInstance Metadata Service — l'endpoint 169.254.169.254 da cui un'istanza legge le proprie credenziali
ALB / NLBApplication / Network Load Balancer — livello 7 e livello 4
CSIContainer Storage Interface — lo standard con cui Kubernetes parla con lo storage
SSMSystems Manager — l'accesso a shell sulle istanze senza SSH né bastion

19 Schema d'insieme

La mappa che hai già in mano, riletta con quello che c'è sopra. Serve come vista d'insieme: utente → ALB → pod, con IAM e IRSA a lato e il pull da ECR verso i worker.

Architettura completa di un laboratorio EKS con VPC, subnet pubblica e privata, ALB, node group, ECR, IAM e IRSA
Schema di riferimento — VPC 10.0.0.0/16, subnet pubblica per l'ALB, subnet privata per i nodi, IRSA per i pod, pull immagini da ECR.

Due precisazioni rispetto allo schema

  • Il gruppo SG-PODS disegnato come un livello a sé esiste solo se abiliti i security group per pod (ENABLE_POD_ENI). Senza quella funzione, i pod ereditano i security group dell'ENI del nodo: il traffico verso ECR e verso i database esce con le regole di SG-NODES.
  • Il pull delle immagini nello schema passa dal nodo, e questo è corretto: le credenziali usate sono quelle del node role, non di IRSA. IRSA vale per le chiamate che fa il processo dentro il container, non per il download dell'immagine — è una distinzione che risolve metà dei ticket "ho dato i permessi ma continua a non scaricare".
risorsa o servizio AWS oggetto Kubernetes identità e permessi rete

Dati del cluster letti dalla console il 2 settembre 2026. Versioni, prezzi e finestre di supporto cambiano: prima di una finestra di upgrade riverifica con aws eks describe-cluster-versions.