Terraform Count

Terraform Count [Save your time by using it]

Terraform is excellent for describing your environment using code. However, copying and pasting the exact resource representation can be dull if you build multiple resources. Fortunately, Terraform gives two ways to handle similar resources for each and count. Later, you will be able to use it on Dynamic Block.

A module is known as a block of re-usable code in its most basic form. Confusingly, a module in Terraform is technically any set of templates in a folder. But, in my belief, it makes more sense to separate modules up into “parent” and “child.” The parent module describes infrastructure by passing variables to the children.

How to use Terraform Count

We use count to deploy multiple resources. The count meta-argument generates numerous resources of a module. You use the count argument within the block and assign a whole number (could by variable or expression), and Terraform generates the number of resources. Each resource is its discrete object generated, refreshed, or destroyed when performing the “terraform apply.” The count is exceptional for resources that share the same configurations.

While count has eternally worked with resources, Hashicorp (the Terraform owner) didn't append support for modules until Terraform v0.13.

Basic Syntax

The Terraform count is a meta-argument determined by the Terraform language. The count can utilize it with modules and with all resource types.

Also, the count meta-argument supports a whole number moreover makes that many instances of the resource or module. Each instance has a different infrastructure object connected with it, and each is individually built, updated, or destroyed when the configuration is applied.

resource "aws_instance" "web-inst" {
  count = 2 # generate two similar EC2 instances
  ami           = "ami-0ed9277fb7eb570c9"
  instance_type = "t3.xlarge"
  tags = {
    Name = "Instance ${count.index}"
    Owner = "Bits Lovers"
  }
}

How to use expressions in the count

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

Terraform CountReal Example 1

Let’s suppose that you need to create multiple Kubernetes secrets for each namespace in your cluster. So, to avoid making it one by one, you can receive an array of namespaces that contains all namespaces from your cluster to create the secrets according to the array length.

resource "kubernetes_secret" "k8_secret" {
  count    = length(var.namespaces)
  provider = kubernetes
  metadata {
    name = "credentials"
    namespace = var.namespaces[count.index]
  }
  data = {
    username = "k8-user"
    password = var.SECRECT
  }
}

Terraform CountReal Example 2

In our second case, it’s beneficial to use count to create multiple DNS entries when we have all DNS in a single array if you need to create them using the same configuration.

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

In this example, we received the array public_dns, containing all DNS we need to create. So, the length of this array will tell us how many DNS we need to make. 

Easy, right? Let’s jump to our following real example.

Terraform CountReal Example 3

Another practical scenario to use count is when we decide if we need to create a specific resource dynamically by changing one variable value or any other conditional.

In this example, we create one Network Load Balance only when the lb_type variable equals “network-lb.” So, if it’s equal, the count value will be 1. If not, it will be zero, which means we will not create that resource.

resource "aws_lb" "network-lb" {
  count = var.lb_type == "network-lb" ? 1 : 0
  name = "bitslovers-nlb"
  load_balancer_type = "network"
  subnets         = split(",", var.subnet_ids)
  idle_timeout    = 120
  internal        = var.enable_internal
  enable_deletion_protection = true
}

In cases like the above, we need to be aware of any resource that depends on that resource, and we need to use the same logic or somehow avoid any issue if this resource is not available.

Terraform Count Limitations

Sadly, the count has two restrictions that significantly decrease its utility. First, while you can apply count to loop across a whole resource, you can’t apply count inside a resource to circle across inline blocks. An inline-block is an argument you placed in a resource of the form:

resource "type" "name" {
  {
 [ARG...]
 }
}

Where TEXT is the name of the inline-block (e.g., tag) and ARG consists of one or more arguments that are defined to that inline-block (like key and value). For instance, consider how our tags are set in the aws_autoscaling_group resource:

resource "aws_autoscaling_group" "example" {
  launch_configuration = aws_launch_configuration.bitslovers.name
  target_group_arns    = [aws_lb_target_group.as_g.arn]
  health_check_type    = "ELB"  
  min_size = var.min_size
  vpc_zone_identifier  = data.aws_subnet_ids.public.ids
  max_size = var.max_size  tag {
    key                 = "Owner"
    propagate_at_launch = true
    value               = var.env_name
  }
}

Here, we are using Terraform Data, check our post to learn mode.

So, each tag requires you to make a new inline-block with key, value, and propagate_at_launch values. The initial code hardcodes a single tag, but you may desire to permit users to pass in custom tags. You might be seduced to employ the count parameter to loop over these tags and render dynamic inline tag blocks. Unfortunately, using count inside an inline block is not supported.

How to Deploy Multiple Resources using 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 determining the number of resources, the for_each argument assumes in a map or set of strings. The number of resources Terraform makes it depends on the number of input objects on this map.

For instance, let’s how we can make the same to create IAM users using for_each:

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

Look for the use of toset to transform the var.list_users list into a set, as for_each only enables sets and maps when utilized on a resource. When for_each loops over this set, it will create each user name known in each.value. The user name will also be understood in each key, though you usually only utilize each.key with maps of key/value pairs.

When you use for_each on a resource, it becomes a map of resources, rather than just one resource (or a set of resources as count). 

Notes on Terraform Count and For Each

Before you decide which one it should be used, there are a couple of ideas to keep in mind when working with for each and Terraform count and for each:

  • You cannot use count and for_each simultaneously in the same module or resource block.
  • When using for_each, the keys of the map must be known values, and sensitivity values are not permitted as arguments.
  • When resources are almost similar, use count.
  • If some arguments demand different values that cannot be derived from the count integer, utilize for_each instead.

Terraform also enables us to use very identical functionality in a for expression (not to be mistaken with the for_each). The fundamental syntax of a for presentation is:

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

The LIST is a list to loop over, and the ITEM represents the local variable name to allocate to an individual item in 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" {
  description = "A list of users"
  type        = list(string)
  default     = ["bits", "lovers", ".com"]
}
output "upper_names" {
  value = [for user in var.users : upper(user)]
}

Conclusion

Employing the Terraform count and for_each technique drives your Terraform configurations more dynamic. Also, it can simplify your code and avoid code duplication.

Boost your Terraform Skills:

How and When to use Terraform Modules?

Do you know what is Terraform Data and How to use it?

Learn How to use Output on your Terraform and share data across multiple configurations.

Create multiple copies of the same resource using Terraform Count.

Learn How to use Terraform Template, to build dynamic configurations.

Do you know the difference between Locals and Do you know what is Terraform Data and How to use it?

Execute Terraform on Gitlab CI.

2 thoughts on “Terraform Count [Save your time by using it]”

  1. how to delete/destroy one of the instances of a resource without the count index??
    for example, i have created 3 buckets & i want to destroy 2nd bucket without the index value of the bucket i.e bucket[1]. so how can i destroy the bucket with the bucket name??

    1. Hello,
      If you created the S3 bucket using count, it means that you are using a List/Array on your Terraform to specify the 3 buckets’ names at the same variable. So, you have to remove the bucket that you want from the Array/List and execute the Terraform again, to destroy the Bucket for you. Remember that, to make it work, the S3 bucket must be empty.

      Thanks

Leave a Comment

Your email address will not be published. Required fields are marked *

Free PDF with a useful Mind Map that illustrates everything you should know about AWS VPC in a single view.