Infrastructure as Code · 멱등성 · 실습
멱등한 리소스 보장 스크립트
목표
디렉터리·파일·줄·권한을 보장하는 멱등 스크립트를 만들고, 두 번 연속 적용해 changed 가 0 이 되는 것으로 멱등성을 증명한다. 상태 파일과 드리프트 감지, 잠금까지 붙인다.
왜 중요한가
멱등성은 같은 작업을 여러 번 실행해도 결과가 동일하다는 성질이고, 자동화를 재실행해도 안전하다는 보증이다. 증명 방법은 두 번 적용해 두 번째 plan 이 변경 없음인지 보는 것이다. 멱등성이 깨지는 자리는 거의 정해져 있다. 전용 모듈 대신 shell 이나 command 로 명령을 직접 실행하는 곳, 그리고 >> 로 줄을 덧붙이는 곳이다. 그래서 이 실습의 스크립트들은 모두 "바꾸기 전에 확인한다"는 같은 골격을 갖는다. 마지막에 붙이는 상태 파일과 잠금은 실제 IaC 도구가 왜 그런 부속을 달고 있는지 몸으로 이해하게 해 준다.
단계
1. /root/iac2/ensure_dir.sh <경로> 를 만든다. 디렉터리가 없으면 만들고 changed, 이미 있으면 unchanged 를 출력한다. 두 경우 모두 종료코드 0 이다. 출력은 그 낱말만 담은 한 줄이어야 한다(채점은 unchanged 를 줄 전체와 정확히 비교한다).
2. /root/iac2/ensure_file.sh <경로> <내용> 을 만든다. 파일이 없거나 내용이 다르면 내용을 써 넣고 changed, 이미 같으면 unchanged 를 출력한다.
3. /root/iac2/ensure_line.sh <파일> "<줄>" 을 만든다. 그 줄이 이미 정확히 있으면 unchanged, 같은 키(줄의 첫 낱말)를 가진 다른 줄이 있으면 그 자리를 새 줄로 교체하고 changed, 아무것도 없으면 끝에 추가하고 changed 를 출력한다. 몇 번을 실행해도 해당 줄은 파일에 정확히 1개여야 하고 나머지 줄은 그대로 남아야 한다.
4. /root/iac2/ensure_mode.sh <파일> <8진수모드> 를 만든다. stat -c %a 로 현재 권한을 읽어 다르면 chmod 후 changed, 같으면 unchanged 를 출력한다. 파일이 존재하지 않으면 0 이 아닌 종료코드를 낸다.
5. /root/iac2/run.sh 를 만든다. 위 네 스크립트를 이용해 리소스를 정확히 4개 보장한다(예: 디렉터리 1 + 파일 2 + 권한 1). 마지막 줄에 요약을 unchanged=<수> changed=<수> 순서로 출력한다. 첫 실행의 출력을 /root/iac2/run1.txt 에, 이어서 두 번째 실행의 출력을 /root/iac2/run2.txt 에 저장한다. 첫 실행의 changed 는 1 이상, 두 번째 실행의 요약은 unchanged=4 changed=0 이어야 한다.
6. /root/iac2/state.json 을 만든다. resources 배열에 방금 보장한 리소스를 기록하고(2개 이상), 각 원소에 path, type(file 또는 dir), sha256 을 넣는다. type 이 dir 이 아닌 항목은 실제 파일이 존재해야 하고 sha256 값이 sha256sum <경로> 의 첫 필드와 정확히 같아야 한다. 최상위에 applied_at(적용 시각) 도 넣는다.
7. /root/iac2/drift.sh <상태파일> 을 만든다. 인자로 받은 상태 파일을 읽어 기록된 리소스를 검증한다. 전부 일치하면 종료코드 0, 내용이 바뀌었거나 파일이 사라졌으면 어긋난 경로를 출력하고 0 이 아닌 코드를 낸다. type 이 dir 인 항목은 존재 여부만 확인한다.
8. /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 보다 먼저 만들어 해시가 어긋나기.
단계 8개
- 디렉터리 보장
- 파일 내용 보장
- 줄 보장 (중복 없이)
- 권한 보장
- 두 번 돌려 멱등성 증명하기
- 상태 파일 남기기
- 드리프트 감지
- 상태 잠금