Skip to main content

Command Palette

Search for a command to run...

Terraform Modules in Action: Building a Production-Ready EKS Cluster from Scratch

Updated
6 min read
Terraform Modules in Action: Building a Production-Ready EKS Cluster from Scratch

As my Terraform journey is getting deeper, today’s learning was a major mindset shift.
Instead of writing one massive main.tf, I learned how Terraform Modules help you design infrastructure that is clean, reusable, scalable, and production-ready.

To apply this concept practically, I built a complete Amazon EKS cluster using Terraform, structured entirely around custom modules. This wasn’t just about spinning up Kubernetes — it was about engineering infrastructure the right way.


What Are Terraform Modules (In Simple Terms)?

A Terraform module is a logical container for related resources.
Think of it like a function in programming:

  • Inputs → variables

  • Logic → resources

  • Outputs → exposed values

Instead of repeating the same code everywhere, you define it once, reuse it many times, and manage it cleanly.

Visual Flow (How a module use the resources of another module )

aws_iam_role.cluster(iam_module 
      ↓
output cluster_role_arn        
      ↓
module.iam.cluster_role_arn
      ↓
passed from root module  
      ↓
var.cluster_role_arn (eks module) 
      ↓
aws_eks_cluster.role_arn

Why Modules Matter in Real Projects

  • Keeps infrastructure organized

  • Makes code reusable

  • Enables team collaboration

  • Reduces errors and duplication

  • Makes infra easier to scale and maintain

For complex systems like EKS, modules are not optional — they’re essential.


Project Overview: EKS Cluster Using Terraform Modules

I created a production-ready EKS cluster using AWS Provider 6.x and Terraform best practices.

High-Level Features

  • Custom VPC with public & private subnets (3 AZs)

  • Amazon EKS (Kubernetes v1.31)

  • Fargate for serverless pod execution (cost-optimized)

  • Core EKS add-ons

  • IAM Roles for Service Accounts (IRSA)

  • Clean modular Terraform structure

This project mimics real-world enterprise EKS setups, not toy examples.

Basic Folder Structure (MANDATORY)


Architecture Breakdown

Core Components

  • VPC: Networking foundation

  • IAM: Secure permissions for EKS & pods

  • EKS: Managed Kubernetes control plane

  • Fargate: Serverless execution for pods

  • ALB Controller: Ingress for external traffic


Module 1: VPC Module (modules/vpc/)

Purpose

Creates the networking foundation required by EKS.

Why This Module Is Critical

  • EKS requires properly tagged subnets

  • Kubernetes uses subnet tags for load balancers

  • NAT Gateway enables private workloads to pull images

Resources Created

  • 1 VPC (10.0.0.0/16)

  • 3 Public Subnets (3 AZs)

  • 3 Private Subnets (3 AZs)

  • Internet Gateway

  • Single NAT Gateway (cost optimization)

  • Elastic IP

  • Route tables & associations

Smart Kubernetes Subnet Tagging

public_subnet_tags = {  "kubernetes.io/role/elb" = "1"}
private_subnet_tags = {  "kubernetes.io/role/internal-elb" = "1"}

This ensures Kubernetes automatically knows where to place load balancers.


Module 2: IAM Module (modules/iam/)

Purpose

Handles all IAM roles and permissions securely.

Why IAM Deserves Its Own Module

  • EKS control plane needs AWS permissions

  • Fargate requires execution roles

  • IRSA allows pods to assume IAM roles (no static keys!)

Resources Created

  • EKS Cluster IAM Role

  • EKS Fargate Execution Role

  • AWS Managed Policy Attachments:

    • AmazonEKSClusterPolicy

    • AmazonEKSVPCResourceController

    • AmazonEKSFargatePodExecutionRolePolicy

This module keeps security clean, auditable, and modular.


Module 3: EKS Module (modules/eks/)

Purpose

Creates the Kubernetes control plane and integrations.

Control Plane Components

  • EKS Cluster

  • KMS Key for etcd encryption

  • CloudWatch Log Group for cluster logs

Fargate

  • Fargate Profile for serverless pod execution

  • No EC2 worker node management

  • Pay only for running pods

Add-ons Installed

  • CoreDNS

  • kube-proxy

  • VPC CNI

IRSA Setup

  • OIDC Provider enabled

  • Allows pods to use AWS services securely

Example of Root Module(main.tf):

module "vpc" {
  source = "./modules/vpc"

  name_prefix     = var.cluster_name
  vpc_cidr        = var.vpc_cidr
  azs             = slice(data.aws_availability_zones.available.names, 0, 3)
  private_subnets = var.private_subnets
  public_subnets  = var.public_subnets

  enable_nat_gateway = true
  single_nat_gateway = true

  # Required tags for EKS
  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"
    Project     = "EKS-Day20"
  }
}

# Custom IAM Module
module "iam" {
  source = "./modules/iam"

  cluster_name = var.cluster_name

  tags = {
    Environment = var.environment
    Terraform   = "true"
    Project     = "EKS-Day20"
  }
}

# Custom EKS Module
module "eks" {
  source = "./modules/eks"

  cluster_name       = var.cluster_name
  kubernetes_version = var.kubernetes_version
  vpc_id             = module.vpc.vpc_id
  subnet_ids         = module.vpc.private_subnets
  cluster_role_arn   = module.iam.cluster_role_arn
  fargate_role_arn   = module.iam.fargate_role_arn

  endpoint_public_access  = true
  endpoint_private_access = true
  public_access_cidrs     = ["0.0.0.0/0"]
  enable_irsa = true

  tags = {
    Environment = var.environment
    Terraform   = "true"
    Project     = "EKS-Day20"
  }
  depends_on = [module.iam]
}

Step-by-Step Deployment Process

Prerequisites

aws --version
terraform --version
kubectl version
helm version

Step 1: Initialize Terraform

terraform init

What happens:

  • AWS provider downloaded

  • Backend initialized

  • Modules prepared


Step 2: Validate Configuration

terraform validate

Expected:

Success! The configuration is valid.

Step 3: Plan the Deployment

terraform plan

Typical output:

Plan: 35 to add, 0 to change, 0 to destroy.

Always review:

  • CIDR blocks

  • Resource names

  • Estimated cost


Step 4: Apply Infrastructure

terraform apply

Type yes when prompted.


Configure kubectl Access

aws eks --region us-east-1 update-kubeconfig --name Terraform-eks

Verify:

kubectl cluster-info
kubectl get nodes

Setting Up AWS Load Balancer Controller (ALB)

This part taught me real-world IRSA implementation.

Step 1: Download IAM Policy

curl -O https://raw.githubusercontent.com/kubernetes-sigs/aws-load-balancer-controller/v2.11.0/docs/install/iam_policy.json

Step 2: Create IAM Policy

aws iam create-policy \  --policy-name AWSLoadBalancerControllerIAMPolicy \  --policy-document file://iam_policy.json

Step 3: Create IAM Role with OIDC Trust

This is where IRSA magic happens.

Trust policy connects:

  • Kubernetes ServiceAccount

  • IAM Role

  • OIDC Provider

Create trust-policy.json:

Create trust-policy.json:
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Federated": "arn:aws:iam::<ACCOUNT_ID>:oidc-provider/oidc.eks.<region>.amazonaws.com/id/<OIDC_ID>"
      },
      "Action": "sts:AssumeRoleWithWebIdentity",
      "Condition": {
        "StringEquals": {
          "oidc.eks.<region>.amazonaws.com/id/<OIDC_ID>:sub": "system:serviceaccount:kube-system:aws-load-balancer-controller",
          "oidc.eks.<region>.amazonaws.com/id/<OIDC_ID>:aud": "sts.amazonaws.com"
        }
      }
    }
  ]
}

Create the role:

aws iam create-role --role-name AmazonEKSLoadBalancerControllerRole --assume-role-policy-document [file://trust-policy.json](file://trust-policy.json)


Step 4: Create ServiceAccount

kubectl create serviceaccount aws-load-balancer-controller -n kube-system

Annotate:

kubectl annotate serviceaccount aws-load-balancer-controller \  -n kube-system \  eks.amazonaws.com/role-arn=arn:aws:iam::<ACCOUNT_ID>:role/AmazonEKSLoadBalancerControllerRole

Step 5: Install ALB Controller Using Helm

helm repo add eks https://aws.github.io/eks-chartshelm repo updatehelm install aws-load-balancer-controller eks/aws-load-balancer-controller -n kube-system \  --set clusterName=<cluster-name> \  --set serviceAccount.create=false \  --set serviceAccount.name=aws-load-balancer-controller \  --set region=<region> \  --set vpcId=<vpc-id>

Verify:

kubectl get deployment -n kube-system aws-load-balancer-controller

Deploying a Sample Application (2048 Game 🎮)

kubectl apply -f https://raw.githubusercontent.com/kubernetes-sigs/aws-load-balancer-controller/v2.5.4/docs/examples/2048/2048_full.yaml

Check ingress:

kubectl get ingress

Copy the domain → open in browser → app is live!

📺 Video That Helped Me Understand this concept:

Full Source Code on GitHub

To keep the article focused on concepts and architecture, I’ve shared the complete Terraform codebase (including all modules, variables, and outputs) on GitHub:

🔗 GitHub Repository: DAY 20
👉 https://github.com/Rajesh180118/aws-terraform-mastery


Final Takeaways from Today

  • Terraform Modules are non-negotiable for real projects

  • EKS setup is 50% infra, 50% IAM & networking

  • IRSA is the right way to give AWS access to pods

  • Fargate simplifies ops and reduces costs

  • Modular Terraform = scalable DevOps mindset

This project made Terraform feel less like a tool and more like an engineering discipline.