GitLab CI/CD · 설정 언어로서의 .gitlab-ci.yml · 실습
서비스 셋 중 하나만 바꿨는데 전부 다시 빌드됐다
목표
parallel:matrix 로 조합만큼 잡을 늘리고 예외 조합과 특정 조합 기다리기를 다룬 뒤, 모노레포를 서비스별 자식 파이프라인과 저장소 구조에서 생성하는 동적 파이프라인으로 나눠 gitlab-ci-local 로 실행합니다.
왜 중요한가
설정이 커지는 방식은 대개 복붙입니다. 파이썬 버전이 하나 늘 때마다 잡을 복사하고, 서비스가 하나 늘 때마다 부모 파일에 블록을 붙이다 보면 한 파일에 모든 팀의 규칙이 쌓이고 모든 변경이 모든 서비스를 다시 빌드합니다. matrix 는 조합을 한 블록으로 줄이고, 자식 파이프라인은 서비스의 설정을 그 서비스의 디렉터리로 돌려보내며, changes 와 동적 생성은 바뀐 것만 돌게 합니다. 대신 잡 이름·산출물·변수가 어떻게 흘러가는지를 알아야 needs 와 규칙을 바르게 걸 수 있습니다.
단계
1. /root/glci-mono 을 git 저장소로 만들고(.gitignore 에 .gitlab-ci-local/) .gitlab-ci.yml 에 stages [build, package, trigger, generate, dynamic] 와 잡 build(stage build)를 두세요. parallel: matrix 로 PY 는 "3.11"·"3.12", OS 는 linux·alpine 조합을 만들고, 스크립트는 dist/py$PY-$OS.txt 에 py=<PY> os=<OS> 를 쓰며 artifacts 로 dist/ 를 올립니다. 커밋하고 실행하세요. 잡 이름이 build: [3.11,linux] 꼴로 넷 생겨야 합니다.
2. build 에 rules 를 더해 OS 가 alpine 이고 PY 가 "3.11" 인 조합만 when: never, 나머지는 when: on_success 로 두세요. 커밋하고 실행하면 잡이 셋만 돌아야 합니다.
3. 잡 package-linux(stage package)를 더해 needs 로 build 의 PY: "3.12", OS: linux 조합 하나만 가리키고(needs:parallel:matrix) 스크립트는 ls dist 로 하세요. 커밋하고 실행하면 package-linux 로그에 py3.12-linux.txt 하나만 보여야 합니다.
4. /root/glci-mono/services/api/ci.yml 에 잡 api-unit(echo "api unit svc=$SVC")을 두고, 부모에 잡 trigger-api(stage trigger)를 더해 변수 SVC: api 와 trigger: include: services/api/ci.yml 을 두세요. 커밋하고 실행하면 자식 파이프라인의 api-unit 로그가 api unit svc=api 여야 합니다.
5. services/web/ci.yml 에 잡 web-unit(echo "web unit svc=$SVC")을 두고 부모에 trigger-web(SVC web)을 더하세요. 두 trigger 잡에는 각각 rules: changes: 로 services/api/**/*·services/web/**/* 를 둡니다. /root/glci-mono-origin.git 에 bare 저장소를 만들어 origin 으로 등록하고, 커밋한 main 을 push 한 뒤 git remote set-head origin main 을 실행하세요. 채점기는 사본에서 web 만 고친 브랜치를 만들어 trigger-web 만 목록에 있는지 봅니다.
6. /root/glci-mono/scripts/generate.sh 가 services/ 아래 디렉터리마다 잡 lint-<이름>(echo "lint <이름>")을 담은 YAML 을 표준 출력으로 내게 하세요. 부모에 잡 generate(stage generate)가 그 출력을 generated.yml 로 저장해 artifacts 로 올리고, 잡 run-generated(stage dynamic, needs: [generate])가 trigger: include: - artifact: generated.yml, job: generate 로 그것을 자식 파이프라인으로 돌리게 합니다. 커밋·push 하고 실행하면 lint-api·lint-web 이 돌아야 합니다.
7. /root/glci-mono/services/billing/ 에 ci.yml 대신 README.md(내용 아무것이나)만 만들어 커밋·push 하세요. 부모 .gitlab-ci.yml 은 고치지 않습니다. 실행하면 동적 자식 파이프라인에 lint-billing 이 새로 생겨 돌아야 합니다. 채점기는 사본에 서비스 디렉터리를 하나 더 만들어서도 확인합니다.
참고
- 이 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 의 차이(실측): 자식 파이프라인은 실험 기능이고 trigger:strategy 를 따르지 않아 자식이 실패해도 trigger 잡이 성공으로 끝납니다.
--variable은 자식에게 넘어가지 않고 trigger 잡의 variables 만 넘어갑니다. - 자식 파이프라인 잡의 로그도
.gitlab-ci-local/output/<잡이름>.log에 남습니다. - [parallel:matrix(Job control)](https://docs.gitlab.com/ci/jobs/job_control/) · [Downstream pipelines](https://docs.gitlab.com/ci/pipelines/downstream_pipelines/) · [needs](https://docs.gitlab.com/ci/yaml/needs/) · [Specify when jobs run with rules](https://docs.gitlab.com/ci/jobs/job_rules/)
단계 7개
- 파이썬 둘·OS 둘, 잡 넷을 한 블록으로
- 지원하지 않는 조합 하나만 뺀다
- 조합 하나의 산출물만 기다리고 받는다
- 서비스 설정은 서비스 디렉터리에 둔다
- 바뀐 서비스의 파이프라인만 켠다
- 저장소 구조를 읽어 자식 파이프라인을 만든다
- 서비스를 하나 더해도 부모 설정은 그대로