for_each 메타-인자
for_each 메타-인자 (for_each reference)
for_each 메타-인자를 사용해 맵이나 문자열 집합의 각 항목에 대해 리소스 인스턴스를 만드는 방법을 설명해 드릴게요. 인스턴스마다 서로 다른 값이 필요할 때 특히 유용해요.
출처: 문서
본문
기본적으로 resource 블록은 실제 인프라 객체 하나를 구성해요. 마찬가지로 module 블록은 자식 모듈의 내용을 구성에 한 번 포함시키고 action 블록에 정의된 동작을 한 번 호출해요.
for_each 메타-인자를 사용하면 고정된 컴퓨트 인스턴스 풀처럼 비슷한 객체 여러 개를 각 객체마다 별도 블록을 작성하지 않고 관리할 수 있어요. resource, module, ephemeral 블록에 for_each 인자가 포함되면 Terraform은 값으로 지정된 목록이나 맵의 각 멤버에 대해 인스턴스를 만들어요. for_each가 action 블록에 나타나면 Terraform은 맵의 목록 멤버나 키-값 쌍 각각에 대해 동작을 한 번씩 호출해요.
사용법 (Usage)
for_each 메타-인자는 맵 또는 문자열 집합을 받으며 그 맵이나 집합의 각 항목에 대해 인스턴스를 만들어요. 각 인스턴스는 개별 인프라 객체와 연결되며, 구성 변경을 적용할 때 Terraform이 각 인스턴스를 생성·업데이트·파괴해요.
toset()과 tomap() 같은 순수 함수를 사용해 for_each 인자에 사용할 맵이나 집합을 만들 수 있어요. for_each 인자가 반복하는 모든 값은 Terraform이 원격 리소스 연산을 수행하기 전에 알려져 있어야 해요. 구성이 적용된 후에만 알 수 있는 리소스 속성(객체가 생성될 때 원격 API가 생성하는 고유 ID 같은)에 대한 참조를 지정하면 오류가 발생해요.
다음 예시는 tomap() 함수로 만든 맵을 참조하는 azurerm_resource_group 리소스 두 개를 만들어요:
resource "azurerm_resource_group" "rg" {
for_each = tomap({
a_group = "eastus"
another_group = "westus2"
})
name = each.key
location = each.value
}
다음 예시는 toset() 함수로 만든 집합을 사용해 aws_iam_user 리소스 네 개를 만들어요:
resource "aws_iam_user" "the-accounts" {
for_each = toset(["Todd", "James", "Alice", "Dottie"])
name = each.key
}
값의 제한 (Limitations on values)
맵의 키 또는 문자열 집합의 모든 값은 알려진 값(knowable value)이어야 해요. 그렇지 않으면 Terraform은 for_each가 apply 전에 결정할 수 없는 의존성을 가지고 있으며 -target이 필요할 수 있다는 오류 메시지를 반환해요.
for_each 인자의 키는 uuid, bcrypt, timestamp 같은 불순(impure) 함수의 결과이거나 그에 의존할 수 없어요. Terraform이 주요 평가 단계에서 불순 함수의 평가를 미루기 때문이에요.
민감한 입력 변수, 민감한 출력, 민감한 리소스 속성 같은 민감 값은 for_each의 인자로 사용할 수 없어요. Terraform이 for_each의 값을 리소스 인스턴스 식별에 사용하고 항상 UI 출력에서 공개하기 때문이에요. 민감 값을 for_each 인자로 사용하려 하면 Terraform이 오류를 반환해요.
민감 데이터를 포함하는 값을 for_each에 사용할 인자로 변환하면, 대부분의 Terraform 함수는 어떤 민감 내용이라도 포함된 인자를 받으면 민감한 결과를 반환해요. 자세한 내용은 함수 인자로 민감 데이터 사용하기를 참고해요. 많은 경우 for 표현식으로 비슷한 결과를 얻을 수 있어요. 예를 들어 민감한 값을 가진 객체지만 비민감한 키를 가진 local.map에 대해 keys(local.map)을 호출하려면 toset([for k,v in local.map : k])로 for_each에 전달할 값을 만들 수 있어요. 자세한 내용은 민감 데이터 관리를 참고해요.
for_each 안의 표현식 (Expressions in for_each)
for_each 메타-인자는 맵 또는 집합 표현식을 받지만, 대부분의 인자와 달리 그 값은 Terraform이 원격 리소스 연산을 수행하기 전에 알려져 있어야 해요. 따라서 for_each는 구성이 적용된 후에야 알 수 있는 리소스 속성(객체가 생성될 때 원격 API가 생성하는 고유 ID 같은)을 참조할 수 없어요.
for_each 값은 원하는 각 리소스 인스턴스당 요소가 하나씩 있는 맵 또는 집합이어야 해요. 시퀀스를 for_each 값으로 사용하려면 toset 함수처럼 명시적으로 집합 값을 반환하는 표현식을 사용해야 해요. 변환 중 예상치 못한 동작을 방지하기 위해 for_each 인자는 목록이나 튜플을 집합으로 암묵적으로 변환하지 않아요. 중첩 데이터 구조나 여러 데이터 구조 요소의 조합에 기반해 리소스 인스턴스를 선언하려면 Terraform 표현식과 함수를 사용해 적절한 값을 유도할 수 있어요. 다음 예시를 참고해요:
인스턴스 참조 (Referring to instances)
for_each가 설정된 블록에서 Terraform은 각 인스턴스의 구성을 수정하는 표현식에 사용할 수 있는 추가 each 객체를 만들어요. 이 객체는 다음 속성을 가져요:
each.key: 이 인스턴스에 해당하는 맵 키 또는 집합 멤버.each.value: 이 인스턴스에 해당하는 맵 값. 집합이 제공되면each.key와 같아요.
Terraform은 for_each 인자를 포함하는 블록과 그와 연결된 인스턴스를 구분해요. Terraform은 for_each에 제공된 값을 사용해 맵 키나 집합 멤버로 인스턴스를 식별해요.
.또는module.— 예:azurerm_resource_group.rg는 블록을 가리켜요..[]또는module.[]— 예:azurerm_resource_group.rg["a_group"]와azurerm_resource_group.rg["another_group"]은 개별 인스턴스를 가리켜요.
이것은 인덱스나 키 없이 참조할 수 있는 count나 for_each가 없는 리소스·모듈과 다르다는 점을 참고해요. 마찬가지로 여러 인스턴스가 있는 자식 모듈의 리소스는 plan 출력과 UI의 다른 곳에 표시될 때 module.[]로 접두사가 붙어요. count나 for_each가 없는 모듈의 경우 모듈 이름만으로 참조할 수 있으므로 주소에 모듈 인덱스가 포함되지 않아요. 중첩된 provisioner 또는 connection 블록 안에서는 특수한 self 객체가 resource 블록 전체가 아니라 현재 리소스 인스턴스를 가리켜요.
리소스 간 for_each 체이닝 (Chain for_each between resources)
for_each를 사용하는 리소스는 다른 곳의 표현식에서 사용될 때 객체의 맵으로 나타나므로, 두 객체 집합 사이에 일대일 관계가 있는 상황에서는 한 리소스를 직접 다른 리소스의 for_each로 사용할 수 있어요.
예를 들어 AWS에서 aws_vpc 객체는 인터넷 게이트웨이처럼 그 VPC에 추가 서비스를 제공하는 많은 객체와 흔히 연관돼요. for_each로 여러 VPC 인스턴스를 선언한다면 그 for_each를 다른 리소스로 체이닝해 각 VPC에 인터넷 게이트웨이를 선언할 수 있어요:
variable "vpcs" {
type = map(object({
cidr_block = string
}))
}
resource "aws_vpc" "example" {
# var.vpcs의 각 요소에 대해 VPC 하나
for_each = var.vpcs
# 여기의 each.value는 var.vpcs의 값
cidr_block = each.value.cidr_block
}
resource "aws_internet_gateway" "example" {
# VPC당 인터넷 게이트웨이 하나
for_each = aws_vpc.example
# 여기의 each.value는 완전한 aws_vpc 객체
vpc_id = each.value.id
}
output "vpc_ids" {
value = {
for k, v in aws_vpc.example : k => v.id
}
# VPC는 인터넷 게이트웨이가 실행 중일 때까지 완전히 기능하지 않음.
depends_on = [aws_internet_gateway.example]
}
이 체이닝 패턴은 인터넷 게이트웨이 인스턴스와 VPC 인스턴스 사이의 관계를 명시적이고 간결하게 선언해서, Terraform에게 둘의 인스턴스 키가 항상 함께 변할 것으로 기대하라고 알려주며, 보통 사람이 유지보수하는 데도 구성을 더 이해하기 쉽게 만들어요.
집합 사용 (Using sets)
Terraform 언어에는 집합 값을 위한 리터럴 문법이 없지만, toset 함수로 문자열 목록을 명시적으로 집합으로 변환할 수 있어요:
locals {
subnet_ids = toset([
"subnet-abcdef",
"subnet-012345",
])
}
resource "aws_instance" "server" {
for_each = local.subnet_ids
ami = "ami-a1b2c3d4"
instance_type = "t2.micro"
subnet_id = each.key # 참고: 집합의 경우 each.key와 each.value는 같음
tags = {
Name = "Server ${each.key}"
}
}
목록에서 집합으로의 변환은 목록 항목의 순서를 버리고 중복 요소를 제거해요. 예를 들어 toset(["b", "a", "b"])는 순서 없이 "a"와 "b"만 포함하는 집합을 만들고 두 번째 "b"를 버려요.
for_each에 문자열 집합으로 사용할 입력 변수가 있는 모듈을 작성한다면 그 타입을 set(string)으로 설정해 명시적 타입 변환 없이 사용할 수 있어요:
variable "subnet_ids" {
type = set(string)
}
resource "aws_instance" "server" {
for_each = var.subnet_ids
#...
}
for_each와 count 사이 선택 (How to choose between for_each and count)
count 메타-인자는 for_each와 비슷한 동작을 수행해요. 일부 인스턴스 인자가 정수에서 직접 유도할 수 없는 서로 다른 값을 가져야 하면 for_each를 사용하고, 거의 동일한 인스턴스를 만들려면 count 인자를 사용해요. 같은 블록에서 count와 for_each 인자를 둘 다 사용할 수 없어요.
지원되는 구성 (Supported constructs)
다음 Terraform 구성 블록에서 for_each를 사용할 수 있어요: data 블록, ephemeral 블록, module 블록, resource 블록. 다음 스택 구성 블록에서도 사용할 수 있어요: component 블록, provider 블록, removed 블록. 다음 쿼리 구성 블록에서도 사용할 수 있어요: list 블록.
예시 (Examples)
여러 ephemeral 리소스 만들기
다음 예시에서 for_each 인자는 서로 다른 데이터베이스 환경을 위한 여러 비밀번호를 만들어요:
locals {
environments = toset(["dev", "staging", "prod"])
}
ephemeral "random_password" "db_passwords" {
for_each = local.environments
length = 16
override_special = "!#$%&*()-_=+[]{}<>:?"
}
resource "aws_db_instance" "databases" {
for_each = local.environments
identifier = "${each.key}-database"
db_name = "${each.key}db"
username = "dbadmin"
password_wo = ephemeral.random_password.db_passwords[each.key].result
# …
}
ephemeral 블록은 local.environments 집합의 각 환경에 대해 random_password ephemeral 리소스를 만들어요. aws_db_instance 리소스는 각 데이터베이스 인스턴스에 생성된 비밀번호를 사용해요. Terraform은 생성된 db_passwords 값을 저장하지 않지만, 값을 잃지 않도록 다른 리소스에서 캡처할 수 있어요. ephemeral 비밀번호 생성·저장·검색·사용 예시는 write-only 인자를 참고해요.
모듈 리소스의 여러 인스턴스 만들기
다음 예시에서 Terraform은 local.instance_configs의 각 키에 대해 EC2 인스턴스 하나를 만들어 인스턴스별로 속성을 커스터마이즈할 수 있게 해요:
locals {
instance_configs = {
"example-instance-1" = { instance_type = "t2.micro" }
"example-instance-2" = { instance_type = "t2.small" }
"example-instance-3" = { instance_type = "t2.medium" }
}
}
module "ec2_instance" {
source = "terraform-aws-modules/ec2-instance/aws"
version = "6.0.2"
for_each = local.instance_configs
name = each.key
ami = data.aws_ami.latest_amazon_linux.id
instance_type = each.value.instance_type
depends_on = [aws_s3_bucket.example]
}
리소스의 여러 인스턴스 만들기
다음 예시에서 module 블록은 for_each 집합의 각 요소에 대해 publish_bucket 모듈의 인스턴스를 만들어요. each.key 속성을 사용해 각 버킷에 고유한 이름을 만들어요.
my_buckets.tf:
module "bucket" {
for_each = toset(["assets", "media"])
source = "./publish_bucket"
name = "${each.key}_bucket"
}
publish_bucket/bucket-and-cloudfront.tf:
variable "name" {}
resource "aws_s3_bucket" "example" {
bucket = var.name
#...
}
resource "aws_iam_user" "deploy_user" {
#...
}
동작을 여러 번 호출하기 (Invoke an action multiple times)
for_each를 사용해 동작을 여러 번 호출할 수 있어요. 예시는 action 블록 문서를 참고해요.
여러 리소스 가져오기 (Import multiple resources)
for_each를 사용해 여러 리소스를 가져올 수 있어요. 예시는 import 블록 문서를 참고해요.
여러 쿼리 목록 만들기 (Create multiple query lists)
for_each를 사용해 쿼리 결과 목록을 여러 개 만들 수 있어요. 예시는 list 블록 문서를 참고해요.