状態ファイルを移したら同じサーバーが二台になった
한국어 원문으로 표시합니다.
목표
상태 파일의 구조를 열어 보고, 상태를 잃었을 때의 중복 생성·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.* 같은 패턴이 무엇까지 잡는지 확인하세요.