AWS EKS: Running Kubernetes on AWS — Complete Guide (2026)
Amazon Elastic Kubernetes Service (EKS) is the managed Kubernetes offering from AWS. It takes care of the control plane — etcd, API server, controller manager — so you can focus on running workloads. This guide walks you through everything from spinning up your first cluster to production-grade patterns: IRSA, cluster autoscaler, the AWS Load Balancer Controller, and a safe upgrade strategy.
Table of Contents
Creating an EKS Cluster with eksctl
eksctl is the official CLI for EKS, built by Weaveworks and adopted by AWS. It translates a simple YAML config into a full CloudFormation stack that provisions the VPC, subnets, security groups, and control plane.
Install eksctl and configure your AWS credentials first:
# macOS
brew tap weaveworks/tap
brew install weaveworks/tap/eksctl
# Verify
eksctl version
aws configure # or use AWS SSO
Create a production-ready cluster config file:
# cluster.yaml
apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig
metadata:
name: prod-cluster
region: us-east-1
version: "1.30"
iam:
withOIDC: true # required for IRSA
vpc:
cidr: 10.0.0.0/16
nat:
gateway: HighlyAvailable # one NAT GW per AZ
managedNodeGroups:
- name: ng-general
instanceType: m6i.xlarge
minSize: 2
maxSize: 10
desiredCapacity: 3
privateNetworking: true
labels:
role: general
tags:
k8s.io/cluster-autoscaler/enabled: "true"
k8s.io/cluster-autoscaler/prod-cluster: "owned"
iam:
attachPolicyARNs:
- arn:aws:iam::aws:policy/AmazonEKSWorkerNodePolicy
- arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly
- arn:aws:iam::aws:policy/AmazonEKS_CNI_Policy
addons:
- name: vpc-cni
version: latest
- name: coredns
version: latest
- name: kube-proxy
version: latest
eksctl create cluster -f cluster.yaml
# Update kubeconfig once cluster is ready
aws eks update-kubeconfig --name prod-cluster --region us-east-1
# Verify
kubectl get nodes
withOIDC: true during cluster creation. Adding it later requires recreating the OIDC provider and re-annotating service accounts.
Managed vs Self-Managed Node Groups
EKS gives you two options for worker nodes. The difference matters for patching and operational overhead.
| Feature | Managed Node Groups | Self-Managed |
|---|---|---|
| OS patching | AWS rolls AMI updates | You manage AMIs |
| Drain on termination | Automatic graceful drain | Manual or custom scripts |
| Launch templates | Supported | Supported |
| Custom AMI | Supported (AL2, Bottlerocket) | Full control |
| Spot support | Yes, via capacity type | Yes |
| Use case | Most workloads | Deep OS customization |
For most teams, managed node groups are the right choice. Use self-managed nodes only if you need a custom kernel module or a non-standard AMI.
Adding a Spot node group for cost savings:
managedNodeGroups:
- name: ng-spot
instanceTypes:
- m6i.xlarge
- m5.xlarge
- m5a.xlarge
spot: true
minSize: 0
maxSize: 20
desiredCapacity: 0
labels:
lifecycle: spot
taints:
- key: spot
value: "true"
effect: NoSchedule
Fargate Profiles
Fargate lets you run pods without managing EC2 instances. AWS provisions the underlying compute per pod, bills by vCPU and memory, and patches the infrastructure for you.
# Add a Fargate profile for the "batch" namespace
eksctl create fargateprofile \
--cluster prod-cluster \
--name batch-profile \
--namespace batch \
--labels workload=batch
Or in the cluster YAML:
fargateProfiles:
- name: fp-batch
selectors:
- namespace: batch
labels:
workload: batch
Fargate is ideal for: batch jobs, low-traffic microservices where you want zero idle EC2 cost, and dev/staging environments.
IAM Roles for Service Accounts (IRSA)
IRSA is the correct way to give pods AWS permissions. Each pod assumes an IAM role via a projected service account token — no more node-level instance profiles or storing AWS credentials in environment variables.
Step 1: Create the IAM policy and role:
# Create a policy for S3 read access
aws iam create-policy \
--policy-name MyAppS3ReadPolicy \
--policy-document '{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Action": ["s3:GetObject", "s3:ListBucket"],
"Resource": [
"arn:aws:s3:::my-app-bucket",
"arn:aws:s3:::my-app-bucket/*"
]
}]
}'
# Create the service account and role in one shot
eksctl create iamserviceaccount \
--cluster prod-cluster \
--namespace default \
--name my-app-sa \
--attach-policy-arn arn:aws:iam::123456789012:policy/MyAppS3ReadPolicy \
--approve
Step 2: Reference the service account in your deployment:
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
template:
spec:
serviceAccountName: my-app-sa # IRSA kicks in here
containers:
- name: app
image: my-app:latest
env:
- name: AWS_REGION
value: us-east-1
The pod will automatically get temporary credentials via the AWS SDK's default credential chain. No AWS_ACCESS_KEY_ID needed.
EKS Add-ons
EKS manages three critical in-cluster components as versioned add-ons: vpc-cni (pod networking), coredns (DNS), and kube-proxy (iptables rules). Starting with EKS 1.25, you can also manage the EBS CSI driver and EFS CSI driver as add-ons.
# List available add-on versions for Kubernetes 1.30
aws eks describe-addon-versions \
--kubernetes-version 1.30 \
--query 'addons[].{Name:addonName,Latest:addonVersions[0].addonVersion}' \
--output table
# Update vpc-cni to latest
aws eks update-addon \
--cluster-name prod-cluster \
--addon-name vpc-cni \
--resolve-conflicts OVERWRITE
# Install EBS CSI driver (required for PersistentVolumes on EBS)
eksctl create iamserviceaccount \
--cluster prod-cluster \
--namespace kube-system \
--name ebs-csi-controller-sa \
--attach-policy-arn arn:aws:iam::aws:policy/service-role/AmazonEBSCSIDriverPolicy \
--approve
aws eks create-addon \
--cluster-name prod-cluster \
--addon-name aws-ebs-csi-driver \
--service-account-role-arn arn:aws:iam::123456789012:role/AmazonEKS_EBS_CSI_DriverRole
Cluster Autoscaler
The Cluster Autoscaler watches for pending pods (unschedulable due to insufficient resources) and scales up node groups. It also scales down underutilized nodes after a configurable delay.
# cluster-autoscaler.yaml (key parts)
apiVersion: apps/v1
kind: Deployment
metadata:
name: cluster-autoscaler
namespace: kube-system
spec:
template:
spec:
serviceAccountName: cluster-autoscaler
containers:
- name: cluster-autoscaler
image: registry.k8s.io/autoscaling/cluster-autoscaler:v1.30.0
command:
- ./cluster-autoscaler
- --v=4
- --stderrthreshold=info
- --cloud-provider=aws
- --skip-nodes-with-local-storage=false
- --expander=least-waste
- --node-group-auto-discovery=asg:tag=k8s.io/cluster-autoscaler/enabled,k8s.io/cluster-autoscaler/prod-cluster
- --balance-similar-node-groups
- --skip-nodes-with-system-pods=false
env:
- name: AWS_REGION
value: us-east-1
--expander=least-waste in production to prefer the node type that wastes the least CPU/memory. Use --expander=priority if you want Spot groups prioritized over On-Demand.
The autoscaler requires the node groups to be tagged with k8s.io/cluster-autoscaler/enabled: true and k8s.io/cluster-autoscaler/CLUSTER_NAME: owned — which the eksctl config above already includes.
AWS Load Balancer Controller
The AWS Load Balancer Controller creates Application Load Balancers (ALB) from Kubernetes Ingress objects and Network Load Balancers (NLB) from Service objects of type LoadBalancer. It replaced the older AWS ALB Ingress Controller.
# Install via Helm
helm repo add eks https://aws.github.io/eks-charts
helm repo update
# Create IRSA for the controller first
eksctl create iamserviceaccount \
--cluster prod-cluster \
--namespace kube-system \
--name aws-load-balancer-controller \
--attach-policy-arn arn:aws:iam::123456789012:policy/AWSLoadBalancerControllerIAMPolicy \
--approve
helm install aws-load-balancer-controller eks/aws-load-balancer-controller \
-n kube-system \
--set clusterName=prod-cluster \
--set serviceAccount.create=false \
--set serviceAccount.name=aws-load-balancer-controller
Ingress that creates an ALB:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-app-ingress
annotations:
kubernetes.io/ingress.class: alb
alb.ingress.kubernetes.io/scheme: internet-facing
alb.ingress.kubernetes.io/target-type: ip
alb.ingress.kubernetes.io/listen-ports: '[{"HTTPS":443}]'
alb.ingress.kubernetes.io/certificate-arn: arn:aws:acm:us-east-1:123456789012:certificate/abc
alb.ingress.kubernetes.io/healthcheck-path: /health
spec:
rules:
- host: api.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: my-app
port:
number: 8080
EKS Upgrade Strategy
EKS minor version upgrades (e.g., 1.29 → 1.30) are in-place for the control plane but require rolling updates of node groups. AWS supports each minor version for approximately 14 months.
Safe upgrade sequence:
# Step 1: Upgrade the control plane
aws eks update-cluster-version \
--name prod-cluster \
--kubernetes-version 1.30
# Wait for control plane update to complete (~15 min)
aws eks describe-cluster --name prod-cluster \
--query 'cluster.status'
# Step 2: Update EKS add-ons to versions compatible with 1.30
aws eks update-addon --cluster-name prod-cluster --addon-name vpc-cni --resolve-conflicts OVERWRITE
aws eks update-addon --cluster-name prod-cluster --addon-name coredns --resolve-conflicts OVERWRITE
aws eks update-addon --cluster-name prod-cluster --addon-name kube-proxy --resolve-conflicts OVERWRITE
# Step 3: Update node group AMI (triggers rolling replacement)
eksctl upgrade nodegroup \
--cluster prod-cluster \
--name ng-general \
--kubernetes-version 1.30
Before any upgrade, run kubectl get events --field-selector type=Warning --all-namespaces and check the AWS deprecation insights in the EKS console for API removals that may break your workloads.
Frequently Asked Questions
The EKS control plane costs $0.10/hour per cluster (~$73/month). You also pay for the underlying EC2 instances or Fargate compute. For Fargate, pricing is $0.04048/vCPU-hour and $0.004445/GB-hour.
Use EKS if: your team knows Kubernetes, you need portability across cloud providers, or you're running a large microservices platform. Use ECS if: you want simpler operations, your team is AWS-focused, and you don't need Kubernetes-specific features. See our ECS guide for a detailed comparison.
Yes. Helm works with any Kubernetes cluster. Configure kubectl for your EKS cluster with aws eks update-kubeconfig and Helm will use that context automatically.
EKS Pod Identity (GA in late 2023) is a newer, simpler alternative to IRSA. It doesn't require OIDC configuration and is easier to manage at scale. For new clusters on EKS 1.24+, prefer Pod Identity. IRSA still works and is valid for existing setups.
Create a dedicated IAM role for CI/CD (e.g., GitHubActionsRole), add it to the EKS aws-auth ConfigMap with eksctl create iamidentitymapping, and use the aws eks update-kubeconfig command in your pipeline after assuming that role.