Terraform: Managing VPCs and Subnets Efficiently
Problem: You have a single VPC, and your var.vpcs
map represents a set of three subnets per availability zone for two environments.
🔹 Step 1: Define Variables
Instead of using a map of empty objects, define input variables correctly:
variable "vpc_subnets" {
type = map(map(object({
public_cidr_block = string
app_cidr_block = string
db_cidr_block = string
})))
}
variable "vpc_cidr_blocks" {
type = map(string)
}
🔹 Step 2: Define CIDR Blocks & Subnets
Example values for environments:
vpc_cidr_blocks = {
"dev" = "10.1.0.0/16"
"stg" = "10.1.0.0/16"
}
vpc_subnets = {
"dev" = {
"ap-southeast-1a" = {
public_cidr_block = "10.1.1.0/24",
app_cidr_block = "10.1.2.0/24",
db_cidr_block = "10.1.3.0/24"
}
"ap-southeast-1b" = {
public_cidr_block = "10.1.4.0/24",
app_cidr_block = "10.1.5.0/24",
db_cidr_block = "10.1.6.0/24"
}
}
"stg" = {
"ap-southeast-1a" = {
public_cidr_block = "10.1.1.0/24",
app_cidr_block = "10.1.2.0/24",
db_cidr_block = "10.1.3.0/24"
}
"ap-southeast-1b" = {
public_cidr_block = "10.1.4.0/24",
app_cidr_block = "10.1.5.0/24",
db_cidr_block = "10.1.6.0/24"
}
}
}
🔹 Step 3: Define VPC Resources
resource "aws_vpc" "vpc" {
for_each = var.vpc_cidr_blocks
cidr_block = each.value
instance_tenancy = var.instance_tenancy
enable_dns_support = var.enable_dns_support
enable_dns_hostnames = var.enable_dns_hostnames
}
🔹 Step 4: Flatten Subnets for for_each
The var.vpc_subnets
structure is not suitable for for_each
. Use flatten
to transform it:
locals {
vpc_subnets = flatten([
for env, azs in var.vpc_subnets : [
for az, subnets in azs : [
for attr_name, cidr_block in subnets : {
env = env
az = az
type = trimsuffix(attr_name, "_cidr_block")
cidr_block = cidr_block
}
]
]
])
}
🔹 Step 5: Use for_each
for Subnets
resource "aws_subnet" "all" {
for_each = {
for subnet in local.vpc_subnets :
"${subnet.env}:${subnet.az}:${subnet.type}" => subnet
}
vpc_id = aws_vpc.vpc[each.value.env].id
cidr_block = each.value.cidr_block
availability_zone = each.value.az
map_public_ip_on_launch = var.map_public_ip_on_launch
}
Example Resource Instances:
- aws_subnet.all[“dev:ap-southeast-1a:public”]
- aws_subnet.all[“dev:ap-southeast-1a:app”]
- aws_subnet.all[“dev:ap-southeast-1a:db”]
- aws_subnet.all[“stg:ap-southeast-1b:public”]
- aws_subnet.all[“stg:ap-southeast-1b:app”]
- aws_subnet.all[“stg:ap-southeast-1b:db”]
🔹 Step 6: Split Subnets by Type
Group subnets by type:
locals {
vpc_subnets_by_type = {
for subnet in local.vpc_subnets :
subnet.type => subnet...
}
}
🔹 Step 7: Separate Subnet Resources
Instead of a single resource, use three separate subnet resources for each type.
// Public Subnets
resource "aws_subnet" "public" {
for_each = {
for subnet in local.vpc_subnets_by_type["public"] :
"${subnet.env}:${subnet.az}" => subnet
}
vpc_id = aws_vpc.vpc[each.value.env].id
cidr_block = each.value.cidr_block
availability_zone = each.value.az
map_public_ip_on_launch = var.map_public_ip_on_launch
}
// App Subnets
resource "aws_subnet" "app" {
for_each = {
for subnet in local.vpc_subnets_by_type["app"] :
"${subnet.env}:${subnet.az}" => subnet
}
vpc_id = aws_vpc.vpc[each.value.env].id
cidr_block = each.value.cidr_block
availability_zone = each.value.az
map_public_ip_on_launch = var.map_public_ip_on_launch
}
// DB Subnets
resource "aws_subnet" "db" {
for_each = {
for subnet in local.vpc_subnets_by_type["db"] :
"${subnet.env}:${subnet.az}" => subnet
}
vpc_id = aws_vpc.vpc[each.value.env].id
cidr_block = each.value.cidr_block
availability_zone = each.value.az
map_public_ip_on_launch = var.map_public_ip_on_launch
}
🔹 Conclusion
By structuring your variables properly and leveraging for_each
with flatten
, you can efficiently declare all necessary subnets while keeping your Terraform code clean and maintainable. 🚀