The Complete Guide to Amazon EKS in 2026: Setup, Costs, and Best Practices
DevOps / SRE Engineer & CEO, Cloud DevOps Solutions
Amazon EKS is the managed Kubernetes control plane on AWS. AWS runs the API server, etcd, and controller manager; you manage worker nodes, networking, and workloads. In 2026, EKS runs Kubernetes 1.29-1.31 and charges $0.10/hour per cluster for the control plane, regardless of node count.
Architecture overview
An EKS cluster consists of:
- Control plane, AWS-managed, multi-AZ, automatically patched
- Data plane, EC2 node groups, Fargate profiles, or EKS Auto Mode
- Networking, VPC CNI (one IP per pod), CoreDNS, kube-proxy
- IAM integration, IRSA maps K8s service accounts to IAM roles without static credentials
┌─────────────────────────────────────────────────┐
│ AWS-managed control plane (across 3 AZs) │
│ API Server │ etcd │ Scheduler │ Controllers │
└──────────────────────┬──────────────────────────┘
│ kubelet
┌──────────────┼──────────────┐
▼ ▼ ▼
Node Group A Node Group B Fargate Profile
(On-Demand) (Spot) (serverless pods)
Creating a cluster
With eksctl (fastest)
# Install eksctl
brew tap weaveworks/tap && brew install weaveworks/tap/eksctl
# Create cluster with managed node group
eksctl create cluster \
--name prod-cluster \
--region us-east-1 \
--nodegroup-name workers \
--node-type m5.xlarge \
--nodes 3 \
--nodes-min 2 \
--nodes-max 10 \
--managed \
--with-oidc \
--ssh-access \
--ssh-public-key my-key
With Terraform (recommended for production)
module "eks" {
source = "terraform-aws-modules/eks/aws"
version = "~> 20.0"
cluster_name = "prod-cluster"
cluster_version = "1.31"
vpc_id = module.vpc.vpc_id
subnet_ids = module.vpc.private_subnets
control_plane_subnet_ids = module.vpc.intra_subnets
cluster_endpoint_public_access = true
eks_managed_node_groups = {
workers = {
min_size = 2
max_size = 10
desired_size = 3
instance_types = ["m5.xlarge"]
capacity_type = "ON_DEMAND"
}
spot = {
min_size = 0
max_size = 20
desired_size = 0
instance_types = ["m5.xlarge", "m5a.xlarge", "m4.xlarge"]
capacity_type = "SPOT"
}
}
}
Node groups vs Fargate
| Feature | Managed Node Groups | Fargate |
|---|---|---|
| Billing | Per EC2 instance | Per vCPU/GB per second |
| AMI patching | Semi-automated (you trigger) | Fully automatic |
| DaemonSets | Supported | Not supported |
| GPUs / large memory | Available | Limited |
| Startup latency | Fast (pre-provisioned) | Slow (15-90s cold start) |
| Best for | General workloads, stateful apps | Batch jobs, burst workloads |
Recommendation: Use managed node groups for your base capacity and Fargate for ephemeral jobs (CI runners, data pipelines).
Networking: VPC CNI
EKS uses the AWS VPC CNI plugin, which assigns real VPC IPs to pods. This simplifies security groups and routing but consumes IP space fast.
# Check available IPs per node
kubectl describe node ip-10-0-1-100.ec2.internal | grep Allocatable -A5
Key configuration:
# Increase ENI prefix delegation (more IPs per node)
kubectl set env daemonset aws-node \
-n kube-system \
ENABLE_PREFIX_DELEGATION=true \
WARM_PREFIX_TARGET=1
For large clusters, use prefix delegation to get 16 IPs per ENI slot instead of 1.
IAM for Service Accounts (IRSA)
IRSA lets pods assume IAM roles without static credentials. This is the correct way to grant AWS API access to workloads.
# Enable OIDC provider (do this once per cluster)
eksctl utils associate-iam-oidc-provider \
--cluster prod-cluster \
--approve
# Create IAM role for a service account
eksctl create iamserviceaccount \
--cluster prod-cluster \
--namespace my-app \
--name my-app-sa \
--attach-policy-arn arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess \
--approve
# Annotate the service account
apiVersion: v1
kind: ServiceAccount
metadata:
name: my-app-sa
namespace: my-app
annotations:
eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/my-app-role
Critical add-ons
Install these on every production EKS cluster:
# EBS CSI driver (for PersistentVolumes)
eksctl create addon --name aws-ebs-csi-driver --cluster prod-cluster
# AWS Load Balancer Controller
helm repo add eks https://aws.github.io/eks-charts
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
# Cluster Autoscaler
helm install cluster-autoscaler autoscaler/cluster-autoscaler \
-n kube-system \
--set autoDiscovery.clusterName=prod-cluster \
--set awsRegion=us-east-1
Cost breakdown (us-east-1, 2026)
| Component | Cost |
|---|---|
| Control plane | $0.10/hr = ~$73/month |
| m5.xlarge On-Demand | $0.192/hr each |
| m5.xlarge Spot | ~$0.06/hr each (saves ~70%) |
| EBS gp3 100GB | ~$8/month |
| NAT Gateway | $0.045/hr + $0.045/GB processed |
| Data transfer out | First 100GB free, then $0.09/GB |
A typical 3-node cluster (m5.xlarge On-Demand) costs ~$140-200/month plus data transfer.
Security hardening checklist
# 1. Enable audit logging
aws eks update-cluster-config \
--name prod-cluster \
--logging '{"clusterLogging":[{"types":["audit","api","authenticator"],"enabled":true}]}'
# 2. Restrict public API access to your IPs
aws eks update-cluster-config \
--name prod-cluster \
--resources-vpc-config endpointPublicAccess=true,publicAccessCidrs="203.0.113.0/32"
# 3. Enable secrets encryption
aws eks create-cluster ... \
--encryption-config resources=secrets,provider.keyArn=arn:aws:kms:...
# 4. Network policies, deny all by default, allow explicitly
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
namespace: production
spec:
podSelector: {}
policyTypes: [Ingress, Egress]
Upgrade strategy
EKS supports N-2 Kubernetes versions. AWS drops support for older versions with ~14 months notice.
# 1. Update control plane (zero-downtime)
aws eks update-cluster-version \
--name prod-cluster \
--kubernetes-version 1.31
# 2. Update managed node group (rolling)
aws eks update-nodegroup-version \
--cluster-name prod-cluster \
--nodegroup-name workers
# 3. Update add-ons
eksctl update addon --name aws-ebs-csi-driver --cluster prod-cluster
Always test in a staging cluster first. The biggest breakages come from deprecated API versions, run kubent before upgrading.
# Check for deprecated APIs
kubectl kube-no-trouble # or: kubent
Key takeaways
- EKS charges $0.10/hr for the control plane; your real cost is worker nodes and data transfer.
- Use Terraform with the
terraform-aws-modules/eksmodule for reproducible clusters. - Enable IRSA immediately, never use static IAM credentials in pods.
- Prefix delegation lets you run more pods per node without adding nodes.
- Install the EBS CSI driver, ALB controller, and Cluster Autoscaler on every production cluster.
- Test upgrades in staging with
kubentto catch deprecated API usage before it breaks production.