변경 검증과 되돌림
110분 안팎
학습 목표
설정 사본을 확보해 실습 서비스 설정을 바꾸고 HTTP 검증 실패 시 사본으로 복구합니다.
개념
되돌릴 수 있다는 말은 검증 결과입니다
설정 사본이 있다는 사실만으로 변경을 안전하게 되돌릴 수 있다고 단정할 수 없습니다. 사본이 적용 직전 값인지, 같은 권한으로 복원됐는지, 실제 앱 응답까지 돌아왔는지 확인해야 합니다. 이번 레슨은 count=3을 제공하는 작은 웹 앱의 데이터 파일 설정을 바꾸고 업무 응답이 달라지면 이전 설정으로 복원합니다. 프로세스가 존재하거나 /health가 200이라는 사실만으로 성공 처리하지 않고 /items의 전체 JSON을 비교합니다.
변경의 경계를 하나의 설정으로 줄입니다
config.json은 data_file 하나만 가지며 old.json·new.json·bad.json 중 하나를 가리킵니다. old와 new의 내용은 count=3이고 bad는 count=9입니다. new는 동작이 유지되는 변경, bad는 문법은 맞지만 업무 검증에 실패하는 변경입니다. 앱의 DB나 실제 저장 데이터를 덮어쓰지 않습니다. 설정 복구로 되돌릴 수 있는 범위를 학습하려고 고정 사본을 사용합니다. 데이터 마이그레이션까지 수행했다면 별도의 데이터 복구 계획이 필요합니다.
사전 검사는 실패 비용을 낮춥니다
preflight는 후보 객체의 키와 데이터 파일 이름, 사본의 JSON 자료형과 count를 검사합니다. 경로가 ../../secret이면 CONFIG_INVALID로 쓰기 전에 거부합니다. count가 음수 또는 논리값이면 DATA_INVALID입니다. 사전 검사에 성공해도 업무 값 9가 맞다는 뜻은 아닙니다. 구조와 경로의 유효성을 먼저 확인하고 의미 검증은 실제 응답 단계에서 확인합니다. 사전 검사 실패는 아직 이전 설정을 적용해 복구할 변경이 없습니다.
사본의 출처와 원본 일치를 확인합니다
transaction.py는 변경 직전 config.json의 바이트와 모드를 읽고 before.json을 새로 만듭니다. xb 모드는 기존 파일이 있으면 실패하므로 이전 변경의 사본을 덮어쓰지 않습니다. 원본 SHA-256과 사본 SHA-256을 비교하고 일치한 뒤 후보를 씁니다. 후보 검증 후 되돌릴 때도 원래 바이트를 사용합니다. JSON을 다시 직렬화해 의미만 같게 만들면 공백·줄바꿈의 체크섬은 달라질 수 있으므로 원본 바이트를 보관하는 이유를 이해합니다.
한 변경마다 새 작업 디렉터리를 씁니다
before.json이나 change.json이 이미 있으면 새 변경용 폴더를 사용합니다. 기존 증거를 삭제한 뒤 계속하지 않습니다. 이 실습은 한 작업자가 쓰는 비공개 디렉터리를 전제로 하며 공유 디렉터리의 동시 변경은 지원하지 않습니다. 사본은 0600으로 만들어 다른 사용자 접근을 제한합니다. 후보 파일의 모드는 적용 전 config.json의 모드로 유지합니다. 설정에 실제 비밀이 들어 있는 환경에서는 화면에 원문을 출력하지 않고 접근 통제된 사본과 해시를 사용합니다.
검증 함수가 받는 기대 값
기대 JSON은 후보에서 계산하지 않고 변경 전 설정이 참조하던 old.json에서 읽습니다. 후보가 count=9일 때 기대 값도 9로 바꾸면 잘못된 변경이 통과하기 때문입니다. verify는 root와 expected를 받고 참·거짓을 반환합니다. 예측 가능한 OSError·ValueError도 검증 실패로 처리합니다. 실제 운영에서는 사전에 승인한 업무 기대 값이 더 강한 기준이 될 수 있으며, 이 실습은 닫힌 사본의 기존 정상 값이 기준입니다.
파일 대역과 HTTP 실측을 구별합니다
check-local.sh의 다섯 테스트는 새 임시 파일을 읽는 verify_fixture로 성공·실패 분기를 확인합니다. 네트워크를 열지 않으므로 파일 복구 로직을 검증할 수 있습니다. check.sh는 이어 test_http.py에서 동일 분기를 실제 루프백 HTTP로 확인합니다. 이 샌드박스는 소켓 bind를 차단하므로 실제 HTTP 검사는 external입니다. 파일 대역 통과를 HTTP 복구 성공으로 적지 않고 실제 PC 또는 준비된 VM의 결과를 따로 남깁니다.
HTTP 실험의 프로세스 수명을 닫습니다
httpcheck.py는 앞 모듈의 앱을 복사한 webapp.py로 127.0.0.1의 임의 포트 서버를 엽니다. 컨텍스트 안에서 /health와 /items를 요청하며 프록시를 사용하지 않고 요청별 상한을 둡니다. 컨텍스트를 벗어나면 shutdown·server_close·thread.join으로 자기 서버가 완료될 때까지 정리합니다. 운영 프로세스를 종료하지 않습니다. 이 방식은 새 설정으로 앱을 다시 로드하는 실험이며 실행 중인 systemd 서비스의 reload 지원을 가정하지 않습니다.
실패 후보 뒤의 세 가지 결과
후보 응답이 맞으면 APPLIED와 종료 0입니다. 후보가 틀렸지만 이전 설정의 해시와 검증이 모두 맞으면 ROLLED_BACK과 종료 1입니다. 복구 뒤에도 응답이 틀리거나 확인할 수 없으면 RECOVERY_FAILED와 종료 2입니다. 되돌림 성공은 변경 성공이 아니므로 비정상 결과를 유지합니다. 담당자는 후보를 계속 적용할지 다시 리뷰하며 복구 실패라면 추가 변경을 멈추고 기록된 사본과 현재 상태를 확보합니다.
복구 확인에도 새 응답을 사용합니다
원래 설정을 복사했으니 응답도 정상일 것이라고 추정하지 않습니다. 해시가 돌아왔는지 검사한 다음 verify를 다시 호출합니다. 복구 검증 요청이 실패하면 restored_ok는 false입니다. test_recovery_failure_distinct는 항상 실패하는 검증 함수를 주입하여 후보 실패와 복구 실패를 구분합니다. 파일 일치만 보고 종료 1로 처리하면 업무가 복구되지 않은 상태를 숨깁니다. 과거 정상 응답이나 캐시된 결과도 복구 후 응답을 대체하지 않습니다.
변경 기록의 허용된 정보
change.json은 UTC 시각, before·candidate·final SHA-256, 후보 검증 결과, 복구 검증 결과와 최종 판정·코드를 담습니다. 원문 설정이나 환경 변수 전체는 저장하지 않습니다. 비교할 수 있는 해시와 고정 결과를 남기면 기록 경로를 넓게 공유하지 않아도 변경의 흐름을 설명할 수 있습니다. 해시만으로 업무 정상이나 비밀 파일의 안전한 공개를 보장하지는 않으므로 증거 파일의 접근 권한도 함께 유지합니다.
starter의 누락과 오류 메시지
starter는 실패 후보 뒤 원래 바이트를 쓰는 줄이 비어 있습니다. test_rollback_hash_mode_and_data에서 ROLLED_BACK 기대와 RECOVERY_FAILED 실제가 다르거나 해시 비교가 틀리면 복원 줄을 확인합니다. 반환 문자열만 바꾸면 최종 파일·권한·업무 값 검사에서 실패합니다. 실제 HTTP 실행에서 bind의 PermissionError: Operation not permitted는 소켓 권한 제한입니다. 이를 데이터 장애로 고치지 않고 실행 가능한 자기 VM에서 검사합니다.
미션에서는 자연 종료 후 반영합니다
VM 미션의 service.env는 old.json 또는 bad.json 절대 경로를 지정합니다. bootcamp-config.service는 8초 유한 수명으로 실행하며 자기 서비스의 ExecMainPID가 0인지 기다린 뒤 다음 설정을 씁니다. 후보 검증 후에도 자연 종료를 확인하고 이전 사본을 복원해 다시 기동합니다. 재기동으로 다른 프로세스를 끊지 않습니다. 유닛 변경 시 daemon-reload가 필요할 수 있지만 환경 파일 복원과 HTTP 확인은 별개의 단계입니다.
완료 기준을 사전에 고정합니다
제출에는 후보 실패 근거, 원본 사본 경로, 복구 전후 체크섬과 파일 모드, 실제 /items 응답, 종료 상태가 들어갑니다. 파일 대역 검사와 실제 HTTP 결과는 다른 항목입니다. systemctl의 일반 사용법은 더 읽기에 맡기고 이 레슨에서는 실패 후 이전 설정으로 복원한 상태를 다시 관측하는 책임을 익힙니다. 다음 레슨은 이 증거를 다른 담당자가 리뷰하고 재현할 수 있는 변경 기록으로 묶습니다.
따라하기
파일 대역의 복구 분기 확인
solution의 예제입니다. 실제 HTTP를 열지 않고 후보 값 9와 이전 값 3을 파일로 비교합니다.
python3 -B demo.py rollback실행 결과
FIXTURE ROLLED_BACK EXIT 1 HASH_RESTORED True DATA_RESTORED True
starter 복원 줄 완성
transaction.py에서 이전 바이트와 모드를 복원하도록 TODO를 구현합니다. 로컬 다섯 검사는 파일 대역입니다.
bash check-local.sh실제 HTTP로 같은 분기 확인
소켓을 허용하는 자기 PC/VM에서 실행합니다. 다섯 로컬 검사 다음 다섯 실제 HTTP 검사가 통과해야 합니다. 이 단계는 external이며 샌드박스에서 실행 완료하지 못했습니다.
bash check.sh누적 미션의 실제 서비스 복구
미션 README의 새 이름·계정·경로·문법 검사를 마친 Linux VM에서 적용과 검증을 수행합니다. /items, 해시, 자연 종료 증거를 change.md에 연결합니다.
sudo /usr/bin/python3 -B vm.py plan
sudo /usr/bin/python3 -B vm.py apply
sudo bash check.sh확인 문제
실습
transaction.py의 실패 후보 복원 TODO를 구현합니다. check-local.sh의 사본 보존·해시·모드·성공/복구/복구 실패·경로 검사 다섯 개를 통과합니다. 실제 HTTP는 check.sh로 별도 검증하며 /health와 /items를 모두 비교합니다. 이 샌드박스의 소켓 제한으로 실제 HTTP는 external/PENDING입니다.
실행 명령
bash check.sh
기대 결과
로컬 5 tests OK와 실제 HTTP 5 tests OK, 종료 0
모범 답안
모범 답안 내려받기더 읽기
면접 질문
- 프로세스와 systemd 서비스 상태의 차이를 설명합니다.
- 백업 결과를 신뢰하기 위한 확인 방법을 설명합니다.