Complete EKS Cluster with Terraform & ALB

Creating a production-ready Amazon EKS cluster from scratch requires orchestrating several interconnected AWS resources — VPC subnets with proper load balancer discovery tags, IAM roles, the Kubernetes control plane, managed node groups, CNI prefix delegation, and the AWS Load Balancer Controller for exposing applications via Application Load Balancers (ALBs).
This guide walks through the end-to-end architecture and Terraform code to spin up a complete EKS cluster with ALB integration.
📺 Video Walkthrough
Architecture Overview
graph TD
User["Internet Traffic"] --> ALB["AWS Application Load Balancer"]
subgraph VPC ["VPC (10.0.0.0/16)"]
subgraph PublicSubnets ["Public Subnets (eu-west-1a, eu-west-1b)"]
ALB
NAT["NAT Gateway"]
end
subgraph PrivateSubnets ["Private Subnets (eu-west-1a, eu-west-1b)"]
Nodes["EKS Worker Nodes (Bottlerocket / AL2023)"]
Pods["Pods (IP Target Mode)"]
end
end
ALB -- "Direct to Pod IP (port 80)" --> Pods
Nodes --> NAT
Components Provisioned
- VPC: 2 public subnets and 2 private subnets across multiple AZs with a dedicated NAT Gateway.
- EKS Control Plane: Kubernetes 1.31+ with API-only access authentication (
authentication_mode = "API"). - Managed Node Group: Spot / On-Demand instances running optimized Bottlerocket or AL2023 AMIs.
- VPC-CNI Add-on: Configured with prefix delegation for high pod density per node.
- AWS Load Balancer Controller: Deployed via Helm to provision ALBs and NLBs automatically with
target-type: ip.
📁 Source Code: aws-eks-terraform / EKS-Cluster-ALB
Step-by-Step Implementation
Step 1. VPC with Load Balancer Subnet Tags
The AWS Load Balancer Controller relies on subnet tags to discover where public and internal load balancers should be placed.
vpc.tf
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "5.13.0"
name = "eks-demo-vpc"
cidr = "10.0.0.0/16"
azs = ["eu-west-1a", "eu-west-1b"]
private_subnets = ["10.0.1.0/24", "10.0.2.0/24"]
public_subnets = ["10.0.101.0/24", "10.0.102.0/24"]
enable_nat_gateway = true
single_nat_gateway = true
enable_dns_hostnames = true
enable_dns_support = true
public_subnet_tags = {
"kubernetes.io/role/elb" = "1"
"subnet_type" = "public"
}
private_subnet_tags = {
"kubernetes.io/role/internal-elb" = "1"
"subnet_type" = "private"
}
}
Step 2. Provision the EKS Cluster
eks.tf
module "eks" {
source = "terraform-aws-modules/eks/aws"
version = "20.24.0"
cluster_name = "eks-demo"
cluster_version = "1.31"
cluster_endpoint_public_access = true
enable_cluster_creator_admin_permissions = true
authentication_mode = "API"
vpc_id = module.vpc.vpc_id
subnet_ids = module.vpc.private_subnets
control_plane_subnet_ids = module.vpc.private_subnets
eks_managed_node_groups = {
primary = {
ami_type = "BOTTLEROCKET_x86_64"
instance_types = ["m7i.large", "m6i.large", "m5.large"]
capacity_type = "SPOT"
min_size = 1
max_size = 3
desired_size = 2
}
}
tags = {
Environment = "dev"
Terraform = "true"
}
}
Step 3. VPC-CNI Add-on with Prefix Delegation
Enabling ENABLE_PREFIX_DELEGATION significantly increases the number of available IP addresses per worker node, preventing IP exhaustion when running high-density container workloads.
cni.tf
resource "aws_eks_addon" "vpc_cni" {
cluster_name = module.eks.cluster_name
addon_name = "vpc-cni"
addon_version = "v1.19.0-eksbuild.1"
resolve_conflicts_on_create = "OVERWRITE"
resolve_conflicts_on_update = "OVERWRITE"
configuration_values = jsonencode({
env = {
ENABLE_PREFIX_DELEGATION = "true"
WARM_PREFIX_TARGET = "1"
ENABLE_POD_ENI = "true"
POD_SECURITY_GROUP_ENFORCING_MODE = "standard"
}
})
}
Step 4. AWS Load Balancer Controller Deployment
Install the AWS Load Balancer Controller via Helm and configure its IAM role using EKS Pod Identity or IRSA.
alb-controller.tf
module "load_balancer_controller_pod_identity" {
source = "terraform-aws-modules/eks-pod-identity/aws"
version = "~> 1.6"
name = "aws-lbc-${module.eks.cluster_name}"
attach_aws_lb_controller_policy = true
associations = {
eks_lbc = {
cluster_name = module.eks.cluster_name
namespace = "kube-system"
service_account = "aws-load-balancer-controller"
}
}
}
resource "helm_release" "aws_load_balancer_controller" {
name = "aws-load-balancer-controller"
namespace = "kube-system"
repository = "https://aws.github.io/eks-charts"
chart = "aws-load-balancer-controller"
version = "1.10.0"
set {
name = "clusterName"
value = module.eks.cluster_name
}
set {
name = "serviceAccount.create"
value = "true"
}
set {
name = "serviceAccount.name"
value = "aws-load-balancer-controller"
}
set {
name = "defaultTargetType"
value = "ip"
}
depends_on = [
module.eks,
module.load_balancer_controller_pod_identity
]
}
Step 5. Expose an Application via Ingress (ALB)
Create a sample deployment, service, and Ingress object. The AWS Load Balancer Controller detects the Ingress and automatically provisions an internet-facing Application Load Balancer.
sample-app.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: echoserver
namespace: default
spec:
replicas: 2
selector:
matchLabels:
app: echoserver
template:
metadata:
labels:
app: echoserver
spec:
containers:
- name: echoserver
image: k8s.gcr.io/e2e-test-images/echoserver:2.5
ports:
- containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: echoserver-svc
namespace: default
spec:
type: ClusterIP
ports:
- port: 80
targetPort: 8080
selector:
app: echoserver
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: echoserver-ingress
namespace: default
annotations:
alb.ingress.kubernetes.io/scheme: internet-facing
alb.ingress.kubernetes.io/target-type: ip
spec:
ingressClassName: alb
rules:
- http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: echoserver-svc
port:
number: 80
Verification & Testing
# 1. Update kubeconfig
aws eks --region eu-west-1 update-kubeconfig --name eks-demo
# 2. Deploy sample app and ingress
kubectl apply -f sample-app.yaml
# 3. Check Ingress status and obtain public ALB DNS name
kubectl get ingress echoserver-ingress
# 4. Test HTTP response
curl -i http://<ALB-DNS-NAME>