VOOZH about

URL: https://dev.to/aws-builders/-terraform-modular-eks-istio-the-part-nobody-explains-2mk3

⇱ πŸ” Terraform Modular EKS + Istio β€” The Part Nobody Explains - DEV Community


When I first modularized my Terraform for EKS, everything looked clean…

πŸ‘

Until it didn’t work.

Modules were correct.
Code was clean.
Folder structure looked β€œperfect”.

But Terraform was behaving in ways I didn’t expect.

Resources were creating in weird order.
Dependencies felt invisible.
And debugging became painful.

That’s when I realized:

πŸ‘‰ Understanding Terraform syntax is easy
πŸ‘‰ Understanding how Terraform thinks is the real game

This blog is about that shift.


🧠 The Biggest Misconception

Initially, I thought Terraform runs like a script:

Step 1 β†’ Step 2 β†’ Step 3

But that assumption is completely wrong.

Terraform doesn’t execute line by line.

πŸ‘‰ It builds a dependency graph first, and only then decides what to create and in what order.

Once this clicked, everything started making sense.


🧩 My Project Structure (Real Setup)

Here’s how I organized everything:

modularized/
β”œβ”€β”€ eks/ # Root module (entry point)
β”‚ β”œβ”€β”€ main.tf
β”‚ β”œβ”€β”€ variables.tf
β”‚ β”œβ”€β”€ providers.tf
β”‚ └── backend.tf
β”œβ”€β”€ modules/
β”‚ β”œβ”€β”€ vpc/
β”‚ β”œβ”€β”€ iam/
β”‚ β”œβ”€β”€ eks-cluster/
β”‚ β”œβ”€β”€ eks-nodes/
β”‚ β”œβ”€β”€ aws-load-balancer-controller/
β”‚ β”œβ”€β”€ istio-base/
β”‚ β”œβ”€β”€ istiod/
β”‚ β”œβ”€β”€ istio-gateway/
β”‚ └── istio-manifests/
└── environments/
 β”œβ”€β”€ dev/
 β”‚ β”œβ”€β”€ terraform.tfvars
 β”‚ └── backend.hcl

At a glance, this looks like just folders.

But in reality:

πŸ‘‰ This is a system design mapped into code


πŸ”— The Real Role of main.tf

Think of main.tf not as a file…

πŸ‘‰ But as an orchestrator

It doesn’t β€œdo” things directly.
It connects modules together using data.


πŸ”Ή Step 1: VPC β€” The Foundation

module "vpc" {
 source = "../modules/vpc"
 vpc_name = var.vpc_name
 vpc_cidr = var.vpc_cidr
 availability_zones = var.availability_zones
 private_subnet_cidrs = var.private_subnet_cidrs
 public_subnet_cidrs = var.public_subnet_cidrs
 cluster_name = var.cluster_name
}

This module creates:

  • VPC
  • Public & private subnets
  • Routing

But the important part is not creation…

πŸ‘‰ It’s what it exports

output "private_subnet_ids" {
 value = aws_subnet.private[*].id
}

🧠 First Realization

Terraform modules don’t β€œtalk” directly.

They communicate through:

πŸ‘‰ Outputs β†’ Inputs


πŸ”Ή Step 2: IAM β€” Identity Layer

module "iam" {
 source = "../modules/iam"
 cluster_name = var.cluster_name
}

This creates:

  • Cluster role
  • Node role
  • IRSA roles

And exposes:

output "eks_cluster_role_arn" {}
output "eks_nodes_role_arn" {}

πŸ”— Step 3: EKS Cluster β€” Where Things Clicked

module "eks_cluster" {
 source = "../modules/eks-cluster"

 cluster_name = var.cluster_name
 cluster_version = var.cluster_version
 cluster_role_arn = module.iam.eks_cluster_role_arn
 private_subnet_ids = module.vpc.private_subnet_ids
 public_subnet_ids = module.vpc.public_subnet_ids
}

This line changed everything for me:

module.iam.eks_cluster_role_arn

πŸ‘‰ This is not just a reference
πŸ‘‰ This is a dependency signal


πŸ’₯ The Aha Moment

I didn’t define any order like:

  • β€œCreate IAM first”
  • β€œThen VPC”
  • β€œThen EKS”

Yet Terraform automatically knew.

Why?

Because:

πŸ‘‰ Dependencies define execution order


πŸ”Ή Step 4: Node Groups β€” Implicit Dependency

module "eks_nodes" {
 source = "../modules/eks-nodes"

 cluster_name = module.eks_cluster.cluster_id
 node_role_arn = module.iam.eks_nodes_role_arn
 subnet_ids = module.vpc.private_subnet_ids
}

Now Terraform understands:

  • Nodes depend on cluster
  • Cluster depends on IAM + VPC

So it builds a graph like:

VPC β†’ IAM β†’ EKS β†’ Nodes

Without you ever writing that flow.


⚠️ Mistake I Made (Important)

At one point, I hardcoded subnet IDs inside my EKS module.

It worked… initially.

But the moment I tried another environment β€” everything broke.

That’s when I understood:

πŸ‘‰ Hardcoding breaks modular design
πŸ‘‰ Outputs make modules reusable


βš™οΈ Variables β€” The Real Power

variable "cluster_name" {
 type = string
}

Values come from:

# environments/dev/terraform.tfvars

cluster_name = "dev-cluster"

πŸ‘‰ Same code
πŸ‘‰ Different environments

No duplication.


🧠 What Terraform Actually Does

When you run:

terraform apply

Terraform does NOT just β€œrun code”.

It:

  1. Reads variables
  2. Resolves all references
  3. Builds dependency graph
  4. Plans execution order
  5. Applies resources

πŸ” Backend β€” Silent but Critical

terraform {
 backend "s3" {
 bucket = "my-tf-state"
 key = "eks/dev/terraform.tfstate"
 region = "us-east-1"
 }
}

This enables:

  • Remote state
  • Team collaboration
  • State locking

Without this, things get messy fast.


🧠 Final Shift in Thinking

At the beginning, Terraform felt like:

πŸ‘‰ β€œWriting infrastructure scripts”

Now it feels like:

πŸ‘‰ β€œDesigning systems using data flow”

That shift changes everything.


πŸ’‘ Key Takeaways

  • main.tf is not execution β€” it’s orchestration
  • Outputs are how modules communicate
  • Dependencies are inferred, not written
  • Variables make environments scalable
  • Terraform is a graph engine, not a script runner

πŸ”— Repo

https://github.com/jayakrishnayadav24/istio-ip-based-routing


πŸš€ Final Thought

Once you stop thinking in terms of files…

And start thinking in terms of data flowing between modules…

Terraform stops being just infrastructure code.

πŸ‘‰ It becomes a system design tool.


If this helped you understand Terraform at a deeper level:

⭐ Star the repo
πŸ” Share with others
πŸ’¬ Let me know what confused you β€” I’ll write about it next

Happy building πŸš€