Devin.KR

후보 검증과 트래픽 전환

90분 안팎

학습 목표

후보 이미지 검증 후 프록시 대상을 바꿉니다.

개념

후보에게 바로 사용자를 보내지 않습니다

새 이미지는 빌드 성공만으로 사용자 경로에 올리지 않습니다. m04에서 테스트를 통과한 산출물인지 확인하고 m06의 비밀 경로와 계정을 연결한 뒤 실제 조회를 검사합니다. 기존 서비스는 요청을 계속 받으며 후보는 별도 주소에서 검증합니다. 이렇게 하면 후보가 실패해도 현재 사용자 경로를 유지할 시간을 얻습니다. 신입의 배포 리뷰에서는 새 컨테이너가 떠 있는지보다 기존 경로를 어느 시점까지 보존했는지 설명하게 합니다. 사전 검사와 전환 후 검사는 서로 다른 실패를 찾습니다.

입력 산출물의 신뢰 경계를 확인합니다

이전 artifacts와 후보 artifacts 두 디렉터리를 입력으로 받습니다. 각각 release.json, test-summary.json, notice-image.tar와 checksums.json을 갖습니다. 상속한 app.ci.verify가 파일 checksum, 실행된 테스트 수, 실패·오류 수, 변경 ID와 이미지 계약을 확인합니다. 모든 파일을 함께 바꿀 수 있는 사람의 위조까지 막는 서명 검사는 아닙니다. 따라서 신뢰한 CI에서 나온 경로를 입력으로 받습니다. 임의 태그를 실행한 뒤 테스트도 성공했을 것이라고 가정하지 않습니다.

식별자의 종류를 구분합니다

트랙은 레지스트리 계정 없이 로컬 image ID를 고정합니다. release의 digestKind는 local-image-id이고 registryDigest는 null입니다. sha256으로 시작한다고 레지스트리 manifest digest와 같다고 부르지 않습니다. docker load 후 inspect의 Id와 명세를 비교하고 OS와 Architecture도 확인합니다. 태그는 사람이 찾는 이름이고 실행 선택은 검증한 ID로 합니다. 이전 archive도 보존해야 후보 실패 때 정확한 코드를 다시 실행할 수 있습니다. 이전과 후보 ID가 같다면 새 readiness가 포함된 빌드인지 먼저 확인합니다.

기존 m06 환경을 입력으로 검사합니다

개인의 project-service, project-net, project-data를 이어받습니다. 기존 컨테이너 이미지가 이전 release와 같고 프로젝트 라벨·네트워크·데이터 마운트·읽기 전용 비밀 경로가 맞는지 검사합니다. 라벨은 소유 범위를 표시하는 메타데이터이며 Docker 권한 격리 기능은 아닙니다. 같은 이름의 다른 자원이나 다른 프로젝트 볼륨을 발견하면 멈춥니다. 이 실습은 OpenTofu 상태를 수정하지 않고 추가 Docker 자원을 생성합니다. 상속 HCL을 다시 적용하여 이전 서비스부터 교체하는 작업과 혼동하지 않습니다.

후보는 별도 사본에서 검증합니다

기존 notice.txt를 읽기 전용으로 채취하고 백업을 검증한 뒤 후보 전용 볼륨에 복원합니다. 현재 API는 조회 전용이라 제목 파일을 복제하여 동일 응답을 비교할 수 있습니다. 로그는 후보 볼륨에 따로 씁니다. 계속 내용이 바뀌는 시스템에서는 단순 복사가 부족하므로 쓰기 중지나 일관된 snapshot과 변경 재생이 필요합니다. 이번 실습은 공유 쓰기를 만들지 않고 원본 안내판 바이트가 유지되는지 마지막에도 확인합니다. 사본에서 성공했다는 사실만으로 쓰기 시스템의 전환까지 보장하지 않습니다.

통과 조건은 세 가지의 교집합입니다

후보 /ready가 200이고 인증한 /notices도 200이며 JSON의 id와 title이 이전 응답과 같아야 합니다. 상태 코드만 비교하면 잘못된 안내판도 통과할 수 있습니다. readiness 성공으로 사용자 요청을 생략하면 인증이나 라우팅 오류를 놓칩니다. body_matches는 키 순서나 공백 대신 파싱한 데이터의 의미를 비교합니다. starter의 allow_candidate는 항상 true라 준비 실패와 응답 불일치 사례에서 실패합니다. 세 조건을 모두 만족하는 판단으로 고치고 조건 하나씩 실패하는 테스트를 유지합니다.

설정 파일과 활성 프록시는 다릅니다

Nginx worker는 읽어 둔 설정으로 요청을 처리하므로 호스트 파일만 바꿔도 즉시 새 대상으로 이동하는 것은 아닙니다. 실습은 같은 디렉터리에 임시 파일을 쓴 뒤 os.replace로 default.conf를 교체하고 nginx -t와 reload를 수행합니다. 디렉터리 자체를 읽기 전용으로 마운트하므로 파일 교체가 컨테이너에도 보입니다. 파일 하나만 마운트하는 방식은 새 파일이 연결되는지 따로 확인해야 합니다. 문법 검사는 설정을 읽을 수 있는지 확인하며 사용자 데이터의 정확성까지 검사하지 않습니다.

새 요청으로 전환을 관측합니다

reload가 끝났다는 사실만으로 새 worker가 요청을 처리했다고 판단하지 않습니다. 교육 프록시는 X-Lab-Backend 헤더로 선택한 컨테이너 이름을 알려 주고 새 요청으로 그 값을 확인합니다. 그 뒤 /notices의 실제 응답을 관측합니다. 이 헤더는 실습 식별용이며 운영에서는 내부 이름 공개 여부를 따로 검토합니다. 기존 연결은 이전 worker에서 마무리될 수 있어 모든 연결이 동시에 바뀐다고 단정하지 않습니다. 설정 교체와 실행 관측을 나누면 파일만 바꾼 배포를 성공으로 기록하는 실수를 줄입니다.

준비 실패 후보는 전환 전에 멈춥니다

unready 컨테이너는 후보 이미지와 인증 경로를 쓰되 데이터 경로를 없는 파일로 지정합니다. health는 응답하고 ready는 503이어야 합니다. /notices를 먼저 호출하면 기본 파일 생성으로 실패 조건을 지울 수 있어 검사 순서가 중요합니다. check.sh는 준비 실패 후보를 거부한 뒤 설정 바이트가 그대로이고 기존 사용자 응답이 같은지 확인합니다. 실패한 후보에 한번 사용자를 연결해 본다는 수정은 보호하려는 경로를 먼저 노출합니다. 현재 경로 보존 자체가 테스트의 성공 조건입니다.

전환 뒤 실패도 복구 경로에 넣습니다

사전 검사를 통과해도 프록시를 거친 요청에서 문제가 생길 수 있습니다. 이전 설정 내용을 보관하고 예외나 실패율 기준 초과에서 그 설정으로 돌아갑니다. 복구 후 실제 이전 image ID와 사용자 응답을 확인합니다. 파일만 되돌리고 reload를 생략하거나 기존 컨테이너를 미리 지우면 복구하지 못합니다. 정상 시험 뒤에도 다음 장애 fixture를 위해 기존 대상으로 돌아갑니다. 이번 검사는 배포를 계속 유지하는 도구가 아니라 정상 전환과 실패 복구를 증명하는 리허설입니다.

오류 위치와 증거를 연결합니다

checksum mismatch는 산출물부터, image mismatch는 선택 명세와 실행 이미지를 조사합니다. connection refused는 포트나 프로세스, 401은 인증 전달, 503은 준비 의존성을 확인합니다. nginx 문법 오류에는 이전 설정으로 돌아가며 토큰을 출력해 조사하지 않습니다. 포트 충돌이면 기존 프로세스를 종료하지 말고 개인 실습 환경을 조정합니다. evidence-m07/release-evidence.json에 양쪽 ID·사전 검사·전환 관측·데이터 비교·최종 복구 대상을 남깁니다. Docker 미실행은 external 대기로 적고 상태 확인용 curl 옵션은 더 읽기로 보냅니다.

worker 전환의 근거: Nginx 설정 변경 공식 문서. reload는 설정을 다시 읽고 새 worker를 시작하며 이전 worker는 처리 중인 요청을 마무리합니다.

따라하기

사전 검사 교집합 확인

세 조건 중 하나만 실패해도 후보를 허용하지 않는 판단을 실행합니다. 이 코드는 조건 모델이며 Docker 전환 증거는 뒤 단계에서 만듭니다.

def allow(ready,user,body):
    return ready==200 and user==200 and body is True
for row in [(200,200,True),(503,200,True),(200,401,True),(200,200,False)]:
    print(allow(*row))

실행 결과

True
False
False
False

새 readiness 이미지의 CI 산출물 만들기

실습 zip의 app/에서 실행합니다. CHANGE_ID에는 자신의 소스 변경을 식별하는 40자리 소문자 16진수 값을 넣습니다. 이전 artifacts는 새 빌드 전에 별도 보존합니다. CI가 release·요약·archive·checksum을 생성하는지 확인하며 Docker 미실행 결과는 출력에 넣지 않습니다.

cd app
./mvnw test
bash ci.sh "$CHANGE_ID"
cd ..

이전과 후보의 검증 입력 연결

m06이 실행한 이전 산출물과 이번 app/artifacts의 절대 경로를 지정합니다. bc-yourname은 자신의 기존 project 이름입니다. 마지막 인자는 m06의 token이 있는 current 디렉터리 자체로 바꿉니다. 토큰 내용은 출력하지 않습니다. 두 이미지 ID가 다르고 CI 입력이 검증되면 rollout-input.json이 생성됩니다.

python3 prepare-rollout.py /절대/옛/artifacts /절대/새/app/artifacts bc-yourname /절대/비밀/current

gate 구현 뒤 실제 프록시 확인

starter의 switch.py를 교집합 조건으로 고칩니다. 빠른 검사 후 Docker 검사에서 준비 실패 전환 금지·정상 전환·복구·원본 데이터 보존을 확인합니다. evidence-m07/release-evidence.json의 실제 ID와 최종 이전 backend를 확인합니다. 외부 실행 결과는 대기 상태입니다.

python3 -B test-preflight.py
bash check.sh

확인 문제

실습

switch.py의 allow_candidate를 완성하여 readiness 200·인증 사용자 응답 200·원본 id와 title 일치를 모두 요구합니다. m06 실행 자원과 이전 CI artifacts를 입력으로 받고 /ready가 추가된 새 CI 이미지를 만듭니다. README대로 prepare-rollout.py와 bash check.sh를 실행합니다. 검사는 실제 Nginx 설정 검사·reload·backend 관측·오류와 시간 초과 복구까지 수행합니다. Python 모델 통과만으로 실제 전환 성공이라고 기록하지 않습니다. Docker 검증은 external입니다. 앞 단계를 한 번에 실행하려면 bash prepare-and-check.sh를 사용합니다. 이 스크립트는 /ready를 뺀 사본으로 m06 자원을 .prior-m06/에서 먼저 만들고, 비밀 디렉터리는 홈 폴더 아래 SECRET_DIR을 씁니다. 명령 앞의 BOOTCAMP_AUTOCLEAN=1은 자동 검증기가 끝난 뒤 자원을 지우는 용도라 학습자는 붙이지 않습니다.

시작 코드·테스트 내려받기

실행 명령

BOOTCAMP_AUTOCLEAN=1 bash prepare-and-check.sh

기대 결과

사전 조건 5개 검사, 실제 /ready 503 후보 거부, 정상 후보 전환 관측과 이전 응답 복구 증거를 남깁니다.

모범 답안모범 답안 내려받기

더 읽기

면접 질문

  • 컨테이너 이미지와 실행 중 컨테이너의 차이를 설명합니다.
  • CI에서 실패한 테스트 이후의 배포 동작을 설명합니다.