
Terraform으로 인프라를 오래 운영하다 보면, 결국 “구조 개선(리팩터링)”을 하게 됩니다.
예를 들면 리소스 이름을 바꾸거나, 단일 리소스를 for_each로 쪼개거나, 모듈을 분리/합치거나, 모듈 호출 이름을 바꾸는 일입니다.
문제는 Terraform의 기본 해석입니다.
공식 문서가 설명하듯, 주소(address)가 바뀌면 Terraform은 보통 이를 기존 리소스 destroy + 새 주소로 create로 해석합니다.
운영에서 이건 “무중단 리팩터링”이 아니라 “장애 유발 변경”이 됩니다.
TL;DR (실무 결론)
- 리팩터링으로 주소가 바뀌는 순간, 기본값은 재생성입니다. 이걸 피하려면
movedblock을 사용합니다. (공식 문서: moved block은 주소 변경을 destroy 없이 수행) movedblock은from/to두 주소를 선언하고, Terraform은 계획을 만들기 전에 state에서from을 찾아to로 rename한 뒤 계획을 진행합니다. (공식 문서: “checks the state… renames… does not destroy”)- 가장 많이 쓰는 패턴 3개: (1) 리소스 rename, (2)
count/for_each도입 시 기존 1개 인스턴스를 특정 키로 이동, (3) 모듈/리소스 경로 리팩터링(모듈 호출 이름 변경/분리). - 운영 절차: moved block 추가 → plan에서 destroy가 사라졌는지 확인 → apply → (모든 사용자가 적용 완료 후) moved block 제거는 “브레이킹 체인지”임을 명시하고 릴리즈합니다. (공식 문서: moved block 제거는 breaking change)
1) moved block이 해결하는 문제: “주소 변경 = destroy/create” 기본 해석
Terraform 공식 문서는 명확하게 말합니다.
기본적으로 Terraform은 주소 변경을 기존 리소스 destroy + 새 주소로 create 지시로 해석합니다.
그리고 moved block을 사용하면 기존 리소스의 주소를 업데이트해서 파괴 없이 이동할 수 있다고 설명합니다.
2) 공식 문서 기준 ‘정확한 동작’
2-1. moved block의 형태 (from/to)
moved block은 두 개의 필수 인자를 가집니다.
from은 “이전 주소”, to는 “새 주소”입니다. (공식 문서: moved block reference)
moved {
from = aws_instance.a
to = aws_instance.b
}
2-2. 계획 생성 전에 state를 확인하고, 주소를 rename 한다
moved block reference 문서는 다음 동작을 설명합니다.
Terraform은 to의 새 계획을 만들기 전에, state에서 from 주소의 기존 객체가 있는지 확인합니다.
그리고 존재하면 그 기존 객체를 to로 rename한 뒤 plan을 만든다고 합니다.
결과적으로 주소 변경 자체는 destroy를 유발하지 않습니다.
3) 실전 패턴 1: 리소스 이름(label)만 바꾸는 rename
가장 단순한 케이스입니다.
리소스 블록의 이름을 바꾸면 주소가 바뀌므로, moved block으로 state 주소를 함께 이동시켜야 합니다.
(공식 문서: Refactor modules / Rename a resource 예시)
# rename 전
resource "aws_instance" "a" {
# ...
}
# rename 후
resource "aws_instance" "b" {
# ...
}
moved {
from = aws_instance.a
to = aws_instance.b
}
4) 실전 패턴 2: 단일 리소스 → for_each로 다중 인스턴스화 (가장 사고가 많이 나는 구간)
공식 문서의 핵심은 이겁니다.
기존에 단일 인스턴스로 존재하던 리소스 주소(예: aws_instance.a)를
for_each로 바꾸는 순간, Terraform은 새 인스턴스 키(예: ["small"])로 주소 체계가 바뀌기 때문에
기본 해석대로라면 destroy/create로 흐를 수 있습니다.
Refactor modules 문서는 기존 객체를 보존하려면 moved block으로 “기존 1개 인스턴스가 새 키 중 어느 것으로 대응되는지”를
명시하라고 안내합니다.
resource "aws_instance" "a" {
for_each = local.instances
instance_type = each.value.instance_type
}
moved {
from = aws_instance.a
to = aws_instance.a["small"]
}
또한 문서는, moved block을 명시적으로 써서 변경 의도를 분명히 하라고 권장합니다.
(예: 기존 단일 리소스에 count를 추가하면 Terraform이 자동으로 인스턴스 0으로 이동을 제안할 수 있으나, 명시적으로 작성 권장)
5) 실전 패턴 3: 모듈 리팩터링(경로 변경)에서 ‘상태 주소’가 바뀌는 경우
모듈을 분리하거나 모듈 호출 이름을 바꾸는 등, 코드 구조를 바꾸면 state 주소도 함께 바뀝니다.
Terraform 공식 문서는 moved block이 리소스뿐 아니라 모듈/리소스 경로 리팩터링에도 사용된다고 설명합니다.
(공식 문서: Refactor modules)
이 글에서는 “모듈 경로의 모든 조합”을 나열하지 않습니다.
대신 원칙만 고정합니다: plan에서 destroy가 뜨면, 그 destroy가 의도된 것인지 아닌지를 먼저 확정하고,
의도되지 않았다면 moved block으로 주소 대응을 문서화해야 합니다.
6) (필수 보강요소 1) 운영 체크리스트: 리팩터링 적용 전에 이 순서로만 진행
- 변경 유형 분류: rename/키 변경(for_each)/모듈 경로 변경 중 무엇인지 명확히 적는다.
- moved block 초안 작성:
from/to를 실제 주소로 적는다(코드 상에서 보이는 주소). - plan 확인(필수): 공식 문서가 의도한 대로라면, 주소 이동은 destroy 없이 처리되어야 한다. destroy가 남아 있으면 중단.
- apply는 1회에 몰아넣지 말 것: 리팩터링 + 실제 스펙 변경(예: instance_type 변경)을 같은 PR에 섞으면 디버깅이 어려워진다. 먼저 주소 이동만 완료한다.
- 팀/파이프라인 전파 계획: moved block 제거는 breaking change가 될 수 있다고 문서가 경고한다. 여러 실행자(개인 노트북/CI)가 있다면, 모두 apply 완료 후 제거한다.
7) (필수 보강요소 2) 트러블슈팅 매트릭스: 증상 → 원인 후보 → 확인 포인트
| 증상 | 원인 후보 | 확인 포인트 |
|---|---|---|
| plan에 destroy/create가 그대로 뜬다 | moved block의 from/to 주소가 실제 state 주소와 불일치 | 공식 문서: Terraform은 plan 전에 state에서 from을 찾는다. from이 정확한지(모듈 경로/인스턴스 키 포함) 재검증 |
| for_each 도입 후 기존 리소스가 없어지고 새로 생기는 것처럼 보인다 | 기존 단일 인스턴스를 어떤 키로 대응할지 moved block에 명시하지 않음 | Refactor modules의 for_each 예시처럼 aws_instance.a → aws_instance.a["key"] 이동 선언이 있는지 확인 |
| apply 후 다른 실행 환경에서 plan이 다시 destroy/create를 제안한다 | 한쪽은 moved block이 있는 커밋으로 apply 했고, 다른 쪽은 moved block 제거된 커밋으로 실행 | 공식 문서: moved block 제거는 breaking change. 실행자(로컬/CI)의 버전/브랜치가 동일한지 확인 |
| moved block을 추가했는데도 예상치 못한 변경(diff)이 많다 | 주소 이동과 동시에 리소스 인자도 변경됨(리팩터링+스펙 변경 혼합) | 주소 이동만 별도 PR로 분리하고, moved block 적용 1회차에서 “주소만” 이동되도록 만든다 |
8) (필수 보강요소 3) FAQ: 현장에서 자주 틀리는 질문 5개
Q1. moved block은 언제까지 남겨둬야 하나요?
Terraform 공식 문서는 moved block을 더 이상 필요로 하지 않을 때 제거할 수 있지만,
제거는 breaking change가 될 수 있다고 경고합니다.
즉, 여러 실행자가 있는 환경에서는 “모두가 moved block이 포함된 버전으로 한 번은 apply를 끝낸 뒤”에 제거하는 게 안전합니다.
Q2. moved block을 쓰면 리소스가 절대 변경되지 않나요?
moved block이 보장하는 것은 “주소 변경이 destroy를 유발하지 않도록 state 주소를 이동시키는 것”입니다.
리소스 인자 자체를 바꾸면, 그 변경은 평소처럼 diff로 잡힙니다.
그래서 운영 체크리스트에서 리팩터링과 스펙 변경을 분리하라고 강조했습니다.
Q3. moved block 없이 terraform state mv만으로도 되지 않나요?
공식 문서에서는 상황에 따라 CLI의 terraform state mv도 언급합니다.
다만 이 글의 목적은 “구성(configuration) 자체에 리팩터링 의도를 기록”하는 것입니다.
moved block은 코드에 남기므로 리뷰/재현/자동화 관점에서 유리합니다.
Q4. moved block은 어떤 주소 문법을 지원하나요?
공식 문서: from/to 주소는 모듈, 리소스, 그리고 child module 내부 리소스까지 선택할 수 있는 주소 문법을 사용한다고 설명합니다.
따라서 모듈 경로를 포함한 실제 주소를 정확히 쓰는 게 핵심입니다.
Q5. moved block을 잘못 쓰면 어떤 일이 생기나요?
공식 문서가 말하는 대로, Terraform은 state에서 from을 찾고 to로 rename 한 뒤 계획을 진행합니다.
따라서 from/to를 잘못 쓰면, 의도한 객체를 못 찾거나 다른 객체를 이동시키려는 시도가 발생할 수 있습니다.
결론은 단순합니다: plan에서 destroy/create가 사라졌는지를 확인하기 전까지는 적용하면 안 됩니다.
9) (필수 보강요소 4) “추정 금지” 검증 절차: moved block이 제대로 작동했는지 확정하는 방법
- 리팩터링 전 plan 캡처: 주소 변경만 했는데 destroy/create가 나온다면(기본 해석), 그 캡처를 남긴다.
- moved block 추가 후 plan 재확인: 주소 이동이 의도대로라면, 해당 리소스의 destroy/create가 사라져야 한다(또는 주소 이동으로 표현되어야 한다).
- apply 후 상태 확인: apply 이후, 새 주소(
to)로 리소스가 state에 잡혀 있는지 확인한다. - 다른 실행자 재현: CI/다른 PC 등 별도 실행 환경에서 동일 커밋으로 plan 했을 때 destroy/create가 재등장하지 않는지 확인한다.
- moved block 제거는 별도 릴리즈: 공식 문서가 “breaking change”라 경고하므로, 제거 PR은 배포/전파가 끝난 뒤에만 진행한다.