# 🔍 Terraform Modular EKS + Istio — The Part Nobody Explains

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 🚀

Total
0
Shares
Leave a Reply

Your email address will not be published. Required fields are marked *

Previous Post

Mistral releases a new open-source model for speech generation

Next Post

How GSC’s branded query filter changes SEO reporting and analysis

Related Posts