[Terraform] count vs for_each Meta-Argument 차이점 - 변수 list(object) 타입 사용
Terraform을 사용하면서 여러 개의 리소스를 생성해야 할 때 대표적으로 count, for_each 두 가지를 사용합니다.
두 개의 차이점과 주의사항에 대해 알아보겠습니다.
Count
count 메타 인수는 정수를 허용하고 전체 리소스, 모듈을 반복하여 만들 수 있습니다.
count로 구성하게 되면 count.index를 활용하여 count에서 생성한 인덱스에 접근 가능합니다.
AWS VPC, Subnet을 생성하는 테라폼 코드를 작성해 보겠습니다.
Subnet을 list(object) 타입으로 정의해서 리소스를 여러 개 생성하겠습니다.
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
tags = {
Name = "vpc"
}
}
variable "subnets" {
type = list(object({
name = string
cidr_block = string
}))
default = [
{
name = "subnet-1"
cidr_block = "10.0.1.0/24"
},
{
name = "subnet-2"
cidr_block = "10.0.2.0/24"
},
{
name = "subnet-3"
cidr_block = "10.0.3.0/24"
},
{
name = "subnet-4"
cidr_block = "10.0.4.0/24"
}
]
}
resource "aws_subnet" "main" {
count = length(var.subnets)
vpc_id = aws_vpc.main.id
cidr_block = var.subnets[count.index].cidr_block
tags = {
Name = var.subnets[count.index].name
}
}
output "subnet_id" {
value = aws_subnet.main[*].id
}
aws_subnet 리소스에서 count으로 list의 길이를 받아서 반복문을 돌립니다.
terraform apply를 하겠습니다.
terraform state list
tfstate파일의 목록이 표시됩니다.
위와 같이 서브넷 리소스가 인덱스를 사용해서 나열됩니다.
count를 사용하면 리소스의 배열로 저장되기 때문에 Terraform에서 리소스를 식별할 때 해당 배열의 인덱스를 사용하게 됩니다.
이를 terraform.tfstate 파일에서도 확인할 수 있습니다.
index_key 값에 배열의 인덱스가 들어갑니다.
이는 특정 서브넷을 삭제를 할 때 문제가 발생합니다.
subnet-1을 삭제해 보겠습니다.
variable "subnets" {
type = list(object({
name = string
cidr_block = string
}))
default = [
# {
# name = "subnet-1"
# cidr_block = "10.0.1.0/24"
# },
{
name = "subnet-2"
cidr_block = "10.0.2.0/24"
},
{
name = "subnet-3"
cidr_block = "10.0.3.0/24"
},
{
name = "subnet-4"
cidr_block = "10.0.4.0/24"
}
]
}
terraform apply
배열의 인덱스가 밀리게 되면서 aws_subnet.main[0] 뒤에 있는 서브넷들이 강제 재생성됩니다.
즉, subnet-1만 삭제하려고 했지만 subnet-2,3,4도 강제 재생성을 하게 됩니다.
이는 원하는 방식이 아닐 것입니다.
그렇기 때문에 이를 피하기 위해서는 count가 아닌 for_each를 사용해야 합니다.
For Each
for_each는 Terraform 언어로 정의된 메타 인수입니다. 모듈 및 모든 리소스 유형과 함께 사용할 수 있습니다.
for_each 메타 인수는 map, set을 허용합니다. each.key와 each.value를 사용하여 해당 키와 값에 접근 가능합니다.
count 대신 for_each 표현식으로 변환하겠습니다.
이때 for_each는 list와 같이 쓸 수 없기 때문에 list를 map으로 변환합니다.
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
tags = {
Name = "vpc"
}
}
variable "subnets" {
type = list(object({
name = string
cidr_block = string
}))
default = [
{
name = "subnet-1"
cidr_block = "10.0.1.0/24"
},
{
name = "subnet-2"
cidr_block = "10.0.2.0/24"
},
{
name = "subnet-3"
cidr_block = "10.0.3.0/24"
},
{
name = "subnet-4"
cidr_block = "10.0.4.0/24"
}
]
}
resource "aws_subnet" "main" {
for_each = { for subnet in var.subnets : subnet.name => subnet } # 변환
vpc_id = aws_vpc.main.id
cidr_block = each.value.cidr_block
tags = {
Name = each.key
}
}
output "subnet_id" {
value = { for k, v in aws_subnet.main : v.tags.Name => v.id }
}
애초에 variable subnets를 map(object)으로 정의할 수도 있지만 key를 정의하고 싶지 않을 때 쓸 수 있는 방법입니다.
변수 정의가 좀 더 깔끔해 집니다.
teraform apply 후 terraform state list으로 확인.
state 목록을 보면 서브넷 리소스가 map 자료형의 key를 사용해서 나열되는 것을 확인할 수 있습니다.
terraform.tfstate를 보면 index_key 값에 map 자료형의 key가 들어간 것을 확인할 수 있습니다.
그렇기 때문에 count를 사용했을 때 발생했던 문제가 생기지 않습니다.
위에서와 똑같이 subnet-1을 삭제하겠습니다.
terraform apply
subnet-1만 삭제가 됩니다!
terraform이 map 자료형의 key를 기준으로 삭제를 했기 때문입니다.
이러한 count, for_each의 특성을 이해하고 terraform을 사용한다면 인프라를 더욱 잘 관리할 수 있습니다.
변경될 리소스에 대해서 count를 사용하지 말고 for_each를 사용하세요!
참고
https://developer.hashicorp.com/terraform/language/meta-arguments/count
https://developer.hashicorp.com/terraform/language/meta-arguments/for_each