Terraform count, for_each

신동수·2024년 3월 12일
0

Terraform

목록 보기
8/10

개요

  • Terraform 의 count 와 for_each 를 예제를 통해 정리하기 위해서 본 포스팅을 작성하였다.

count

하나의 Resource 또는 Module 에서 여러 인프라 리소스를 만들어내고 싶을 때 사용하는 Meta-Argument 이다.

  • 숫자 표현식을 허용
  • 생성할 리소스가 동일한 경우 for_each 보다는 count 사용이 더 쉬움

구조

# Terraform tree 구조
.
├── _variables_
│   ├── common-test.yaml
│   └── iam_info.yaml
├── main.tf
├── modules
│   └── iam
│       ├── iam.tf
│       └── variables.tf
├── provider.tf
└── variables.tf
# modules/iam/iam.tf

resource "aws_iam_user" "iam" {
  count = length(var.iam_info.user_list)

  name = var.iam_info.user_list[count.index]
  path = "/"

}

위 코드에서는 count 를 사용하여 아래 yaml 파일에 적용된 {iam_info.user_list} 의 길이를 받아와서 iam 사용자를 생성하게 된다.

# _variables_/dev/iam_info.yaml

user_list:
  - Aiden
  - Jay
  - John

결과

# terraform console 을 통한 값 확인
% terraform console
> local.iam_info
{
  "user_list" = [
    "Aiden",
    "Jay",
    "John",
  ]
}
> local.iam_info.user_list
[
  "Aiden",
  "Jay",
  "John",
]

# terraform plan 을 통한 값 확인
% terraform plan

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # module.iam.aws_iam_user.iam[0] will be created
  + resource "aws_iam_user" "iam" {
      + arn           = (known after apply)
      + force_destroy = false
      + id            = (known after apply)
      + name          = "Aiden"
      + path          = "/"
      + tags_all      = (known after apply)
      + unique_id     = (known after apply)
    }

  # module.iam.aws_iam_user.iam[1] will be created
  + resource "aws_iam_user" "iam" {
      + arn           = (known after apply)
      + force_destroy = false
      + id            = (known after apply)
      + name          = "Jay"
      + path          = "/"
      + tags_all      = (known after apply)
      + unique_id     = (known after apply)
    }

  # module.iam.aws_iam_user.iam[2] will be created
  + resource "aws_iam_user" "iam" {
      + arn           = (known after apply)
      + force_destroy = false
      + id            = (known after apply)
      + name          = "John"
      + path          = "/"
      + tags_all      = (known after apply)
      + unique_id     = (known after apply)
    }

count 사용 시 리소스를 인덱스로 식별할 수 있기 때문에 취약하다.
list의 요소 중 중간 요소가 삭제된다면 의도치 않은 변경사항이 발생할 수 있음.

for_each

하나의 Resource 혹은 Module에서 여러 인프라 리소스를 만들어내고 싶을 때 사용하는 Meta-Argument이다.

  • map 혹은 set 형식을 사용할 수 있다.
    - set : 유일한 값의 요쇼들로 이루어진 list
    - [1,2,3]
    - map : Key-Value 형식의 데이터
    - {Key1:Value1, Key2:Value2}
    - key 값은 string 이여야 한다.
  • for_each의 map 또는 set의 각 항목에 대해 인스턴스를 생성한다.

구조

# Terraform tree 구조
.
├── main.tf
├── modules
│   └── vpc
│       ├── subnet.tf
│       ├── variables.tf
│       └── vpc.tf
├── provider.tf
└── variables.tf
# modules/vpc/subnet.tf

variable "pub-subnets" {
  type = map(object({
    cidr_block = string
    availability_zone = string
    type       = string
  }))
  default = {
    public-a       = { cidr_block = "172.20.1.0/24",  availability_zone = "ap-northeast-2a", type = "public" }
    public-b       = { cidr_block = "172.20.2.0/24",  availability_zone = "ap-northeast-2b", type = "public" }
    public-c       = { cidr_block = "172.20.3.0/24",  availability_zone = "ap-northeast-2c", type = "public" }
  }
}

resource "aws_subnet" "dev-pubilc-subnet" {
  for_each = var.pub-subnets
  vpc_id     = aws_vpc.dev-vpc.id
  cidr_block = each.value.cidr_block
  availability_zone= each.value.availability_zone
  tags = merge(
    {
      Name = "dev-${each.key}-subnet"
      Type = each.value.type
    }
  )
}

위 코드에서는 for_each 의 map 을 사용하여 퍼블릭 서브넷의 cidr_block, availability_zone, type 을 정의 하였다.
cidr_block = each.value.cidr_block,와 each.value.availability_zone에서는 값이 cidr_blockavailability_zone 의 값이 순서대로 들어갈 것이다.
tags 에서 Name = "dev-${each.key}-subnet" 에는 public-a, public-b, public-c 가 들어갈 것이다.

  • each.key : map 혹은 set의 Key 값이다.
  • each.value : map 혹은 set의 Value 값이다.
    • 만약 set 형식일 경우 each.value의 결괏값은 each.key와 동일하다.

결과

# terraform plan 을 통한 값 확인
% terraform plan

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # module.vpc.aws_subnet.dev-pubilc-subnet["public-a"] will be created
  + resource "aws_subnet" "dev-pubilc-subnet" {
      + arn                                            = (known after apply)
      + assign_ipv6_address_on_creation                = false
      + availability_zone                              = "ap-northeast-2a"
      + availability_zone_id                           = (known after apply)
      + cidr_block                                     = "172.20.1.0/24"
      + enable_dns64                                   = false
      + enable_resource_name_dns_a_record_on_launch    = false
      + enable_resource_name_dns_aaaa_record_on_launch = false
      + id                                             = (known after apply)
      + ipv6_cidr_block_association_id                 = (known after apply)
      + ipv6_native                                    = false
      + map_public_ip_on_launch                        = false
      + owner_id                                       = (known after apply)
      + private_dns_hostname_type_on_launch            = (known after apply)
      + tags                                           = {
          + "Name" = "dev-public-a-subnet"
          + "Type" = "public"
        }
      + tags_all                                       = {
          + "Name" = "dev-public-a-subnet"
          + "Type" = "public"
        }
      + vpc_id                                         = (known after apply)
    }

  # module.vpc.aws_subnet.dev-pubilc-subnet["public-b"] will be created
  + resource "aws_subnet" "dev-pubilc-subnet" {
      + arn                                            = (known after apply)
      + assign_ipv6_address_on_creation                = false
      + availability_zone                              = "ap-northeast-2b"
      + availability_zone_id                           = (known after apply)
      + cidr_block                                     = "172.20.2.0/24"
      + enable_dns64                                   = false
      + enable_resource_name_dns_a_record_on_launch    = false
      + enable_resource_name_dns_aaaa_record_on_launch = false
      + id                                             = (known after apply)
      + ipv6_cidr_block_association_id                 = (known after apply)
      + ipv6_native                                    = false
      + map_public_ip_on_launch                        = false
      + owner_id                                       = (known after apply)
      + private_dns_hostname_type_on_launch            = (known after apply)
      + tags                                           = {
          + "Name" = "dev-public-b-subnet"
          + "Type" = "public"
        }
      + tags_all                                       = {
          + "Name" = "dev-public-b-subnet"
          + "Type" = "public"
        }
      + vpc_id                                         = (known after apply)
    }

  # module.vpc.aws_subnet.dev-pubilc-subnet["public-c"] will be created
  + resource "aws_subnet" "dev-pubilc-subnet" {
      + arn                                            = (known after apply)
      + assign_ipv6_address_on_creation                = false
      + availability_zone                              = "ap-northeast-2c"
      + availability_zone_id                           = (known after apply)
      + cidr_block                                     = "172.20.3.0/24"
      + enable_dns64                                   = false
      + enable_resource_name_dns_a_record_on_launch    = false
      + enable_resource_name_dns_aaaa_record_on_launch = false
      + id                                             = (known after apply)
      + ipv6_cidr_block_association_id                 = (known after apply)
      + ipv6_native                                    = false
      + map_public_ip_on_launch                        = false
      + owner_id                                       = (known after apply)
      + private_dns_hostname_type_on_launch            = (known after apply)
      + tags                                           = {
          + "Name" = "dev-public-c-subnet"
          + "Type" = "public"
        }
      + tags_all                                       = {
          + "Name" = "dev-public-c-subnet"
          + "Type" = "public"
        }
      + vpc_id                                         = (known after apply)
    }

YAML 을 통한 다른 예시

# modules/vpc/subnet.tf

resource "aws_subnet" "subnets_public" {
  for_each = var.vpc_info.cidr_blocks_public

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

  tags = merge(
    {
      Name = "${var.common_info.env}-${each.value.subnet_name}"
    },
    var.common_tags
  )
}

위 코드에서도 동일하게 for_each 를 사용하였다. 위와 다른점은 yaml 파일을 만들어 정의된 파일에서 값을 가져 오는거라 코드의 재사용성과 코드가 더 간결해진 것을 확인할 수 있다.

# _variables_/dev/vpc_info.yaml

# VPC
cidr_block_vpc: 172.21.0.0/16
vpc_name: vpc

# VPC - Subnet
cidr_blocks_public:
  public_a:
    subnet_name: public-a
    cidr_block: 172.21.0.0/22
    availability_zone: ap-northeast-2a
  public_b:
    subnet_name: public-b
    cidr_block: 172.21.4.0/22
    availability_zone: ap-northeast-2b
  public_c:
    subnet_name: public-c
    cidr_block: 172.21.8.0/22
    availability_zone: ap-northeast-2c

결과

# terraform plan 을 통한 값 확인
% terraform plan

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # module.vpc.aws_subnet.subnets_public["public_a"] will be created
  + resource "aws_subnet" "subnets_public" {
      + arn                                            = (known after apply)
      + assign_ipv6_address_on_creation                = false
      + availability_zone                              = "ap-northeast-2a"
      + availability_zone_id                           = (known after apply)
      + cidr_block                                     = "172.21.0.0/22"
      + enable_dns64                                   = false
      + enable_resource_name_dns_a_record_on_launch    = false
      + enable_resource_name_dns_aaaa_record_on_launch = false
      + id                                             = (known after apply)
      + ipv6_cidr_block_association_id                 = (known after apply)
      + ipv6_native                                    = false
      + map_public_ip_on_launch                        = false
      + owner_id                                       = (known after apply)
      + private_dns_hostname_type_on_launch            = (known after apply)
      + tags                                           = {
          + "Environment" = "Develop"
          + "Name"        = "dev-public-a"
          + "Project"     = "IaC-Terraform"
        }
      + tags_all                                       = {
          + "Environment" = "Develop"
          + "Name"        = "dev-public-a"
          + "Project"     = "IaC-Terraform"
        }
      + vpc_id                                         = (known after apply)
    }

  # module.vpc.aws_subnet.subnets_public["public_b"] will be created
  + resource "aws_subnet" "subnets_public" {
      + arn                                            = (known after apply)
      + assign_ipv6_address_on_creation                = false
      + availability_zone                              = "ap-northeast-2b"
      + availability_zone_id                           = (known after apply)
      + cidr_block                                     = "172.21.4.0/22"
      + enable_dns64                                   = false
      + enable_resource_name_dns_a_record_on_launch    = false
      + enable_resource_name_dns_aaaa_record_on_launch = false
      + id                                             = (known after apply)
      + ipv6_cidr_block_association_id                 = (known after apply)
      + ipv6_native                                    = false
      + map_public_ip_on_launch                        = false
      + owner_id                                       = (known after apply)
      + private_dns_hostname_type_on_launch            = (known after apply)
      + tags                                           = {
          + "Environment" = "Develop"
          + "Name"        = "dev-public-b"
          + "Project"     = "IaC-Terraform"
        }
      + tags_all                                       = {
          + "Environment" = "Develop"
          + "Name"        = "dev-public-b"
          + "Project"     = "IaC-Terraform"
        }
      + vpc_id                                         = (known after apply)
    }

  # module.vpc.aws_subnet.subnets_public["public_c"] will be created
  + resource "aws_subnet" "subnets_public" {
      + arn                                            = (known after apply)
      + assign_ipv6_address_on_creation                = false
      + availability_zone                              = "ap-northeast-2c"
      + availability_zone_id                           = (known after apply)
      + cidr_block                                     = "172.21.8.0/22"
      + enable_dns64                                   = false
      + enable_resource_name_dns_a_record_on_launch    = false
      + enable_resource_name_dns_aaaa_record_on_launch = false
      + id                                             = (known after apply)
      + ipv6_cidr_block_association_id                 = (known after apply)
      + ipv6_native                                    = false
      + map_public_ip_on_launch                        = false
      + owner_id                                       = (known after apply)
      + private_dns_hostname_type_on_launch            = (known after apply)
      + tags                                           = {
          + "Environment" = "Develop"
          + "Name"        = "dev-public-c"
          + "Project"     = "IaC-Terraform"
        }
      + tags_all                                       = {
          + "Environment" = "Develop"
          + "Name"        = "dev-public-c"
          + "Project"     = "IaC-Terraform"
        }
      + vpc_id                                         = (known after apply)
    }

결론

위 예시처럼 Resource 블록의 반복되는 사용을 줄이기 위해 count, for_each 을 알아보았다.
요약을 하자만 count 는 사용할 때 순서가 중요하며, 리소스에 영향을 줄 수 있다. 순서와 인덱스에 주의가 필요하지만 사용이 좀 더 간단하다.
for_each 는 리소스의 키에 따라 독립적으로 관리되므로, 한 리소스의 변경이 다른 리소스의 영향을 주지 않는다. 안정적이며 예측 가능한 리소스 관리가 필요 권장된다.

profile
조금씩 성장하는 DevOps 엔지니어가 되겠습니다. 😄

0개의 댓글

관련 채용 정보