Chapter 5: VPC Module - Complete Analysis
Overview
A deep dive into a production-ready Terraform VPC module for AWS: networking primitives, hardened defaults, and EKS-ready configuration. Learn how subnets, routing, NAT gateways, flow logs, and security boundaries are implemented so you can standardize your AWS network foundation across dev/stage/prod without drift.
5.1 Module Purpose & Design
Purpose
The VPC module provides a complete, production-ready networking foundation for AWS infrastructure. It creates:
The VPC module provides a complete, production-ready networking foundation for AWS infrastructure.
The Friction
- ✕ Hardcoded CIDRs: Managing IP overlaps across 10+ environments is math hell.
- ✕ Security Group Spaghetti: "Allow All" becomes the default when things break.
- ✕ Manual Peering: Connecting VPCs manually leads to routing loops.
The R8way Solution
- ✓ Dynamic Subnetting: cidrsubnets() calculates non-overlapping ranges automatically.
- ✓ Deny-All Defaults: Security Groups start closed; strict allow-listing required.
- ✓ TGW Integration: Transit Gateway support built-in for hub-and-spoke topology.
Design Philosophy
1. Community Module Usage
Instead of reinventing the wheel,
we leverage the battle-tested terraform-aws-modules/vpc/aws
library.
- ✓ Immediate access to patches
- ✓ Proven handling of edge cases
- ✓ Constant maintenance
2. Secure by Default
We adhere to a Zero Trust architecture from day one. Security is not an afterthought.
- ✓ Deny-All Defaults: Closed SGs
- ✓ Audit Trails: Flow Logs on
- ✓ Isolation: Private Subnets
3. EKS Optimized
Topology pre-configured for Kubernetes, eliminating common "Cluster Stuck" errors.
- ✓ Auto-Discovery tags
- ✓ Topology Aware routing
- ✓ Scale Ready IP space
4. Cost Awareness
Our defaults prevent "Bill Shock" by optimizing expensive networking components.
- ✓ Smart NAT (Single GW)
- ✓ Toggle-able HA
- ✓ Right-Sizing Logs
5.2 Code Walkthrough
File:modules/vpc/main.tf
Let's analyze this file line by line:
Lines 1-3: Module Declaration
- module "vpc" : Declares a module named "vpc" (local name)
- source : Uses the community VPC module from Terraform Registry
- version : Pins to version 5.0.0 for stability
Why Version 5.0.0?:
- Support for latest AWS features
- Security patches
- Bug fixes
- Backward compatibility
Lines 5-6: Basic VPC Configuration
- name : Dynamic VPC name using environment ( dev-vpc , prod-vpc )
- cidr : VPC CIDR block from variable (e.g., 10.10.0.0/16 )
Naming Convention:
- Clear identification in AWS Console
- Easy filtering and searching
- Consistent naming across environments
Lines 8-10: Subnet Configuration
- azs : List of availability zones (e.g., ["us-east-1a", "us-east-1b"] )
- private_subnets : CIDR blocks for private subnets (workloads)
- public_subnets : CIDR blocks for public subnets (load balancers, NAT)
Subnet Design:
- Private Subnets: No direct internet access, NAT Gateway required
- Public Subnets: Direct internet access via Internet Gateway
- Best Practice: Workloads in private, only gateways in public
Lines 12-15: NAT Gateway Configuration
- enable_nat_gateway = true : Always enable NAT (required for private subnet internet access)
- single_nat_gateway : Single NAT for cost savings (default: true )
- one_nat_gateway_per_az : One NAT per AZ for HA (default: false )
- enable_vpn_gateway = false : VPN disabled (no site-to-site VPN)
Cost Consideration:
- Single NAT: ~$32/month + data transfer
- One per AZ: ~$96/month + data transfer (3 AZs)
- Recommendation: Start with single NAT, add per-AZ if needed
Lines 17-22: VPC Flow Logs
- enable_flow_log : Master switch for flow logs (default: false )
- create_flow_log_cloudwatch_log_group : Auto-create CloudWatch log group
- create_flow_log_cloudwatch_iam_role : Auto-create IAM role for flow logs
- flow_log_max_aggregation_interval = 60 : Aggregate logs every 60 seconds
- flow_log_destination_type : CloudWatch Logs or S3 (default: CloudWatch)
Flow Logs Purpose:
- Network traffic auditing
- Security incident investigation
- Compliance requirements (SOC 2, PCI-DSS)
- Performance analysis
⚠️ Cost Note: Flow logs generate significant CloudWatch costs at scale. Consider S3 for long-term storage.
Lines 24-27: Security Group Hardening
- manage_default_security_group = true : Take control of default SG
- default_security_group_ingress = [] : No inbound rules (deny all)
- default_security_group_egress = [] : No outbound rules (deny all)
Security Best Practice:
- Default security groups often allow all traffic
- This hardening prevents accidental exposure
- Explicit security groups must be created for workloads
- Follows "deny by default" security model
Lines 29-38: EKS Subnet Tags
Public Subnet Tags:
- kubernetes.io/role/elb = "1": Allow ELB to use these subnets
- kubernetes.io/cluster/<name> = "shared": Mark for EKS cluster discovery
Private Subnet Tags:
- kubernetes.io/role/internal-elb = "1": Allow internal ELB to use these subnets
- kubernetes.io/cluster/<name> = "shared": Mark for EKS cluster discovery
Why These Tags?:
- Kubernetes needs subnet tags to discover network topology
- ELB controller uses tags to choose subnets
- "shared" allows multiple clusters (if needed)
Tag Values:
- "1" = Enable this role for the subnet
- "shared" = Subnet can be shared across clusters (vs. "owned")
Lines 40-43: Default Tags
- Environment = var.env : Tag with environment name
- Terraform = "true" : Mark as Terraform-managed
Note:
- Project = "Production-Grade-IaC"
- ManagedBy = "Terragrunt"
- Owner = "PlatformTeam"
5.3 Variables (variables.tf)
Complete Variable Reference
Purpose: Environment identifier (dev, staging, prod)
Usage: Used in resource naming and tagging
Example: env = "dev"
→ VPC named dev-vpc
Purpose: VPC IP address range
Format: CIDR notation (e.g., 10.10.0.0/16)
Recommendations:
- Use RFC 1918 private ranges: 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16
- Common sizes:
- /16 (65,536 IPs) - Large environments
- /20 (4,096 IPs) - Medium environments
- /24 (256 IPs) - Small environments
Example:
- Dev: 10.10.0.0/16
- Staging: 10.20.0.0/16
- Prod: 10.30.0.0/16
Purpose: List of availability zones for subnet placement
Format: List of AZ names
Best Practices:
- Use at least 2 AZs for high availability
- Use 3 AZs for production workloads
- Match AZs to instance requirements (some instance types limited to certain AZs)
Example: ["us-east-1a", "us-east-1b", "us-east-1c"]
Purpose: CIDR blocks for private subnets (workloads)
Format: List of CIDR blocks (one per AZ)
Requirements:
- Must match length of azs variable
- Must be within vpc_cidr range
- Must not overlap with public_subnets
Example: ["10.10.1.0/24", "10.10.2.0/24"]
(one per AZ)
Subnet Sizing Guide:
- /24 = 256 IPs (sufficient for most workloads)
- /22 = 1,024 IPs (large node groups)
- /20 = 4,096 IPs (very large deployments)
Purpose: CIDR blocks for public subnets (load balancers, NAT)
Format: List of CIDR blocks (one per AZ)
Requirements:
- Must match length of azs variable
- Must be within vpc_cidr range
- Must not overlap with private_subnets
Example: ["10.10.101.0/24", "10.10.102.0/24"]
Note: Public subnets typically need fewer IPs (only gateways), so /24
is usually sufficient.
Purpose: EKS cluster name for subnet tagging
Usage: Used in EKS subnet tags for cluster discovery
Default: Empty string (no EKS tags if not provided)
Example: cluster_name = "primary"
→ Tags include kubernetes.io/cluster/primary
Purpose: Master switch for VPC Flow Logs
Default: false
(disabled)
Recommendation: Enable for production environments
Cost: ~$0.50 per GB ingested (CloudWatch Logs)
Purpose: Where to send flow logs
Options:
- "cloud-watch-logs": CloudWatch Logs (default, easier integration)
- "s3": S3 bucket (cheaper for long-term storage)
Recommendation:
- Use CloudWatch for real-time analysis
- Use S3 for compliance archives
Purpose: Cost optimization option
Default: true
(single NAT Gateway)
Use Cases:
- true: Cost-sensitive, single NAT in first AZ
- false: Requires one_nat_gateway_per_az = true for HA
Cost Impact:
- Single NAT: ~$32/month
- Per-AZ NAT: ~$96/month (3 AZs)
Purpose: High availability option
Default: false
(disabled)
Use Cases:
- true: High availability, one NAT per AZ
- false: Single NAT (cost optimization)
Requirements:
- Requires at least one public subnet per AZ
- Requires enable_nat_gateway = true
5.4 Outputs (outputs.tf)
Complete Output Reference
Purpose: VPC identifier for use by other modules
Format: vpc-xxxxxxxxx
Usage: Required by EKS and RDS modules
Example: dependency.vpc.outputs.vpc_id
Purpose: Private subnet IDs for workload placement
Format: List of subnet IDs (e.g., ["subnet-xxx", "subnet-yyy"])
Usage: Used by EKS module for node groups, RDS module for DB subnets
Example: dependency.vpc.outputs.private_subnets
Purpose: Public subnet IDs for load balancers
Format: List of subnet IDs
Usage: Used by EKS for public load balancers, NAT gateways
Example: dependency.vpc.outputs.public_subnets
Note: This output is provided for completeness but may not be used by EKS/RDS modules (which prefer private subnets).
5.5 Use Cases & Examples
Example 1: Basic Dev Environment
Configuration (values.yaml):
Result:
- VPC: 10.10.0.0/16 (65,536 IPs)
- 2 AZs for cost savings
- Private subnets: /24 each (256 IPs each)
- Public subnets: /24 each (256 IPs each)
- Single NAT Gateway (~$32/month)
- Flow logs disabled (cost savings)
Example 2: Production Environment
Configuration (values.yaml):
Result:
- VPC: 10.30.0.0/16 (production CIDR range)
- 3 AZs for high availability
- Private subnets: /24 each (256 IPs each, 3 subnets)
- Public subnets: /24 each (256 IPs each, 3 subnets)
- NAT Gateway per AZ (~$96/month, high availability)
- Flow logs enabled (auditing and compliance)
Example 3: Large Scale Environment
Configuration (values.yaml):
Result:
- VPC: 10.40.0.0/16 (large CIDR range)
- 3 AZs for high availability
- Private subnets: /22 each (1,024 IPs each, for large node groups)
- Public subnets: /24 each (256 IPs each, sufficient for gateways)
- NAT Gateway per AZ (high availability)
- Flow logs to S3 (cost-effective for long-term storage)
5.6 Customization Guide
Adding VPC Endpoints
To add VPC endpoints (for private access to AWS services), customize the module:
Option 1: Extend the Module
Create modules/vpc/endpoints.tf:
Option 2: Add to Existing Module
Edit modules/vpc/main.tf:
Custom Security Groups
The module hardens the default security group, but you can add custom security groups:
Custom Route Tables
The module creates route tables automatically, but you can add custom ones:
5.7 Common Patterns
Pattern 1: Multi-Region
Pattern 2: Environment Isolation
5.8 Troubleshooting
Issue: Subnet Overlap
Error:
Solution:
- Ensure private_subnets and public_subnets don't overlap
- Ensure all subnets are within vpc_cidr range
- Verify CIDR calculations
Issue: Insufficient IP Addresses
Error:
Solution:
- Increase subnet size (e.g., /24 → /22)
- Use larger VPC CIDR block
- Verify subnet sizes match workload requirements
Issue: EKS Tags Missing
Error:
Solution:
- Verify cluster_name variable is set
- Check subnet tags include kubernetes.io/cluster/<name>
- Ensure tags match cluster name
5.9 Chapter Summary
Executive Summary
-
★Key Features
- Production Ready: Pre-configured with public/private subnets, NAT Gateways, and Route Tables.
- Security First: Hardened default security groups (Deny All) and optional VPC Flow Logs.
- EKS Optimization: Automatic subnet tagging for Kubernetes Load Balancer discovery (kubernetes.io/role/elb).
- Cost Flexibility: Toggle between Single NAT Gateway (Dev) and Multi-AZ NAT (Prod) via variables.
-
★Integration
- Inputs: Fully configurable via values.yaml (CIDR, AZs, Subnets).
- Outputs: Exports vpc_id and subnet IDs for consumption by EKS and RDS modules.