I moved the state file and ended up with two identical servers
한국어 원문으로 표시합니다.
목표
상태 파일의 구조를 열어 보고, 상태를 잃었을 때의 중복 생성·lineage 안전장치·import 복구·동시 적용 잠금·상태 분할·커밋 규칙을 OpenTofu 로 하나씩 일으켜 확인합니다.
왜 중요한가
상태는 코드와 실제 인프라를 잇는 유일한 기록이라, 사고는 대개 코드가 아니라 상태에서 납니다. 상태가 사라지면 도구는 이미 있는 것을 모르고 또 만들고, 엉뚱한 상태로 덮어쓰면 남의 기록을 지웁니다. 복구는 import 로 하나씩 되돌리는 길뿐이라 버전 관리된 백엔드가 기본이 됩니다. 잠금이 있어야 두 적용이 겹치지 않고, 수명이 다른 층을 한 상태에 두지 않아야 매일 바뀌는 쪽 때문에 거의 안 바뀌는 쪽이 위험해지지 않습니다.
단계
/root/iac-state/main.tf에random_id.server(byte_length 4)와,out/server-<그 hex>.txt에server <hex>한 줄을 쓰는local_file.inventory를 두고 init·apply 하세요. 그다음 상태 파일을/root/iac-state/backup/terraform.tfstate로 복사하고, 그 사본에서 읽은 값으로/root/iac-state/anatomy.txt에lineage=<값>,serial=<값>,resources=<리소스 블록 수>세 줄을 적으세요./root/iac-state/lost/를 만들고terraform.tfstate와terraform.tfstate.backup(있으면)을 그곳으로 옮기세요.tofu plan출력을/root/iac-state/lost-plan.txt에 저장한 뒤 apply 하세요. 채점기는 옮긴 상태와 새 상태가 서로 다른 lineage 이고, out/ 에 서버 파일이 두 개 생겼는지 봅니다.tofu state push lost/terraform.tfstate로 옛 상태를 되돌려 놓으려 해 보세요. 출력(오류 포함)을/root/iac-state/push.txt에 저장합니다.-force는 쓰지 않습니다. 채점기는 지금 상태의 lineage 가 옛 상태와 여전히 다른지도 봅니다./root/iac-state/rebuild/에 같은main.tf를 복사하고,import블록으로 옛 상태(lost/terraform.tfstate)의random_id.server를 가져오세요. import 식별자는 옛 상태의 그 리소스 속성에서 찾습니다. rebuild 에서 init·apply 한 뒤 계획이 깨끗해야 합니다. 채점기는 rebuild 상태의 hex 가 옛 서버의 hex 와 같은지 봅니다./root/iac-state/lockdemo/main.tf에 local-exec 로 15초 자는terraform_data.slow를 두고 init 하세요. apply 를 백그라운드로 시작하고, 끝나기 전에 같은 디렉터리에서tofu plan을 돌려 출력을/root/iac-state/lock.txt에 저장하세요. 이어서tofu plan -lock-timeout=60s를 돌려 잠금이 풀릴 때까지 기다린 뒤의 출력을/root/iac-state/lockwait.txt에 저장합니다. 채점기는 임시 디렉터리에서 같은 상황을 직접 만들어도 확인합니다./root/iac-state/network/에random_id.vpc(byte_length 3)와 출력vpc_id = "vpc-<hex>"를,/root/iac-state/app/에는terraform_remote_state데이터 소스(local 백엔드,../network/terraform.tfstate)로 그 출력을 읽어out/app.conf에vpc=<vpc_id>한 줄을 쓰는local_file.app을 두세요. network 를 먼저, app 을 나중에 init·apply 합니다. 두 디렉터리 모두 계획이 깨끗해야 합니다./root/iac-state/.gitignore를 만드세요.git check-ignore기준으로terraform.tfstate,terraform.tfstate.backup,lost/terraform.tfstate,.terraform/(그 안의 파일),change.tfplan같은 계획 파일은 무시되고,.terraform.lock.hcl과main.tf,app/main.tf는 무시되지 않아야 합니다. 채점기는 임시 git 저장소에 이 .gitignore 를 두고 확인합니다.
참고
- 파드에는 OpenTofu 1.9.0 과 local·random 프로바이더 미러가 있어 인터넷 없이 돕니다. 이 실습의 백엔드는 모두 local 입니다.
- local 백엔드의 잠금은 운영체제 파일 잠금이라, 적용 중인 프로세스가 죽으면 잠금도 풀립니다. 그래서 강제 해제(force-unlock)가 필요한 '남은 잠금' 은 원격 백엔드에서만 생기며 이 파드에서는 재현하지 않습니다.
- 흔한 실수: 2단계에서 terraform.tfstate.backup 을 남겨 두는 것 — 백업이 있다고 도구가 자동으로 되살리지는 않지만, 나중에 어느 쪽이 진짜인지 헷갈리게 합니다.
- 흔한 실수: import 로 되찾을 때 식별자에 hex 를 넣는 것. 리소스마다 받는 식별자가 다르니 프로바이더 문서를 확인합니다.
- State · State Locking · tofu state push · Import · random_id (Import) · terraform_remote_state · Backend: local
상태 파일을 열어 본다
/root/iac-state/main.tf 에 random_id.server(byte_length 4)와, out/server-<그 hex>.txt 에 server <hex> 한 줄을 쓰는 local_file.inventory 를 두고 init·apply 하세요. 그다음 상태 파일을 /root/iac-state/backup/terraform.tfstate 로 복사하고, 그 사본에서 읽은 값으로 /root/iac-state/anatomy.txt 에 lineage=<값>, serial=<값>, resources=<리소스 블록 수> 세 줄을 적으세요.
상태 파일은 JSON 입니다. lineage 는 이 상태가 처음 만들어질 때 붙는 고유 번호이고, serial 은 쓸 때마다 올라갑니다. jq 로 .lineage, .serial, .resources | length 를 보세요.
상태를 잃었더니 같은 서버가 하나 더 생겼다
/root/iac-state/lost/ 를 만들고 terraform.tfstate 와 terraform.tfstate.backup(있으면)을 그곳으로 옮기세요. tofu plan 출력을 /root/iac-state/lost-plan.txt 에 저장한 뒤 apply 하세요. 채점기는 옮긴 상태와 새 상태가 서로 다른 lineage 이고, out/ 에 서버 파일이 두 개 생겼는지 봅니다.
도구는 상태에 없는 리소스를 '아직 만들지 않은 것' 으로 봅니다. 이름이 무작위로 정해지는 리소스라 충돌 없이 조용히 하나가 더 생깁니다. 계획 출력의 Plan 줄을 읽으세요.
옛 상태를 덮어쓰려 했더니 거절됐다
tofu state push lost/terraform.tfstate 로 옛 상태를 되돌려 놓으려 해 보세요. 출력(오류 포함)을 /root/iac-state/push.txt 에 저장합니다. -force 는 쓰지 않습니다. 채점기는 지금 상태의 lineage 가 옛 상태와 여전히 다른지도 봅니다.
lineage 가 다른 두 상태는 '같은 인프라의 다른 시점' 이 아니라 '서로 다른 두 기록' 입니다. 도구가 이것을 막아 주는 이유를 생각해 보세요. -force 는 이 안전장치를 끄는 옵션입니다.
잃은 서버를 import 로 되찾는다
/root/iac-state/rebuild/ 에 같은 main.tf 를 복사하고, import 블록으로 옛 상태(lost/terraform.tfstate)의 random_id.server 를 가져오세요. import 식별자는 옛 상태의 그 리소스 속성에서 찾습니다. rebuild 에서 init·apply 한 뒤 계획이 깨끗해야 합니다. 채점기는 rebuild 상태의 hex 가 옛 서버의 hex 와 같은지 봅니다.
random_id 는 import 를 지원하고, 식별자로 b64_url 값을 받습니다(프로바이더 문서). import 블록의 to 와 id 두 가지만 쓰면 되고, 계획에 'will be imported' 가 보여야 합니다. 새로 만들어지는 random_id 가 있으면 틀린 것입니다.
적용하는 동안 다른 계획은 잠금에 막힌다
/root/iac-state/lockdemo/main.tf 에 local-exec 로 15초 자는 terraform_data.slow 를 두고 init 하세요. apply 를 백그라운드로 시작하고, 끝나기 전에 같은 디렉터리에서 tofu plan 을 돌려 출력을 /root/iac-state/lock.txt 에 저장하세요. 이어서 tofu plan -lock-timeout=60s 를 돌려 잠금이 풀릴 때까지 기다린 뒤의 출력을 /root/iac-state/lockwait.txt 에 저장합니다. 채점기는 임시 디렉터리에서 같은 상황을 직접 만들어도 확인합니다.
잠금은 상태를 쓸 수 있는 모든 명령이 자동으로 잡습니다. 기본으로는 기다리지 않고 바로 실패하며, -lock-timeout 을 주면 그 시간만큼 기다립니다. 백그라운드 작업은 & 로 띄우고 wait 로 끝을 기다립니다.
수명이 다른 층은 상태를 나누고 출력으로만 잇는다
/root/iac-state/network/ 에 random_id.vpc(byte_length 3)와 출력 vpc_id = "vpc-<hex>" 를, /root/iac-state/app/ 에는 terraform_remote_state 데이터 소스(local 백엔드, ../network/terraform.tfstate)로 그 출력을 읽어 out/app.conf 에 vpc=<vpc_id> 한 줄을 쓰는 local_file.app 을 두세요. network 를 먼저, app 을 나중에 init·apply 합니다. 두 디렉터리 모두 계획이 깨끗해야 합니다.
app 상태에는 네트워크 리소스가 들어가지 않고 데이터 소스만 들어갑니다. 그래서 app 을 매일 바꿔도 네트워크는 잠기거나 계획에 끼지 않습니다. 대신 app 은 network 의 출력 이름에 의존하게 됩니다.
상태와 계획은 커밋하지 않고, 잠금 파일은 커밋한다
/root/iac-state/.gitignore 를 만드세요. git check-ignore 기준으로 terraform.tfstate, terraform.tfstate.backup, lost/terraform.tfstate, .terraform/(그 안의 파일), change.tfplan 같은 계획 파일은 무시되고, .terraform.lock.hcl 과 main.tf, app/main.tf 는 무시되지 않아야 합니다. 채점기는 임시 git 저장소에 이 .gitignore 를 두고 확인합니다.
상태와 저장한 계획에는 값이 평문으로 들어갑니다(다음 실습에서 직접 봅니다). 잠금 파일은 프로바이더 버전과 해시를 고정하므로 코드와 함께 리뷰돼야 합니다. *.tfstate.* 같은 패턴이 무엇까지 잡는지 확인하세요.