ステージ名のタイプミス一つでデプロイジョブが静かに消えた
한국어 원문으로 표시합니다.
목표
GitLab 설정 스키마로 검증하는 도구를 게이트로 감싸 팀 정책을 더하고, 커밋 전 훅·파이프라인 첫 잡·include 를 푼 저장소 전체 검증에 걸며, 병합 요청 리뷰용으로 두 커밋 사이 잡 목록의 변화를 뽑습니다.
왜 중요한가
파이프라인 설정의 오류는 대개 조용합니다. 스테이지 이름 오타나 들여쓰기 한 칸이 잡을 지우거나 파이프라인 생성을 막는데, 그 사실은 푸시한 뒤에야 드러납니다. 검증을 사람의 눈 대신 도구에 맡기되, 도구가 무엇을 잡고 무엇을 놓치는지 알아야 그 틈을 정책으로 메울 수 있습니다. 같은 검사를 커밋 전과 파이프라인 첫 잡 두 곳에 두는 이유는 로컬 훅은 건너뛸 수 있기 때문이고, 리뷰에서는 텍스트 diff 보다 '어떤 잡이 생기고 사라지고 자동이 되는가' 가 더 중요한 정보입니다.
단계
/root/glci-gate을 git 저장소로 만들고(.gitignore 에.gitlab-ci-local/)/root/glci-gate/ci-validate.sh <설정파일>을 만드세요. 그 파일 하나를 임시 git 저장소의.gitlab-ci.yml로 넣고gitlab-ci-local --list로 검증해, 거부되면INVALID <이유>(도구 출력에서 의미 있는 첫 줄)와 1, 통과하면VALID와 0 으로 끝냅니다..gitlab-ci.yml에는 아래 files 의 정상 설정을 두고 커밋하세요. 채점기는 없는 스테이지·허용 밖 when·없는 needs 대상·YAML 문법 오류 표본으로 확인합니다.- ci-validate.sh 가 도구의 검증을 통과한 파일에 팀 정책 두 가지를 더 적용하게 하세요. 한 잡에
rules와only/except가 함께 있으면POLICY <잡> rules-with-only-except, artifacts 에 paths 가 있는데expire_in이 없으면POLICY <잡> artifacts-without-expire_in을 잡 이름 순으로 한 줄씩 출력하고 2 로 끝냅니다. 정책을 모두 지키면VALID와 0 입니다. 숨김 잡(점으로 시작)과 예약 키(stages·variables·default·include·workflow 등)는 잡이 아닙니다. /root/glci-gate/hooks/pre-commit을 만들어 커밋하고, 같은 파일을.git/hooks/pre-commit으로 복사해 실행 권한을 주세요. 훅은 스테이징된.gitlab-ci.yml이 있을 때만 그 스테이징된 내용(git show :.gitlab-ci.yml)을 ci-validate.sh 로 검증해, 통과하지 않으면 이유를 표준 오류로 내고 커밋을 막습니다. 채점기는 사본에서 틀린 설정과 정책 위반 설정을 커밋해 보고, 설정과 무관한 파일 커밋은 막지 않는지도 봅니다./root/glci-gate/broken.yml에 아래 files 의 틀린 설정을 그대로 두고, ci-validate.sh 로 한 번에 하나씩 드러나는 문제를 고쳐/root/glci-gate/fixed.yml을 만드세요. 잡 이름(build·unit·deploy)과 각 잡의 script 는 바꾸지 않습니다. fixed.yml 은 VALID 여야 하고, unit 은 build 를 기다려야 하며, deploy 는 main 에서 수동 승인(allow_failure false)으로 만들어져야 합니다. 두 파일 모두 커밋합니다(훅은 .gitlab-ci.yml 만 봅니다)..gitlab-ci.yml에 잡ci-lint(stage.pre,bash ci-validate.sh .gitlab-ci.yml)를 더해 커밋하세요. 실행하면 ci-lint 가 VALID 로 통과하고 나머지 잡이 돌아야 합니다. 채점기는 사본에서 정책 위반(만료 없는 산출물)을 넣은 설정으로 돌려 ci-lint 가 실패하고 build 가 시작하지 않는지 봅니다./root/glci-gate/ci-validate-repo.sh <저장소>를 만드세요. 저장소를 임시 사본으로 떠(훅은 빼고) 커밋한 뒤gitlab-ci-local --preview로 include 가 풀린 합쳐진 설정을 얻고, 실패하면INVALID <이유>와 1, 성공하면 합쳐진 설정을 ci-validate.sh 에 넘겨 그 결과(VALID 0·POLICY 2)를 그대로 냅니다. 이 저장소에 돌리면 VALID 여야 합니다. 채점기는 include 한 파일 쪽에 오류·정책 위반을 넣은 사본으로 확인합니다./root/glci-gate/ci-diff.sh <저장소> <옛커밋> <새커밋> [브랜치]를 만드세요. 두 커밋을 각각 임시 작업 트리로 꺼내, 브랜치(기본 main)의 파이프라인으로 계산하도록--variable CI_COMMIT_BRANCH=<브랜치>를 준gitlab-ci-local --list-csv-all로 잡 이름과 when 을 얻고, 잡 이름 순으로ADDED <잡>,REMOVED <잡>,CHANGED <잡> <옛when>-><새when>을 출력합니다. 원래 저장소의 작업 트리와 브랜치는 건드리지 않고 임시 작업 트리는 지웁니다. 채점기는 사본에서 잡을 더하고 빼고 when 을 바꾼 커밋을 만들어 확인합니다.
참고
- 이 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 4.75.1 의 검증(실측): when 허용 값·없는 스테이지·없는 needs·없는 extends·YAML 문법·script 없음은 거부하고, rules 와 only 혼용·만료 없는 산출물은 통과시킵니다. GitLab 의 CI Lint(프로젝트의 파이프라인 편집기·Lint API)는 서버 기능이라 여기서 부를 수 없습니다.
- 훅이 커밋을 막는지 시험할 때는 사본 저장소에서 하세요. 원래 저장소에서 막힌 커밋은 스테이징만 남습니다.
- Validate GitLab CI/CD configuration(CI Lint) · CI Lint API · CI/CD YAML syntax reference · GitLab 설정 검증 소스(processable.rb) · gitlab-ci-local
스키마와 참조를 도구에게 검증시킨다
/root/glci-gate 을 git 저장소로 만들고(.gitignore 에 .gitlab-ci-local/) /root/glci-gate/ci-validate.sh <설정파일> 을 만드세요. 그 파일 하나를 임시 git 저장소의 .gitlab-ci.yml 로 넣고 gitlab-ci-local --list 로 검증해, 거부되면 INVALID <이유>(도구 출력에서 의미 있는 첫 줄)와 1, 통과하면 VALID 와 0 으로 끝냅니다. .gitlab-ci.yml 에는 아래 files 의 정상 설정을 두고 커밋하세요. 채점기는 없는 스테이지·허용 밖 when·없는 needs 대상·YAML 문법 오류 표본으로 확인합니다.
gitlab-ci-local 은 GitLab 의 설정 스키마로 먼저 검증하고, 스테이지·needs·extends 참조가 실제로 있는지도 봅니다. 문제가 있으면 종료 코드가 0 이 아닙니다. 출력에는 원격 저장소 안내 같은 잡음이 섞이니 걸러서 첫 이유만 남기세요.
도구가 통과시키는 것을 팀 정책으로 잡는다
ci-validate.sh 가 도구의 검증을 통과한 파일에 팀 정책 두 가지를 더 적용하게 하세요. 한 잡에 rules 와 only/except 가 함께 있으면 POLICY <잡> rules-with-only-except, artifacts 에 paths 가 있는데 expire_in 이 없으면 POLICY <잡> artifacts-without-expire_in 을 잡 이름 순으로 한 줄씩 출력하고 2 로 끝냅니다. 정책을 모두 지키면 VALID 와 0 입니다. 숨김 잡(점으로 시작)과 예약 키(stages·variables·default·include·workflow 등)는 잡이 아닙니다.
이 도구는 rules 와 only 를 섞은 잡과 만료 없는 산출물을 통과시킵니다(실측). GitLab 서버는 앞의 것을 key may not be used with rules 로 거부합니다(GitLab 소스의 설정 검증). 도구 하나의 판정을 게이트의 전부로 믿지 말고, 차이를 알면 그만큼 정책으로 메웁니다.
틀린 설정은 커밋부터 막는다
/root/glci-gate/hooks/pre-commit 을 만들어 커밋하고, 같은 파일을 .git/hooks/pre-commit 으로 복사해 실행 권한을 주세요. 훅은 스테이징된 .gitlab-ci.yml 이 있을 때만 그 스테이징된 내용(git show :.gitlab-ci.yml)을 ci-validate.sh 로 검증해, 통과하지 않으면 이유를 표준 오류로 내고 커밋을 막습니다. 채점기는 사본에서 틀린 설정과 정책 위반 설정을 커밋해 보고, 설정과 무관한 파일 커밋은 막지 않는지도 봅니다.
훅은 작업 트리 파일이 아니라 커밋될 내용(인덱스)을 봐야 합니다 — 고친 뒤 add 하지 않은 상태로 커밋하면 작업 트리는 멀쩡해도 커밋되는 것은 옛 내용입니다. .git/hooks 는 저장소에 올라가지 않으므로 팀에 나누려면 추적되는 위치에 두고 설치 방법을 안내합니다.
네 군데 틀린 설정을 고친다
/root/glci-gate/broken.yml 에 아래 files 의 틀린 설정을 그대로 두고, ci-validate.sh 로 한 번에 하나씩 드러나는 문제를 고쳐 /root/glci-gate/fixed.yml 을 만드세요. 잡 이름(build·unit·deploy)과 각 잡의 script 는 바꾸지 않습니다. fixed.yml 은 VALID 여야 하고, unit 은 build 를 기다려야 하며, deploy 는 main 에서 수동 승인(allow_failure false)으로 만들어져야 합니다. 두 파일 모두 커밋합니다(훅은 .gitlab-ci.yml 만 봅니다).
도구는 첫 오류에서 멈추므로 고칠 때마다 다시 돌려 다음 오류를 봅니다. 스테이지 이름 오타, 없는 잡을 가리키는 needs, 허용되지 않는 when 값, rules 와 only 의 혼용, 만료 없는 산출물이 섞여 있습니다.
파이프라인의 첫 잡이 자기 설정을 검증한다
.gitlab-ci.yml 에 잡 ci-lint(stage .pre, bash ci-validate.sh .gitlab-ci.yml)를 더해 커밋하세요. 실행하면 ci-lint 가 VALID 로 통과하고 나머지 잡이 돌아야 합니다. 채점기는 사본에서 정책 위반(만료 없는 산출물)을 넣은 설정으로 돌려 ci-lint 가 실패하고 build 가 시작하지 않는지 봅니다.
훅은 로컬에서 건너뛸 수 있으므로(--no-verify) 서버 쪽에도 같은 검사가 있어야 합니다. .pre 스테이지에 두면 다른 모든 잡보다 먼저 돌아 틀린 설정으로 러너 시간을 쓰기 전에 멈춥니다. 검사 스크립트는 저장소 안에 있으니 잡에서 그대로 부를 수 있습니다.
include 한 파일까지 합쳐서 검증한다
/root/glci-gate/ci-validate-repo.sh <저장소> 를 만드세요. 저장소를 임시 사본으로 떠(훅은 빼고) 커밋한 뒤 gitlab-ci-local --preview 로 include 가 풀린 합쳐진 설정을 얻고, 실패하면 INVALID <이유> 와 1, 성공하면 합쳐진 설정을 ci-validate.sh 에 넘겨 그 결과(VALID 0·POLICY 2)를 그대로 냅니다. 이 저장소에 돌리면 VALID 여야 합니다. 채점기는 include 한 파일 쪽에 오류·정책 위반을 넣은 사본으로 확인합니다.
파일 하나짜리 검증은 include 한 파일의 오류를 보지 못합니다 — 그 파일은 사본에 없으니까요. --preview 는 include·extends·앵커를 모두 푼 결과를 내므로, 그 결과에 정책을 적용하면 여러 파일에 흩어진 설정도 한 번에 검사됩니다.
리뷰어에게 파이프라인이 어떻게 바뀌는지 보여 준다
/root/glci-gate/ci-diff.sh <저장소> <옛커밋> <새커밋> [브랜치] 를 만드세요. 두 커밋을 각각 임시 작업 트리로 꺼내, 브랜치(기본 main)의 파이프라인으로 계산하도록 --variable CI_COMMIT_BRANCH=<브랜치> 를 준 gitlab-ci-local --list-csv-all 로 잡 이름과 when 을 얻고, 잡 이름 순으로 ADDED <잡>, REMOVED <잡>, CHANGED <잡> <옛when>-><새when> 을 출력합니다. 원래 저장소의 작업 트리와 브랜치는 건드리지 않고 임시 작업 트리는 지웁니다. 채점기는 사본에서 잡을 더하고 빼고 when 을 바꾼 커밋을 만들어 확인합니다.
설정 파일의 diff 는 include·extends·rules 가 섞이면 실제로 무엇이 바뀌는지 알려 주지 않습니다. 파이프라인이 만들 잡 목록끼리 비교하면 '이 병합으로 운영 배포가 자동이 된다' 같은 변화가 한 줄로 드러납니다. git worktree add --detach 로 커밋을 다른 디렉터리에 꺼낼 수 있는데, 그 상태에는 브랜치가 없어 브랜치 조건 rules 가 모두 빠지므로 브랜치를 변수로 넘깁니다. 그 경고는 --ignore-predefined-vars 로 끕니다.