Terraform 실전 · 동적 블록과 복잡한 형 · 실습
서비스 셋 × 환경 셋을 손으로 펼쳤더니 스무 블록이 됐다
목표
중첩 객체 변수를 받아 맵으로 바꾸고, 이중 반복을 flatten 과 setproduct 두 방식으로 펼쳐 같은 결과를 내고, dynamic 블록으로 선택적 블록을 만들고 그 한계를 오류로 확인한 뒤, 펼친 행렬을 파일과 보고서로 렌더합니다.
왜 중요한가
인프라 코드가 길어지는 진짜 이유는 리소스가 많아서가 아니라 같은 모양이 반복되어서입니다. 그 반복을 손으로 펼치면 고칠 곳이 스무 군데가 되고, 리뷰하는 사람은 스무 블록이 정말 같은지 눈으로 대조해야 합니다. HCL 은 이것을 두 갈래로 풉니다 — 값을 만드는 쪽과 블록을 만드는 쪽입니다. 값 쪽(for 표현식·flatten·setproduct·객체 형)은 얼마든지 접고 펼 수 있고, 블록 쪽(dynamic)은 프로바이더 스키마가 허락하는 범위 안에서만 만들 수 있습니다. 이 차이를 모르면 dynamic 으로 모든 것을 짜려다 읽을 수 없는 설정을 만들게 됩니다. 실무에서 dynamic 이 가장 값어치 있게 쓰이는 자리는 사실 반복이 아니라 '이 블록을 넣을까 말까' 라는 선택이고, 반복은 대개 for_each 와 값 표현식이 맡습니다.
단계
1. /root/tfa-dyn/variables.tf 에 list(object({ name = string, port = number, envs = list(string) })) 형의 services 변수를 선언하고, 이름이 겹치지 않는지 보는 validation 을 붙이세요. /root/tfa-dyn/terraform.tfvars 에는 api(8080, dev·prod), web(8081, dev), batch(9090, dev·stage·prod) 세 개를 적습니다. /root/tfa-dyn/main.tf 에는 프로바이더 선언과 jsonencode(var.services) 를 out/services.json 에 쓰는 local_file.services 를 두고 init·apply 하세요.
2. /root/tfa-dyn/svc.tf 에 local.by_name(이름을 키로 잡은 맵)을 만들고, 그것으로 for_each 를 돌려 서비스마다 /root/tfa-dyn/out/svc/<이름>.conf 를 쓰는 local_file.svc 를 선언하세요. 파일 내용은 name=<이름> 과 port=<번호> 두 줄입니다. apply 한 뒤 상태의 인스턴스 키가 무엇인지 보세요.
3. /root/tfa-dyn/flat.tf 에 서비스 × 그 서비스의 환경을 모두 펼친 local.pairs(각 항목은 env·name·port 를 가진 객체)를 flatten 으로 만들고, "<env>/<name>" 을 키로 잡은 local.by_key 맵을 jsonencode 해 /root/tfa-dyn/out/flat.json 에 쓰세요. 항목은 여섯 개가 나와야 합니다.
4. /root/tfa-dyn/product.tf 에 local.all_envs(모든 환경의 중복 없는 목록)를 만들고, setproduct(local.all_envs, var.services) 의 모든 조합 중 그 서비스가 실제로 쓰는 환경만 남겨 local.combo_by_key 를 만든 뒤 /root/tfa-dyn/out/product.json 에 쓰세요. 3단계 결과와 완전히 같아야 합니다.
5. /root/tfa-dyn/cert.tf 에 var.with_subject(bool, 기본 true)와 ED25519 개인키, 그리고 dynamic "subject" 로 주체 블록을 켜고 끄는 tls_self_signed_cert.c 를 두세요. 주체는 common_name = "api.internal", organization = "LabHub" 입니다. 인증서는 /root/tfa-dyn/out/cert.pem 에 씁니다. 먼저 -var with_subject=false 로 적용해 openssl x509 -noout -subject 결과를 /root/tfa-dyn/out/no-subject.txt 에 저장하고, 그다음 기본값으로 다시 적용해 같은 명령의 결과를 /root/tfa-dyn/out/subject.txt 에 저장하세요.
6. /root/tfa-dyn/cert.tf 의 dynamic "subject" 가 두 개의 블록을 만들도록 잠깐 고쳐 apply 하고, 실패 출력을 /root/tfa-dyn/out/limit-error.txt 에 저장하세요. 그다음 원래대로 되돌려 다시 적용하고 계획이 깨끗한 상태로 마칩니다.
7. /root/tfa-dyn/rendered.tf 에 3단계의 local.by_key 로 for_each 를 돌려 /root/tfa-dyn/rendered/<env>-<name>.conf 여섯 개를 만드세요. 내용은 env=, name=, port= 세 줄입니다.
8. /root/tfa-dyn/summary.tf 에 환경별 항목 수(by_env), 이름을 정렬한 목록(services), 서비스 포트의 합(total_ports) 세 가지를 담은 /root/tfa-dyn/out/summary.json 을 만드세요. 세 값은 모두 HCL 표현식으로 계산합니다.
참고
- 파드에는 OpenTofu 1.9.0 과 local·tls 프로바이더 미러가 있어 인터넷 없이 돕니다.
- 각 단계는 새 .tf 파일을 더하는 방식입니다. 앞 단계 파일을 지우지 마세요 — 뒤 단계가 그 locals 를 씁니다.
- 흔한 실수: for_each 에 리스트를 그대로 주는 것. 집합이나 맵이어야 하고, 리스트는 for 표현식으로 맵으로 바꿉니다.
- 흔한 실수: jsonencode 결과를 사람이 읽으려고 그대로 여는 것. jq 로 정렬해 보면 두 방식의 결과를 바로 견줄 수 있습니다.
- [dynamic Blocks](https://opentofu.org/docs/language/expressions/dynamic-blocks/) · [flatten](https://opentofu.org/docs/language/functions/flatten/) · [setproduct](https://opentofu.org/docs/language/functions/setproduct/) · [tls_self_signed_cert](https://search.opentofu.org/provider/hashicorp/tls/latest/docs/resources/self_signed_cert)
단계 8개
- 중첩 객체를 변수 형으로 받는다
- 리스트를 맵으로 바꿔 for_each 에 넘긴다
- 이중 반복을 flatten 으로 한 겹으로 편다
- 같은 행렬을 setproduct 로 다시 만든다
- dynamic 으로 블록을 있다·없다로 만든다
- dynamic 은 스키마가 허락하는 만큼만 만든다
- 펼친 행렬을 파일로 렌더한다
- 펼친 값을 다시 접어 보고서로 만든다