Terraform for_each loop Example

Overview

In this blog post, we will discuss how to use Terraform's for_each loop to deploy three subnets in three availability zones along with their respective CIDR blocks and tags.

One of the most powerful and useful features of Terraform is the ability to use loops, which help you create and manage multiple resources with similar configurations.

Prerequisites

Before diving in, ensure that you have the following prerequisites:

  1. Terraform installed
  2. An AWS account with access keys configured
  3. AWS CLI installed and configured

Now, let's create a directory for our Terraform project and initialize it.

$ mkdir for_each
$ cd for_each
$ terraform init

As I mentioned before, our ultimate goal here is to deploy three subnets across three AZs using for_each loop. There are multiple ways to achieve it, let's look at a couple of ways to do it. My preference is Option 1.

Option - 1

Here is the Terraform configuration file and let's break it down.

provider "aws" {
  region  = "eu-west-1"
}

locals {
  subnets = {
    "subnet-1" = { cidr_block = "10.10.1.0/24", availability_zone = "eu-west-1a", tag_name = "private-subnet-1a" }
    "subnet-2" = { cidr_block = "10.10.2.0/24", availability_zone = "eu-west-1b", tag_name = "private-subnet-2b" }
    "subnet-3" = { cidr_block = "10.10.3.0/24", availability_zone = "eu-west-1c", tag_name = "private-subnet-3c" }
  }
}

resource "aws_vpc" "vpc_test" {
  cidr_block = "10.10.0.0/16"

  tags = {
    Name = "vpc_test-vpc"
  }
}

resource "aws_subnet" "private" {
  for_each = local.subnets

  cidr_block        = each.value.cidr_block
  vpc_id            = aws_vpc.vpc_test.id
  availability_zone = each.value.availability_zone

  tags = {
    Name = each.value.tag_name
  }
}

Provider and Region - We start by specifying the AWS provider and the region eu-west-1 where we intend to deploy our resources.

Locals - We then define a local variable called subnets that contains a map with the desired subnet configurations. Each key is a subnet name (subnet-1), and each value is a map with the cidr_block, availability_zone, and tag_name for the subnet.

VPC - Next, we create a VPC with a CIDR block of 10.10.0.0/16 and a name tag of example-vpc

Subnets - The final step is to use the for_each loop to create multiple subnets by iterating over the subnets local variable. For each subnet, we assign the cidr_block, availability_zone, and tag_name values from the local variable.

The for_each loop iterates over the keys and values in the local.subnets map. For each iteration, each.key represents the current key (subnet name), and each.value represents the corresponding value (a map with cidr_block, availability_zone, and tag_name).

On the first iteration, the key will be subnet-1 and the value will be cidr_block = "10.10.1.0/24", availability_zone = "eu-west-1a", tag_name = "private-subnet-1a"If you want to access the tag_name, you can use each.value.tag_name which will result in private-subnet-1a

The aws_subnet resource block sets the cidr_block, vpc_id, availability_zone, and tags attributes using the values from the each.value map. This way, Terraform automatically creates the desired subnets with their respective configurations.

Option - 2

If you want to get creative and don't want to define tags inside the map, this option is for you.

provider "aws" {
  region = "eu-west-1"
}

locals {
  subnets = {
    "eu-west-1a" = "10.10.1.0/24"
    "eu-west-1b" = "10.10.2.0/24"
    "eu-west-1c" = "10.10.3.0/24"
  }
}

resource "aws_vpc" "test-vpc" {
  cidr_block = "10.10.0.0/16"

  tags = {
    Name = "test-vpc"
  }
}

resource "aws_subnet" "private-subnet" {
  for_each = local.subnets

  cidr_block        = each.value
  vpc_id            = aws_vpc.test-vpc.id
  availability_zone = each.key

  tags = {
    Name = "private-subnet-1${substr(each.key, -1, 1)}"
  }
}

Everything is self-explanatory at this point except for the tags. Let's break it down, shall we?

The expression Name = "private-subnet-1${substr(each.key, -1, 1)}" is used to generate a subnet name based on the Availability Zone key in the for_each loop.

  1. each.key - This is a reference to the current key of the loop iteration, which in this example is the Availability Zones (eu-west-1a or eu-west-1b or eu-west-1c)
  2. substr(each.key, -1, 1) - The substr function is used to extract a portion of a string. It takes three arguments: the input string, the starting index, and the length of the substring. In this case, we pass each.key as the input string, -1 as the starting index, and 1 as the length. The negative starting index -1 means that we start counting from the end of the string, so this expression extracts the last character of the Availability Zone name ('a', 'b', or 'c')
  3. "private-subnet-1${substr(each.key, -1, 1)}" - This is a string interpolation that combines the string private-subnet-1 with the last character of the AZ name extracted in the previous step. The resulting string will be private-subnet-1a or private-subnet-1b or private-subnet-1c depending on the current each.key value.

Conclusion

Using Terraform's for_each loop simplifies the deployment of multiple resources in AWS and makes the code more readable.