Terraform 실전 · count 와 for_each · 실습
count 에서 for_each 로, 파괴 없이 이주하기
목표
count 와 for_each 의 차이를 상태 주소로 확인하고, 이미 적용된 자원을 파괴 없이 for_each 로 이주시키는 절차를 익힙니다.
왜 중요한가
count 와 for_each 는 문법 선택처럼 보이지만 실제로는 각 인스턴스의 식별자를 무엇으로 삼을 것인가를 정하는 결정입니다. count 는 순서를, for_each 는 이름을 식별자로 씁니다. 목록 가운데 항목을 하나 지워 보면 차이가 드러납니다 — count 는 뒤쪽이 한 칸씩 당겨지면서 남아 있어야 할 자원까지 재생성되고, for_each 는 해당 키만 사라집니다. 실제 클라우드였다면 이 차이가 "설정 한 줄 지웠는데 서버 두 대가 재생성됐다"로 나타납니다. 이미 적용된 자원의 주소를 바꿔야 할 때는 moved 블록을 쓰세요. moved 는 상태만 옮기고 실물은 건드리지 않으며, 코드에 리팩터링 이력이 남아 다른 사람이 plan 만 돌려도 자동으로 따라옵니다. 이주가 제대로 됐는지 판정하는 기준은 하나입니다 — 계획에 파괴가 한 건도 없어야 합니다.
단계
1. /root/tf/foreach/main.tf 에 local_file 리소스 node 를 count = 3 으로 선언하고 tofu init 후 적용하세요. 그다음 주소를 옮기기 전에 tofu state list 결과를 /root/tf/foreach/out/count-state.txt 로 저장하세요. 이 파일에는 local_file.node[0], local_file.node[1], local_file.node[2] 세 줄이 들어 있어야 하며(문자열 키 형태가 들어 있으면 안 됩니다), 7번의 이주 전 스냅숏 역할을 합니다.
2. node 의 파일 이름을 /root/tf/foreach/out/node-${count.index}.txt, 내용을 node-${count.index} 로 하세요. 이름을 세 번 손으로 적지 말고 반드시 반복 색인으로 만들어야 합니다. out 아래 node-*.txt 는 정확히 3개여야 하고, 각 파일의 첫 줄은 node-0, node-1, node-2 여야 합니다.
3. local_file 리소스 zone 을 for_each = toset(["kr", "us", "eu"]) 로 만들고 파일은 /root/tf/foreach/out/zone-<키>.txt 세 개가 생기게 하세요. 상태의 키는 eu, kr, us 여야 합니다.
4. local_file 리소스 service 를 맵으로 도세요. 키는 api, web, worker 이고 각 값에는 포트가 들어갑니다(web 의 포트는 반드시 8080). /root/tf/foreach/out/service-web.txt 안에는 name=web 과 port=8080 이 각각 한 줄로 들어가야 합니다.
5. for 표현식으로 서비스 이름을 키, 포트를 값으로 갖는 출력 service_ports 를 정의하고 tofu output -json 결과를 /root/tf/foreach/out/outputs.json 로 저장하세요. 항목은 3개, web 은 8080 이어야 합니다.
6. variable "enable_debug"(bool, 기본값 false)를 만들고 local_file 리소스 debug 에 count = var.enable_debug ? 1 : 0 을 쓰세요. 파일 경로는 /root/tf/foreach/out/debug.txt 이고 내용은 자유입니다(스위치는 인스턴스가 최대 하나라 색인이 밀릴 일이 없다는 점이 count 를 계속 써도 되는 이유입니다). 스위치를 켠 계획(tofu plan -var enable_debug=true)의 출력을 /root/tf/foreach/out/toggle.txt 로 저장하되(1 to add 가 보여야 합니다) 적용하지는 마세요. 최종 상태에는 debug 인스턴스가 0개이고 /root/tf/foreach/out/debug.txt 도 없어야 합니다.
7. node 를 for_each = { n0 = 0, n1 = 1, n2 = 2 } 로 바꾸세요. 맵의 키는 새 주소용이고 값은 옛 색인이므로, 파일 이름과 내용은 each.key 가 아니라 each.value 로 만들어 node-0.txt·node-1.txt·node-2.txt 와 그 내용이 1~2번과 완전히 같게 유지해야 합니다(이름을 바꾸면 이주가 아니라 재생성이 되어 계획에 파괴가 뜹니다). 그리고 moved 블록 3개로 local_file.node[0] 을 local_file.node["n0"] 으로 옮기는 식으로 옛 주소와 새 주소를 이어 주세요. 적용한 뒤 tofu plan 출력을 /root/tf/foreach/out/after-plan.txt 로 저장하면 No changes 여야 하고 파괴가 한 건도 없어야 합니다.
8. /root/tf/foreach/out/addresses.json 을 만드세요. addresses 키에 tofu state list 의 주소를 배열로 담고(node 3 + zone 3 + service 3 = 9개 이상), 숫자 색인이 붙은 주소는 하나도 없어야 합니다. numeric_index_count 키에는 숫자 색인 주소의 개수를 적으며 이주가 끝났다면 0 입니다.
참고
- 이 실습의 상태 파일은
/root/tf/foreach/terraform.tfstate이고, count 로 만든 결과물은/root/tf/foreach/out/node-0.txt,/root/tf/foreach/out/node-1.txt,/root/tf/foreach/out/node-2.txt입니다. 이주 뒤에도 이 파일 이름은 그대로여야 합니다. tofu state list는 주소를 한 줄에 하나씩 뱉습니다.jq -R -s로 통째로 읽어 배열로 접으면 8번이 편합니다.- 1번의
count-state.txt와 8번의addresses.json은 같은 명령의 이주 전·후 스냅숏입니다. 실제 이주 작업에서도 옮기기 전 주소 목록과 상태 사본을 먼저 남기는 것이 첫 단계입니다. 기록이 없으면 "빠뜨린 자원이 없다"를 증명할 방법이 없습니다. moved블록은 한 번 적용한 뒤에도 코드에 남겨 두는 것이 관례입니다. 다른 사람의 상태에서도 같은 이주가 일어나야 하기 때문입니다.- 6번의
toggle.txt는 다른 변경이 모두 적용된 뒤에 뽑아야 계획에 스위치 하나만 잡힙니다. - 흔한 실수 1: 7번에서 파일 이름을
node-${each.key}.txt로 바꿔 버리는 것. 이름이 달라지면 이주가 아니라 재생성이 되고 계획에 파괴가 뜹니다. - 흔한 실수 2: 리스트를
for_each에 그대로 넘기는 것.toset()으로 집합으로 바꿔야 합니다. - 흔한 실수 3: 이주를 끝낸 뒤에
count-state.txt를 뽑는 것. 그 파일은 옮기기 전 기록이어야 의미가 있습니다.
단계 8개
- count 로 세 개 만들고 옮기기 전 주소 남기기
- count.index 로 인스턴스 구분하기
- 집합을 도는 for_each 쓰기
- 맵을 도는 for_each 와 each.value
- for 표현식으로 출력 맵 만들기
- 조건부 count 로 기능 스위치 만들기
- moved 블록으로 count 를 for_each 로 이주하기
- 전체 주소 목록과 이주 완료 보고서 만들기