GitLab CI/CD · 실행 모델 — 순서·조건·전달 · 실습
캐시에 넣어 둔 빌드 결과가 배포 잡에서 사라졌다
목표
파이프라인을 여러 번 돌려 캐시가 맞고 빗나가고 무효화되는 것을 로그로 보고, 산출물의 제외·받는 쪽 제한·dotenv 값 전달을 확인한 뒤 산출물 점검 스크립트를 만듭니다.
왜 중요한가
산출물은 없으면 뒤 잡이 실패해야 맞고, 캐시는 없어도 모든 잡이 돌아야 맞습니다. 둘을 섞으면 캐시가 비는 날 배포 잡이 빈 디렉터리를 조용히 내보내거나, 만료 없는 산출물이 저장소 용량을 채웁니다. 캐시는 키를 무엇으로 만드느냐가 전부라서, 키를 고정 문자열로 두면 낡은 의존성을 끌고 다니고 소비자까지 올리게 두면 오염이 번집니다. 산출물은 '무엇이 올라갔나' 를 사람이 매번 확인할 수 없으니 점검을 자동화해 두는 편이 안전합니다.
단계
1. /root/glci-transfer 을 git 저장소로 만들고(.gitignore 에 .gitlab-ci-local/), .gitlab-ci.yml 에 stages [build, test, deploy] 와 잡 둘을 두세요. package(build)는 dist/app.tgz 와 dist/app.js.map 을 만들고 artifacts 로 dist/ 를 올리되 dist/*.map 은 제외하고 expire_in: 1 week 를 둡니다. ship(deploy)은 test -f dist/app.tgz && echo has-tgz, test -e dist/app.js.map && echo has-map || echo no-map, test -d public && echo has-public || echo no-public 를 실행합니다. 실행하면 ship 로그가 has-tgz·no-map·no-public 이어야 합니다.
2. 잡 docs(build)를 더해 public/index.html 을 만들고 artifacts 로 public/(expire_in 2 days)을 올리세요. ship 에는 dependencies: [package] 를 두어 package 의 산출물만 받게 합니다. 실행하면 ship 로그가 여전히 has-tgz·no-map·no-public 이어야 합니다.
3. requirements.txt 에 requests==2.32.3 한 줄을 두고 커밋 대상에 넣으세요. 잡 deps(build)는 cache 를 key: files: [requirements.txt], paths: [.deps/], policy: pull-push 로 두고, mkdir -p .deps 뒤 .deps/installed 가 있으면 cache-hit, 없으면 cache-miss 를 출력하고 그 파일을 만듭니다. 두 번 연속 실행하면 miss 다음 hit 이 나와야 합니다. 채점기는 사본에서 requirements.txt 를 바꿔 다시 miss 가 나는지도 봅니다.
4. 잡 둘을 더하세요. unit(test)은 deps 와 같은 키·경로의 cache 를 policy: pull 로 두고 .deps/installed 가 있으면 unit-hit, 없으면 unit-miss 를 출력한 뒤 .deps/junk 를 만듭니다. verify-cache(deploy)도 같은 캐시를 pull 로 받아 .deps/junk 가 있으면 junk-saved, 없으면 junk-not-saved 를 출력합니다. 실행하면 unit-hit 과 junk-not-saved 가 나와야 합니다.
5. deps 의 cache 를 목록 두 개로 바꾸세요. 첫째는 key: files: [requirements.txt] 에 prefix: py 를 더한 .deps/(pull-push), 둘째는 key: tools-$CI_COMMIT_REF_SLUG 인 .tools/ 입니다. deps 스크립트에 .tools/lint 가 있으면 tools-hit, 없으면 tools-miss 를 출력하고 만드는 줄을 더합니다. unit·verify-cache 의 키에도 같은 prefix 를 둡니다. 두 번 실행하면 둘째 실행에서 두 캐시가 모두 hit 이어야 합니다.
6. 잡 version(build)이 VERSION=1.4.2-${CI_COMMIT_SHORT_SHA} 한 줄을 build.env 에 쓰고 artifacts: reports: dotenv: build.env 로 올리게 하세요. 잡 announce(deploy)는 needs: [version] 으로 echo "version=$VERSION" 을 실행합니다. 실행하면 announce 로그가 version=1.4.2-<짧은 커밋 해시> 여야 합니다.
7. /root/glci-transfer/artifact-audit.sh <저장소> 를 만드세요. 저장소를 임시 사본으로 떠 모든 파일을 커밋하고 파이프라인을 돌린 뒤, 올라간 산출물(.gitlab-ci-local/artifacts 아래)에 *.map·*.env·*.pem 이나 1MiB 가 넘는 파일이 있으면 BAD <잡>/<경로> …(공백 구분, 정렬) 한 줄과 3, 없으면 OK 와 0, 파이프라인이 실패하면 ERROR 와 1 로 끝냅니다. 리포트(.gitlab-ci-reports/ 아래)는 점검에서 뺍니다. 원래 저장소에는 아무것도 남기지 않습니다. 채점기는 이 저장소(OK)와 산출물에 새는 파일을 넣은 사본으로 확인합니다.
참고
- 이 VM 에는 GitLab 서버와 러너가 없고, gitlab-ci-local 4.75.1 이 .gitlab-ci.yml 을 GitLab 과 같은 규칙으로 해석해 shell 로 잡을 실행합니다.
image:를 적으면 도커로 돌리려 하므로 쓰지 않습니다. 보호 변수·마스킹·CI_JOB_TOKEN·러너 태그·병합 요청 파이프라인 생성은 서버 기능이라 여기서 재현되지 않습니다. - 실행: 저장소 루트에서
gitlab-ci-local --shell-isolation --no-artifacts-to-source(잡마다 따로 된 작업 디렉터리, 산출물을 저장소에 되쓰지 않음), 잡 목록:gitlab-ci-local --list-csv-all, 해석된 설정:gitlab-ci-local --preview. gitlab-ci-local 은 git 이 추적하는 파일만 잡에 넘기므로 파일을 만들면git add하세요. 채점기는 저장소를 사본으로 떠 모든 파일을 커밋한 뒤 같은 도구로 다시 돌립니다. - 캐시는 저장소의
.gitlab-ci-local/cache/<키>에 쌓입니다. '러너가 바뀌어 캐시가 없는 상황' 은 이 디렉터리를 지우고 다시 돌려 보면 됩니다. artifacts:when: on_failure 는 gitlab-ci-local 에서 재현되지 않습니다(실측). - 흔한 실수: 소비자 잡을 pull-push 로 두어 매번 캐시를 다시 올리는 것. 흔한 실수: dotenv 로 넘긴 값을 뒤 잡의 rules 에서 쓰려는 것.
- [Caching in GitLab CI/CD](https://docs.gitlab.com/ci/caching/) · [Job artifacts](https://docs.gitlab.com/ci/jobs/job_artifacts/) · [artifacts:reports(dotenv)](https://docs.gitlab.com/ci/yaml/artifacts_reports/) · [CI/CD YAML syntax reference](https://docs.gitlab.com/ci/yaml/)
단계 7개
- 산출물은 필요한 것만 올린다
- 배포 잡은 쓰지도 않을 문서까지 받지 않는다
- 잠금 파일이 바뀌면 캐시도 바뀐다
- 소비자는 캐시를 받기만 한다
- 수명이 다른 캐시는 키를 나눈다
- 앞 잡이 계산한 값을 뒤 잡에 넘긴다
- 산출물에 들어가면 안 되는 파일을 잡아낸다