First time at Zeet?

25 Apr
2024
-
22
min read

A Simple Terraform for_each Tutorial for Efficient Coding

Boost your Terraform skills with this guide on utilizing the Terraform for_each feature. Simplify your coding and increase productivity today.

Jack Dwyer

Product
How To
Content
heading2
heading3
heading4
heading5
heading6
heading7

Share this article

What Are Meta-Arguments in Terraform?

person working with terraform for_each

What Is Terraform Meta - Arguments? I'm really into meta-arguments in Terraform. They are special arguments that you chuck into the resource, data, or module blocks to tweak the behavior of these blocks. Different block types in Terraform have different meta-arguments that they support. For instance, the resource block goes for the count meta-argument. This guy also supports the for_each meta-argument plus a few others.

Maximizing Resource Deployment Efficiency

A resource block in Terraform by default splurts out just the one infrastructure resource, could be a storage bucket or load balancer or compute instance. But what if you want to kickstart oodles of infrastructure resources of the same type? That's where meta-arguments like count and for_each come in. 

Harnessing the Power of Terraform's Count and For_each Meta-Arguments

These are the two saviors when you need to get your mitts on multiple instances of an infrastructure resource in Terraform, but what is Terraform by the way? Instead of tapping away at individual resource blocks for each instance of an infrastructure resource you're gunning for, you can bang out a single resource block then slam in a meta-argument like count or for_each to whip up as many instances as you fancy.

Banging out a resource block for each bit of infrastructure you're slinging with count isn't the best move. I'll explain why in a bit.

Why Is the 'count' Meta-Argument Problematic?

dev student understanding terraform for_each

Lists are ordered collections which means that the order of every element within a list is significant. The count meta_argument works with a list type, meaning every element has an index position - as seen below - when you run the Terraform state list command to list all resources in the Terraform.tfstate file

aws_instance.sandbox[0]
aws_instance.sandbox[1]
aws_instance.sandbox[2]

Maintaining Stability in Terraform Infrastructure with Index Referencing

From above, every element is referenced using an index number, and not even the string values in the sandboxes variable in the code snippet seen earlier, i.e., default = ["sandbox_one", "sandbox_two", "sandbox_three"]. If you remove any element that is not the last element in the list above, you will get unexpected infrastructure resource changes when you run Terraform plan to see the execution plan.

For example, if you remove the element at index 0 (i.e., sandbox_one), the following happens:

- The current element at index 1 (i.e., sanbox_two) will now become the new element at index 0, i.e., sandbox_two is now at index 0
- The current element at index 2 (i.e., sanbox_three) will now be the new element at index 1, i.e., sandbox_three is now at index 1
- There will be no element at index 3, and this index will be destroyed

This is where the flexibility of the for_each meta-argument comes into play. Since for_each works with unordered collections like a map or set, elements are instead referenced by string values. Let’s dive deeper into the for_each meta-argument in the following sections.

Empowering Engineering Teams with Zeet's CI/CD Platform

Zeet helps you to get more from your cloud, Kubernetes, and Terraform investments and helps your engineering team become strong individual contributors through our CI/CD & deployment platform. 

Contact Zeet to learn more about how Zeet helps you get seamless cloud deployments every time, and helps your team to become a top-performing engineering team.

Zeet Terraform and Helm Product Overview

Related Reading

How the Terraform for_each Meta-Argument Improves Terraform Configurations

dev team working together on terraform for_each

Terraform for_each is a meta argument that helps in creating multiple instances of a defined resource. It also provides us with the flexibility of dynamically setting the attributes of each resource instance created, depending on the type of variables being used to create real-world replicas.

for_each primarily works with a set of strings (set(string)) and map of strings (map(string)). The provided string values are used to set instance-specific attributes.

Customizing Resources with Terraform's for_each

For example, when creating multiple subnets, we can specify different CIDR ranges for each subnet created using the same resource block.

When for_each meta-argument is used in a resource block, a special object each is automatically available to refer to each instance created by the for_each. each object is used to refer to the values provided in set, and key and value pairs provided in a map type variable

Examples of Using the for_each Meta-Argument in Terraform

powerful examples of using terraform for_each

Let us take a real-world example to better understand the for_each meta-argument.

Say you have a map of instance configurations where the string value for each key is an identifier for the EC2 instance, and the value is another map containing the instance type and AMI ID.

#main.tf variable "instances" {  description = "Map of instance configurations"  type = map(object({    ami           = string    instance_type = string  }))  default = {    "amzlinux" = {    ami           = "ami-02d3fd86e6a2f5122"      instance_type = "t2.micro"    },    "ubuntu" = {      ami           = "ami-0ce2cb35386fc22e9"      instance_type = "t2.small"    }  } } resource "aws_instance" "servers" {  for_each      = var.instances  ami           = each.value.ami  instance_type = each.value.instance_type  tags = {    Name = "env0-Server-${each.key}"  } }


Let’s break everything down:

  • variable "instances" defines a map where each element represents an EC2 instance configuration. For instance, "amzlinux" and "ubuntu" are identifiers for these configurations, each specifying an AMI ID and an instance type.
  • resource "aws_instance" "servers" uses for_each to iterate over each element in the var.instances map. For each element, it creates an EC2 instance with the specified AMI and instance type.
  • each.key in this context refers to the key in the map (e.g., "amzlinux", "ubuntu"), which we use to uniquely name each instance with the Name tag.
    each.value.ami and each.value.instance_type access the nested values for each instance's configuration.
  • After successfully running the Terraform workflow (init->plan->apply), we have provisioned these two instances using for_each.’


Using for_each with map

The map type provides a “set” of key value pairs, offering more customization options for more complex recurring resources to be provisioned.

In this case, the number of instances to be created is equal to the length of the map object. Along with the each.value, we can also leverage the string value stored in each.key. This is a next step where instead of one, there are two attributes to be set dynamically using the same Terraform resource block.

variable "instance_map" {  type = map(string)  default = {    "inst_a" = "Instance A",    "inst_b" = "Instance B"  } }


The instance_map variable above has a default value of two key-value pairs.

We use this variable in the for_each meta-argument to create two EC2 instances, as shown in the configuration block below.

resource "aws_instance" "by_map" {  for_each = var.instance_map  ami = "ami-0b08bfc6ff7069aff"  instance_type = "t2.micro"  tags = {    Name = each.value  ID = each.key  }


The for_each meta-argument in the above code snippet is responsible for creating two EC2 instances.

The Name and ID tag refer to the value and key strings set in the default value of the instance_map variable. The plan command output confirms the same.

Related Reading

How Can Terraform for_each Be Applied Within Dynamic Blocks?

how to use terraform for_each

In Terraform, dynamic blocks come in handy when constructing repetitive nested blocks without code duplication. One common use case is the incorporation of the for_each meta-argument in dynamic blocks. The for_each argument allows the creation of a distinct resource for each element in a map or list. Let’s take a look at a code snippet to understand this concept better:

``` hcl resource "aws_security_group" "test_sg" {  name   = "test_sg"  vpc_id = var.vpc_id  dynamic "ingress" {    for_each = var.ingress_ports    content {      from_port   = ingress.value      to_port     = ingress.value      protocol    = "tcp"      c idr_blocks = ["0.0.0.0/0"]      description = "Allows ingress for port ${ingress.value}"    }  } }


In the code above, we are defining an AWS security group resource that permits ingress traffic into the specified VPC using vpc_id variable. Instead of manually duplicating the nested ingress block, a dynamic block is employed within the resource block, which dynamically generates repeatable ingress blocks using the for_each argument. 

Within a dynamic block, the block’s name (e.g., ingress in the example) is used and not each. By leveraging for_each with dynamic blocks, multiple nested blocks can be easily created It is important to note that dynamic blocks in Terraform can also be utilized inside other block types, such as data, provider, and provisioner blocks.

Key Considerations When Working With the for_each Meta-Argument?

key considerations related to terraform for_each

When working with the for_each meta-argument in Terraform, it's essential to understand how to reference both blocks and block instances. This involves using specific syntax to refer to each instance and the block itself. 

For example, when referencing a resource block, you would use: <resource_type>.<resource_name>. To refer to an instance, you would use <resource_type>.<resource_name>[<key>]. 

Limitations and Considerations with for_each in Terraform

There are several important limitations and considerations to keep in mind when using the for_each meta-argument in Terraform. For example, the keys or values used for iteration in for_each serve as identifiers for the multiple resources they create. This means that they are always visible in the Terraform UI output and the state file. 

Handling Sensitive Values in Terraform's for_each

Sensitive values cannot be used as arguments in for_each implementations. This includes sensitive input variables, sensitive outputs, and sensitive resource attributes.

Preparing Keys and Values for Terraform's for_each

The keys or values of a for_each must be known before a Terraform apply operation. These values cannot depend on the result of any impure function like a timestamp because they are evaluated later during the main evaluation step. It is also crucial to use descriptive keys or values to easily identify resources.

Expressions and Chaining for_each in Terraform

When using for_each in Terraform, the types of values used must be a set or a map. If you have a list of values, you can use the to set type conversion function to convert it to a set. Nested data structures that are not suitable values for for_each can be handled using the for construct or other helpful built-in functions like flatten.

It is important to note that Terraform does not support nested loops in the resource block. You can only use one for_each meta-argument, and it cannot be nested. Chaining for_each can be done when there is a one-to-one relationship between objects. This allows you to use one resource as the for_each of another to create associations between resources, such as provisioning an AWS EBS volume for every EC2 instance.

Related Reading

Advantages of Using Terraform For_each‍

person understanding benefits of terraform for_each

Terraform for_each is a game-changer when it comes to managing dynamic resources. With for_each, I can define a list of project names and provision a dynamic number of S3 buckets based on that list. This ability to dynamically create resources based on a collection of items allows for flexible and efficient infrastructure provisioning.

Conditional Resource Creation with Terraform for_each

When combined with Terraform's conditional expressions, for_each enables the creation of resources based on specific criteria within the data it iterates over. This granular control allows me to conditionally create resources, ensuring that only the necessary resources are created, updated, or destroyed. This level of control adds a layer of sophistication to infrastructure management that was not previously possible.

Improved Code Reusability with Terraform for_each

With for_each, I can write modular and reusable code. Instead of duplicating resource blocks for each instance of a resource, I can define a single block that is applied to each item in a collection. This approach abstracts common configuration elements into a parameterized block, allowing for dynamic resource creation. I can even combine for_each with modules to keep my code DRY (Don't Repeat Yourself), promoting efficiency and maintainability in my infrastructure as code.

Zeet Contact Us

Get Control of Your Releases With Zeet's CI/CD & Deployment Platform for Kubernetes and Terraform

Zeet helps you to get more from your cloud, Kubernetes, and Terraform investments. Our platform is designed to assist your engineering team in becoming strong individual contributors. 

With Zeet, your team can achieve seamless cloud deployments every time. Our CI/CD & deployment platform is designed to help your team become a top-performing engineering team. Reach out to Zeet to discover how we can help you maximize your investments in cloud, Kubernetes, and Terraform and help your team achieve its full potential.

Subscribe to Changelog newsletter

Jack from the Zeet team shares DevOps & SRE learnings, top articles, and new Zeet features in a twice-a-month newsletter.

Thank you!

Your submission has been processed
Oops! Something went wrong while submitting the form.