Amazon EKS — Terraform EKS Module
For production EKS clusters, Terraform gives you full control, state management, and repeatability. The official terraform-aws-modules/eks/aws module simplifies the setup significantly.
Why Terraform for EKS?
| Benefit | Description |
|---|---|
| Reproducible | Same config creates identical clusters every time |
| State Management | Track what exists via S3 + DynamoDB remote state |
| Modular | Separate VPC, EKS, add-ons into reusable modules |
| Plan Before Apply | terraform plan shows exactly what will change |
| Multi-Environment | Same modules for dev, staging, and prod |
| Version Control | Infrastructure changes tracked in Git |
Project Structure
terraform/
├── main.tf # Provider and module calls
├── variables.tf # Input variables
├── outputs.tf # Output values
├── versions.tf # Provider version constraints
├── terraform.tfvars # Variable values
└── backend.tf # S3 remote state config
Provider Configuration
# versions.tf
terraform {
required_version = ">= 1.5.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
kubernetes = {
source = "hashicorp/kubernetes"
version = "~> 2.25"
}
helm = {
source = "hashicorp/helm"
version = "~> 2.12"
}
}
}
provider "aws" {
region = var.region
}
# Configure Kubernetes provider to use the EKS cluster
provider "kubernetes" {
host = module.eks.cluster_endpoint
cluster_ca_certificate = base64decode(module.eks.cluster_certificate_authority_data)
exec {
api_version = "client.authentication.k8s.io/v1beta1"
command = "aws"
args = ["eks", "get-token", "--cluster-name", module.eks.cluster_name]
}
}
VPC Module
# main.tf — VPC
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "~> 5.0"
name = "${var.cluster_name}-vpc"
cidr = "10.0.0.0/16"
azs = ["${var.region}a", "${var.region}b", "${var.region}c"]
private_subnets = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
public_subnets = ["10.0.101.0/24", "10.0.102.0/24", "10.0.103.0/24"]
enable_nat_gateway = true
single_nat_gateway = false # One per AZ for production HA
enable_dns_hostnames = true
enable_dns_support = true
# Tags required for EKS subnet discovery
public_subnet_tags = {
"kubernetes.io/role/elb" = 1
"kubernetes.io/cluster/${var.cluster_name}" = "shared"
}
private_subnet_tags = {
"kubernetes.io/role/internal-elb" = 1
"kubernetes.io/cluster/${var.cluster_name}" = "shared"
}
tags = {
Environment = var.environment
Terraform = "true"
}
}
EKS Module
# main.tf — EKS
module "eks" {
source = "terraform-aws-modules/eks/aws"
version = "~> 20.0"
cluster_name = var.cluster_name
cluster_version = "1.29"
cluster_endpoint_public_access = true
cluster_endpoint_private_access = true
# EKS Add-ons
cluster_addons = {
coredns = {
most_recent = true
}
kube-proxy = {
most_recent = true
}
vpc-cni = {
most_recent = true
before_compute = true # Ensure VPC CNI is ready before nodes
configuration_values = jsonencode({
env = {
ENABLE_PREFIX_DELEGATION = "true"
}
})
}
aws-ebs-csi-driver = {
most_recent = true
service_account_role_arn = module.ebs_csi_irsa.iam_role_arn
}
}
vpc_id = module.vpc.vpc_id
subnet_ids = module.vpc.private_subnets
# Cluster access
enable_cluster_creator_admin_permissions = true
# Managed Node Groups
eks_managed_node_groups = {
general = {
ami_type = "AL2023_x86_64_STANDARD"
instance_types = ["m5.large"]
min_size = 2
max_size = 6
desired_size = 3
labels = {
role = "general"
}
tags = {
Environment = var.environment
}
}
spot = {
ami_type = "AL2023_x86_64_STANDARD"
instance_types = ["m5.large", "m5a.large", "m4.large"]
capacity_type = "SPOT"
min_size = 0
max_size = 10
desired_size = 2
labels = {
role = "spot-worker"
}
}
}
tags = {
Environment = var.environment
Terraform = "true"
}
}
Variables
# variables.tf
variable "region" {
description = "AWS region"
type = string
default = "us-east-1"
}
variable "cluster_name" {
description = "EKS cluster name"
type = string
default = "my-eks-cluster"
}
variable "environment" {
description = "Environment name"
type = string
default = "production"
}
Outputs
# outputs.tf
output "cluster_name" {
description = "EKS cluster name"
value = module.eks.cluster_name
}
output "cluster_endpoint" {
description = "EKS cluster API endpoint"
value = module.eks.cluster_endpoint
}
output "cluster_version" {
description = "EKS cluster Kubernetes version"
value = module.eks.cluster_version
}
output "configure_kubectl" {
description = "Command to configure kubectl"
value = "aws eks update-kubeconfig --region ${var.region} --name ${module.eks.cluster_name}"
}
Remote State Backend
# backend.tf
terraform {
backend "s3" {
bucket = "my-terraform-state-bucket"
key = "eks/terraform.tfstate"
region = "us-east-1"
dynamodb_table = "terraform-lock"
encrypt = true
}
}
Deploy
# Initialize
terraform init
# Review the plan
terraform plan
# Apply
terraform apply
# Configure kubectl
aws eks update-kubeconfig --region us-east-1 --name my-eks-cluster
# Verify
kubectl get nodes
When to Choose Terraform Over eksctl
| Scenario | Use |
|---|---|
| Learning, quick experiments | eksctl |
| Production clusters | Terraform |
| Multi-environment setups | Terraform |
| Need to manage VPC separately | Terraform |
| CI/CD pipeline for infrastructure | Terraform |
| Team collaboration on infra | Terraform |
| One-off cluster, no IaC requirement | eksctl |
What’s Next?
For completeness, let’s also look at creating a cluster via the AWS Console.