VOOZH about

URL: https://dev.to/jayakrishnayadav24/terraform-modular-eks-istio-part-1-2j14

⇱ Terraform Modular EKS + Istio β€” Part 1 - DEV Community


VPC Module (Complete Code + Real Explanation)

Before touching EKS, Istio, or routing β€” everything depends on one thing:

πŸ‘‰ Your network

If the VPC is wrong:

  • Nodes won’t join
  • ALB won’t work
  • Pods won’t get IPs
  • Routing will fail in weird ways

So in this part, I’ll walk through the entire VPC module from my setup, using the exact code β€” and explain what each part is doing and why it exists.


πŸ“‚ Module Files

This module consists of 3 files:

modules/vpc/
β”œβ”€β”€ main.tf
β”œβ”€β”€ variables.tf
└── outputs.tf

πŸ“„ variables.tf

This file defines what inputs the module expects.

variable "vpc_name" {
 description = "Name of the VPC"
 type = string
}

variable "vpc_cidr" {
 description = "CIDR block for VPC"
 type = string
 default = "10.0.0.0/16"
}

variable "availability_zones" {
 description = "Availability zones"
 type = list(string)
}

variable "private_subnet_cidrs" {
 description = "CIDR blocks for private subnets"
 type = list(string)
}

variable "public_subnet_cidrs" {
 description = "CIDR blocks for public subnets"
 type = list(string)
}

variable "cluster_name" {
 description = "Name of the EKS cluster"
 type = string
}

🧠 What this means

This module is not hardcoded.

Everything is controlled from outside:

  • CIDR ranges
  • AZs
  • Subnet layout
  • Cluster name

πŸ‘‰ That’s what makes it reusable across:

  • dev
  • staging
  • prod

πŸ“„ main.tf (Core Logic)

This is where actual infrastructure is created.


1. VPC

resource "aws_vpc" "main" {
 cidr_block = var.vpc_cidr
 enable_dns_support = true
 enable_dns_hostnames = true

 tags = {
 Name = var.vpc_name
 }
}

Why this matters

  • cidr_block β†’ defines network size
  • enable_dns_support β†’ required for internal communication
  • enable_dns_hostnames β†’ required for:

    • EKS
    • ALB
    • service discovery

πŸ‘‰ If DNS is off, things break silently.


2. Private Subnets (EKS Nodes)

resource "aws_subnet" "private_subnets" {
 count = length(var.private_subnet_cidrs)
 vpc_id = aws_vpc.main.id
 cidr_block = var.private_subnet_cidrs[count.index]
 availability_zone = var.availability_zones[count.index]

What’s happening

  • count creates multiple subnets
  • Each subnet is tied to an AZ

πŸ”₯ Important Tags

tags = {
 Name = "${var.vpc_name}-private-${var.availability_zones[count.index]}"
 "kubernetes.io/role/internal-elb" = "1"
 "kubernetes.io/cluster/${var.cluster_name}" = "owned"
}

Why these tags matter

These are not optional

  • internal-elb
    β†’ used by AWS to place internal load balancers

  • cluster tag
    β†’ required for EKS to discover subnets

πŸ‘‰ Missing this = ALB or services fail later


3. Public Subnets (ALB Layer)

resource "aws_subnet" "public_subnets" {
 count = length(var.public_subnet_cidrs)
 vpc_id = aws_vpc.main.id
 cidr_block = var.public_subnet_cidrs[count.index]
 availability_zone = var.availability_zones[count.index]
 map_public_ip_on_launch = true

Key difference

map_public_ip_on_launch = true

πŸ‘‰ Instances here get public IPs


Tag Difference

"kubernetes.io/role/elb" = "1"

πŸ‘‰ This tells AWS:

β€œUse this subnet for internet-facing load balancers”


4. Internet Gateway

resource "aws_internet_gateway" "igw" {
 vpc_id = aws_vpc.main.id

 tags = {
 Name = "${var.vpc_name}-igw"
 }
}

Purpose

Allows:

πŸ‘‰ Public subnet β†’ Internet


5. Elastic IP (for NAT)

resource "aws_eip" "nat" {
 count = length(var.public_subnet_cidrs)
 domain = "vpc"

 tags = {
 Name = "${var.vpc_name}-nat-${count.index + 1}"
 }

 depends_on = [aws_internet_gateway.igw]
}

Why EIP?

NAT Gateway needs a public IP


6. NAT Gateway (Critical)

resource "aws_nat_gateway" "nat" {
 count = length(var.public_subnet_cidrs)
 allocation_id = aws_eip.nat[count.index].id
 subnet_id = aws_subnet.public_subnets[count.index].id

What it does

Private Subnet β†’ NAT β†’ Internet

πŸ‘‰ Nodes can:

  • pull Docker images
  • access APIs

BUT:

πŸ‘‰ They don’t get public IP (secure)


7. Private Route Table

resource "aws_route_table" "private" {
 count = length(var.private_subnet_cidrs)
 vpc_id = aws_vpc.main.id

 route {
 cidr_block = "0.0.0.0/0"
 nat_gateway_id = aws_nat_gateway.nat[count.index].id
 }
}

Meaning

All outbound traffic β†’ NAT


8. Public Route Table

resource "aws_route_table" "public" {
 vpc_id = aws_vpc.main.id

 route {
 cidr_block = "0.0.0.0/0"
 gateway_id = aws_internet_gateway.igw.id
 }
}

Meaning

Public subnet β†’ direct internet


9. Route Table Associations

resource "aws_route_table_association" "private" {
 count = length(var.private_subnet_cidrs)
 subnet_id = aws_subnet.private_subnets[count.index].id
 route_table_id = aws_route_table.private[count.index].id
}
resource "aws_route_table_association" "public" {
 count = length(var.public_subnet_cidrs)
 subnet_id = aws_subnet.public_subnets[count.index].id
 route_table_id = aws_route_table.public.id
}

Why needed

πŸ‘‰ Subnets don’t automatically get routing

You must attach:

  • subnet β†’ route table

πŸ“„ outputs.tf

This file exposes values for other modules.

output "vpc_id" {
 description = "ID of the VPC"
 value = aws_vpc.main.id
}

output "private_subnet_ids" {
 description = "IDs of the private subnets"
 value = aws_subnet.private_subnets[*].id
}

output "public_subnet_ids" {
 description = "IDs of the public subnets"
 value = aws_subnet.public_subnets[*].id
}

Why outputs matter

These are used by:

  • EKS cluster
  • Node groups
  • ALB controller

Example:

private_subnet_ids = module.vpc.private_subnet_ids

πŸ‘‰ This creates dependency automatically.


🧠 Final Architecture

Internet
 β”‚
Internet Gateway
 β”‚
Public Subnets (ALB)
 β”‚
NAT Gateway
 β”‚
Private Subnets (EKS Nodes)

⚠️ Real Things That Break in Production

  • Missing subnet tags β†’ ALB won’t create
  • No NAT β†’ nodes can’t pull images
  • Wrong AZ mapping β†’ cluster unstable
  • Public nodes β†’ security issue

🧠 Key Takeaways

  • VPC is the foundation of everything
  • Subnet tagging is critical for EKS
  • NAT enables private nodes
  • Outputs drive Terraform dependencies

πŸš€ Next

In Part 2:

πŸ‘‰ IAM Module β€” IRSA explained properly
πŸ‘‰ How pods assume AWS roles
πŸ‘‰ Why OIDC is required


If you're building EKS in production, this part is not optional β€” this is where most issues actually start.