[Terraform] 반복문
카테고리: terraform
태그: terraform
🎯반복문
테라폼은 각각 조금씩 다른 상황에 사용하도록 고안된 몇 가지 반복문 구성을 제공합니다.
- count 매개 변수
- for_each 표현식
- for 표현식
- for 문자열 지시어
count 매개 변수를 이용한 반복
모든 테라폼 리소스에는 count라는 메타 매개 변수가 있습니다. count는 테라폼의 가장 오래되고 단순하며 제한된 반복 구조로써 count에 생성하고자 하는 리소스 사본 수만 정의하면 됩니다. count를 사용하여 3명의 IAM 사용자를 생성하는 방법은 다음과 같습니다.
resource "aws_ima_user" "example" {
count = 3
name = "neo".${count.index}
}
count.index: 반복문 안에 있는 각각의 반복을 가리키는 인덱스- 앞의 코드에서 plan 명령을 실행하면 테라폼이 각각 다른 이름을 가진 3명의 IAM 사용자 ‘neo0’, ‘neo1’, ‘neo2’를 생성합니다.
리소스 배열
리소스에 count를 사용한 후에는 하나의 리소스가 아니라 리소스의 배열이 됩니다. aws_iam_user.example은 이제 IAM 사용자의 배열이므로 표준 구문을 사용하여 해당 리소스인 <PROVIDER>_<TYPE>.<NAME>.<ATTRIBUTE>에서 속성을 읽는 대신 배열 조회 구문을 이용해 배열에서 인덱스를 지정함으로써 IAM 사용자를 명시해야 합니다.
예를 들어 IAM 사용자 중 하나의 ARN을 출력 변수로 제공하려면 다음과 같이 작성합니다. (IAM 사용자 전체의 ARN을 원하면 인덱스 대신 스플랫 연산자인 *를 사용해야 합니다.)
output "neo_arn" {
value = "aws_iam_user.example[0].arn"
description = "The ARN for user neo0"
}
count 매개 변수의 2가지 제약
첫 번째로 count를 사용하여 전체 리소스를 반복할 수는 있지만 리소스 내에서 인라인 블록을 반복할 수는 없습니다. 예를 들어 aws_autoscaling_group 리소스에서 태그를 설정하는 방법을 살펴봅니다.
resource "aws_autoscaling_group" "example" {
launch_configuration = aws_launch_configuration.example.name
vpc_zone_identifier = data.aws_subnet_ids.default.ids
target_group_arns = [aws_lb_target_group.asg.arn]
health_check_type = "ELB"
min_size = var.min_size
max_size = var.max_size
tag {
key = "Name"
value = var.cluster_name
propagate_at_launch = true
}
}
각각의 tag를 사용하려면 key, value, propagate_at_launch에 대한 값으로 새 인라인 블록을 만들어야 합니다. count 매개변수를 사용하여 이러한 태그를 반복하고 동적인 인라인 tag 블록을 생성하려고 시도할 수도 있지만 인라인 블록 내에서 count 사용은 지원하지 않습니다.
두 번째 제약은 변경하려고 할 때 발생합니다. 이전에 생성했던 IAM 사용자 목록에서 ‘neo1’를 제거했다고 가정하고 terraform plan을 실행하면 테라폼이 ‘neo1’ IAM 사용자를 삭제하는 대신 ‘neo1’ IAM 사용자의 이름을 ‘neo2’로 바꾸고 원래 ‘neo2’ 였던 사용자를 삭제할 것이라고 나타냅니다.
앞서 count 매개 변수를 사용하여 리소스를 생성하면 해당 리소스는 배열이 된다고 했습니다. 테라폼은 해당 배열의 인덱스로 배열 내의 각 리소스를 식별합니다. 즉, 처음 3명의 사용자 이름으로 apply를 실행한 후 이러한 IAM 사용자에 대한 테라폼의 내부 표현은 다음과 같습니다.
aws_iam_user.example[0]: neo0
aws_iam_user.example[1]: neo1
aws_iam_user.example[2]: neo2
이때 배열의 중간 항목을 제거하면 모든 항목이 1칸씩 앞으로 당겨집니다. 테라폼의 내부 표현으로 나타내면 다음과 같으며 이 변화를 대략 인덱스 1에서는 버킷을 neo1에서 neo2로 바꾸고 인덱스 2에서는 버킷을 삭제한다로 해석합니다.
aws_iam_user.example[0]: neo0
aws_iam_user.example[1]: neo2
즉, count를 사용하여 리소스 목록을 만들 었을 때 목록 중간에서 항목을 제거하면 테라폼은 해당 항목 뒤에 있는 모든 리소스를 삭제한 다음 해당 리소스를 처음부터 다시 만드는 것입니다. 결과적으로 neo0와 neo2라는 2명의 사용자가 남아 요청대로 수행되지만 이처럼 리소스를 삭제하고 수정하는 것은 아마도 원했던 방식은 아닐 것입니다.
for_each 표현식을 사용한 반복문 처리
앞서 count 매개 변수의 2가지 제약 사항에 대해 알아보았습니다. for_each 표현식은 이러한 한계를 해결하는데 사용할 수 있습니다. for_each 표현식을 사용하면 리스트, 집합, 맵을 사용하여 전체 리소스의 여러 복사본 또는 리소스 내 인라인 블록의 여러 복사본을 생성할 수 있습니다.
먼저 for_each를 사용하여 리소스의 여러 복사본을 만드는 구문은 다음과 같습니다.
resource "<PROVIDER>_<TYPE>" "<NAME>" {
for_each = <COLLECTION>
[CONFIG ...]
}
PROVIDER: aws 같은 공급자 이름TYPE: instance와 같이 해당 공급자에서 생성할 리소스 유형NAME: my_instance와 같이 테라폼 코드 전체에서 이 리소스를 참조하기 위해 사용할 식별자COLLECTION: 루프를 처리할 집합 또는 맵 (리스트는 지원되지 않음)CONFIG: 해당 리소스와 관련된 하나 이상의 인수,each.key또는each.value를 사용하여 COLLECTION에서 현재 항목의 키와 값에 접근할 수 있음.
예를 들어 for_each를 사용하여 3명의 IAM 사용자를 생성하는 방법을 살펴봅니다. 먼저 리스트를 집합으로 변환하기 위해 toset을 사용합니다. 또한 for_each가 이 집합을 반복하면 each_value에서 각 사용자 이름을 사용할 수 있습니다.
resource "aws_iam_user" "example" {
for_each = toset(var.user_name) # 리스트를 집합(set)으로 변환
name = each.value
}
Note: set(집합) 변수 유형은 list 와 비슷하지만 순서를 유지하지 않고 중복 값을 허용하지 않는다. 예를 들어 set[5, 1, 1, 2]의 경우 1, 2, 5를 리턴한다. 중복 값은 허용되지 않았고 값은 정렬되었다.
리소스 맵
for_each를 사용한 후에는 하나의 리소스 또는 count를 사용한 것과 같은 리소스 배열이 되는 것이 아니라 리소스 맵이 됩니다. 이 의미를 확인하려면 all_users 출력 변수를 추가합니다.
output "all_users" {
value = aws_iam_user.example
}
terraform apply를 실행하면 다음 작업을 수행합니다.
$ terraform apply
(...)
Apply complate! Resources: 3 added, 0 changed, 0 destroyed.
Outputs:
all_users = {
"neo0" = {
"arn" = "arn:aws:iam::123456789012:user/neo0"
"force_destroy" = false
"id" = "neo0"
"name" = "neo0"
"path" = "/"
"tags" = {}
}
"neo2" = {
"arn" = "arn:aws:iam::123456789012:user/neo2"
"force_destroy" = false
"id" = "neo2"
"name" = "neo2"
"path" = "/"
"tags" = {}
}
"neo1" = {
"arn" = "arn:aws:iam::123456789012:user/neo1"
"force_destroy" = false
"id" = "neo1"
"name" = "neo1"
"path" = "/"
"tags" = {}
}
}
테라폼이 IAM 사용자 3명을 생성했습니다. 그리고 all_users 출력 변수가 for_each의 키, 즉 이 경우 사용자 이름을 키로 가지며 값이 해당 리소스의 전체 출력인 맵을 포함합니다.
for_each 표현식의 장점
- for_each를 사용해 리소스를 맵으로 처리하면 컬렉션 중간의 항목도 안전하게 제거할 수 있습니다.
- 리소스 내에서 여러 개의 인라인 블록을 만들 수 있습니다.
인라인 블록을 동적으로 생성하는 구문
dynamic "<VAR_NAME>" {
for_each = <COLLECTION>
content {
[CONFIG...]
}
}
VAR_NAME: 각 반복의 값을 저장할 변수에 사용할 이름COLLECTION: 반복되는 리스트 또는 맵, for_each를 집합과 함께 사용하는 경우 key는 인덱스가 되고 value는 해당 인덱스 목록에 있는 항목이 됩니다.content: 각 반복에서 생성되는 항목,.key 및 .value를 사용해 `COLLECTION`에 있는 현재 항목의 키와 값에 액세스할 수 있습니다.
예를 들어 for_each를 사용하면 모듈에서 ASG에 대한 tag 인라인 블록을 동적으로 생성할 수 있습니다.
1. 먼저 사용자 정의 태그를 지정할 수 있게 하려면 tags라는 새로운 맵 입력 변수를 추가합니다.
variable "custom_tags" {
description = "Custom tags to set on the Instances in the ASG"
type = map(string)
default = {}
}
2. 다음으로 루트 모듈에서 다음과 같이 일부 사용자 정의 태그를 설정합니다.
module "webserver_cluster" {
(...)
custom_tags = {
Owner = "team-foo"
DeployedBy = "terraform"
}
}
3. aws_autoscaling_group 리소스에서 for_each를 사용하여 tag 블록을 동적으로 생성
resource "aws_autoscaling_group" "example" {
launch_configuration = aws_launch_configuration.example.name
vpc_zone_identifier = data.aws_subnet_ids.default.ids
target_group_arns = [aws_lb_target_group.asg.arn]
health_check_type = "ELB"
min_size = var.min_size
max_size = var.max_size
tag {
key = "Name"
value = var.cluster_name
propagate_at_launch = true
}
dynamic "tag" {
for_each = var.custom_tags
content {
key = tag.key
value = tag.value
propagate_at_launch = true
}
}
}
for 표현식을 이용한 반복문
리소스와 인라인 블록을 반복하는 방법을 알아보았습니다. 하지만 단일 값을 생성하기 위해 반복이 필요한 경우에는 어떻게 해야 할까요? names의 리스트를 가진 테라폼 코드를 작성했다고 가정합니다.
variable "names" {
description = "A list of names"
type = list(string)
default = ["neo", "trinity", "morpheus"]
}
이때 모든 이름을 대문자로 변환하려면 어떻게 해야할까요? 테라폼은 for 표현식의 형태로 유사한 기능을 제공합니다. for 표현식의 기본 구문은 다음과 같습니다.
for <ITEM> in <LIST> : <OUTPUT>
LIST: 반복할 리스트ITEM: LIST의 각 항목에 할당할 로컬 변수의 이름OUTPUT: ITEM을 어떤 식으로든 변환하는 표현식입니다.
예를 들어 var.names의 이름 목록을 대문자로 변환하는 테라폼 코드는 다음과 같습니다.
variable "names" {
description = "A list of names"
type = list(string)
default = ["neo", "trinity", "morpheus"]
}
output "upper_names" {
value = [for name in var.names : upper(name)]
}
$ terraform apply
Outputs:
upper_names = [
"NEO",
"TRINITY",
"MORPHEUS"
]
조건 지정
또한 파이썬의 리스트 내포 구문과 비슷하게 조건을 지정하여 결과 리스트를 필터링할 수 있습니다.
variable "names" {
description = "A list of names"
type = list(string)
default = ["neo", "trinity", "morpheus"]
}
output "short_upper_names" {
value = [for name in var.names : upper(name) if length(name) < 5]
}
$ terraform apply
Outputs:
short_upper_names = [
"NEO",
]
맵 반복
for 구문은 리스트 외에도 다음과 같은 구문을 사용하여 맵을 반복할 수도 있습니다.
for <KEY>, <VALUE> in <MAP> : <OUTPUT>
MAP: 반복되는 맵KEY: MAP의 각 키에 할당할 로컬 변수VALUE: MAP의 각 값에 할당할 로컬 변수OUTPUT: KEY와 VALUE를 어떤 식으로든 변환하는 표현식입니다.
예를 들면 다음과 같습니다.
variable "hero_thusand_faces" {
description = "map"
type = map(string)
default = {
neo = "hero"
trinity = "love interest"
morpheus = "mentor"
}
}
output "bios" {
value = [for name, role in var.hero_thusand_faces : "${name} is the ${role}"]
}
$ terraform apply
Outputs:
bios = [
"morpheus is the mentor",
"neo is the hero",
"trinity is the love interest",
]
맵 출력
for 표현식을 리스트가 아닌 맵을 출력하는 데 사용할 수도 있습니다.
# 리스트를 반복하고 맵을 출력
{for <ITEM> in <LIST> : <OUTPUT_KEY> => <OUTPUT_VALUE>}
# 맵을 반복하고 맵을 출력
{for <KEY>, <VALUE> in <MAP> : <OUTPUT_KEY> => <OUTPUT_VALUE>}
식을 대괄호 대신 중괄호로 묶고 각 반복마다 단일 값을 출력하는 대신 키와 값을 화살표로 구분하여 출력하는 것이 유일한 차이점입니다. 예를 들어 다음은 맵을 변환하여 모든 키와 값을 대문자로 만드는 방법입니다.
variable "hero_thusand_faces" {
description = "map"
type = map(string)
default = {
neo = "hero"
trinity = "love interest"
morpheus = "mentor"
}
}
output "upper_roles" {
value = {for name, role in var.hero_throusand_faces : upper(name) => upper(role)}
}
$ terraform apply
Outputs:
upper_roles = {
"MORPHEUS" = "MEONTOR",
"NEO" = "HERO",
"TRINITY" = "LOVE INTEREST"
}
문자열 지시자를 사용하는 반복문
문자열 지시자를 사용하면 문자열 보간과 유사한 구문으로 문자열 내에서 for 반목문, if문 같은 제어문을 사용할 수 있습니다. 다만 달러 부호와 중괄호 대신 백분율 부호를 사용한다는 차이가 있습니다.
Note: 문자열 보간 예)
"Hello, ${var.name}"
테라폼은 두 가지 유형의 문자열 지시자, for 반복문과 조건문을 지원합니다. 이번에는 for 반복문을 살펴보고 다음 포스팅에서 조건문을 다룹니다. for 문자열 지시자는 다음 구문을 사용합니다.
%{ for <ITEM> in <COLLECTION> }<BODY>${ endfor }
COLLECTION: 반복한 리스트 또는 맵ITEM: COLLECTION의 각 항목에 할당할 로컬 변수 이름BODY: ITEM을 참조할 수 있는 각각의 반복을 렌더링하는 대상
예를 들면 다음과 같습니다.
variable "names" {
description = "Names to render"
type = list(string)
default = ["neo", "trinity", "morpheus"]
}
output "for_directive" {
value = <<EOF
%{ for name in var.names }
${name}
%{ endfor }
EOF
}
$ terraform apply
Outputs:
for_directive =
neo
trinity
morpheus
출력에 줄 바꿈이 추가된 것에 주의합니다. 스페이스나 줄 바굼 같은 공백을 없애기 위해 문자열 지시자의 앞이나 뒤에 물결표(~)를 사용할 수 있습니다. 문자열 지시자 시작에 공백이 있으면 지시자 앞에, 문자열 지시자 끝에 공백이 있으면 지시자 뒤에 물결표를 사용합니다.
output "for_directive_strip_marker" {
value = <<EOF
%{~ for name in var.names }
${name}
%{~ endfor }
EOF
}
$ terraform aply
Outputs:
for_directive_strip_marker =
neo
trinity
morpheus
👍 개인 공부 기록용 블로그입니다. 오류나 조언이 있으시면 언제든지 댓글 혹은 메일로 남겨주시면 감사하겠습니다! 😄
댓글남기기