Terraform 표현식 이해하기: 값·참조·조건식·for 식을 구분하는 기준

반응형

Terraform에서 arbitrary expressions는 별도의 특수 문법 이름이라기보다, 인수 값에 리터럴만 쓰지 않고 참조·연산·함수·조건식처럼 값을 계산하는 표현식을 사용할 수 있다는 뜻에 가깝다.

기존 메모에서는 이를 “임의 표현식”이라고 번역하고 내장 함수와 연산자를 쓸 수 있다고만 적었다. 핵심은 맞지만, 모든 위치에서 모든 표현식을 쓸 수 있는 것은 아니다. 어떤 블록과 인수에 표현식이 놓였는지에 따라 허용되는 타입과 참조 범위가 달라진다.

인수 오른쪽에는 값이 되는 표현식이 온다

Terraform 설정의 기본 모양은 블록, 인수, 표현식으로 나뉜다.

resource "example_server" "app" {
  name = "api-server"
}
  • resource는 블록 유형이다.
  • example_server, app은 블록 레이블이다.
  • name은 인수 이름이다.
  • "api-server"는 문자열 값을 만드는 리터럴 표현식이다.

리터럴 대신 다른 값을 참조하거나 계산할 수도 있다.

variable "environment" {
  type = string
}

locals {
  service_name = "api-${var.environment}"
}

local.service_name의 오른쪽은 문자열 템플릿이며, var.environment를 참조해 새 값을 만든다. 표현식의 결과는 항상 어떤 값이 되고, 그 값에는 타입이 있다.

Terraform 값의 타입부터 확인한다

Terraform 표현식이 만드는 주요 타입은 다음과 같다.

구분 예시 주의할 점
string "prod" 문자열 템플릿에서 다른 타입이 변환될 수 있다.
number 3, 1.5 정수와 실수를 별도 타입으로 나누지 않는다.
bool true 조건식의 조건은 논리값이어야 한다.
list·tuple ["a", "b"] 순서가 있고 인덱스로 접근한다.
set toset(["a", "b"]) 순서와 중복이 없다. 인덱스로 접근하지 않는다.
map·object { name = "api" } 키 또는 속성 이름으로 접근한다.
null null 문맥에 따라 값을 생략한 것처럼 처리될 수 있다.

자동 타입 변환이 가능한 문맥도 있지만, 그 동작에 기대면 읽기 어렵고 조건식에서 예상하지 못한 타입이 나올 수 있다. 의도가 분명해야 할 때는 tostring, tonumber, tolist 같은 변환 함수를 명시한다.

자주 쓰는 표현식 네 가지

참조와 문자열 템플릿

locals {
  full_name = "${var.environment}-api"
}

리소스 속성, 입력 변수, 로컬 값, 모듈 출력 등을 참조할 수 있다. 다만 아직 결정되지 않은 리소스 속성은 계획 단계에서 unknown 값이 될 수 있다. 이를 빈 문자열이나 null과 같다고 취급하면 안 된다.

연산자

locals {
  enough_replicas = var.replica_count >= 2
  use_cache       = var.environment == "prod" && var.cache_enabled
}

산술, 비교, 동등성, 논리 연산자를 조합할 수 있다. 복잡한 조건은 괄호와 의미 있는 로컬 이름으로 나누는 편이 읽기 쉽다.

조건식

locals {
  instance_class = var.environment == "prod" ? "large" : "small"
}

조건식은 condition ? true_value : false_value 형태다. 두 결과는 Terraform이 하나의 공통 타입으로 판단할 수 있어야 한다. 숫자와 문자열처럼 자동 변환이 가능한 조합도 있지만, 유지보수를 위해 결과 타입을 맞추는 편이 낫다.

for 식

variable "service_names" {
  type = list(string)
}

locals {
  normalized_names = [
    for name in var.service_names : lower(trimspace(name))
  ]
}

for 식은 컬렉션을 다른 컬렉션으로 변환한다. 블록을 반복 생성하는 for_each와 이름이 비슷하지만 역할은 다르다.

  • for 식: 값을 변환해 새 값 하나를 만든다.
  • for_each: 리소스나 모듈 인스턴스를 여러 개 만든다.
  • dynamic 블록: 반복 가능한 중첩 블록을 동적으로 만든다.

모든 문맥이 arbitrary한 것은 아니다

HashiCorp 문서도 일부 문맥에서는 표현식 사용이 제한된다고 명시한다.

예를 들어 variable 블록의 type에는 일반 값 표현식이 아니라 타입 제약 문법이 온다. required_providers의 버전 제약처럼 특정 형식의 문자열을 요구하는 자리도 있다. 리소스 인수는 provider 스키마가 요구하는 타입을 따라야 한다.

따라서 오류를 볼 때는 “표현식 문법이 맞는가”만 보지 말고 다음을 함께 확인한다.

  1. 이 위치가 일반 값 표현식을 허용하는가?
  2. 표현식 결과 타입이 인수가 요구하는 타입과 맞는가?
  3. 참조한 값이 현재 단계에서 알려져 있는가?
  4. null, 빈 컬렉션, unknown을 같은 의미로 오해하지 않았는가?

terraform console로 작은 식부터 확인한다

리터럴과 함수, 조건식의 동작은 terraform console에서 빠르게 확인할 수 있다.

terraform console
> 1 + 2 * 3
7

> [for s in ["API", " Web "] : lower(trimspace(s))]
[
  "api",
  "web",
]

변수나 리소스 참조가 포함된 식은 현재 설정과 상태에 따라 결과가 달라질 수 있다. 콘솔에서 리터럴 예제가 계산됐다는 사실이 실제 provider 설정과 terraform plan까지 유효하다는 뜻은 아니다.

표현식을 작성한 뒤에는 다음 경계를 나눠 확인한다.

terraform fmt -check
terraform validate
terraform plan
  • fmt는 형식을 정리한다.
  • validate는 구성의 문법과 내부 일관성을 확인한다.
  • plan은 provider와 현재 상태를 포함해 예상 변경을 계산한다.

이 글의 예제는 Terraform 언어 문법을 설명하기 위한 것이며 실제 인프라에 적용한 결과를 의미하지 않는다.

참고 자료

반응형
KEEP READING
카테고리 전체 보기 →

댓글