LabHub
学习 学习路径 课程

基础设施即代码

从名单删掉的人,文件只在脚本那边留了下来

在 LabHub 中继续学习

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

목표

같은 명단을 명령형 스크립트와 OpenTofu 에 먹여, 명단이 줄었을 때 두 방식이 무엇을 하는지 비교하고 소유 범위·주소 체계(count 와 for_each)·moved 이동까지 손으로 확인합니다.

왜 중요한가

명령형 스크립트는 '지금 무엇을 할지' 만 알아서, 명단에서 빠진 것을 치우려면 과거에 무엇을 만들었는지를 따로 기억해야 합니다. 선언형 도구는 그 기억을 상태 파일로 가지고 있어 코드에서 사라진 것을 삭제로 계산합니다. 그렇다고 모든 것을 아는 것은 아닙니다 — 도구는 자기가 만든 것만 알고, 같은 디렉터리에 누가 손으로 넣은 파일은 계획에 나타나지 않습니다. 그리고 주소를 위치 번호로 잡으면 목록 앞쪽 한 줄 때문에 뒤의 리소스가 모두 교체되는 사고가 나므로, 키로 주소를 잡고 옮길 때는 moved 로 상태에 알립니다.

단계

  1. /root/iac-conv/users.txtalice, bob, carol 을 한 줄에 하나씩 적으세요. /root/iac-conv/imperative.sh <명단파일> <출력디렉터리> 는 명단의 이름마다 <출력>/<이름>.txtuser <이름> 을 쓰고 <출력>/registry.log 에 이름을 한 줄 덧붙입니다. 이 스크립트를 /root/iac-conv/users.txt/root/iac-conv/imp두 번 실행하세요.
  2. /root/iac-conv/main.tf 에서 같은 users.txt 를 읽어(빈 줄 제외) 이름마다 local_file.user["<이름>"] 를 for_each 로 선언하세요. 파일은 ${path.module}/decl/<이름>.txt, 내용은 user <이름> 한 줄입니다. init 과 apply 를 하세요. 채점기는 상태의 키가 users.txt 의 이름과 정확히 같은지, 계획이 깨끗한지 봅니다.
  3. users.txt 에서 carol 을 지우세요. 명령형 스크립트를 같은 인자로 한 번 더 돌리고, 선언 쪽은 tofu plan -out=/root/iac-conv/remove.tfplan 으로 계획을 저장한 뒤 그 계획을 적용하세요. 채점기는 저장된 계획이 carol 하나의 삭제만 담았는지, decl/carol.txt 는 사라지고 imp/carol.txt 는 남았는지 봅니다.
  4. 누군가 손으로 /root/iac-conv/decl/mallory.txt 를 만들었습니다(직접 만드세요, 내용은 아무것이나). tofu plan -detailed-exitcode 가 여전히 0 인 것을 확인하세요. 그다음 /root/iac-conv/orphans.sh <관리디렉터리> <작업디렉터리> 를 만들어 관리 디렉터리에 있지만 상태가 모르는 파일의 이름을 한 줄에 하나씩 출력하게 하고, decl 에 대한 결과를 /root/iac-conv/orphans.txt 에 저장하세요.
  5. /root/iac-conv/count-demo/main.tfnames 변수(기본값 ["a", "b", "c"])와 count 로 만든 local_file.n 을 두세요. 파일은 out/<인덱스>.txt, 내용은 그 이름 한 줄입니다. 적용한 뒤, 기본값에서 "a" 를 지우고 tofu plan -out=/root/iac-conv/count-demo/shift.tfplan 으로 저장만 합니다(적용하지 않음). /root/iac-conv/count-demo/shift.txt 에 바뀌는 리소스마다 <주소> <update|replace|delete|create> 를 적고, 마지막으로 기본값을 ["a", "b", "c"] 로 되돌려 두세요.
  6. /root/iac-conv/foreach-demo/main.tf 에 같은 names 변수와 for_each = toset(var.names) 로 만든 local_file.n 을 두세요. 파일은 out/<이름>.txt, 내용은 이름 한 줄입니다. 적용한 뒤 기본값에서 "a" 를 지우고 tofu plan -out=/root/iac-conv/foreach-demo/drop.tfplan 으로 저장만 합니다. /root/iac-conv/foreach-demo/drop.txt 에 5단계와 같은 형식으로 적고, 기본값을 다시 ["a", "b", "c"] 로 되돌려 두세요.
  7. /root/iac-conv/count-demolocal_file.n 을 이름을 키로 쓰는 for_each 로 바꾸되 파일 이름(out/0.txt·out/1.txt·out/2.txt)과 내용은 그대로 두고, moved 블록 세 개로 n[0]n["a"], n[1]n["b"], n[2]n["c"] 를 알리세요. 계획에 생성·삭제·교체가 하나도 없는 것을 확인한 뒤 적용합니다. 채점기는 상태 주소가 이름 키로 바뀌었고 계획이 깨끗한지 봅니다.
  8. /root/iac-conv/converge.sh <작업디렉터리> <관리디렉터리> 를 만드세요. apply 한 뒤 두 번째 plan -detailed-exitcode 가 0 이 아니면 not-converged 로 시작하는 줄과 2, orphans.sh 로 찾은 낯선 파일이 있으면 stray 로 시작하는 줄(파일 이름 포함)과 3, 둘 다 괜찮으면 converged 와 0, apply·plan 이 실패하면 error 로 시작하는 줄과 1 로 끝냅니다. 채점기는 /root/iac-conv 의 사본(mallory.txt 가 있으므로 3)과 스스로 만든 임시 디렉터리로 확인합니다. 원본에서는 돌리지 않아도 됩니다.

참고

명령형 스크립트를 두 번 돌린다

/root/iac-conv/users.txtalice, bob, carol 을 한 줄에 하나씩 적으세요. /root/iac-conv/imperative.sh <명단파일> <출력디렉터리> 는 명단의 이름마다 <출력>/<이름>.txtuser <이름> 을 쓰고 <출력>/registry.log 에 이름을 한 줄 덧붙입니다. 이 스크립트를 /root/iac-conv/users.txt/root/iac-conv/imp두 번 실행하세요.

> 는 덮어쓰고 >> 는 덧붙입니다. 파일 내용은 두 번 돌려도 같지만 registry.log 는 돌린 횟수만큼 늘어납니다. 이 차이가 멱등하지 않은 명령형 스크립트의 모습입니다.

같은 명단을 선언으로 적는다

/root/iac-conv/main.tf 에서 같은 users.txt 를 읽어(빈 줄 제외) 이름마다 local_file.user["<이름>"] 를 for_each 로 선언하세요. 파일은 ${path.module}/decl/<이름>.txt, 내용은 user <이름> 한 줄입니다. init 과 apply 를 하세요. 채점기는 상태의 키가 users.txt 의 이름과 정확히 같은지, 계획이 깨끗한지 봅니다.

file() 로 읽고 split·compact·toset 으로 집합을 만듭니다. for_each 는 집합이나 맵만 받습니다. 리소스 주소의 대괄호 안이 인덱스 숫자가 아니라 이름이라는 점을 tofu state list 로 확인하세요.

명단에서 한 줄 지우기 — 한쪽만 치운다

users.txt 에서 carol 을 지우세요. 명령형 스크립트를 같은 인자로 한 번 더 돌리고, 선언 쪽은 tofu plan -out=/root/iac-conv/remove.tfplan 으로 계획을 저장한 뒤 그 계획을 적용하세요. 채점기는 저장된 계획이 carol 하나의 삭제만 담았는지, decl/carol.txt 는 사라지고 imp/carol.txt 는 남았는지 봅니다.

선언형 도구는 상태에 기록된 것과 코드의 차이를 계산하므로, 코드에서 사라진 키는 삭제로 나타납니다. 명령형 스크립트는 지금 명단만 보고 과거에 만든 것을 모릅니다.

소유 범위 밖의 파일은 선언형 도구도 모른다

누군가 손으로 /root/iac-conv/decl/mallory.txt 를 만들었습니다(직접 만드세요, 내용은 아무것이나). tofu plan -detailed-exitcode 가 여전히 0 인 것을 확인하세요. 그다음 /root/iac-conv/orphans.sh <관리디렉터리> <작업디렉터리> 를 만들어 관리 디렉터리에 있지만 상태가 모르는 파일의 이름을 한 줄에 하나씩 출력하게 하고, decl 에 대한 결과를 /root/iac-conv/orphans.txt 에 저장하세요.

tofu show -json 의 values 에 상태가 아는 리소스와 속성이 있습니다. local_file 의 filename 과 디렉터리의 파일 목록을 비교하세요. 선언형 도구가 수렴시키는 범위는 선언한 리소스뿐입니다.

count 목록의 맨 앞을 지웠더니 뒤가 전부 바뀐다

/root/iac-conv/count-demo/main.tfnames 변수(기본값 ["a", "b", "c"])와 count 로 만든 local_file.n 을 두세요. 파일은 out/<인덱스>.txt, 내용은 그 이름 한 줄입니다. 적용한 뒤, 기본값에서 "a" 를 지우고 tofu plan -out=/root/iac-conv/count-demo/shift.tfplan 으로 저장만 합니다(적용하지 않음). /root/iac-conv/count-demo/shift.txt 에 바뀌는 리소스마다 <주소> <update|replace|delete|create> 를 적고, 마지막으로 기본값을 ["a", "b", "c"] 로 되돌려 두세요.

count 의 주소는 위치 번호입니다. 맨 앞을 빼면 뒤의 원소가 한 칸씩 당겨져 같은 번호에 다른 값이 들어갑니다. local_file 은 내용이 바뀌면 교체입니다.

for_each 에서는 빠진 것만 빠진다

/root/iac-conv/foreach-demo/main.tf 에 같은 names 변수와 for_each = toset(var.names) 로 만든 local_file.n 을 두세요. 파일은 out/<이름>.txt, 내용은 이름 한 줄입니다. 적용한 뒤 기본값에서 "a" 를 지우고 tofu plan -out=/root/iac-conv/foreach-demo/drop.tfplan 으로 저장만 합니다. /root/iac-conv/foreach-demo/drop.txt 에 5단계와 같은 형식으로 적고, 기본값을 다시 ["a", "b", "c"] 로 되돌려 두세요.

for_each 의 주소는 키입니다. 키 "b" 와 "c" 는 목록의 위치와 무관하게 그대로라서 계획에 나타나지 않습니다.

count 를 for_each 로, 아무것도 지우지 않고 옮긴다

/root/iac-conv/count-demolocal_file.n 을 이름을 키로 쓰는 for_each 로 바꾸되 파일 이름(out/0.txt·out/1.txt·out/2.txt)과 내용은 그대로 두고, moved 블록 세 개로 n[0]n["a"], n[1]n["b"], n[2]n["c"] 를 알리세요. 계획에 생성·삭제·교체가 하나도 없는 것을 확인한 뒤 적용합니다. 채점기는 상태 주소가 이름 키로 바뀌었고 계획이 깨끗한지 봅니다.

moved 블록은 상태의 주소만 바꾸라는 약속입니다. 새 주소의 설정이 옛 객체와 속성까지 같아야 이동만 일어납니다 — 파일 이름이 달라지면 이동 뒤에 교체가 따라옵니다. 이름에서 위치 번호를 얻으려면 for 식으로 맵을 만듭니다.

적용하고, 두 번째 계획과 낯선 파일까지 확인하는 수렴 점검

/root/iac-conv/converge.sh <작업디렉터리> <관리디렉터리> 를 만드세요. apply 한 뒤 두 번째 plan -detailed-exitcode 가 0 이 아니면 not-converged 로 시작하는 줄과 2, orphans.sh 로 찾은 낯선 파일이 있으면 stray 로 시작하는 줄(파일 이름 포함)과 3, 둘 다 괜찮으면 converged 와 0, apply·plan 이 실패하면 error 로 시작하는 줄과 1 로 끝냅니다. 채점기는 /root/iac-conv 의 사본(mallory.txt 가 있으므로 3)과 스스로 만든 임시 디렉터리로 확인합니다. 원본에서는 돌리지 않아도 됩니다.

멱등성(두 번째 계획이 비었다)과 수렴(내 영역에 내가 모르는 것이 없다)은 다른 질문입니다. 앞 단계의 orphans.sh 를 그대로 부르세요. 두 번째 계획이 비지 않는 설정은 timestamp() 같은 매번 바뀌는 값에서 나옵니다.