Production EKS Cluster with Terraform - VPC, Node Groups & IRSA from Scratch

Project Links

Tech Stack

TerraformAWS EKSKubernetesAWS VPCHelmIRSACluster AutoscalerAWS Load Balancer Controller
Infrastructure

Production EKS Cluster with Terraform - VPC, Node Groups & IRSA from Scratch

A production-grade EKS cluster provisioned with Terraform — Custom VPC with private subnets across 3 AZs, separate system and application node groups, VPC endpoints for private AWS traffic, AWS Load Balancer Controller, and Cluster Autoscaler. The infrastructure foundation for all Kubernetes deployments.

Why I built this: After setting up EKS clusters manually a few times and repeatedly forgetting steps, I built a repeatable, fully codified version — everything as Terraform, nothing as tribal knowledge.

Built in two parts:

  • Part 1 — Networking: VPC, public/private subnets across 3 AZs, NAT Gateways (one per AZ), Internet Gateway, route tables. Each environment (dev/staging/production) gets its own CIDR range and its own Terraform state file in S3.

  • Part 2 — EKS Cluster: takes VPC/subnet IDs from Part 1, deploys the cluster into private subnets with system and application node groups, IRSA, AWS Load Balancer Controller, and Cluster Autoscaler.

What's included:

  • VPC module — 3 AZs, private subnets for nodes, public subnets for load balancers, NAT gateways, required EKS subnet tags

  • EKS cluster — public + private endpoint access, IRSA enabled

  • Node groups — system (t3.medium, on-demand) and application (m6i.large, Spot-configurable)

  • AWS Load Balancer Controller — Helm install with required IAM role

  • Cluster Autoscaler — scales node groups based on pending pods

  • Remote state — S3 backend with DynamoDB locking

EKS Terraform Foundation full architecture diagram

Built in Two Parts

Part 1 — Networking provisions the VPC, public and private subnets across 3 AZs, NAT Gateways (one per AZ), Internet Gateway, and route tables. Each environment (dev, staging, production) gets its own CIDR range and its own Terraform state file in S3.

Part 2 — EKS Cluster takes the VPC and subnet IDs from Part 1 as inputs and deploys the cluster into the private subnets. Separate system and application node groups, IRSA for pod-level IAM, AWS Load Balancer Controller, and Cluster Autoscaler.

Key architectural decisions:

  • One NAT Gateway per AZ — a shared NAT Gateway is a single point of failure; if that AZ goes down, nodes in other AZs lose internet access and can't pull images. One per AZ contains the blast radius to a single AZ.

  • Separate system and application node groups — CoreDNS, kube-proxy, and the Load Balancer Controller run on tainted system nodes (dedicated=system:NoSchedule), so a traffic spike on app workloads can't evict CoreDNS and break cluster DNS.

  • IRSA over node instance profiles — each add-on gets its own IAM role via the cluster OIDC provider, so a compromised pod can't access permissions it was never assigned.

  • Separate Terraform state per environment — dev, staging, and production each write to a different S3 key; destroying dev has zero impact on production.

How to use it:

git clone https://github.com/TisigheLivinstone/eks-terraform-foundation
cd eks-terraform-foundation

# Create the state backend first (one time)
cd bootstrap && terraform init && terraform apply

# Then create the cluster
cd ../environments/production
terraform init
terraform plan
terraform apply
aws eks update-kubeconfig --name production --region eu-west-1
kubectl get nodes
kubectl get pods -A

What I learned: Subnet tags are not optional — EKS needs specific tags to know which subnets to use for load balancers and nodes. Missing them on the first attempt cost two hours of debugging why the Load Balancer Controller wasn't provisioning anything.

Results:

  • Full cluster provisioning: under 15 minutes from terraform apply

  • Node scaling from 2 → 8 nodes: under 3 minutes under load

Read Part 1 — Networking · Read Part 2 — EKS Cluster · GitHub

Read the full write-up

Detailed article covering the architecture, implementation, and lessons learned.

Read article →