An important hazard to consider with the aws_availability_zones data source is that the set of available zones can change over time, and so it's important to write your configuration so that you don't find yourself trapped in a situation where Terraform thinks you intend to replace a subnet that you are currently using and therefore cannot destroy.
A key part of that is ensuring that Terraform understands that each of the subnets belongs to a specific availability zone, so that when the set of availability zones changes Terraform can either add a new subnet for a new availability zone or remove an existing subnet for a now-removed availability zone, without affecting the others that haven't changed. The easiest way to achieve that is to use resource for_each with the set of availability zones:
resource "aws_subnet" "public" {
for_each = aws_avaiability_zones.available.names
# ...
}
The above will declare subnet instances with addresses that each include the availability zone name, like this:
aws_subnet.public["eu-west-1a"]
aws_subnet.public["eu-west-1b"]
aws_subnet.public["eu-west-1e"]
Because they are identified by the availability zone name, Terraform can see that each subnet belongs to a particular availability zone.
For subnets in particular there is an additional challenge: we must assign each subnet its own CIDR block, which means we need a systematic way of allocating IP address space to availability zones so that the networks won't get renumbered by future changes to the set of availability zones.
The documentation for the aws_availability_zone data source includes an example of declaring a mapping table that assigns each region and each availability zone a number between 1 and 14 which is then used to populate one of the octets of the IP address to create a separate prefix per (region, AZ) pair. That example creates only a single VPC and a single subnet, but we can expand on that by using for_each to do it for each of the availability zones, as long as we update the mapping tables whenever we use a new region or a new availability zone suffix letter is assigned (up to 14 of each):
variable "region_number" {
# Arbitrary mapping of region name to number to use in
# a VPC's CIDR prefix.
default = {
us-east-1 = 1
us-west-1 = 2
us-west-2 = 3
eu-central-1 = 4
ap-northeast-1 = 5
}
}
variable "az_number" {
# Assign a number to each AZ letter used in our configuration
default = {
a = 1
b = 2
c = 3
d = 4
e = 5
f = 6
# and so on, up to n = 14 if that many letters are assigned
}
}
data "aws_region" "current" {}
# Determine all of the available availability zones in the
# current AWS region.
data "aws_availability_zones" "available" {
state = "available"
}
# This additional data source determines some additional
# details about each VPC, including its suffix letter.
data "aws_availability_zone" "all" {
for_each = aws_avaiability_zones.available.names
name = each.key
}
# A single VPC for the region
resource "aws_vpc" "example" {
cidr_block = cidrsubnet("10.1.0.0/16", 4, var.region_number[data.aws_region.current.name])
}
# A subnet for each availability zone in the region.
resource "aws_subnet" "example" {
for_each = aws_availability_zone.all
vpc_id = aws_vpc.example.id
availability_zone = each.key
cidr_block = cidrsubnet(aws_vpc.example.cidr_block, 4, var.az_number[each.value.name_suffix])
}
For example, if we were working in us-west-2 and there were availability zones us-west-2a and us-west-2c, the above would declare:
- A single
aws_vpc.example with CIDR block 10.1.48.0/20, where 48 is the decimal representation of hex 0x30, where 3 is the number for us-west-2.
- A subnet
aws_subnet.example["us-west-2a"] in us-west-2a with CIDR block 10.1.49.0/24, where 49 is the decimal representation of hex 0x31.
- A subnet
aws_subnet.example["us-west-2c"] in us-west-2c with CIDR block 10.1.51.0/24, where 51 is the decimal representation of hex 0x33.
Notice that there is no subnet for 10.1.50.0/24, because 50 (hex 0x32) is reserved for a hypothetical us-west-2b. By allocating these addresses statically by subnet letter we can ensure that they will not change over time as availability zones are added and removed.