# Network Foundation with TERRAFORM...!!!

# Day 1 — The `vpc` Module: Laying the Network Foundation

> **Series: Zero to Production on AWS EKS with Terraform** — Part 3 · Day 1 Layer 1 of 5 — the foundation. Module of the day: `vpc`.
>
> **Last updated:** 26 October 2026

**In short:** The `vpc` Terraform module builds the private network an EKS platform stands on: a multi-AZ VPC with public and private subnets, NAT gateways, route tables and optional VPC endpoints. Workloads live in private subnets, only load balancers face the internet, and its outputs feed every module above it.

In Day 0 we built the mental model; in "The Final Picture" we saw the whole platform. Now we start building it for real — bottom up. And the bottom of everything is the **network**.

No network, no cluster. No cluster, no apps. So Day 1 is the `vpc` module.

---

## What is a VPC?

A **VPC (Virtual Private Cloud)** is *your own private slice of AWS's data center*. Think of it as renting an empty office building:

- It has a **street address range** (the CIDR, e.g. `10.0.0.0/16`) — the pool of internal IP addresses everything inside will use.
- It's split into **rooms** (subnets) — some with a public door to the street (**public subnets**), some locked away inside (**private subnets**).
- It has a **front door** (Internet Gateway) and a **one-way outbound exit** (NAT Gateway) so people inside can call out, but strangers can't walk in.

Everything we build in the rest of this series lives *inside* this building.

---

## What does the `vpc` module create?

One call creates a multi-AZ VPC with public and private subnets, an Internet Gateway, NAT gateways, route tables and optional VPC endpoints. Our module wraps the battle-tested `terraform-aws-modules/vpc/aws` and adds the AWS-endpoint and tagging conventions our platform expects. One call gives you:

- A **VPC** with the CIDR you choose.
- **Public + private subnets**, spread across **multiple Availability Zones** (different physical buildings — so one AZ failing doesn't take you down).
- An **Internet Gateway** for the public subnets.
- **NAT Gateway(s)** so private workloads can reach the internet *outbound* (pull container images, call APIs) without being reachable *inbound*.
- **Route tables** wiring it all together.
- **VPC Endpoints** (optional) — private on-ramps to AWS services (S3, ECR, STS, EC2, ELB, CloudWatch Logs) so that traffic never leaves AWS's backbone. The **S3 gateway endpoint** is especially worth it: it's free and saves NAT charges on image layers.

### Why do multiple AZs and private subnets matter?

Multi-AZ gives you resilience and private subnets give you security.

- **Multi-AZ** = resilience. Your nodes and load balancers are spread across 2–3 zones.
- **Private subnets for workloads** = security. Your Kubernetes nodes have no public IP. The only thing the internet can reach is the load balancer you explicitly create.

---

## Which inputs does the `vpc` module need?

Per environment you mainly set the name, CIDR, AZs, subnet ranges, cluster name, NAT strategy and endpoints. The module exposes a focused set of variables. The ones you'll actually set per-environment:

| Variable | What it controls | Example |
| --- | --- | --- |
| `vpc_name` | Name tag for the VPC | `"prod-vpc"` |
| `vpc_cidr` | The whole IP range | `"10.0.0.0/16"` |
| `availability_zones` | Which AZs to span | `["us-east-1a","us-east-1b"]` |
| `private_subnet_cidrs` | Ranges for private rooms | `["10.0.1.0/24","10.0.2.0/24"]` |
| `public_subnet_cidrs` | Ranges for public rooms | `["10.0.101.0/24","10.0.102.0/24"]` |
| `cluster_name` | Used to **tag subnets for EKS** | `"prod-cluster"` |
| `enable_nat_gateway` / `single_nat_gateway` / `one_nat_gateway_per_az` | Outbound internet strategy & cost | `true` / `false` / `true` |
| `enable_s3_gateway_endpoint` | Free private S3 access | `true` |
| `enable_ecr_api_endpoint`, `enable_ecr_dkr_endpoint`, `enable_sts_endpoint`, … | Private on-ramps to AWS services | `true` |
| `enable_flow_logs` | Record accepted/rejected traffic to CloudWatch — gold for audits and "why can't A reach B?" debugging | `true` |
| `environment`, `tags` | Env label + standard tags | `"prod"` |

### How does subnet tagging help EKS load balancers?

Subnet tags tell the ALB controller where to place public and internal load balancers. The module tags subnets with Kubernetes discovery tags:

- `kubernetes.io/role/elb` → "put **public** load balancers here"
- `kubernetes.io/role/internal-elb` → "put **internal** load balancers here"

You set this now, on Day 1, and the **ALB controller two layers up auto-discovers it**. The foundation quietly sets up the floors above it.

---

## What does the `vpc` module output?

It outputs the VPC ID, subnet IDs, route table IDs, NAT gateway IDs and the endpoint security group. These outputs are the *contract* — they become the inputs of every module above:

| Output | Used by | Why |
| --- | --- | --- |
| `vpc_id` | `eks`, `security-group`, `tgw` | "which network am I in?" |
| `private_subnets` | `eks` (node placement), `tgw` | where nodes & attachments live |
| `public_subnets` | public load balancers | where internet-facing LBs go |
| `private_route_table_ids` | `tgw` / `tgw-attachment` | to add cross-network routes |
| `natgw_ids` | reference/debugging | the outbound exits |
| `vpc_endpoint_sg_id` | endpoint traffic rules | firewall for the private on-ramps |

> **Remember the contract from "The Final Picture":** `vpc.outputs.private_subnets` → `eks.subnet_ids`. That single wire is why Terraform knows to build the VPC *before* the cluster.

---

## What does a real `vpc` module call look like?

Here is how a consumer stack uses the module:

```hcl
module "vpc" {
  source = "git::https://github.com/<org>/devops-terraform-modules.git//modules/vpc?ref=v1.4.0"

  vpc_name             = "prod-vpc"
  vpc_cidr             = "10.0.0.0/16"
  cluster_name         = "prod-cluster"
  availability_zones   = ["us-east-1a", "us-east-1b"]
  private_subnet_cidrs = ["10.0.1.0/24", "10.0.2.0/24"]
  public_subnet_cidrs  = ["10.0.101.0/24", "10.0.102.0/24"]

  enable_nat_gateway     = true
  one_nat_gateway_per_az = true     # resilient (1 NAT per AZ) — costs more
  single_nat_gateway     = false

  enable_s3_gateway_endpoint = true
  enable_ecr_api_endpoint    = true
  enable_ecr_dkr_endpoint    = true

  environment = "prod"
  tags        = { Project = "platform", ManagedBy = "terraform" }
}
```

---

## What are the `vpc` module gotchas?

The big ones are CIDR sizing, the NAT cost-vs-resilience choice, endpoint savings and keeping workloads out of public subnets.

- **CIDR sizing is forever-ish.** Resizing a VPC later is painful. Pick a `/16` and carve sensible `/24`s. Leave room to grow.
- `single_nat_gateway` **vs** `one_nat_gateway_per_az` is a cost-vs-resilience knob. Dev → single NAT (cheap). Prod → one per AZ (no cross-AZ data charges, survives an AZ outage).
- **Endpoints save real money.** If your nodes pull a lot of images, the ECR + S3 endpoints pay for themselves by cutting NAT data-processing fees.
- **Don't put workloads in public subnets.** Public subnets are only for load balancers and NAT. Everything else goes private.

---

## How do you verify the VPC was created?

Check the VPC CIDR and the subnet spread with the AWS CLI after `terraform apply`:

```bash
# the VPC exists with the CIDR you set
aws ec2 describe-vpcs --filters Name=tag:Name,Values=prod-vpc \
  --query 'Vpcs[].CidrBlock'

# subnets landed across AZs
aws ec2 describe-subnets --filters Name=vpc-id,Values=<vpc_id> \
  --query 'Subnets[].[AvailabilityZone,CidrBlock,MapPublicIpOnLaunch]' --output table
```

---

## Frequently asked questions

### Do I need NAT gateways for a private EKS cluster?

Yes, if private workloads need outbound internet access, for example to pull container images or call APIs. NAT gateways let private subnets reach out without being reachable inbound.

### Should I use a single NAT gateway or one per AZ?

It's a cost-vs-resilience choice. A single NAT gateway is cheaper and fine for dev; one NAT gateway per AZ suits prod because it avoids cross-AZ data charges and survives an AZ outage.

### Why add an S3 gateway endpoint to a VPC?

The S3 gateway endpoint is free and keeps S3 traffic on AWS's backbone. It cuts NAT data-processing fees, especially when nodes pull a lot of image layers.

### What CIDR size should I pick for an EKS VPC?

Pick a `/16` and carve sensible `/24` subnets, leaving room to grow. Resizing a VPC later is painful, so CIDR sizing is effectively permanent.

### Which subnet tags does the AWS Load Balancer Controller use?

It uses `kubernetes.io/role/elb` for public load balancers and `kubernetes.io/role/internal-elb` for internal ones. The `vpc` module sets these tags so the controller can auto-discover subnets.

---

## What's the takeaway?

- The `vpc` module gives you a **resilient, multi-AZ private network** in one call.
- Workloads live in **private** subnets; only load balancers face the internet.
- Its **outputs** (`vpc_id`, `private_subnets`, route tables) are the inputs everything else depends on.
- The **subnet tags** you set today are what makes load-balancing "just work" on Day 5.

**Next — Day 2:** we connect things. Firewalls with the `security-group` module, and bridging separate networks with `tgw` / `tgw-attachment`. 🔌
