每次 apply 服务都会重启
한국어 원문으로 표시합니다.
목표
선언형 도구 안에 붙인 부작용(재시작·일회성 작업)이 언제 일어나는지를 트리거로 통제하고, 실패한 작업이 남기는 tainted 상태와 교체 순서를 확인한 뒤, 두 번 적용 시험으로 멱등성을 증명합니다.
왜 중요한가
파일을 쓰는 것은 몇 번 해도 결과가 같지만, 서비스 재시작이나 데이터 마이그레이션은 돌 때마다 흔적이 남습니다. 그래서 '무엇이 바뀌었을 때만 돌린다' 는 조건이 멱등성의 핵심이고, 선언형 도구에서는 그 조건이 트리거 값으로 표현됩니다. 트리거에 매번 바뀌는 값을 넣으면 계획이 절대 비지 않아 CI 의 두 번 적용 시험이 깨지고, 실패한 작업은 tainted 로 표시돼 다음 적용에서 다시 돌기 때문에 그 작업 자체가 재시도에 안전해야 합니다. 교체가 정지와 시작의 순서로 일어난다는 것도 무중단 설계의 출발점입니다.
단계
/root/iac-prov/noisy/main.tf에port변수(기본 8080),out/app.conf에port=<값>을 쓰는local_file.conf, 그리고triggers_replace = timestamp()인terraform_data.restart를 두세요. restart 는 생성될 때 local-exec 로out/restarts.log에restart한 줄을 덧붙입니다. init 한 뒤 apply 를 두 번 하세요. 채점기는 이 디렉터리에서 계획을 새로 세워 매번 변경이 나오는지 봅니다./root/iac-prov/app/main.tf를 1단계와 같게 두되 restart 의 트리거를 설정 파일 내용의 해시(local_file.conf의content_sha256)로 바꾸세요. init 한 뒤 apply 를 두 번 하고 계획이 깨끗한지 확인합니다. 채점기는 사본에서 port 를 다른 값으로 준 계획을 세워 restart 가 교체되는지, 같은 값이면 아무것도 바뀌지 않는지 봅니다./root/iac-prov/app/out/restarts.log의 줄 수를 센 뒤 apply 를 두 번 더 하고 다시 세어,/root/iac-prov/app/proof.txt에before=<수> after=<수>한 줄로 적으세요. 채점기는 사본에서 apply 를 두 번 돌려 기록이 실제로 늘지 않는지도 확인합니다./root/iac-prov/app/terraform.tfvars에port = 8081을 두고 apply 하세요.out/app.conf가port=8081이 되고, restarts.log 가 proof.txt 의 after 보다 정확히 한 줄 늘어야 합니다.app/main.tf에terraform_data.migrate를 더하세요. 생성될 때 local-exec 로${path.module}/ready.flag가 있는지 검사하고, 있으면out/migrate.log에migrated한 줄을 덧붙입니다(없으면 명령이 실패). ready.flag 가 없는 채로 apply 해 출력을/root/iac-prov/app/taint.txt에 저장하고, 이어서tofu plan출력을/root/iac-prov/app/taint-plan.txt에 저장하세요. 채점기는 사본에서 ready.flag 없이 migrate 를 다시 만들어 tainted 가 되는지도 봅니다./root/iac-prov/app/ready.flag를 만들고 apply 하세요. migrate 가 교체되며 이번에는 성공해야 하고, 그 뒤 계획은 깨끗해야 합니다. restart 는 이 과정에서 다시 돌지 않아야 합니다(restarts.log 줄 수가 4단계 뒤와 같음).app/main.tf에worker_version변수(기본v1)와terraform_data.worker를 더하세요. input 과 triggers_replace 는 둘 다 그 변수이고, 생성 시out/worker.log에start <버전>,when = destroy프로비저너로stop <버전>을 덧붙입니다. v1 로 적용한 뒤 terraform.tfvars 에worker_version = "v2"를 더해 다시 적용하세요. worker.log 가start v1,stop v1,start v2순서여야 합니다./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의 사본, 그리고 설정이 깨진 임시 디렉터리로 확인합니다.
참고
- 파드에는 OpenTofu 1.9.0 과 local 프로바이더 미러가 있어 인터넷 없이 돕니다. terraform_data 는 내장 리소스라 프로바이더가 필요 없습니다.
- 프로비저너는 생성 시점(기본)과
when = destroy두 가지이고, destroy 프로비저너는self만 참조할 수 있습니다. - 흔한 실수: 상태를 지우거나 사본을 원본과 섞어 restarts.log 줄 수 비교가 어긋나는 것. 채점용 사본은 채점기가 따로 만듭니다.
- 공식 문서도 프로비저너를 최후의 수단으로 권합니다. 여기서는 부작용이 언제 도는지를 눈으로 보기 위해 씁니다.
- Provisioners · lifecycle · tofu plan · Resource Behavior
적용할 때마다 재시작이 돈다
/root/iac-prov/noisy/main.tf 에 port 변수(기본 8080), out/app.conf 에 port=<값> 을 쓰는 local_file.conf, 그리고 triggers_replace = timestamp() 인 terraform_data.restart 를 두세요. restart 는 생성될 때 local-exec 로 out/restarts.log 에 restart 한 줄을 덧붙입니다. init 한 뒤 apply 를 두 번 하세요. 채점기는 이 디렉터리에서 계획을 새로 세워 매번 변경이 나오는지 봅니다.
timestamp() 는 실행할 때마다 다른 값이라 트리거가 늘 바뀝니다. triggers_replace 가 바뀌면 terraform_data 는 교체되고, 교체는 곧 생성이라 생성 시점 프로비저너가 다시 돕니다.
설정이 바뀔 때만 재시작하도록 트리거를 잡는다
/root/iac-prov/app/main.tf 를 1단계와 같게 두되 restart 의 트리거를 설정 파일 내용의 해시(local_file.conf 의 content_sha256)로 바꾸세요. init 한 뒤 apply 를 두 번 하고 계획이 깨끗한지 확인합니다. 채점기는 사본에서 port 를 다른 값으로 준 계획을 세워 restart 가 교체되는지, 같은 값이면 아무것도 바뀌지 않는지 봅니다.
local_file 은 content_sha256 같은 계산된 속성을 내보냅니다. 트리거에 쓰는 값이 '무엇이 바뀌면 부작용이 필요한가' 의 정의입니다.
두 번 더 적용해도 재시작 기록이 늘지 않음을 남긴다
/root/iac-prov/app/out/restarts.log 의 줄 수를 센 뒤 apply 를 두 번 더 하고 다시 세어, /root/iac-prov/app/proof.txt 에 before=<수> after=<수> 한 줄로 적으세요. 채점기는 사본에서 apply 를 두 번 돌려 기록이 실제로 늘지 않는지도 확인합니다.
변경이 없는 적용에서는 어떤 리소스도 생성되지 않으므로 생성 시점 프로비저너도 돌지 않습니다. 줄 수는 wc -l < 파일 로 숫자만 얻을 수 있습니다.
설정이 정말 바뀌면 딱 한 번 재시작한다
/root/iac-prov/app/terraform.tfvars 에 port = 8081 을 두고 apply 하세요. out/app.conf 가 port=8081 이 되고, restarts.log 가 proof.txt 의 after 보다 정확히 한 줄 늘어야 합니다.
terraform.tfvars 는 자동으로 읽힙니다. 계획에서 local_file.conf 와 terraform_data.restart 가 함께 교체되는 것을 확인하세요.
실패한 프로비저너는 리소스를 tainted 로 남긴다
app/main.tf 에 terraform_data.migrate 를 더하세요. 생성될 때 local-exec 로 ${path.module}/ready.flag 가 있는지 검사하고, 있으면 out/migrate.log 에 migrated 한 줄을 덧붙입니다(없으면 명령이 실패). 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.tf 에 worker_version 변수(기본 v1)와 terraform_data.worker 를 더하세요. input 과 triggers_replace 는 둘 다 그 변수이고, 생성 시 out/worker.log 에 start <버전>, 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 에서 먼저 끝나니 종료 코드를 받아서 판단하세요.