ScanSkill
Sign up for daily dose of tech articles at your inbox.
Loading

How to Use count and for_each in Terraform

How to Use count and for_each in Terraform
How to Use count and for_each in Terraform

In this article, you’ll get to learn in detail how to use count and for_each in Terraform.

Terraform is an IaC tool by HashiCorp that lets you define cloud and on-premise resources in human-readable configuration files. If you are already using Terraform for your infrastructures, then you might have done copying-pasting the same resource configurations multiple times. Which is pretty much a dumb approach. Terraform has two ways to handle such similar resources: count and for each. In this, we’ll walk through how to use count first then for_each.

Terraform count argument allows for deployment or handling of multiple resources with the same configuration block. This can be used with dynamic blocks(Know more: Dynamic Block in Terraform). Terraform count eliminates the copying and pasting of the same exact resource configurations.

Prerequisites

How to use count and for_each in Terraform

1. Terraform count

You can use count to deploy multiple resources which has similar configurations. This count meta-argument generates numerous resources of a module. In Terraform, the count argument is used within the block and assigned a whole number— expression or variable, and then Terraform generates the number of resources. Every resource is its discrete object generated, updated, or destroyed when performing the “terraform apply.”

Note: Hashicorp didn’t append support for modules until Terraform v0.13.

How to use count in Terraform

The Terraform count is a meta-argument and it can be utilized with modules and with all resource types. The general syntax is:

count = 4 # 4 similar resources.

For example, the following configuration is to configure 3 similar EC2 instances on AWS:

resource "aws_instance" "ex-instance" {
  count         = 3 
  ami           = "ami-65465sdfds4sdf51"
  instance_type = "t2.medium"
  tags = {
    Name  = "ScanSkill Example Instance ${count.index}"
  }
}

With this configuration, each instance has a different infrastructure object connected with it, and each is individually built, updated, or destroyed when the configuration is applied.

Practical Implementation of count in Terraform

The count meta-argument uses numeric expressions. But, the count value must be defined before Terraform execute remote resource operations. This means count can’t point to any resource attributes that aren’t identified until after a configuration is applied (like a unique ID created by the remote API during an object creation).

Now, let’s dive into examples of how to use the count meta-argument in Terraform:

Example-1: Multiple resource creation

Let’s say, you need multiple elastic node instances with different subnets. To avoid making it one by one, you can use count meta-argument to assign subnet values individually.

variable "public_subnet_ids" {
  default = ["subnet-05fdddddfsffc1fdd8", "subnet-05fdddd7sdfc1fdd8", "subnet-0a7207e3sf9b5fcaf", "subnet-0hjdfbhdbdsbsj83b3"]
  type   = list(any)
}
resource "aws_instance" "elastic_nodes" {
  count                  = 4
  ami                    = var.elastic_aws_ami
  instance_type          = var.elastic_aws_instance_type
  subnet_id              = var.public_subnet_ids[count.index]
  vpc_security_group_ids = [aws_security_group.elasticsearch_sg.id]
  key_name               = aws_key_pair.elastic_ssh_key.key_name
  iam_instance_profile   = "${aws_iam_instance_profile.elastic_ec2_instance_profile.name}"
  associate_public_ip_address = true
  tags = {
    Name = "ElasticSearch Node Instance-${count.index}"
  }
}

Here, 4 node instances are created with different subnets associated with each one of them. Also, the tag is generated according to the count value.

Easy Peasy!

You

Example-2: Get an array of values

Another scenario might be the case where you have an array of values.

For example, you can use count to create multiple DNS entries when you have all DNS in a single array as:

resource "aws_route53_record" "public-dns-record" {
  count           = length(var.public_dns_record)
  zone_id         = var.public_dns_zone_id
  name            = var.public_dns_record[count.index]
  type            = "CNAME"
  records         = [var.cname]
  ttl             = "90"
}

In this example, you will receive the array public_dns_record, containing all DNS you need to create.

Example-2: Dynamic Variable Value

Another scenario is when you need to create a specific resource dynamically by changing the value of one variable or any other conditional. Let’s say, in the following example, a resource will be created only when the value of variable node_type equals “slave”. So that if the value is equal to slave, the count will be 1, which means resources will be created otherwise not(as the count value becomes 0)

resource "aws_instance" "additional_elasticsearch_instance" {
  count                  = var.ec2_type == "slave" ? 1 : 0
  ami                    = var.elastic_aws_ami
  instance_type          = var.elastic_aws_instance_type
  subnet_id              = var.subnet_id
  vpc_security_group_ids = [aws_security_group.elasticsearch_sg.id]
  key_name               = aws_key_pair.elastic_ssh_key.key_name
  iam_instance_profile   = "${aws_iam_instance_profile.elastic_ec2_instance_profile.name}"
}

In a similar case like this, you need to be aware of any resource that depends on that resource or not, and you need to use the same logic or not.

Note: Unfortunately, using count inside an inline-block is not supported.

2. Terraform for_each

As we saw in the count argument, the for_each meta-argument generates multiple module or resource block instances. But this time, instead of defining the number of resources, the for_each argument assumes a map or set of strings. The number of resources created depends on the number of input objects mapped.

For instance, the following configuration is to create IAM users using for_each:

resource "aws_iam_user" "users" {
  for_each = toset(var.list_users)
  name     = each.value
}

Here, toset is used to transform the variable values from var.list_users list into a set. The for_each loops over the set and it creates each user name known in each.value.

Note: when you use for_each on a resource, it becomes a map of resources rather than just one resource.

Notes on Terraform count and for_each

The followings are the important points to keep in mind when using for_each and count:

  • For almost similar resources, use count*.*
  • If you need different values for some arguments that cannot be derived from the count, then use for_each.
  • You cannot use count and for_each in the same module or resource block.
  • When using for_each, the keys of the map must be known values.

Additional: for expression in Terraform

Like for_each, Terraform also provides us to use very identical functionality in a for expression. The fundamental syntax of a for expression is:

[for <ITEM> in <LIST> : <OUTPUT>]

Example: for expression

The LIST is a list to loop over, and the ITEM represents the local variable name to allocate to an individual item in the LIST. Finally, the OUTPUT represents an expression that changes ITEM somehow. For instance, here is the Terraform code to convert the list of names:

variable "users" {
  type        = list(string)
  default     = ["sagar", "sahas", "shuvam", "abish"]
}
output "uppercase_names" {
  value = [for user in var.users : upper(user)]
}

That’s it.

Conclusion

In this article, you went through the implementation of Terraform count and for_each technique on your Terraform configurations more dynamically. Also, you learned how these meta-arguments can simplify your code and avoid code duplication.

Thank you!

Sign up for daily dose of tech articles at your inbox.
Loading