Amazon EKS — VPC Design for EKS
A well-designed VPC is the foundation of your EKS cluster. Poor VPC design leads to IP exhaustion, security gaps, and operational headaches. Let’s get it right from the start.
Reference Architecture
The recommended VPC architecture for EKS:
VPC (10.0.0.0/16 — 65,536 IPs)
├── Public Subnets (3 AZs)
│ ├── 10.0.101.0/24 (AZ-a) — NAT GW, ALB, bastion
│ ├── 10.0.102.0/24 (AZ-b) — NAT GW, ALB
│ └── 10.0.103.0/24 (AZ-c) — NAT GW, ALB
├── Private Subnets (3 AZs)
│ ├── 10.0.1.0/24 (AZ-a) — Worker nodes, pods
│ ├── 10.0.2.0/24 (AZ-b) — Worker nodes, pods
│ └── 10.0.3.0/24 (AZ-c) — Worker nodes, pods
└── Intra Subnets (optional, 3 AZs)
├── 10.0.51.0/24 (AZ-a) — EKS control plane ENIs
├── 10.0.52.0/24 (AZ-b) — EKS control plane ENIs
└── 10.0.53.0/24 (AZ-c) — EKS control plane ENIs
Subnet Types
| Subnet Type | Route to Internet | Use Case |
|---|---|---|
| Public | Direct (via Internet Gateway) | NAT Gateways, internet-facing ALBs, bastion hosts |
| Private | Via NAT Gateway | Worker nodes, pods, internal ALBs |
| Intra | No internet route | EKS control plane ENIs (maximum isolation) |
Subnet Sizing
VPC CNI assigns a VPC IP to every pod. This means you need more IPs than a typical VPC setup.
How Many IPs Do You Need?
Total IPs needed = (Number of nodes) + (Max pods per node × Number of nodes)
For example, 10 nodes with 58 pods each:
10 + (58 × 10) = 590 IPs minimum
Recommended Subnet Sizes
| Subnet Size | IPs Available | Good For |
|---|---|---|
| /24 | 251 usable | Small clusters (dev/test) |
| /22 | 1,019 usable | Medium clusters |
| /20 | 4,091 usable | Large clusters |
| /19 | 8,187 usable | Very large clusters |
Tip: Use /19 or /20 for private subnets in production. IP exhaustion is the most common VPC issue in EKS.
Subnet Tagging
EKS and the AWS Load Balancer Controller require specific subnet tags for auto-discovery:
All EKS Subnets
kubernetes.io/cluster/<cluster-name> = shared
Public Subnets (Internet-Facing Load Balancers)
kubernetes.io/role/elb = 1
Private Subnets (Internal Load Balancers and Nodes)
kubernetes.io/role/internal-elb = 1
Terraform Example
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "~> 5.0"
name = "eks-vpc"
cidr = "10.0.0.0/16"
azs = ["us-east-1a", "us-east-1b", "us-east-1c"]
private_subnets = ["10.0.0.0/19", "10.0.32.0/19", "10.0.64.0/19"]
public_subnets = ["10.0.96.0/24", "10.0.97.0/24", "10.0.98.0/24"]
enable_nat_gateway = true
single_nat_gateway = false # HA: one per AZ
enable_dns_hostnames = true
public_subnet_tags = {
"kubernetes.io/role/elb" = 1
"kubernetes.io/cluster/my-cluster" = "shared"
}
private_subnet_tags = {
"kubernetes.io/role/internal-elb" = 1
"kubernetes.io/cluster/my-cluster" = "shared"
}
}
NAT Gateway Patterns
| Pattern | Cost | HA | Use Case |
|---|---|---|---|
| Single NAT Gateway | ~$33/mo | No | Dev/test only |
| NAT Gateway per AZ | ~$99/mo (3 AZs) | Yes | Production |
| No NAT Gateway | $0 | — | Fully private (VPC endpoints only) |
When Nodes Need Outbound Internet
Nodes in private subnets need a NAT Gateway to:
- Pull container images from Docker Hub or other public registries
- Call AWS APIs (unless VPC endpoints are configured)
- Access external services
Reducing NAT Gateway Costs
- Use VPC endpoints for ECR, S3, STS, CloudWatch (avoid routing AWS traffic through NAT)
- Pull images from ECR (free data transfer via VPC endpoint)
- Use a single NAT Gateway for non-production environments
Multi-AZ Design
Always deploy across at least 3 Availability Zones for production:
- Worker nodes should be spread across 3 AZs
- Each AZ gets its own private and public subnet
- Pod Disruption Budgets (PDBs) ensure pods survive AZ failures
- Use topology spread constraints for even distribution
topologySpreadConstraints:
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
app: my-app
VPC Endpoints for EKS
Reduce NAT Gateway dependency by creating VPC endpoints:
| Service | Endpoint Type | Purpose |
|---|---|---|
com.amazonaws.region.ec2 |
Interface | EC2 API calls |
com.amazonaws.region.ecr.api |
Interface | ECR API |
com.amazonaws.region.ecr.dkr |
Interface | ECR Docker pull |
com.amazonaws.region.s3 |
Gateway | S3 (ECR image layers) |
com.amazonaws.region.sts |
Interface | STS (for IRSA tokens) |
com.amazonaws.region.logs |
Interface | CloudWatch Logs |
com.amazonaws.region.elasticloadbalancing |
Interface | ELB API |
What’s Next?
Let’s understand how the VPC CNI plugin assigns IP addresses to pods.