테라폼 기초 강좌 #4 데이터 소스와 표현식: 만들지 않고 읽어 오기, 조건식과 for
지금까지의 코드는 모든 것을 직접 만들었습니다. 그런데 실무의 테라폼 코드는 이미 존재하는 것들 사이에서 동작합니다. 다른 팀이 만든 VPC 안에 서버를 띄워야 하고, AWS가 관리하는 최신 AMI ID를 가져와야 하고, 지금 어느 계정에서 실행 중인지 알아야 합니다. 이번 글에서는 만들지 않고 읽어 오는 도구인 데이터 소스, 그리고 읽어 온 값을 가공하는 표현식을 다루겠습니다. #2의 블록 문법과 #3의 변수를 전제로 진행합니다.
data 블록: 만들지 않고 읽어 옵니다 #
resource 블록이 “존재해야 한다"라는 선언이라면, data 블록은 “존재하는 것을 찾아 달라"라는 조회입니다. 가장 흔한 예가 AMI 조회입니다. EC2 인스턴스를 만들려면 AMI ID가 필요한데, 이 ID는 리전마다 다르고 새 버전이 나올 때마다 바뀝니다. 하드코딩하면 금방 낡은 이미지가 됩니다.
data "aws_ami" "al2023" {
most_recent = true
owners = ["amazon"]
filter {
name = "name"
values = ["al2023-ami-2023*-x86_64"]
}
}
resource "aws_instance" "web" {
ami = data.aws_ami.al2023.id
instance_type = "t3.micro"
}참조 문법은 리소스와 거의 같고, 앞에 data.가 붙는 것만 다릅니다. plan을 실행할 때마다 조건에 맞는 최신 AMI를 조회하므로 ID를 코드에 적을 일이 없어집니다. 실행 중인 계정 정보를 주는 aws_caller_identity, 현재 리전을 주는 aws_region, 기존 VPC를 찾는 aws_vpc처럼 provider마다 다양한 데이터 소스가 있으며, 문서에서 Resources 옆의 Data Sources 목록으로 확인할 수 있습니다. 다른 팀이나 콘솔에서 만든 리소스를 참조만 하고 싶을 때, 관리 권한을 가져오지 않고 읽는 것이 데이터 소스의 역할입니다.
조건식: 환경에 따라 값 바꾸기 #
#3에서 만든 env 변수를 활용할 시간입니다. 운영 환경만 인스턴스를 키우고 싶다면 조건식을 씁니다.
resource "aws_instance" "web" {
ami = data.aws_ami.al2023.id
instance_type = var.env == "prod" ? "m7i.large" : "t3.micro"
}조건 ? 참일 때 : 거짓일 때 형태이며, 많은 프로그래밍 언어의 삼항 연산자와 같습니다. 뒤에 #6에서 보겠지만 이 조건식을 count와 조합하면 “운영 환경에만 리소스를 만든다” 같은 분기도 가능합니다. 다만 조건식이 세 겹, 네 겹으로 중첩되기 시작하면 읽을 수 없는 코드가 되므로, 그때는 locals로 이름을 붙여 나누는 편이 낫습니다.
for 표현식과 스플랫: 컬렉션 가공 #
리스트나 맵을 변형할 때는 for 표현식을 씁니다. 대괄호로 감싸면 리스트가, 중괄호로 감싸면 맵이 나옵니다.
locals {
team = ["alice", "bob", "carol"]
# 리스트 → 리스트: 각 이름에 접두사 붙이기
usernames = [for name in local.team : "dev-${name}"]
# 리스트 → 맵: 이름을 키로 하는 태그 맵 만들기
member_tags = { for name in local.team : name => "member" }
# 조건 필터: if를 붙여 걸러내기
admins = [for name in local.team : name if name != "carol"]
}리소스 여러 개에서 같은 속성만 뽑을 때는 스플랫(splat) 문법 [*]이 지름길입니다. 예를 들어 서브넷을 여러 개 만들었다면 aws_subnet.public[*].id가 ID만 모은 리스트가 됩니다. for로도 같은 일을 할 수 있지만, “전부에서 이 속성만"이라는 흔한 경우를 짧게 쓰라고 있는 문법입니다.
내장 함수: 만들 수는 없고 골라 쓰는 것 #
테라폼에는 함수를 직접 정의하는 문법이 없습니다. 대신 내장 함수가 넉넉하게 준비되어 있습니다. 자주 만나는 것들만 추리면 다음과 같습니다.
| 함수 | 하는 일 |
|---|---|
length(list) | 컬렉션의 길이 |
merge(map1, map2) | 맵 합치기(공통 태그 + 개별 태그 패턴) |
format("app-%s", var.env) | 문자열 포매팅 |
file("policy.json") | 파일 내용을 문자열로 읽기 |
jsonencode({...}) | HCL 값을 JSON 문자열로(IAM 정책 작성의 표준) |
toset(list) | 리스트를 셋으로(#6의 for_each에서 필요) |
이 중 merge는 실전에서 매일 쓰게 됩니다. 모든 리소스에 붙는 공통 태그를 locals에 두고, 리소스별 태그와 합치는 패턴입니다.
tags = merge(local.common_tags, { Name = "web-server" })전체 목록을 외울 필요는 없고, 함수 문서를 열어 두고 필요할 때 찾으면 됩니다.
terraform console: 표현식 실험장 #
표현식이 어떤 값을 만드는지 apply 없이 확인하고 싶을 때가 있습니다. terraform console을 실행하면 대화형 프롬프트가 열립니다.
$ terraform console
> [for name in ["alice", "bob"] : "dev-${name}"]
[
"dev-alice",
"dev-bob",
]
> format("app-%s", "prod")
"app-prod"현재 디렉터리의 변수와 리소스도 읽을 수 있어서, data.aws_ami.al2023.id를 쳐 보면 실제 조회 결과가 나옵니다. for 표현식을 처음 쓸 때 여기서 몇 번 굴려 보고 코드에 옮기면 plan을 반복하며 시행착오하는 시간이 크게 줄어듭니다.
정리 #
이번 글에서 다룬 내용입니다.
data블록은 테라폼이 만들지 않은 기존 리소스를 조회하며,data.타입.이름.속성으로 참조합니다. 최신 AMI 조회가 대표 사례입니다- 조건식
조건 ? a : b로 환경별 분기를 만들고, 중첩이 깊어지면 locals로 나눕니다 - for 표현식은 대괄호로 리스트를, 중괄호로 맵을 만들며
if로 필터링합니다. 같은 속성만 모을 때는 스플랫[*]이 짧습니다 - 함수는 직접 만들 수 없고 내장 함수를 씁니다.
merge로 공통 태그를 합치는 패턴과jsonencode는 실전 단골입니다 terraform console은 표현식을 apply 없이 실험하는 도구입니다
다음 글(#5 state의 정체)에서는 #1부터 계속 미뤄 온 질문에 답합니다. 테라폼은 자기가 만든 것을 어떻게 기억하고 있을까요? terraform.tfstate 파일을 열어 보며 테라폼 동작 원리의 중심을 들여다보겠습니다.