LabHub
배우기 러닝패스 코스

Infrastructure as Code

I moved the state file and ended up with two identical servers

LabHub 에서 이어서 보기

한국어 원문으로 표시합니다.

목표

상태 파일의 구조를 열어 보고, 상태를 잃었을 때의 중복 생성·lineage 안전장치·import 복구·동시 적용 잠금·상태 분할·커밋 규칙을 OpenTofu 로 하나씩 일으켜 확인합니다.

왜 중요한가

상태는 코드와 실제 인프라를 잇는 유일한 기록이라, 사고는 대개 코드가 아니라 상태에서 납니다. 상태가 사라지면 도구는 이미 있는 것을 모르고 또 만들고, 엉뚱한 상태로 덮어쓰면 남의 기록을 지웁니다. 복구는 import 로 하나씩 되돌리는 길뿐이라 버전 관리된 백엔드가 기본이 됩니다. 잠금이 있어야 두 적용이 겹치지 않고, 수명이 다른 층을 한 상태에 두지 않아야 매일 바뀌는 쪽 때문에 거의 안 바뀌는 쪽이 위험해지지 않습니다.

단계

  1. /root/iac-state/main.tfrandom_id.server(byte_length 4)와, out/server-<그 hex>.txtserver <hex> 한 줄을 쓰는 local_file.inventory 를 두고 init·apply 하세요. 그다음 상태 파일을 /root/iac-state/backup/terraform.tfstate 로 복사하고, 그 사본에서 읽은 값으로 /root/iac-state/anatomy.txtlineage=<값>, serial=<값>, resources=<리소스 블록 수> 세 줄을 적으세요.
  2. /root/iac-state/lost/ 를 만들고 terraform.tfstateterraform.tfstate.backup(있으면)을 그곳으로 옮기세요. tofu plan 출력을 /root/iac-state/lost-plan.txt 에 저장한 뒤 apply 하세요. 채점기는 옮긴 상태와 새 상태가 서로 다른 lineage 이고, out/ 에 서버 파일이 두 개 생겼는지 봅니다.
  3. tofu state push lost/terraform.tfstate 로 옛 상태를 되돌려 놓으려 해 보세요. 출력(오류 포함)을 /root/iac-state/push.txt 에 저장합니다. -force 는 쓰지 않습니다. 채점기는 지금 상태의 lineage 가 옛 상태와 여전히 다른지도 봅니다.
  4. /root/iac-state/rebuild/ 에 같은 main.tf 를 복사하고, import 블록으로 옛 상태(lost/terraform.tfstate)의 random_id.server 를 가져오세요. import 식별자는 옛 상태의 그 리소스 속성에서 찾습니다. rebuild 에서 init·apply 한 뒤 계획이 깨끗해야 합니다. 채점기는 rebuild 상태의 hex 가 옛 서버의 hex 와 같은지 봅니다.
  5. /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 에 저장합니다. 채점기는 임시 디렉터리에서 같은 상황을 직접 만들어도 확인합니다.
  6. /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.confvpc=<vpc_id> 한 줄을 쓰는 local_file.app 을 두세요. network 를 먼저, app 을 나중에 init·apply 합니다. 두 디렉터리 모두 계획이 깨끗해야 합니다.
  7. /root/iac-state/.gitignore 를 만드세요. git check-ignore 기준으로 terraform.tfstate, terraform.tfstate.backup, lost/terraform.tfstate, .terraform/(그 안의 파일), change.tfplan 같은 계획 파일은 무시되고, .terraform.lock.hclmain.tf, app/main.tf 는 무시되지 않아야 합니다. 채점기는 임시 git 저장소에 이 .gitignore 를 두고 확인합니다.

참고

상태 파일을 열어 본다

/root/iac-state/main.tfrandom_id.server(byte_length 4)와, out/server-<그 hex>.txtserver <hex> 한 줄을 쓰는 local_file.inventory 를 두고 init·apply 하세요. 그다음 상태 파일을 /root/iac-state/backup/terraform.tfstate 로 복사하고, 그 사본에서 읽은 값으로 /root/iac-state/anatomy.txtlineage=<값>, serial=<값>, resources=<리소스 블록 수> 세 줄을 적으세요.

상태 파일은 JSON 입니다. lineage 는 이 상태가 처음 만들어질 때 붙는 고유 번호이고, serial 은 쓸 때마다 올라갑니다. jq 로 .lineage, .serial, .resources | length 를 보세요.

상태를 잃었더니 같은 서버가 하나 더 생겼다

/root/iac-state/lost/ 를 만들고 terraform.tfstateterraform.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.confvpc=<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.hclmain.tf, app/main.tf 는 무시되지 않아야 합니다. 채점기는 임시 git 저장소에 이 .gitignore 를 두고 확인합니다.

상태와 저장한 계획에는 값이 평문으로 들어갑니다(다음 실습에서 직접 봅니다). 잠금 파일은 프로바이더 버전과 해시를 고정하므로 코드와 함께 리뷰돼야 합니다. *.tfstate.* 같은 패턴이 무엇까지 잡는지 확인하세요.