LabHub
学习 学习路径 课程

基础设施即代码

每次 apply 服务都会重启

在 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 에서 먼저 끝나니 종료 코드를 받아서 판단하세요.