LabHub
배우기 러닝패스 코스

Infrastructure as Code

Every apply restarted the service

LabHub 에서 이어서 보기

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

목표

선언형 도구 안에 붙인 부작용(재시작·일회성 작업)이 언제 일어나는지를 트리거로 통제하고, 실패한 작업이 남기는 tainted 상태와 교체 순서를 확인한 뒤, 두 번 적용 시험으로 멱등성을 증명합니다.

왜 중요한가

파일을 쓰는 것은 몇 번 해도 결과가 같지만, 서비스 재시작이나 데이터 마이그레이션은 돌 때마다 흔적이 남습니다. 그래서 '무엇이 바뀌었을 때만 돌린다' 는 조건이 멱등성의 핵심이고, 선언형 도구에서는 그 조건이 트리거 값으로 표현됩니다. 트리거에 매번 바뀌는 값을 넣으면 계획이 절대 비지 않아 CI 의 두 번 적용 시험이 깨지고, 실패한 작업은 tainted 로 표시돼 다음 적용에서 다시 돌기 때문에 그 작업 자체가 재시도에 안전해야 합니다. 교체가 정지와 시작의 순서로 일어난다는 것도 무중단 설계의 출발점입니다.

단계

  1. /root/iac-prov/noisy/main.tfport 변수(기본 8080), out/app.confport=<값> 을 쓰는 local_file.conf, 그리고 triggers_replace = timestamp()terraform_data.restart 를 두세요. restart 는 생성될 때 local-exec 로 out/restarts.logrestart 한 줄을 덧붙입니다. init 한 뒤 apply 를 두 번 하세요. 채점기는 이 디렉터리에서 계획을 새로 세워 매번 변경이 나오는지 봅니다.
  2. /root/iac-prov/app/main.tf 를 1단계와 같게 두되 restart 의 트리거를 설정 파일 내용의 해시(local_file.confcontent_sha256)로 바꾸세요. init 한 뒤 apply 를 두 번 하고 계획이 깨끗한지 확인합니다. 채점기는 사본에서 port 를 다른 값으로 준 계획을 세워 restart 가 교체되는지, 같은 값이면 아무것도 바뀌지 않는지 봅니다.
  3. /root/iac-prov/app/out/restarts.log 의 줄 수를 센 뒤 apply 를 두 번 더 하고 다시 세어, /root/iac-prov/app/proof.txtbefore=<수> after=<수> 한 줄로 적으세요. 채점기는 사본에서 apply 를 두 번 돌려 기록이 실제로 늘지 않는지도 확인합니다.
  4. /root/iac-prov/app/terraform.tfvarsport = 8081 을 두고 apply 하세요. out/app.confport=8081 이 되고, restarts.log 가 proof.txt 의 after 보다 정확히 한 줄 늘어야 합니다.
  5. app/main.tfterraform_data.migrate 를 더하세요. 생성될 때 local-exec 로 ${path.module}/ready.flag 가 있는지 검사하고, 있으면 out/migrate.logmigrated 한 줄을 덧붙입니다(없으면 명령이 실패). ready.flag 가 없는 채로 apply 해 출력을 /root/iac-prov/app/taint.txt 에 저장하고, 이어서 tofu plan 출력을 /root/iac-prov/app/taint-plan.txt 에 저장하세요. 채점기는 사본에서 ready.flag 없이 migrate 를 다시 만들어 tainted 가 되는지도 봅니다.
  6. /root/iac-prov/app/ready.flag 를 만들고 apply 하세요. migrate 가 교체되며 이번에는 성공해야 하고, 그 뒤 계획은 깨끗해야 합니다. restart 는 이 과정에서 다시 돌지 않아야 합니다(restarts.log 줄 수가 4단계 뒤와 같음).
  7. app/main.tfworker_version 변수(기본 v1)와 terraform_data.worker 를 더하세요. input 과 triggers_replace 는 둘 다 그 변수이고, 생성 시 out/worker.logstart <버전>, when = destroy 프로비저너로 stop <버전> 을 덧붙입니다. v1 로 적용한 뒤 terraform.tfvars 에 worker_version = "v2" 를 더해 다시 적용하세요. worker.log 가 start v1, stop v1, start v2 순서여야 합니다.
  8. /root/iac-prov/twice.sh <작업디렉터리> 를 만드세요. apply 한 뒤 plan -detailed-exitcode 로 두 번째 계획을 확인해, 비었으면 idempotent 와 0, 변경이 남았으면 not-idempotent 로 시작하는 줄과 2, apply 나 plan 이 실패하면 error 로 시작하는 줄과 1 로 끝냅니다. 채점기는 /root/iac-prov/app/root/iac-prov/noisy 의 사본, 그리고 설정이 깨진 임시 디렉터리로 확인합니다.

참고

적용할 때마다 재시작이 돈다

/root/iac-prov/noisy/main.tfport 변수(기본 8080), out/app.confport=<값> 을 쓰는 local_file.conf, 그리고 triggers_replace = timestamp()terraform_data.restart 를 두세요. restart 는 생성될 때 local-exec 로 out/restarts.logrestart 한 줄을 덧붙입니다. init 한 뒤 apply 를 두 번 하세요. 채점기는 이 디렉터리에서 계획을 새로 세워 매번 변경이 나오는지 봅니다.

timestamp() 는 실행할 때마다 다른 값이라 트리거가 늘 바뀝니다. triggers_replace 가 바뀌면 terraform_data 는 교체되고, 교체는 곧 생성이라 생성 시점 프로비저너가 다시 돕니다.

설정이 바뀔 때만 재시작하도록 트리거를 잡는다

/root/iac-prov/app/main.tf 를 1단계와 같게 두되 restart 의 트리거를 설정 파일 내용의 해시(local_file.confcontent_sha256)로 바꾸세요. init 한 뒤 apply 를 두 번 하고 계획이 깨끗한지 확인합니다. 채점기는 사본에서 port 를 다른 값으로 준 계획을 세워 restart 가 교체되는지, 같은 값이면 아무것도 바뀌지 않는지 봅니다.

local_file 은 content_sha256 같은 계산된 속성을 내보냅니다. 트리거에 쓰는 값이 '무엇이 바뀌면 부작용이 필요한가' 의 정의입니다.

두 번 더 적용해도 재시작 기록이 늘지 않음을 남긴다

/root/iac-prov/app/out/restarts.log 의 줄 수를 센 뒤 apply 를 두 번 더 하고 다시 세어, /root/iac-prov/app/proof.txtbefore=<수> after=<수> 한 줄로 적으세요. 채점기는 사본에서 apply 를 두 번 돌려 기록이 실제로 늘지 않는지도 확인합니다.

변경이 없는 적용에서는 어떤 리소스도 생성되지 않으므로 생성 시점 프로비저너도 돌지 않습니다. 줄 수는 wc -l < 파일 로 숫자만 얻을 수 있습니다.

설정이 정말 바뀌면 딱 한 번 재시작한다

/root/iac-prov/app/terraform.tfvarsport = 8081 을 두고 apply 하세요. out/app.confport=8081 이 되고, restarts.log 가 proof.txt 의 after 보다 정확히 한 줄 늘어야 합니다.

terraform.tfvars 는 자동으로 읽힙니다. 계획에서 local_file.conf 와 terraform_data.restart 가 함께 교체되는 것을 확인하세요.

실패한 프로비저너는 리소스를 tainted 로 남긴다

app/main.tfterraform_data.migrate 를 더하세요. 생성될 때 local-exec 로 ${path.module}/ready.flag 가 있는지 검사하고, 있으면 out/migrate.logmigrated 한 줄을 덧붙입니다(없으면 명령이 실패). ready.flag 가 없는 채로 apply 해 출력을 /root/iac-prov/app/taint.txt 에 저장하고, 이어서 tofu plan 출력을 /root/iac-prov/app/taint-plan.txt 에 저장하세요. 채점기는 사본에서 ready.flag 없이 migrate 를 다시 만들어 tainted 가 되는지도 봅니다.

생성 시점 프로비저너가 실패하면 리소스 자체는 만들어졌어도 '제대로 끝나지 않은 것' 으로 표시되고, 다음 계획은 그것을 교체하려 합니다. tofu state show 나 상태 JSON 의 instances[].status 에서도 보입니다.

원인을 고치고 다시 적용하면 한 번만 교체된다

/root/iac-prov/app/ready.flag 를 만들고 apply 하세요. migrate 가 교체되며 이번에는 성공해야 하고, 그 뒤 계획은 깨끗해야 합니다. restart 는 이 과정에서 다시 돌지 않아야 합니다(restarts.log 줄 수가 4단계 뒤와 같음).

tainted 리소스는 다음 적용에서 교체됩니다. 재시도가 안전하려면 그 작업이 여러 번 돌아도 괜찮아야 합니다 — 이 migrate 가 두 번 돌면 무엇이 남는지 migrate.log 로 확인하세요.

교체는 옛것의 정지 뒤에 새것의 시작이다

app/main.tfworker_version 변수(기본 v1)와 terraform_data.worker 를 더하세요. input 과 triggers_replace 는 둘 다 그 변수이고, 생성 시 out/worker.logstart <버전>, when = destroy 프로비저너로 stop <버전> 을 덧붙입니다. v1 로 적용한 뒤 terraform.tfvars 에 worker_version = "v2" 를 더해 다시 적용하세요. worker.log 가 start v1, stop v1, start v2 순서여야 합니다.

destroy 프로비저너는 자기 자신(self)만 참조할 수 있습니다 — 변수를 직접 쓰면 오류입니다. 기본값인 create_before_destroy=false 에서는 옛 객체를 먼저 지우고 새 객체를 만듭니다.

CI 에 넣을 두 번 적용 시험

/root/iac-prov/twice.sh <작업디렉터리> 를 만드세요. apply 한 뒤 plan -detailed-exitcode 로 두 번째 계획을 확인해, 비었으면 idempotent 와 0, 변경이 남았으면 not-idempotent 로 시작하는 줄과 2, apply 나 plan 이 실패하면 error 로 시작하는 줄과 1 로 끝냅니다. 채점기는 /root/iac-prov/app/root/iac-prov/noisy 의 사본, 그리고 설정이 깨진 임시 디렉터리로 확인합니다.

읽기에서 말한 'CI 에서 플레이북을 두 번 돌리는 팀' 을 선언형 도구로 옮긴 것입니다. set -e 를 쓰면 2 에서 먼저 끝나니 종료 코드를 받아서 판단하세요.