클라우드에서 인프라를 구축을 할 때 비용 절감, 배포 속도 향상, 일관성, 안정성 및 재사용성을 고려하여 웹 콘솔로 구축하기보다는 IaC(Infrastructure as Code) 도구를 활용하여 구축하는 것이 좋습니다. 오픈소스이며 IaC 도구 중 가장 많이 사용하는 것이 Terraform입니다. Terraform 코드를 효율적이고 효과적으로 작성하기 위해서는 Terraform에서 제공되는 기능들을 적절하게 사용하는 것이 중요합니다. 그렇지 않으면 예상과 다른 결과가 발생할 수 있습니다. 이번 글에서는 Terraform에서 제공하는 각 반복문의 특징과 차이점을 알아보겠습니다.

Terraform 반복문

일반적인 선언적 언어는 반복문이 없지만, Terraform에서는 다양한 함수를 지원하여 반복문을 사용할 수 있습니다.
대표적으로 사용하는 반복문은 count, for_each가 있습니다. 각각의 특징은 아래와 같습니다.

  • count 매개 변수: 리소스와 모듈을 반복
  • for_each 표현식: 리소스 및 리소스 내의 인라인 블록과 모듈 반복

각 반복문의 사용 방법을 요약하자면,

  • 생성하려는 리소스가 거의 동일하고 변경되지 않을 때 count를 활용한다면 빠르고 간결하게 생성할 수 있습니다.
  • 고유한 이름, 데이터가 들어 있는 리소스, 재생성되지 말아야 할 리소스 등에는 for_each를 활용한다면 안전하게 생성 및 변경할 수 있습니다.

Terraform 코드를 실행해 보면서 countfor_each의 차이점에 대해 알아보겠습니다.

Count

count 매개 변수는 정수를 허용하고 전체 리소스, 모듈을 반복하여 만들 수 있습니다.
count로 구성하게 되면 count.index를 활용하여 count에서 생성한 인덱스에 접근 가능합니다.

아래 코드는 AWS IAM 사용자를 생성하는 코드입니다.
만약 3명의 사용자를 생성하려면 어떻게 해야 할까요?
Terraform 리소스에서 사용할 수 있는 count를 활용하여 단순하게 반복할 수 있습니다.

resource "aws_iam_user" "Rei" {
name = "Rei"
}

아래 코드에서 count를 사용하지만 생성되지 않습니다.
IAM 사용자의 이름은 고유해야 생성할 수 있는데 이름이 같아서 오류가 발생합니다.
count는 단순히 생성할 리소스의 수를 반복하여 작업하기 때문입니다.

resource "aws_iam_user" "count" {
count = 3
name = "Rei"
}

count.index를 사용하여 이름 뒤에 인덱스를 추가하여 고유한 이름을 만들 수 있습니다.

resource "aws_iam_user" "count" {
count = 3
name = "Rei${count.index}"
}

terraform apply 명령을 사용하면 생성하려는 리소스가 리스트로 저장되는 것을 확인할 수 있고, IAM 사용자에 Rei0, Rei1, Rei2가 생성된다는 것을 확인할 수 있습니다.

  # aws_iam_user.count[0] will be created
+ resource "aws_iam_user" "count" {
+ name = "Rei0"
(...)
}

# aws_iam_user.count[1] will be created
+ resource "aws_iam_user" "count" {
+ name = "Rei1"
(...)
}

# aws_iam_user.count[2] will be created
+ resource "aws_iam_user" "count" {
+ name = "Rei2"
(...)
}

Input Variable을 이용한 Count 활용

만들고 싶은 IAM 사용자를 리스트 타입의 variable로 선언하고, length 내장 함수를 활용하여 리소스를 생성할 수 있습니다.

variable "user_name" {
type = list(string)
default = ["joe", "kim", "lee", "park"]
}

resource "aws_iam_user" "count" {
count = length(var.user_name)
name = var.user_name[count.index]
}

위와 같이 리소스에 count를 사용하게 되면 각각의 리소스가 아니라 리소스의 배열로 저장됩니다.
apply 명령 및 tfstate에서 확인 가능합니다.
IAM 사용자에 joe, kim, lee, park이 생성된다는 것을 확인할 수 있습니다.

  # aws_iam_user.count[0] will be created
+ resource "aws_iam_user" "count" {
+ name = "joe"
(...)
}

# aws_iam_user.count[1] will be created
+ resource "aws_iam_user" "count" {
+ name = "kim"
(...)
}

# aws_iam_user.count[2] will be created
+ resource "aws_iam_user" "count" {
+ name = "lee"
(...)
}

# aws_iam_user.count[3] will be created
+ resource "aws_iam_user" "count" {
+ name = "park"
(...)
}

Output Value에서 Count 활용

각 리소스에 대한 출력 값을 읽으려면, 해당 리소스가 배열로 저장되었으므로 인덱스를 지정하여 속성을 읽을 수 있습니다. 인덱스 대신 *을 사용하면 모든 인덱스 조회가 가능합니다.

output "first_arn"{
value = aws_iam_user.count[0].arn
}

output "all_arn"{
value = aws_iam_user.count[*].arn
}
Outputs:
first_arn = "arn:aws:iam::<num>:user/joe"
all_arn = [
"arn:aws:iam::<num>:user/joe",
"arn:aws:iam::<num>:user/kim",
"arn:aws:iam::<num>:user/lee",
"arn:aws:iam::<num>:user/park",
]

Count 제약사항

count에는 두 가지 제약 사항이 있습니다.

첫 번째는, count는 전체 리소스를 반복하지만 리소스 내에 있는 인라인 블록은 반복하지 않습니다.
대표적인 인라인 블록 코드입니다. count는 인라인 블록인 tags에 Key 또는 Value 값에 해당하는 인수들에 대해서 지원되지 않습니다.

resource "<resource>" "<name>" {
(...)
tags = {
"Name" = ""
"<key>" = "<value>"
}
}

두 번째는, count 값 변경 시 발생합니다.

  # aws_iam_user.count[1] will be updated in-place
~ resource "aws_iam_user" "count" {
~ name = "kim" -> "lee"
(...)
}

# aws_iam_user.count[2] will be updated in-place
~ resource "aws_iam_user" "count" {
~ name = "lee" -> "park"
(...)
}

# aws_iam_user.count[3] will be destroyed
# (because index [3] is out of range for count)
- resource "aws_iam_user" "count" {
- id = "park" -> null
- name = "park" -> null
(...)
}

앞서 선언한 변수에서 kim을 제거하면 kim을 삭제하는 것이 아니라 kim -> lee, lee -> parck, park -> null 순서로 삭제 및 생성합니다.

count는 앞서 설명드린 대로 리소스의 배열로 저장됩니다. Terraform에서 리소스를 식별할 때 해당 배열의 인덱스를 사용합니다. count로 생성한 리소스의 중간 값을 제거하면 위와 같이 뒤의 모든 값들이 삭제 후 새로 생성하게 됩니다. 최종 결과물은 동일하지만 재생성되지 말아야 할 리소스라면 문제가 발생할 수 있습니다.

For_each

for_each 표현식은 list, setmap을 반복하여 전체 리소스, 리소스 내 인라인 블록, 모듈을 반복하여 만들 수 있습니다. for_each로 구성되면 each.keyeach.value를 사용하여 해당 키와 값에 접근 가능합니다.

리소스에 for_each를 사용할 때는 리스트가 지원되지 않습니다. Collection에는 반복할 set, map을 정의하고 Config에 each.keyeach.value를 사용하여 Collection에 정의된 키와 값을 가져올 수 있습니다.

resource "<PROVIDER>_<TYPE>" "<NAME>" {
for_each = <Coleection>

[Config ...]
}

Input Variable을 이용한 for_each 활용

variable은 이전에 선언한 것과 동일한 것을 사용하겠습니다.
tosetlistset으로 변환하는 데 사용합니다. for_each는 리소스에서 사용될 때는 setmap을 지원합니다. 해당 리소스가 반복하는 동안 each.value를 통해 사용자 이름을 정의했습니다.

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

terraform apply 명령을 사용하면 생성하려는 리소스를 set으로 확인할 수 있고, IAM 사용자에 joe, kim, lee, park이 생성된다는 것을 확인할 수 있습니다.
for_each도 각각의 리소스가 아니라 하나의 리소스에 배열로 저장됩니다.

  # aws_iam_user.for_each["joe"] will be created
+ resource "aws_iam_user" "for_each" {
+ name = "joe"
(...)
}

# aws_iam_user.for_each["kim"] will be created
+ resource "aws_iam_user" "for_each" {
+ name = "kim"
(...)
}

# aws_iam_user.for_each["lee"] will be created
+ resource "aws_iam_user" "for_each" {
+ name = "lee"
(...)
}

# aws_iam_user.for_each["park"] will be created
+ resource "aws_iam_user" "for_each" {
+ name = "park"
(...)
}

Output Value에서 for_each 활용

해당 리소스에 대한 출력 값을 읽기 위해서는 배열 구문을 활용하여 값을 지정하여 속성을 읽을 수 있습니다.
values 내장 함수와 *을 사용하여 모든 인덱스 조회가 가능합니다.

output "first_arn"{
value = aws_iam_user.for_each["joe"].arn
}
output "all_users" {
value = values(aws_iam_user.for_each)[*].arn
}
Outputs:
first_arn = "arn:aws:iam::<num>:user/joe"
all_users = [
"arn:aws:iam::<num>:user/joe",
"arn:aws:iam::<num>:user/kim",
"arn:aws:iam::<num>:user/lee",
"arn:aws:iam::<num>:user/park",
]

for_each는 앞서 설명드린 대로 하나의 리소스에 map으로 저장됩니다.
variable의 값 중 중간을 제거해도 해당 리소스 값만 변경 및 삭제됩니다.
count와 다르게 삭제 및 생성을 반복하지 않고 원하는 값만 삭제 가능합니다.

  # aws_iam_user.for_each["kim"] will be destroyed
# (because key ["kim"] is not in for_each map)
- resource "aws_iam_user" "for_each" {
- id = "kim" -> null
- name = "kim" -> null
(...)
}

맺음말

지금까지 Terraform 반복문 countfor_each에 대해서 알아보았습니다. 반복문마다 각 특징을 인지한 후 사용하면 코드 작성 및 State 관리의 큰 도움이 될 거라 생각합니다.

인포그랩은 GitLab 및 DevOps에 대한 맞춤 기술 지원을 제공합니다. GitLab(Omnibus/Cloud Native Hybrid) 구축 관련한 지원이 필요하시면 문의하기 로 연락 주십시오.