LabHub
배우기 러닝패스 코스

Terraform/OpenTofu Fundamentals

A Read-Only Block Ended Up in the State File

LabHub 에서 이어서 보기

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

목표

data 블록이 resource 와 무엇이 다른지를 상태·계획·파괴 세 자리에서 직접 확인합니다. 읽기가 언제 일어나는지, 무엇을 소유하지 않는지, 원본이 없거나 바뀌면 어떻게 되는지를 OpenTofu 로 하나씩 일으켜 봅니다.

왜 중요한가

설정 파일에서 data 블록은 resource 만큼 자주 나오는데, 둘을 같은 것으로 읽으면 사고가 조용히 납니다. data 는 대상을 만들지도 지우지도 않습니다 — destroy 를 돌려도 원본은 그대로 남고, 그래서 남의 팀이 만든 것을 읽어 쓰는 설정이 안전해집니다. 대신 data 는 상태에 저장된 값을 믿지 않고 계획마다 다시 읽습니다. 원본이 바뀌면 그것은 드리프트가 아니라 입력이 바뀐 것이고, 하류가 통째로 교체될 수 있습니다. 읽을 대상의 이름이 아직 정해지지 않았거나 depends_on 이 걸려 있으면 읽기가 적용 시점으로 밀리는데, 이때 계획에는 값이 통째로 '알 수 없음' 으로 나와 리뷰가 어려워집니다. 이 네 가지를 손으로 겪어 두면 남의 설정을 읽을 때 data 블록에서 눈이 멈추게 됩니다.

단계

  1. /root/tfb-data/base/seed.txtalpha 한 줄을 직접 만들고, 같은 디렉터리의 main.tf 에 그 파일을 읽는 data "local_file" "seed" 와 그 내용을 copy.txt 에 쓰는 resource "local_file" "copy" 를 두세요. init·apply 하고 계획이 깨끗해야 합니다.
  2. /root/tfb-data/base/terraform.tfstate 를 열어 mode 가 data 인 항목과 managed 인 항목의 주소를 각각 찾고, /root/tfb-data/shape.txtdata=<그 주소>managed=<그 주소> 두 줄을 적으세요. 주소는 data.local_file.seed 처럼 mode 접두어까지 포함한 형태입니다.
  3. /root/tfb-data/own/ 에 같은 모양의 설정을 두되 seed.txtowned 한 줄로 만드세요. init·apply 로 copy.txt 를 만든 뒤 tofu destroy -auto-approve 를 돌리고 그 출력을 /root/tfb-data/own-destroy.txt 에 저장하세요. 다시 apply 하지 않습니다.
  4. /root/tfb-data/deferred/main.tfrandom_pet.name(length 2), 그 이름이 들어간 made-<이름>.txt 를 쓰는 local_file.made, 그 파일을 읽는 data.local_file.back, 읽은 값을 echo.txt 에 쓰는 local_file.echo 를 두세요. init 한 뒤 plan 출력을 /root/tfb-data/deferred-plan.txt 에 저장하고 apply 하세요.
  5. /root/tfb-data/gate/ready.txtready 한 줄로 먼저 만들어 두세요. 같은 디렉터리 main.tfnull_resource.prepare(triggers v = "1"), depends_on = [null_resource.prepare] 를 붙인 data.local_file.ready, 읽은 값을 mirror.txt 에 쓰는 local_file.mirror 를 두고 init 하세요. plan 출력을 /root/tfb-data/gate-plan.txt 에 저장한 뒤 apply 하세요.
  6. /root/tfb-data/missing/main.tf 에 없는 파일 absent.txt 를 읽는 data.local_file.absent 와 그 내용을 내보내는 출력 absent 를 두고 init 하세요. plan 을 돌려 오류 출력을 /root/tfb-data/missing-plan.txt 에 저장하세요. 명령이 실패해도 괜찮습니다. absent.txt 는 만들지 마세요.
  7. /root/tfb-data/tfdata/main.tfterraform_data.note(input "v1")와 그 output 을 내보내는 출력 note 를 두고 init·apply 하세요. 그다음 input 을 "v2" 로 바꾸고 plan 출력을 /root/tfb-data/tfdata-plan.txt 에 저장한 뒤 apply 하세요. 마지막으로 /root/tfb-data/mode-compare.txtterraform_data=<tfdata 상태에서 읽은 mode>local_file_seed=<base 상태에서 읽은 mode> 두 줄을 적으세요.
  8. /root/tfb-data/base/seed.txtbeta 한 줄로 바꾸고 /root/tfb-data/base 에서 plan 을 돌려 출력을 /root/tfb-data/data-change.txt 에 저장한 뒤 apply 하세요. 계획이 왜 교체(replace)로 나오는지 확인하고, 마지막에 계획이 다시 깨끗해져야 합니다.

참고

이미 있는 파일을 data 로 읽는다

/root/tfb-data/base/seed.txtalpha 한 줄을 직접 만들고, 같은 디렉터리의 main.tf 에 그 파일을 읽는 data "local_file" "seed" 와 그 내용을 copy.txt 에 쓰는 resource "local_file" "copy" 를 두세요. init·apply 하고 계획이 깨끗해야 합니다.

data 블록은 만들지 않고 읽기만 합니다. 읽은 값은 data.<타입>.<이름>.<속성> 으로 참조합니다. local 프로바이더의 file 데이터 소스는 content 속성에 파일 내용을 담아 줍니다.

상태에서 읽는 것과 만드는 것을 가른다

/root/tfb-data/base/terraform.tfstate 를 열어 mode 가 data 인 항목과 managed 인 항목의 주소를 각각 찾고, /root/tfb-data/shape.txtdata=<그 주소>managed=<그 주소> 두 줄을 적으세요. 주소는 data.local_file.seed 처럼 mode 접두어까지 포함한 형태입니다.

상태 JSON 의 각 항목에는 mode·type·name 이 있습니다. mode 가 data 면 주소 앞에 data. 가 붙고, managed 면 붙지 않습니다. tofu state list 출력과 견줘 보세요.

destroy 해도 읽기만 한 원본은 남는다

/root/tfb-data/own/ 에 같은 모양의 설정을 두되 seed.txtowned 한 줄로 만드세요. init·apply 로 copy.txt 를 만든 뒤 tofu destroy -auto-approve 를 돌리고 그 출력을 /root/tfb-data/own-destroy.txt 에 저장하세요. 다시 apply 하지 않습니다.

destroy 는 상태가 소유한 것만 지웁니다. data 로 읽기만 한 파일은 상태에 기록은 남아도 소유물이 아닙니다. destroy 직전 상태는 terraform.tfstate.backup 으로 남습니다.

읽을 대상이 아직 없으면 읽기가 apply 로 밀린다

/root/tfb-data/deferred/main.tfrandom_pet.name(length 2), 그 이름이 들어간 made-<이름>.txt 를 쓰는 local_file.made, 그 파일을 읽는 data.local_file.back, 읽은 값을 echo.txt 에 쓰는 local_file.echo 를 두세요. init 한 뒤 plan 출력을 /root/tfb-data/deferred-plan.txt 에 저장하고 apply 하세요.

데이터 소스의 인자가 plan 시점에 알 수 없는 값이면 읽기를 미룹니다. 계획 출력에서 그 데이터 소스 줄이 어떻게 적히는지 그대로 읽어 보세요.

depends_on 을 붙이면 있는 파일도 plan 때 못 읽는다

/root/tfb-data/gate/ready.txtready 한 줄로 먼저 만들어 두세요. 같은 디렉터리 main.tfnull_resource.prepare(triggers v = "1"), depends_on = [null_resource.prepare] 를 붙인 data.local_file.ready, 읽은 값을 mirror.txt 에 쓰는 local_file.mirror 를 두고 init 하세요. plan 출력을 /root/tfb-data/gate-plan.txt 에 저장한 뒤 apply 하세요.

원본이 이미 있어도 depends_on 이 있으면 도구는 그 의존이 끝나기 전에는 읽지 않습니다. 1단계의 계획 출력과 견줘 보면 차이가 한 줄로 드러납니다.

원본이 없으면 계획 자체가 실패한다

/root/tfb-data/missing/main.tf 에 없는 파일 absent.txt 를 읽는 data.local_file.absent 와 그 내용을 내보내는 출력 absent 를 두고 init 하세요. plan 을 돌려 오류 출력을 /root/tfb-data/missing-plan.txt 에 저장하세요. 명령이 실패해도 괜찮습니다. absent.txt 는 만들지 마세요.

데이터 소스의 읽기는 계획 단계에서 일어납니다. 그래서 읽을 수 없으면 적용까지 가지도 못하고 계획이 멈춥니다. 오류 메시지의 제목과 원인 줄을 그대로 저장하세요.

terraform_data 는 data 가 아니라 managed 다

/root/tfb-data/tfdata/main.tfterraform_data.note(input "v1")와 그 output 을 내보내는 출력 note 를 두고 init·apply 하세요. 그다음 input 을 "v2" 로 바꾸고 plan 출력을 /root/tfb-data/tfdata-plan.txt 에 저장한 뒤 apply 하세요. 마지막으로 /root/tfb-data/mode-compare.txtterraform_data=<tfdata 상태에서 읽은 mode>local_file_seed=<base 상태에서 읽은 mode> 두 줄을 적으세요.

이름만 보면 데이터 소스 같지만 상태의 mode 를 보면 답이 나옵니다. 계획 출력의 동작 낱말도 데이터 소스와 다릅니다 — 데이터 소스는 read, 이쪽은 생명주기가 있는 리소스입니다.

원본이 바뀌면 드리프트가 아니라 입력이 바뀐 것이다

/root/tfb-data/base/seed.txtbeta 한 줄로 바꾸고 /root/tfb-data/base 에서 plan 을 돌려 출력을 /root/tfb-data/data-change.txt 에 저장한 뒤 apply 하세요. 계획이 왜 교체(replace)로 나오는지 확인하고, 마지막에 계획이 다시 깨끗해져야 합니다.

데이터 소스는 상태에 저장된 값을 믿지 않고 계획마다 다시 읽습니다. 그래서 원본이 바뀌면 상태가 어긋난 것이 아니라 하류 리소스의 입력이 바뀐 것으로 잡힙니다. local_file 의 content 는 제자리 수정이 안 되는 속성입니다.