When should you use for_each and count?

Terraform Guide Book | for_each, count or not
이민석's avatar
Mar 06, 2024
When should you use for_each and count?

Introduction

Thank you for clicking through to my arcticle. I've been a DevOps engineer for 2 years in dev-team of 7 engineers.

My name is MINSEOK, LEE, but I use Unchaptered as an alias on the interenet. So, you can call me anythings "MINSEOK, LEE" or "Unchaptered" to ask something.

Topics

  • What is the difference for_each and count

  • When should you use for_each and count?

What is the difference for_each and count?

Jen's Space - [Terraform] count vs for_each Meta-Argument 차이점 - 변수 list(object) 타입 사용

You shouldn't use "count" if the length of array is likely to change.
Generally, you may to use "for_each".

For more information, see "count" case 1, 2 and "for_each" case 1, 2.

The "count" case 1

When length is 3 and you change 2nd value, terraform recreate only 1 resource.
> Plan : 1 to add, 0 to change, 1 to destroy

  • AS-IS

    variable "subnets" {
        type = list(object({
            name = string
            cidr_block = string
        }))
        default = [
            {
                name = "subnet-1"
                cidr_block = "30.0.1.0/24"
            },
            {
                name = "subnet-2"
                cidr_block = "30.0.2.0/24" # ✅
            },
            {
                name = "subnet-3"
                cidr_block = "30.0.3.0/24"
            }
        ]
    }
  • TO-BE

    variable "subnets" {
        type = list(object({
            name = string
            cidr_block = string
        }))
        default = [
            {
                name = "subnet-1"
                cidr_block = "30.0.1.0/24"
            },
            {
                name = "subnet-2"
                cidr_block = "30.0.4.0/24" # ✅
            },
            {
                name = "subnet-3"
                cidr_block = "30.0.3.0/24"
            }
        ]
    }
  • Plan : 1 to add, 0 to change, 1 to destroy

The "count" case 2

When length is 3 and you remove 2nd element, terraform recreate only 1 resource and destroy 1 resource more.
> Plan : 1 to add, 0 to change, 2 to destroy

  • AS-IS

    variable "subnets" {
        type = list(object({
            name = string
            cidr_block = string
        }))
        default = [
            {
                name = "subnet-1"
                cidr_block = "30.0.1.0/24"
            },
            {
                name = "subnet-2"
                cidr_block = "30.0.2.0/24" # ✅
            },
            {
                name = "subnet-3"
                cidr_block = "30.0.3.0/24"
            }
        ]
    }
  • TO-BE

    variable "subnets" {
        type = list(object({
            name = string
            cidr_block = string
        }))
        default = [
            {
                name = "subnet-1"
                cidr_block = "30.0.1.0/24"
            },
            {
                name = "subnet-3"
                cidr_block = "30.0.3.0/24"
            }
        ]
    }
  • Plan : 1 to add, 0 to change, 2 to destroy

    Changes to Outputs:
      ~ aws_subnet_ids = [
            "subnet-abcd1234efgh5678i",
          - "subnet-abcd1234efgh5678i",
          - "subnet-abcd1234efgh5678i",
          + (known after apply),
        ]

The "for_each" case 1

When length is 3 and you remove "subnet-2", terrafrom remove 1 resource.
> Plan: 0 to add, 0 to change, 1 to destroy.

  • AS-IS

    # [Variable]
    variable "profile" {
      type = string
      default = "eksprac"
    }
    
    variable "subnets" {
        type = list(object({
            name = string
            cidr_block = string
        }))
        default = [
            {
                name = "subnet-1"
                cidr_block = "30.0.1.0/24"
            },
            {
                name = "subnet-2"
                cidr_block = "30.0.2.0/24"
            },
            {
                name = "subnet-3"
                cidr_block = "30.0.3.0/24"
            }
        ]
    }
    
    # [Provider]
    provider "aws" {
        profile = var.profile
    }
    
    # [Resource]
    resource "aws_vpc" "aws_vpc" {
        cidr_block = "30.0.0.0/16"
    }
    
    resource "aws_subnet" "aws_subnets" {
        for_each = { for subnet in var.subnets : subnet.name => subnet.cidr_block }
        vpc_id = aws_vpc.aws_vpc.id
    
        cidr_block = each.value
        tags = {
            Name = each.key
        }
    }
    
    # [Output]
    output "aws_subnet_ids" {
      value = { for k, v in aws_subnet.aws_subnets : v.tags.Name => v.id }
    }
  • TO-BE

    # [Variable]
    variable "profile" {
      type = string
      default = "eksprac"
    }
    
    variable "subnets" {
        type = list(object({
            name = string
            cidr_block = string
        }))
        default = [
            {
                name = "subnet-1"
                cidr_block = "30.0.1.0/24"
            },
            {
                name = "subnet-3"
                cidr_block = "30.0.3.0/24"
            }
        ]
    }
    
    # [Provider]
    provider "aws" {
        profile = var.profile
    }
    
    # [Resource]
    resource "aws_vpc" "aws_vpc" {
        cidr_block = "30.0.0.0/16"
    }
    
    resource "aws_subnet" "aws_subnets" {
        for_each = { for subnet in var.subnets : subnet.name => subnet.cidr_block }
        vpc_id = aws_vpc.aws_vpc.id
    
        cidr_block = each.value
        tags = {
            Name = each.key
        }
    }
    
    # [Output]
    output "aws_subnet_ids" {
      value = { for k, v in aws_subnet.aws_subnets : v.tags.Name => v.id }
    }
  • Plan: 0 to add, 0 to change, 1 to destroy.

    Changes to Outputs:
      ~ aws_subnet_ids = {
          - subnet-2 = "subnet-05d79581d967cd923"
            # (2 unchanged attributes hidden)
        }

The "for_each" case 2

When length is 3 and you change "subnet-2" to "subnet-5", terrafrom recreate 1 resource.
> Plan: 1 to add, 0 to change, 1 to destroy.

  • AS-IS

    # [Variable]
    variable "profile" {
      type = string
      default = "eksprac"
    }
    
    variable "subnets" {
        type = list(object({
            name = string
            cidr_block = string
        }))
        default = [
            {
                name = "subnet-1"
                cidr_block = "30.0.1.0/24"
            },
            {
                name = "subnet-2"          # 😊
                cidr_block = "30.0.2.0/24"
            },
            {
                name = "subnet-3"
                cidr_block = "30.0.3.0/24"
            }
        ]
    }
    
    # [Provider]
    provider "aws" {
        profile = var.profile
    }
    
    # [Resource]
    resource "aws_vpc" "aws_vpc" {
        cidr_block = "30.0.0.0/16"
    }
    
    resource "aws_subnet" "aws_subnets" {
        for_each = { for subnet in var.subnets : subnet.name => subnet.cidr_block }
        vpc_id = aws_vpc.aws_vpc.id
    
        cidr_block = each.value
        tags = {
            Name = each.key
        }
    }
    
    # [Output]
    output "aws_subnet_ids" {
      value = { for k, v in aws_subnet.aws_subnets : v.tags.Name => v.id }
    }
  • TO-BE

    # [Variable]
    variable "profile" {
      type = string
      default = "eksprac"
    }
    
    variable "subnets" {
        type = list(object({
            name = string
            cidr_block = string
        }))
        default = [
            {
                name = "subnet-1"
                cidr_block = "30.0.1.0/24"
            },
            {
                name = "subnet-5"          # 😊
                cidr_block = "30.0.2.0/24"
            },
            {
                name = "subnet-3"
                cidr_block = "30.0.3.0/24"
            }
        ]
    }
    
    # [Provider]
    provider "aws" {
        profile = var.profile
    }
    
    # [Resource]
    resource "aws_vpc" "aws_vpc" {
        cidr_block = "30.0.0.0/16"
    }
    
    resource "aws_subnet" "aws_subnets" {
        for_each = { for subnet in var.subnets : subnet.name => subnet.cidr_block }
        vpc_id = aws_vpc.aws_vpc.id
    
        cidr_block = each.value
        tags = {
            Name = each.key
        }
    }
    
    # [Output]
    output "aws_subnet_ids" {
      value = { for k, v in aws_subnet.aws_subnets : v.tags.Name => v.id }
    }
  • Plan: 1 to add, 0 to change, 1 to destroy.

    Changes to Outputs:
      ~ aws_subnet_ids = {
          - subnet-2 = "subnet-05d79581d967cd923"
          + subnet-5 = (known after apply)
            # (2 unchanged attributes hidden)
        }

When should you use for_each and count?

However, both for_each and count have the issue that the resource is dependent on the array. So for resources that you won't touch once created, it might be better to create them manually.

Share article

Unchaptered