An Idempotent Resource-Assurance Script
한국어 원문으로 표시합니다.
목표
디렉터리·파일·줄·권한을 보장하는 멱등 스크립트를 만들고, 두 번 연속 적용해 changed 가 0 이 되는 것으로 멱등성을 증명한다. 상태 파일과 드리프트 감지, 잠금까지 붙인다.
왜 중요한가
멱등성은 같은 작업을 여러 번 실행해도 결과가 동일하다는 성질이고, 자동화를 재실행해도 안전하다는 보증이다. 증명 방법은 두 번 적용해 두 번째 plan 이 변경 없음인지 보는 것이다. 멱등성이 깨지는 자리는 거의 정해져 있다. 전용 모듈 대신 shell 이나 command 로 명령을 직접 실행하는 곳, 그리고 >> 로 줄을 덧붙이는 곳이다. 그래서 이 실습의 스크립트들은 모두 "바꾸기 전에 확인한다"는 같은 골격을 갖는다. 마지막에 붙이는 상태 파일과 잠금은 실제 IaC 도구가 왜 그런 부속을 달고 있는지 몸으로 이해하게 해 준다.
단계
/root/iac2/ensure_dir.sh <경로>를 만든다. 디렉터리가 없으면 만들고changed, 이미 있으면unchanged를 출력한다. 두 경우 모두 종료코드 0 이다. 출력은 그 낱말만 담은 한 줄이어야 한다(채점은unchanged를 줄 전체와 정확히 비교한다)./root/iac2/ensure_file.sh <경로> <내용>을 만든다. 파일이 없거나 내용이 다르면 내용을 써 넣고changed, 이미 같으면unchanged를 출력한다./root/iac2/ensure_line.sh <파일> "<줄>"을 만든다. 그 줄이 이미 정확히 있으면unchanged, 같은 키(줄의 첫 낱말)를 가진 다른 줄이 있으면 그 자리를 새 줄로 교체하고changed, 아무것도 없으면 끝에 추가하고changed를 출력한다. 몇 번을 실행해도 해당 줄은 파일에 정확히 1개여야 하고 나머지 줄은 그대로 남아야 한다./root/iac2/ensure_mode.sh <파일> <8진수모드>를 만든다.stat -c %a로 현재 권한을 읽어 다르면chmod후changed, 같으면unchanged를 출력한다. 파일이 존재하지 않으면 0 이 아닌 종료코드를 낸다./root/iac2/run.sh를 만든다. 위 네 스크립트를 이용해 리소스를 정확히 4개 보장한다(예: 디렉터리 1 + 파일 2 + 권한 1). 마지막 줄에 요약을unchanged=<수> changed=<수>순서로 출력한다. 첫 실행의 출력을/root/iac2/run1.txt에, 이어서 두 번째 실행의 출력을/root/iac2/run2.txt에 저장한다. 첫 실행의 changed 는 1 이상, 두 번째 실행의 요약은unchanged=4 changed=0이어야 한다./root/iac2/state.json을 만든다.resources배열에 방금 보장한 리소스를 기록하고(2개 이상), 각 원소에path,type(file또는dir),sha256을 넣는다.type이dir이 아닌 항목은 실제 파일이 존재해야 하고sha256값이sha256sum <경로>의 첫 필드와 정확히 같아야 한다. 최상위에applied_at(적용 시각) 도 넣는다./root/iac2/drift.sh <상태파일>을 만든다. 인자로 받은 상태 파일을 읽어 기록된 리소스를 검증한다. 전부 일치하면 종료코드 0, 내용이 바뀌었거나 파일이 사라졌으면 어긋난 경로를 출력하고 0 이 아닌 코드를 낸다.type이dir인 항목은 존재 여부만 확인한다./root/iac2/apply-lock.sh를 만든다. 잠금 파일 경로는LOCK_FILE환경변수로 받고 없으면/root/iac2/.lock을 쓴다. 잠금이 없으면 조건부로 만들어 잡고, 할 일을 한 뒤 정상 종료할 때 반드시 지운다(종료코드 0, 잠금 파일이 남으면 안 된다). 이미 잠겨 있으면 기다리지 말고 종료코드 2 로 즉시 실패하며lock이라는 낱말이 들어간 메시지를 출력한다. 이때 남의 잠금 파일을 지우면 안 된다.
참고
- 5번의 요약은 반드시
unchanged=를 먼저,changed=를 뒤에 쓴다. 채점은grep -oE 'changed=[0-9]+'의 마지막 매치를 총계로 보는데,unchanged=4안에도changed=4라는 문자열이 들어 있기 때문이다. - 잠금의 조건부 생성은
set -o noclobber뒤: > "$LOCK_FILE"처럼 한다.[ -e ]로 확인한 뒤 만드는 방식은 두 프로세스가 동시에 통과할 수 있다. - 정리용
trap은 잠금을 실제로 잡은 뒤에 건다. 먼저 걸면 획득에 실패한 프로세스가 남의 잠금을 지운다. - 대기 시간의 기본값이 0초, 즉 즉시 실패인 이유는 CI 에서 조용히 매달리는 것보다 빨리 실패해 사람이 상황을 보는 편이 낫기 때문이다.
- 흔한 실수:
>>로 줄 추가하기(실행할 때마다 늘어난다), 무조건 파일을 덮어써 항상 changed 내기, state.json 을 run.sh 보다 먼저 만들어 해시가 어긋나기.
디렉터리 보장
/root/iac2/ensure_dir.sh <경로> 를 만든다. 디렉터리가 없으면 만들고 changed, 이미 있으면 unchanged 를 출력한다. 두 경우 모두 종료코드 0 이다. 출력은 그 낱말만 담은 한 줄이어야 한다(채점은 unchanged 를 줄 전체와 정확히 비교한다).
ensure_dir.sh <경로> 는 없으면 만들고 changed, 이미 있으면 unchanged 를 출력합니다. 둘 다 종료코드 0 입니다. 출력은 그 한 낱말만 담은 한 줄이어야 합니다. 채점은 unchanged 를 줄 전체와 정확히 비교합니다.
파일 내용 보장
/root/iac2/ensure_file.sh <경로> <내용> 을 만든다. 파일이 없거나 내용이 다르면 내용을 써 넣고 changed, 이미 같으면 unchanged 를 출력한다.
ensure_file.sh <경로> <내용> 입니다. 쓰기 전에 현재 내용과 비교하세요. 없거나 다르면 쓰고 changed, 같으면 손대지 말고 unchanged. 무조건 쓰면 항상 changed 가 되어 멱등성 증명이 깨집니다.
줄 보장 (중복 없이)
/root/iac2/ensure_line.sh <파일> "<줄>" 을 만든다. 그 줄이 이미 정확히 있으면 unchanged, 같은 키(줄의 첫 낱말)를 가진 다른 줄이 있으면 그 자리를 새 줄로 교체하고 changed, 아무것도 없으면 끝에 추가하고 changed 를 출력한다. 몇 번을 실행해도 해당 줄은 파일에 정확히 1개여야 하고 나머지 줄은 그대로 남아야 한다.
ensure_line.sh <파일> "<줄>" 입니다. >> 로 덧붙이면 실행할 때마다 줄이 쌓입니다. 같은 줄이 이미 있으면 unchanged, 같은 키(첫 낱말)의 다른 줄이 있으면 그 자리를 교체하고 changed, 없으면 끝에 추가하고 changed. 다른 줄은 그대로 남아야 합니다.
권한 보장
/root/iac2/ensure_mode.sh <파일> <8진수모드> 를 만든다. stat -c %a 로 현재 권한을 읽어 다르면 chmod 후 changed, 같으면 unchanged 를 출력한다. 파일이 존재하지 않으면 0 이 아닌 종료코드를 낸다.
ensure_mode.sh <파일> <8진수모드> 는 stat -c %a 로 현재 권한을 읽어 비교합니다. 다르면 chmod 후 changed, 같으면 unchanged. 없는 파일에 chmod 를 시도하고 성공했다고 보고하면 안 되니 존재 확인을 먼저 하세요.
두 번 돌려 멱등성 증명하기
/root/iac2/run.sh 를 만든다. 위 네 스크립트를 이용해 리소스를 정확히 4개 보장한다(예: 디렉터리 1 + 파일 2 + 권한 1). 마지막 줄에 요약을 unchanged=<수> changed=<수> 순서로 출력한다. 첫 실행의 출력을 /root/iac2/run1.txt 에, 이어서 두 번째 실행의 출력을 /root/iac2/run2.txt 에 저장한다. 첫 실행의 changed 는 1 이상, 두 번째 실행의 요약은 unchanged=4 changed=0 이어야 한다.
run.sh 로 리소스 정확히 4개를 보장하고, 마지막 줄에 unchanged=<수> changed=<수> 순서로 요약을 냅니다. 순서가 중요합니다. 첫 실행 출력은 /root/iac2/run1.txt, 이어서 두 번째 실행 출력은 /root/iac2/run2.txt 로 저장하세요.
상태 파일 남기기
/root/iac2/state.json 을 만든다. resources 배열에 방금 보장한 리소스를 기록하고(2개 이상), 각 원소에 path, type(file 또는 dir), sha256 을 넣는다. type 이 dir 이 아닌 항목은 실제 파일이 존재해야 하고 sha256 값이 sha256sum <경로> 의 첫 필드와 정확히 같아야 한다. 최상위에 applied_at(적용 시각) 도 넣는다.
/root/iac2/state.json 에 방금 적용한 리소스를 기록합니다. 각 원소는 path, type, sha256 을 갖고, 파일 항목의 해시는 sha256sum <경로> 첫 필드와 정확히 같아야 합니다. 최상위에 applied_at 도 넣으세요. run.sh 를 돌린 뒤에 만들어야 해시가 맞습니다.
드리프트 감지
/root/iac2/drift.sh <상태파일> 을 만든다. 인자로 받은 상태 파일을 읽어 기록된 리소스를 검증한다. 전부 일치하면 종료코드 0, 내용이 바뀌었거나 파일이 사라졌으면 어긋난 경로를 출력하고 0 이 아닌 코드를 낸다. type 이 dir 인 항목은 존재 여부만 확인한다.
drift.sh <상태파일> 은 인자로 받은 상태 파일을 읽습니다. 경로를 하드코딩하면 채점이 만든 임시 상태 파일을 못 읽습니다. 내용 변경과 파일 삭제를 모두 잡아야 하고, 어긋난 경로를 출력해야 사람이 무엇을 볼지 압니다.
상태 잠금
/root/iac2/apply-lock.sh 를 만든다. 잠금 파일 경로는 LOCK_FILE 환경변수로 받고 없으면 /root/iac2/.lock 을 쓴다. 잠금이 없으면 조건부로 만들어 잡고, 할 일을 한 뒤 정상 종료할 때 반드시 지운다(종료코드 0, 잠금 파일이 남으면 안 된다). 이미 잠겨 있으면 기다리지 말고 종료코드 2 로 즉시 실패하며 lock 이라는 낱말이 들어간 메시지를 출력한다. 이때 남의 잠금 파일을 지우면 안 된다.
apply-lock.sh 는 LOCK_FILE 환경변수를 존중합니다. 잠금이 없으면 조건부로 만들고 정상 종료 때 반드시 지웁니다(0). 이미 잠겨 있으면 기다리지 말고 종료코드 2 로 즉시 실패하며 잠금 때문임을 메시지로 알립니다. 남의 잠금 파일은 절대 지우지 마세요.