Terraform 실전 · count 와 for_each · 이론
count 와 for_each — 색인이냐 키냐
한 줄 요약
count 와 for_each 의 차이는 "몇 개를 만드느냐"가 아니라 각 인스턴스에 어떤 이름표를 붙이느냐다. 그리고 그 이름표는 상태 파일에 그대로 박힌다.
왜 이게 필요했나
서버 세 대를 만들려고 count = 3 을 쓴다. 잘 동작한다. 몇 달 뒤 목록이 변수로 바뀌고 count = length(var.servers) 가 된다. 여기까지도 괜찮다. 사고는 가운데 항목을 하나 지울 때 난다.
count 로 만든 인스턴스의 주소는 [0], [1], [2] 다. 목록에서 두 번째 항목을 지우면 세 번째가 두 번째 자리로 당겨진다. 도구가 보는 세상에서는 "[1] 의 내용이 바뀌었고 [2] 가 사라졌다"가 된다. 그래서 계획에는 지우려던 한 대가 아니라 두 대의 재생성이 뜬다. 실제 클라우드였다면 멀쩡히 돌던 서버가 파괴되고 다시 만들어진다.
for_each 는 이 문제를 이름으로 푼다. 주소가 ["web-a"], ["web-b"] 처럼 의미 있는 키가 되므로, 목록에서 하나를 빼면 그 키 하나만 사라진다. 나머지는 미동도 하지 않는다.
어떻게 동작하나
| 항목 | count | for_each |
| --- | --- | --- |
| 받는 값 | 숫자 | 집합(set) 또는 맵(map) |
| 인스턴스 주소 | res[0] | res["kr"] |
| 안에서 쓰는 참조 | count.index | each.key, each.value |
| 중간 항목 삭제 | 뒤쪽이 밀려 재생성 | 해당 키만 삭제 |
| 잘 맞는 곳 | 개수만 의미 있을 때, 켜고 끄는 스위치 | 각 항목에 정체성이 있을 때 |
실무 기준은 단순하다. 각 인스턴스가 서로 구별되는 이름을 가질 수 있으면 for_each, 그냥 개수만 의미가 있으면 count. count 를 계속 써도 되는 대표적인 자리는 기능 스위치다. count = var.enable_debug ? 1 : 0 은 "있거나 없거나"를 표현하는 관용구이고, 인스턴스가 하나뿐이라 밀림 문제가 없다.
리스트를 for_each 에 그대로 넘길 수는 없다. 리스트는 순서가 있고 for_each 는 순서 없는 키를 요구하므로 toset(...) 으로 바꿔야 한다. 이때 키는 원소 값 자체가 된다. 맵을 넘기면 키는 맵의 키, each.value 는 그 값이 된다 — 이쪽이 각 항목에 여러 속성을 붙이기 좋아서 실무에서 더 많이 쓰인다.
for 표현식은 또 다른 물건이다. for_each 가 리소스를 여러 개 만드는 메타 인자라면, for 는 값 하나를 다른 모양으로 바꾸는 표현식이다. { for k, v in var.services : k => v.port } 처럼 맵을 다시 맵으로 접는 데 쓴다. 출력이나 지역값을 정리할 때 거의 반드시 쓰게 된다.
현장에서 만나는 모습
첫째, 이주는 반드시 moved 로. 이미 count 로 적용된 자원을 for_each 로 바꾸면 도구는 옛 주소가 사라지고 새 주소가 생겼다고 판단한다. 즉 전부 파괴하고 전부 다시 만든다. moved 블록으로 "res[0] 은 res["n0"] 이 되었다"고 알려 주면 파괴 없이 상태 주소만 옮겨진다. 계획에 파괴가 한 건이라도 보이면 이주가 아니라 재생성이 벌어지고 있다는 신호다.
둘째, 옮기기 전에 지금 주소를 남긴다. 이주 작업의 첫 단계는 코드 수정이 아니라 기록이다. state list 결과와 상태 사본을 먼저 파일로 남겨야 한다. 이주가 끝난 뒤 "빠뜨린 자원이 없다"를 말하려면 옮기기 전 목록과 옮긴 뒤 목록을 나란히 놓고 개수와 대응 관계를 확인해야 하는데, 전자를 남기지 않았다면 확인할 방법 자체가 없다. 사고는 대개 20개 중 19개만 옮겼을 때 나고, 남은 하나는 다음 apply 가 조용히 파괴한다.
셋째, 키를 계산하지 마라. for_each 의 키가 다른 리소스의 속성처럼 apply 시점에야 정해지는 값이면 계획 단계에서 인스턴스 목록을 알 수 없어 오류가 난다. 키는 변수나 상수처럼 계획 시점에 확정되는 값이어야 한다.
넷째, 키 이름은 바꾸기 어렵다. 한번 정한 키는 사실상 자원의 식별자가 되므로, 나중에 az-a 를 ap-northeast-2a 로 고치는 것도 이주 작업이 된다. 이때도 답은 moved 다. 그래서 처음 키를 정할 때 "이 이름이 2년 뒤에도 말이 되는가"를 한 번 더 생각하는 편이 싸게 먹힌다.
다음 실습에서 할 것
count 로 세 개를 만들어 숫자 색인이 상태에 박히는 것을 확인하고 옮기기 전 주소 목록을 파일로 남긴다. 그다음 toset 과 맵으로 for_each 를 두 가지 방식으로 써 본다. for 표현식으로 출력 맵을 만들고, 조건부 count 로 기능 스위치를 표현한다. 마지막에는 moved 블록으로 count 를 for_each 로 이주시켜 파괴 없이 주소만 바꾸는 것을 계획으로 증명한다.