moved 블록

도은호·2025년 9월 23일

terraform

목록 보기
20/32

moved리소스의 주소(address)가 바뀌었을 때, 기존 상태(State)를 파괴 없이 새 주소로 이전하라고 Terraform에 알려주는 선언형 리팩터링 도구. (Terraform v1.1+)


1) 개념 (왜 필요한가?)

  • Terraform은 리소스를 주소로 식별해요. 예)
    aws_instance.web, aws_subnet.public["ap-northeast-2a"], aws_sg.alb[0]
  • 리팩터링(이름 변경, count → for_each 전환, 리소스 분리/병합)으로 주소가 바뀌면 기본적으로 TF는 “옛 주소 Destroy, 새 주소 Create” 를 계획합니다.
  • 이때 moved 블록을 두면: Destroy/Create 없이 State만 새 주소로 이동 → 다운타임/재생성 방지!

문법(모듈 최상위에 선언)

moved {
  from = <이전_주소>
  to   = <새_주소>
}

보통 “새 주소가 존재하는 모듈” 안에 moved를 둡니다. 여러 항목을 옮길 땐 moved 블록을 여러 개 나란히 작성.


2) 언제 쓰나?

  • 리소스 이름 변경: aws_security_group.webaws_security_group.alb
  • countfor_each 전환: 인덱스 기반을 키 기반으로 바꿈
  • 하나의 리소스를 여러 개로 분리하거나, 반대로 병합하면서 주소가 달라질 때
  • 모듈 내부 폴더/구조 리팩터링 중 같은 모듈 내 주소가 변경될 때

주의: 일반적으로 모듈 경계를 넘는 이동(부모↔자식)moved만으로 해결되지 않습니다. 그런 경우엔 상황에 따라 terraform state mv(아래 비교) 등을 병행하세요.


3) 예제

A) 리소스 이름 변경

# 이전
resource "aws_security_group" "web" { ... }

# 이후 (이름만 바꿈)
resource "aws_security_group" "alb" { ... }

# 상태 이전
moved {
  from = aws_security_group.web
  to   = aws_security_group.alb
}

효과: 실제 SG는 그대로, State만 web → alb 주소로 이동.


B) countfor_each 전환 (인덱스를 키로 매핑)

# 이전 (count)
resource "aws_subnet" "public" {
  count      = 2
  cidr_block = var.public_cidrs[count.index]
  # ...
}

# 이후 (for_each, 키는 AZ 이름)
resource "aws_subnet" "public" {
  for_each   = { "ap-northeast-2a" = "10.0.1.0/24", "ap-northeast-2c" = "10.0.2.0/24" }
  cidr_block = each.value
}

# 상태 이전(인덱스→키를 1:1로 대응)
moved { from = aws_subnet.public[0] to = aws_subnet.public["ap-northeast-2a"] }
moved { from = aws_subnet.public[1] to = aws_subnet.public["ap-northeast-2c"] }

팁: 매핑을 정확히 하지 않으면 누락된 인스턴스가 새로 생성되거나 기존이 파괴될 수 있어요.


C) 리소스 쪼개기(분리) — 보안그룹 규칙을 전용 리소스로

# 이전: SG 블록 안에 인라인 ingress 한 개
resource "aws_security_group" "web" {
  name = "web-sg"
  # ...
  ingress { from_port=80 to_port=80 protocol="tcp" cidr_blocks=["0.0.0.0/0"] }
}

# 이후: 전용 리소스로 분리
resource "aws_security_group" "web" { name = "web-sg" /* ingress 제거 */ }

resource "aws_security_group_rule" "web_http" {
  type = "ingress"
  from_port=80 to_port=80 protocol="tcp"
  cidr_blocks=["0.0.0.0/0"]
  security_group_id = aws_security_group.web.id
}

# 상태 이전 (인라인 → 전용 리소스의 특정 주소로)
moved {
  from = aws_security_group.web.ingress[0]
  to   = aws_security_group_rule.web_http
}

효과: 기존 인라인 규칙이 새 리소스로 자연스럽게 이전되어 재생성 최소화.


D) 키 변경(Stable Key 재설계) — for_each 키 리네이밍

# 이전
resource "aws_iam_user" "u" {
  for_each = { a = "alice", b = "bob" }
  name     = each.value
}

# 이후 (key b → bob, key a → alice-kr 처럼 변경)
resource "aws_iam_user" "u" {
  for_each = { bob = "bob", alice-kr = "alice" }
  name     = each.value
}

moved { from = aws_iam_user.u["a"] to = aws_iam_user.u["alice-kr"] }
moved { from = aws_iam_user.u["b"] to = aws_iam_user.u["bob"] }

포인트: 키는 주소의 일부라 바뀌면 새 리소스로 인식됩니다. moved로 안전 이전!


4) 사용 순서 (안전한 절차)

  1. 리팩터링 코드 작성: 새 주소로 리소스/키/모듈 구조 변경
  2. 같은 모듈에 moved 블록 추가 (필요한 경우 여러 개)
  3. terraform plan으로 Destroy/Create가 없는지 확인
  4. terraform apply로 상태 이전 수행
  5. 팀/환경 모두 적용 끝나면 moved 블록을 한동안 유지(새로 합류한 환경 배려) 후 정리

5) 체크리스트 & 흔한 실수

  • from/to정확한 주소여야 해요. (키/인덱스 철저히 매핑)
  • 누락된 인스턴스가 없는지 plan에서 확인 (destroy/create가 뜨면 매핑 재점검)
  • 가능한 한 한 번에 작은 단위로 이동 (대량 이동은 위험)
  • for_each 키는 안정적인 값으로 설계 (환경/가변 값 기반 키는 피하기)
  • moved는 주로 같은 모듈 안의 주소 변경에 사용
    (모듈 경계 이동·특수 케이스는 terraform state mv가 필요할 수 있음)
  • 프로바이더/리전 전환 같은 큰 변경은 순수 moved만으론 불가

6) moved vs terraform state mv

항목moved (선언형)terraform state mv (명령형)
적용 범위코드에 남음(리뷰/재현성, CI에 유리)한 번만 실행(그 스테이트에만)
협업/환경 수여러 환경에 일관 적용환경마다 반복 실행 필요
크로스 모듈 이동제한적/비권장가능
사용 난이도쉽고 안전 (Plan으로 검증)숙련 필요, 잘못하면 드리프트
추천 용도일반적인 리팩터링(이름/키/모드 전환)특수 처리/긴급 핫픽스/경계 넘는 이동

실무에선 기본은 moved, 예외 상황만 state mv를 보완적으로 사용.


7) 자주 묻는 질문(FAQ)

Q. moved 블록은 영원히 놔둬야 하나요?
A. 필수는 아니지만, 모든 환경/파이프라인이 변경을 소화할 때까지 유지하는 걸 권장합니다. 이후 깔끔하게 제거해도 돼요.

Q. moved로 타입(리소스 종류)까지 바꿀 수 있나요?
A. 보통 같은 타입/동등한 인스턴스 간 주소 이전에 적합합니다. 프로바이더/타입이 바뀌면 재생성될 수 있어요.

Q. moved만 추가했는데도 새로 만들겠다고 해요.
A. from/to 매핑이 틀렸거나 누락, 혹은 실제 속성 변경이 교체 조건을 만들어서 그래요. Plan에서 원인을 확인하고 매핑을 보완하세요.


요약

moved = “이 주소에서 저 주소로 상태만 옮겨라”는 선언.
이름·키·반복 방식이 바뀌어도 파괴 없이 리팩터링하려면, 작은 단위로 moved를 추가하고 plan으로 검증하자.


.tfstate.tfstate.backup

  • moved 블록은 “리소스 주소만 바뀌었다”는 걸 Terraform에 알려 상태(state)파괴 없이 새 주소로 이전하라고 지시한다.
  • 이때 실제로 변경되는 것은 .tfstate 파일의 내용(주소 키) 이고, 적용 시 기존 파일은 .tfstate.backup 으로 자동 백업된다.
  • 즉, 직접적 연동은 없음(moved가 백업을 ‘사용’하진 않음). 다만 moved를 적용할 때 state가 쓰여지면서 backup이 생성되는 부수 효과가 생긴다.

1) 동작 순서

moved 블록을 추가하고 apply를 하면 Terraform은:

  1. 현재 state(.tfstate) 를 읽음
  2. moved { from → to } 매핑대로 state 내부의 리소스 주소를 재매핑
  3. 클라우드 리소스는 건드리지 않음(파괴/생성 없이 “키만 이동”)
  4. 새 state를 쓰기 전에 기존 state를 .tfstate.backup 으로 복사
  5. .tfstate 를 저장

Plan만 하고 Apply를 하지 않으면 .tfstate/.tfstate.backup은 그대로다.
상태 파일이 실제로 바뀌는 시점은 Apply다.

2) 예제로 이해하기

변경 전

resource "aws_security_group" "web" { ... }

변경 후 (이름만 바꿈)

resource "aws_security_group" "alb" { ... }

moved {
  from = aws_security_group.web
  to   = aws_security_group.alb
}

결과

  • 적용 전 Plan: moved from aws_security_group.web to aws_security_group.alb 표시

  • 적용 후:

    • 실제 AWS SG는 그대로 유지
    • .tfstate 안의 주소가 web → alb로 바뀜
    • 같은 폴더에 .tfstate.backup(적용 직전 상태) 생김

3) 로컬 vs 원격 백엔드

  • 로컬 백엔드(local): 워킹 디렉터리에 terraform.tfstateterraform.tfstate.backup 파일이 생김.
  • 원격 백엔드(S3 등): 로컬 파일이 보이지 않는다. 대신 버킷 객체 버전 관리가 사실상 백업 역할을 한다(권장: S3 버전관리+KMS 암호화, DynamoDB ).

어떤 백엔드든 **moved는 “state의 키 이동”**을 수행하고, 백업은 ‘state 쓰기’ 과정의 일반 동작이다.

4) 상태 점검 팁 (리팩터링 후 확인)

# 변경 전/후 주소 목록 비교
terraform state list

# 특정 리소스의 상태 보기
terraform state show aws_security_group.alb
  • Plan에 Destroy/Create가 뜨면 매핑이 틀렸을 수 있음 → movedfrom/to를 재확인.
  • 한 번에 많이 옮기지 말고 작은 단위로 이동 → 리스크 감소.

5) 체크리스트

  • movedstate 키 이동만 한다(실물 리소스 무변경)
  • Apply할 때 .tfstate가 갱신되고 **.tfstate.backup**이 자동 생성
  • 원격 백엔드는 버전관리로 이전 버전을 복구 가능
  • 상태 파일/백업은 절대 Git에 커밋 금지(민감 데이터 포함 가능)
  • 문제 시 수동 복구는 terraform state mv(주의) 또는 백업/버전에서 복원
profile
`•.¸¸.•´´¯`••._.• 🎀 𝒸𝓇𝒶𝓏𝓎 𝓅𝓈𝓎𝒸𝒽💞𝓅𝒶𝓉𝒽 🎀 •._.••`¯´´•.¸¸.•`

0개의 댓글