CI 작업과 산출물
65분 안팎
학습 목표
검증·빌드·산출물 전달을 의존 순서로 묶습니다.
개념
버튼 두 개를 하나의 실행 경로로 묶습니다
개발자가 테스트 버튼과 이미지 빌드 버튼을 따로 누르면 다른 소스나 예전 보고서를 섞기 쉽습니다. 파이프라인은 검증한 변경과 만들어진 산출물 사이의 순서를 코드로 고정합니다. 이 레슨은 Maven 검증, 보고서 집계, Docker 이미지 생성, 아카이브 저장, 명세 작성의 의존 관계를 만듭니다. GitHub 계정 없이도 같은 함수의 제어 흐름을 fixture로 검사하며 실제 외부 CI가 실행되었다고 주장하지 않습니다.
단계마다 입력과 출력을 적습니다
검증 단계는 소스와 JDK를 받아 JAR와 Surefire 보고서를 만듭니다. 보고서 단계는 XML을 읽어 실행 수와 실패 수를 판단합니다. 이미지 단계는 그 JAR를 작은 문맥에 넣고 Dockerfile로 이미지를 만듭니다. 전달 단계는 docker save의 tar와 릴리스 명세를 함께 보관합니다. 각 단계가 무엇을 받는지 적으면 다음 단계가 소스를 다시 빌드하는지 아니면 검사한 결과를 그대로 사용하는지 검토할 수 있습니다.
package와 보고서 검사를 모두 둡니다
./mvnw -q package가 실패하면 실제 파이프라인은 즉시 멈춥니다. 성공하면 ci.py의 reports가 TEST-*.xml을 읽어 실행 건수와 failures·errors를 확인합니다. 명령 상태와 보고서 조건은 서로 보완합니다. 테스트를 건너뛴 실행이나 보고서가 없는 실행은 Maven 상태만으로 충분히 설명할 수 없습니다. 이전 단계에서 성공이라는 글자가 보였다는 이유로 reports를 생략하면 이 레슨의 0건 차단 기준이 사라집니다.
이번 실행의 보고서만 읽습니다
ci.py는 Maven 호출 전에 전용 surefire-reports 디렉터리의 TEST XML을 제거합니다. 그래야 테스트가 새로 생성하지 못했는데 예전 성공 보고서를 읽는 상황을 막습니다. 저장소나 사용자 홈을 통째로 삭제하지 않고 이 빌드의 생성 파일로 범위를 좁힙니다. 같은 작업 디렉터리에서 동시에 두 빌드를 실행하면 서로 영향을 줄 수 있으므로 각 CI 실행은 독립 checkout 또는 독립 압축 해제 폴더를 사용합니다.
오래된 성공 명세도 치웁니다
artifacts의 release.json, checksums.json, test-summary.json, notice-image.tar는 새 실행의 앞에서 정리합니다. 이후 실패하면 성공 릴리스 명세가 남지 않아야 합니다. 보고서 차단 뒤에도 예전 release가 있으면 사용자가 현재 실행의 결과로 착각할 수 있습니다. fixture는 먼저 성공 빌드를 대역으로 수행하고 이어 실패·0건·전체 건너뜀을 실행하여 명세 부재를 검사합니다. 한 번의 빈 폴더 검사보다 이 순서가 오래된 결과 혼동을 더 잘 드러냅니다.
이미지 문맥에는 필요한 파일만 넣습니다
앞 모듈의 Dockerfile과 .dockerignore를 그대로 사용하며 target의 JAR를 build-context/app.jar로 복사합니다. 저장소 전체를 Docker 문맥으로 사용하지 않습니다. 이미지의 비루트 사용자와 데이터 경로 계약은 m03에서 이어받습니다. 이 레슨에서는 같은 JAR가 어떤 조건을 통과한 뒤 복사되는지 살핍니다. 원본 JAR의 SHA256을 명세에 넣으면 이미지와 연결된 Java 산출물의 바이트도 다음 작성자가 비교할 수 있습니다.
함수 대역으로 실행 순서를 검증합니다
fixture는 ci.build 함수를 실제로 호출하되 subprocess 실행기와 inspect 응답만 대체합니다. 성공에서는 test, build, inspect, save 순서가 events에 남아야 합니다. 실패나 실행 0건에서는 test만 있어야 합니다. 단순히 문자열로 예상 순서를 적는 검사가 아니라 작성한 함수가 어떤 외부 명령을 요청했는지 관찰합니다. 가짜 tar는 임시 폴더 안에만 있으며 실제 이미지를 만들었다는 증거가 아닙니다.
GitHub 정의도 같은 스크립트를 부릅니다
제공 워크플로는 checkout, JDK 17 준비, bash ci.sh 호출, artifacts 업로드 순서입니다. 단일 job의 단계 순서와 기본 성공 조건으로 빌드가 실패하면 업로드를 생략합니다. 별도 job으로 나눈다면 needs로 검증 job의 의존 관계를 선언하고 전달할 파일을 명확히 해야 합니다. job 이름의 위아래 위치만으로 실행 순서를 보장하지 않습니다. 여기서는 구현 경로가 둘로 갈라지지 않도록 실제 작업을 ci.sh 한 곳에 모읍니다.
진단 로그와 릴리스 업로드를 구분합니다
always 조건은 실패한 작업의 진단 자료를 모을 때 사용할 수 있지만 검증된 릴리스 업로드에 무심코 붙이지 않습니다. continue-on-error도 실패를 허용할 의도가 없는 검증 단계에는 사용하지 않습니다. 제공 정의는 artifacts 폴더만 전달하고 if-no-files-found를 error로 설정합니다. 워크스페이스 전체를 업로드하면 환경 파일과 불필요한 캐시까지 섞일 수 있습니다. 자료를 받는 사람이 배포 입력과 실패 진단을 구분할 수 있게 이름과 범위를 정합니다.
변경 ID는 입력 시점에 고정합니다
ci.sh는 40자리 소문자 Git SHA를 인자로 받습니다. 로컬 실행에서는 자신이 검토한 변경의 SHA를 전달하고 GitHub에서는 checkout한 실행의 GITHUB_SHA를 넘깁니다. 이 문자열은 비밀이 아니며 릴리스 출처를 추적하는 연결값입니다. 단지 형식이 맞는 문자열을 넣었다고 실제 checkout과 일치하는 증명이 생기지는 않습니다. 테스트한 소스와 ID의 관계는 실행 시스템의 checkout 기록을 함께 검토하여 확인합니다.
로컬과 외부의 차이를 남깁니다
계정 없는 fixture 검사는 네트워크와 Docker 없이 제어 흐름을 검증합니다. 실제 로컬 경로는 JDK 17과 Docker daemon이 필요합니다. GitHub 경로는 runner가 checkout과 도구 준비를 담당합니다. 같은 ci.sh를 사용해도 도구 캐시, CPU 아키텍처, 기반 이미지가 다를 수 있으므로 비트가 같은 빌드라고 단정하지 않습니다. 명세에는 실제 inspect에서 관측한 플랫폼을 남겨 전달 조건을 확인하게 합니다.
starter의 관문을 복구합니다
l03 starter의 reports 함수는 차단 조건이 꺼져 있습니다. executed가 0이거나 failures 또는 errors가 있으면 ValueError를 발생시키도록 고칩니다. 검사에서 report zero와 pipeline failed 등 어떤 항목이 먼저 실패하는지 확인하고 원인을 해당 함수에 연결합니다. Docker 명령을 아예 없애 모든 빌드를 차단하는 수정은 성공 fixture를 깨뜨립니다. 검증한 변경은 진행하고 검증되지 않은 변경은 멈추는 양쪽 동작을 완성합니다.
명령 실패를 단계별로 진단합니다
no test reports는 보고서 경로·테스트 선택·새 생성 여부를 살핍니다. test gate blocked는 수량을 보고 실패·오류·실행 없음 중 원인을 좁힙니다. Docker 연결 오류는 테스트 실패가 아니라 이미지 실행 도구 경계의 문제입니다. COPY 실패는 문맥의 app.jar가 있는지 확인합니다. 업로드 경로 오류라면 artifacts 생성 여부와 실행 위치를 먼저 봅니다. 모든 실패를 테스트 오류라는 하나의 이름으로 묶지 않습니다.
미션의 전달 자료를 정합니다
완료한 산출물은 artifacts/release.json, test-summary.json, notice-image.tar, checksums.json 네 파일입니다. 다음 IaC와 배포 모듈은 이 명세에서 이미지 ID와 전달 파일을 읽습니다. 실행 중 포트나 임시 볼륨을 CI가 배포 환경이라고 선언하지 않습니다. GitHub 작업과 산출물 전달의 문법은 공식 문서에서 확인하고 tee의 상태 보존 배경은 더 읽기로 보완합니다. 제출에는 fixture 검사와 실제 이미지 검사 여부를 분리해서 적습니다.
단계 의존성과 산출물 전달은 GitHub 워크플로 문법과 공식 산출물 안내를 기준으로 작성했습니다.
따라하기
관문 완성
ci.py의 reports 함수에서 False로 꺼진 차단 조건을 executed·failures·errors로 고칩니다.
python3 -m py_compile ci.py로컬 순서 확인
solution에서 직접 실행한 결과입니다. starter를 수정한 뒤 order와 fresh manifest 항목도 같은 결과가 나오는지 확인합니다.
bash check.sh실행 결과
PASS gate pass PASS log pass PASS gate fail PASS log fail PASS report success PASS report failure PASS report error PASS report zero PASS report all-skipped PASS report negative PASS integrity original PASS integrity tamper rejected PASS CI artifact verified PASS pipeline ok PASS order ok PASS fresh manifest ok PASS pipeline failed PASS order failed PASS fresh manifest failed PASS pipeline zero PASS order zero PASS fresh manifest zero PASS pipeline skipped PASS order skipped PASS fresh manifest skipped RESULT: 0 failure(s); fixture only, no Docker build
워크플로 검토
같은 ci.sh 호출과 성공 후 업로드 순서를 읽습니다. 외부 GitHub 실행은 하지 않았으므로 실행 출력으로 채우지 않습니다.
cat .github/workflows/ci.yml실제 빌드 경로
미션 zip에서 자신의 실제 SHA를 넣어 실행합니다. artifacts 네 파일이 생기고 inspect에서 읽은 값이 release에 들어가는지 확인합니다. 미실행 명령의 출력은 비웁니다.
bash ci.sh "$CHANGE_ID"
python3 -B ci.py verify artifacts확인 문제
실습
ci.py의 reports 차단 조건을 완성하여 실패·오류·실행 0건이면 이미지 명령과 명세 생성에 도달하지 않게 합니다. 성공 뒤 실패 실행에서 옛 명세도 없어야 합니다. 검사 코드는 수정하지 않습니다. 로컬 fixture는 실제 Docker 실행을 입증하지 않습니다.
실행 명령
bash check.sh
기대 결과
모든 PASS와 RESULT: 0 failure(s); fixture only, no Docker build
모범 답안
모범 답안 내려받기더 읽기
면접 질문
- CI에서 실패한 테스트 이후의 배포 동작을 설명합니다.