Terraform 실전 · 동적 블록과 복잡한 형 · 이론
값으로 접는 반복, 블록으로 펴는 반복
한 줄 요약
HCL 의 반복은 값을 만드는 쪽(for·flatten·setproduct)과 블록을 만드는 쪽(dynamic)으로 갈린다. 앞은 마음대로 접고 펼 수 있고, 뒤는 프로바이더 스키마가 허락하는 만큼만 만들 수 있다.
왜 이게 필요했나 — 반복을 손으로 펴면 생기는 일
서비스 셋을 환경 셋에 올린다고 하자. 손으로 펴면 아홉 블록이다. 처음에는 읽기 쉽다. 문제는 여섯 달 뒤 "모든 환경의 헬스체크 경로를 바꿔라" 가 왔을 때다. 아홉 곳을 고쳐야 하고, 리뷰어는 아홉 곳이 정말 똑같이 바뀌었는지 눈으로 대조해야 한다. 한 곳이 빠져도 계획은 통과한다.
그래서 반복은 값으로 접어 둔다. 입력은 하나, 규칙은 하나, 결과는 아홉 개다. 이러면 고칠 곳이 한 곳이 되고, 리뷰는 "규칙이 맞나" 하나만 보면 된다. 대신 값 표현식을 읽는 훈련이 필요해진다 — 이 맞바꿈을 감수할 만한 지점이 어디인지가 이 모듈의 주제다.
어떻게 동작하나
1. 형을 먼저 정한다. 중첩 객체 형은 문서 노릇을 한다.
variable "services" { type = list(object({ name = string port = number envs = list(string) }))}형을 적어 두면 tfvars 에서 철자가 틀렸을 때 apply 전에 막힌다. 여기에 validation 을 더해 "이름이 겹치면 안 된다" 같은 규칙도 값 단계에서 잡을 수 있다. 이름이 겹치면 뒤에서 만드는 for_each 키가 조용히 하나로 합쳐지는데, 이 사고는 계획에 "삭제 1건" 으로만 보여서 원인을 찾기 어렵다.
2. 리스트는 맵으로 바꿔서 for_each 에 넘긴다. for_each 는 집합이나 맵만 받는다. count 와 달리 인스턴스 주소가 키라서, 가운데 항목이 빠져도 뒤가 밀리지 않는다.
3. 이중 반복은 펴서 하나로 만든다. 방법이 둘 있다.
# (가) 안에서 밖으로 — 필요한 것만 만든다pairs = flatten([ for s in var.services : [ for e in s.envs : { env = e, name = s.name, port = s.port } ]])# (나) 다 만들고 거른다combos = [ for pair in setproduct(local.all_envs, var.services) : { env = pair[0], name = pair[1].name, port = pair[1].port } if contains(pair[1].envs, pair[0])]둘은 같은 결과를 낼 수 있지만 성질이 다르다. flatten 쪽은 입력에 있는 것만 만들고, setproduct 쪽은 모든 조합을 만든 뒤 버린다. 조합 수가 크면 뒤쪽은 헛일을 많이 한다. 반대로 "빠진 조합을 찾아내는" 것이 목적이라면 모든 조합을 먼저 만드는 쪽이 자연스럽다.
4. dynamic 은 블록을 만든다. 값이 아니라 중첩 블록을 설정 값에서 만들어 내는 장치다.
dynamic "subject" { for_each = var.with_subject ? [1] : [] content { common_name = "api.internal" }}여기서 중요한 것은 몇 개를 만들 수 있느냐가 프로바이더 스키마에 달려 있다는 점이다. 스키마가 최대 한 개만 허용하는 블록에 두 개를 만들면, 계획은 세워지지만 값 검증에서 "최대 몇 개까지인데 몇 개가 왔다" 로 막힌다. 실습 6단계에서 이 오류를 직접 받아 본다.
현장에서 만나는 모습
dynamic 이 실무에서 가장 자주 쓰이는 자리는 사실 반복이 아니라 선택적 블록이다. 빈 컬렉션을 주면 블록이 아예 만들어지지 않는다는 성질을 이용해 "로깅 설정은 prod 에서만", "암호화 블록은 이 옵션이 켜졌을 때만" 을 표현한다. 예전에 조건부 블록을 흉내내려고 리소스를 통째로 두 벌 만들던 방식보다 훨씬 낫다.
반대로 쓰지 말아야 할 자리도 분명하다. 공식 문서도 dynamic 은 읽기 어려워지므로 남용하지 말라고 적는다. 블록 세 개가 서로 조금씩 다른 경우, 동적으로 만들면 "무엇이 다른지" 가 데이터 안으로 숨어 리뷰에서 보이지 않는다. 그럴 때는 그냥 세 블록을 적는 쪽이 낫다. 기준은 단순하다 — 개수가 입력에 따라 변하면 동적으로, 개수는 고정이고 값만 다르면 손으로.
마지막으로 렌더 결과를 파일로 내보내는 습관이 크게 도움이 된다. jsonencode 로 펼친 행렬을 파일에 쓰면, 사람이 눈으로 확인할 수 있고 CI 에서 diff 를 뜰 수도 있다. 계획 출력만으로는 "왜 이 조합이 빠졌지" 를 추적하기 어렵다.
다음 실습에서 할 것
/root/tfa-dyn 에서 중첩 객체 변수를 받아 맵으로 바꾸고, 같은 행렬을 flatten 과 setproduct 두 방식으로 만들어 결과가 같은지 파일로 대조합니다. 그다음 dynamic 으로 인증서 주체 블록을 켜고 꺼서 실제 인증서를 열어 확인하고, 두 개를 만들려다 스키마에 막히는 오류를 받아 본 뒤, 펼친 행렬을 파일 여섯 개와 요약 보고서로 렌더합니다.