LabHub
배우기 러닝패스 코스

GitLab CI/CD

三つのサービスのうち一つしか変えていないのに全部ビルドされた

LabHub 에서 이어서 보기

한국어 원문으로 표시합니다.

목표

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: matrixPY"3.11"·"3.12", OSlinux·alpine 조합을 만들고, 스크립트는 dist/py$PY-$OS.txtpy=<PY> os=<OS> 를 쓰며 artifacts 로 dist/ 를 올립니다. 커밋하고 실행하세요. 잡 이름이 build: [3.11,linux] 꼴로 넷 생겨야 합니다.
  2. build 에 rules 를 더해 OSalpine 이고 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: apitrigger: 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.shservices/ 아래 디렉터리마다 잡 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 이 새로 생겨 돌아야 합니다. 채점기는 사본에 서비스 디렉터리를 하나 더 만들어서도 확인합니다.

참고

파이썬 둘·OS 둘, 잡 넷을 한 블록으로

/root/glci-mono 을 git 저장소로 만들고(.gitignore 에 .gitlab-ci-local/) .gitlab-ci.yml 에 stages [build, package, trigger, generate, dynamic] 와 잡 build(stage build)를 두세요. parallel: matrixPY"3.11"·"3.12", OSlinux·alpine 조합을 만들고, 스크립트는 dist/py$PY-$OS.txtpy=<PY> os=<OS> 를 쓰며 artifacts 로 dist/ 를 올립니다. 커밋하고 실행하세요. 잡 이름이 build: [3.11,linux] 꼴로 넷 생겨야 합니다.

matrix 의 한 항목 안에 여러 변수를 적으면 모든 조합이 잡이 됩니다. 버전 값은 따옴표로 감싸야 3.10 이 3.1 이 되는 일이 없습니다. 목록은 gitlab-ci-local --list-csv-all 로 봅니다.

지원하지 않는 조합 하나만 뺀다

build 에 rules 를 더해 OSalpine 이고 PY"3.11" 인 조합만 when: never, 나머지는 when: on_success 로 두세요. 커밋하고 실행하면 잡이 셋만 돌아야 합니다.

matrix 변수는 rules:if 에서 보통 CI/CD 변수처럼 쓸 수 있습니다. 조합을 빼려고 matrix 를 두 항목으로 쪼개면 늘어날 때마다 목록을 다시 계산해야 하지만, 규칙으로 빼면 예외만 적으면 됩니다.

조합 하나의 산출물만 기다리고 받는다

package-linux(stage package)를 더해 needs 로 build 의 PY: "3.12", OS: linux 조합 하나만 가리키고(needs:parallel:matrix) 스크립트는 ls dist 로 하세요. 커밋하고 실행하면 package-linux 로그에 py3.12-linux.txt 하나만 보여야 합니다.

matrix 로 생긴 잡을 needs 에 이름 그대로(build: [3.12,linux]) 적는 대신 parallel:matrix 로 변수 값을 적어 가리킵니다. 가리킨 조합의 산출물만 받습니다.

서비스 설정은 서비스 디렉터리에 둔다

/root/glci-mono/services/api/ci.yml 에 잡 api-unit(echo "api unit svc=$SVC")을 두고, 부모에 잡 trigger-api(stage trigger)를 더해 변수 SVC: apitrigger: include: services/api/ci.yml 을 두세요. 커밋하고 실행하면 자식 파이프라인의 api-unit 로그가 api unit svc=api 여야 합니다.

trigger 잡은 스크립트 대신 다른 파이프라인을 만듭니다. include 로 가리킨 파일이 자식 파이프라인 전체 설정이 되고, trigger 잡의 variables 는 자식에게 넘어갑니다. 서비스 팀이 자기 디렉터리의 파일만 고치면 되게 하는 구조입니다.

바뀐 서비스의 파이프라인만 켠다

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 만 목록에 있는지 봅니다.

모노레포에서 모든 서비스를 매번 빌드하면 파이프라인 시간이 서비스 수만큼 늘어납니다. changes 는 원격 기본 브랜치와 비교한 변경을 보므로(gitlab-ci-local 기준) 원격이 있어야 합니다.

저장소 구조를 읽어 자식 파이프라인을 만든다

/root/glci-mono/scripts/generate.shservices/ 아래 디렉터리마다 잡 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 이 돌아야 합니다.

자식 파이프라인의 설정을 잡이 실행 중에 만들 수 있습니다. 서비스가 늘 때마다 부모 설정을 고치는 대신 규칙(디렉터리 = 잡)을 스크립트에 둡니다. 생성된 설정 안의 include 에는 CI/CD 변수를 쓸 수 없다는 제약이 공식 문서에 있습니다.

서비스를 하나 더해도 부모 설정은 그대로

/root/glci-mono/services/billing/ci.yml 대신 README.md(내용 아무것이나)만 만들어 커밋·push 하세요. 부모 .gitlab-ci.yml 은 고치지 않습니다. 실행하면 동적 자식 파이프라인에 lint-billing 이 새로 생겨 돌아야 합니다. 채점기는 사본에 서비스 디렉터리를 하나 더 만들어서도 확인합니다.

동적 파이프라인의 규칙이 저장소 구조에 있으므로, 디렉터리가 생기는 것만으로 잡이 생깁니다. 반대로 services 아래에 서비스가 아닌 디렉터리를 두면 그것도 잡이 된다는 뜻이라 규칙을 문서로 남겨 두어야 합니다.