InfoGrab DocsInfoGrab Docs

CI 구성 내부 구조

요약

GitLab 프로젝트의 파이프라인은 GitLab CI/CD의 workflow:rules 키워드 기능으로 생성됩니다. 다음 시나리오에서는 항상 파이프라인이 생성됩니다. 파이프라인 생성은 다음 CI/CD 변수에도 영향을 받습니다.

워크플로 규칙#

GitLab 프로젝트의 파이프라인은 GitLab CI/CD의 workflow:rules 키워드 기능으로 생성됩니다.

다음 시나리오에서는 항상 파이프라인이 생성됩니다.

  • 스케줄, 푸시, 병합 등을 포함한 main 브랜치입니다.
  • 머지 리퀘스트입니다.
  • 태그입니다.
  • 스테이블, auto-deploy, 보안 브랜치입니다.

파이프라인 생성은 다음 CI/CD 변수에도 영향을 받습니다.

  • $FORCE_GITLAB_CI가 설정되어 있으면 파이프라인이 생성됩니다. 사용을 권장하지 않습니다. $FORCE_GITLAB_CI 사용 지양을 참고합니다.
  • $GITLAB_INTERNAL이 설정되어 있지 않으면 파이프라인이 생성되지 않습니다.

그 밖의 경우에는 파이프라인이 생성되지 않습니다(예를 들어 MR이 없는 브랜치를 푸시하는 경우입니다).

이 워크플로 규칙의 단일 진실 공급원(Single Source Of Truth, SSOT)은 .gitlab-ci.yml에 정의되어 있습니다.

$FORCE_GITLAB_CI 사용 지양#

파이프라인은 매우 복잡하므로 어떤 종류의 파이프라인을 트리거하려는지 명확히 이해해야 합니다. 어떤 job을 실행해야 하고 어떤 job을 실행하지 않아야 하는지 알아야 합니다.

$FORCE_GITLAB_CI로 파이프라인을 강제로 트리거하면 그 파이프라인이 어떤 종류인지 정확히 알 수 없습니다. 그 결과 원하는 job을 실행하지 못하거나, 관심 없는 job을 너무 많이 실행할 수 있습니다.

더 많은 컨텍스트와 배경은 다음에서 확인할 수 있습니다. 예상치 못한 실행을 막기 위한 일괄 변경 지양

현재 이를 사용하는 곳의 목록은 다음과 같으며, $FORCE_GITLAB_CI 사용에서 벗어나도록 노력해야 합니다.

$FORCE_GITLAB_CI를 사용하지 않고 파이프라인을 활성화하는 방법은 다음 섹션을 참고합니다.

$FORCE_GITLAB_CI의 대안#

기본적으로 서로 다른 파이프라인을 활성화하기 위해 서로 다른 변수를 사용합니다. 그 예가 $START_AS_IF_FOSS입니다. 크로스 프로젝트 FOSS 파이프라인을 트리거하려면 $START_AS_IF_FOSS와 함께 $ENABLE_RSPEC_UNIT, $ENABLE_RSPEC_SYSTEM 등의 다른 변수들을 설정해, as-if-foss 크로스 프로젝트 다운스트림 파이프라인에서 실행하려는 각 job을 활성화합니다.

$FORCE_GITLAB_CI보다 이 방식이 나은 점은 $START_AS_IF_FOSS가 이 목적으로만 사용되기 때문에 파이프라인을 실행할 방식을 완전히 제어할 수 있고, 이 변수 아래에서 파이프라인이 동작하는 방식을 바꾸어도 다른 유형의 파이프라인에는 영향을 주지 않는다는 점입니다. 반면 $FORCE_GITLAB_CI는 여러 목적으로 사용되기 때문에 그 파이프라인이 정확히 무엇인지 알 수 없습니다.

기본 이미지#

기본 이미지는 .gitlab-ci.yml에 정의되어 있습니다.

여기에는 Ruby, Go, Git, Git LFS, Chrome, Node, Yarn, PostgreSQL, Graphics Magick이 포함됩니다.

파이프라인에서 사용하는 이미지는 gitlab-org/gitlab-build-images 프로젝트에서 구성되며, 이 프로젝트는 중복성을 위해 gitlab/gitlab-build-images로 푸시 미러링됩니다.

빌드 이미지의 현재 버전은 "Used by GitLab section"에서 확인할 수 있습니다.

기본 변수#

사전 정의된 CI/CD 변수 외에도 각 파이프라인에는 .gitlab-ci.yml에 정의된 기본 변수가 포함됩니다.

변수 명명 규칙#

2025년 3월부터 모놀리스 CI 파이프라인에만 사용되는 새 환경 변수에는 GLCI_ 접두사를 붙이기 시작했습니다.

이를 통해 환경 변수가 CI용(GLCI_)인지, 제품용(GITLAB_)인지, GitLab이 소유하지 않은 도구 및 시스템용인지 추적할 수 있습니다. 이는 파이프라인 구성에서 환경 변수 변경의 영향을 더 잘 평가하는 데 도움이 됩니다.

필수 CI 변수#

일부 CI/CD job은 실행에 특정 변수가 필요합니다. 선택적 변수와 달리 이 변수들이 정의되어 있지 않으면 해당 job은 전부 건너뜁니다.

GLCI_MEDIUM_RUNNER_REQUIRED#

이 변수는 최소 4코어와 16GB RAM을 갖춘 러너가 필요한 시스템(기능) 테스트 job을 활성화합니다. Chrome 버전 133 이상은 안정적으로 실행되기 위해 추가 컴퓨팅 리소스가 필요합니다. 그렇지 않으면 PostgreSQL 데이터베이스와 Rails 애플리케이션에 리소스가 부족해 시스템 테스트 job이 예측할 수 없게 불안정해집니다.

CI/CD 설정에서 이 변수를 정의하고, 최소 4코어와 16GB RAM을 갖춘 러너 태그로 설정합니다. 전체 구성 세부 정보는 이 변수를 도입한 MR의 테스트 섹션을 참고합니다.

정의되어 있지 않으면(기본값은 빈 문자열) 시스템 테스트 job이 실행되지 않습니다. 이렇게 하면 기여자의 개인 포크처럼 러너 용량이 충분하지 않은 환경에서 리소스를 많이 소모하는 테스트가 실행되는 것을 방지합니다.

시스템 테스트를 실행해야 하는 환경에는 다음이 포함되며, 이 변수가 필요합니다.

  • 정규 gitlab-org/gitlab 프로젝트
  • GitLab Community 포크
  • 보안 포크
  • dev.gitlab.org 포크

환경별 세부 정보는 같은 MR의 테스트 환경 섹션을 참고합니다.

Stage#

현재 Stage는 다음과 같습니다.

  • sync: 이 stage는 gitlab-org/gitlab의 변경 사항을 gitlab-org/gitlab-foss로 동기화하는 데 사용됩니다.
  • prepare: 이 stage에는 이후 stage의 job에 필요한 아티팩트를 준비하는 job이 포함됩니다.
  • build-images: 이 stage에는 이후 stage의 job이나 다운스트림 파이프라인에 필요한 Docker 이미지를 준비하는 job이 포함됩니다.
  • fixtures: 이 stage에는 프론트엔드 테스트에 필요한 픽스처를 준비하는 job이 포함됩니다.
  • lint: 이 stage에는 린팅과 정적 분석 job이 포함됩니다.
  • test: 이 stage에는 대부분의 테스트와 DB/마이그레이션 job이 포함됩니다.
  • post-test: 이 stage에는 test stage의 job에서 데이터를 수집하거나 리포트를 빌드하는 job이 포함됩니다(예를 들어 커버리지, Knapsack 메타데이터 등입니다). Docs Review App job도 포함됩니다.
  • qa: 이 stage에는 review stage에서 배포된 Review App에 대해 QA 작업을 수행하는 job이 포함됩니다.
  • post-qa: 이 stage에는 qa stage의 job에서 데이터를 수집하거나 리포트를 빌드하는 job이 포함됩니다(예를 들어 Review App 성능 리포트입니다).
  • pages: 이 stage에는 여러 리포트를 GitLab Pages로 배포하는 job이 포함됩니다(예를 들어 coverage-ruby와 webpack-report가 있습니다. webpack-report는 https://gitlab-org.gitlab.io/gitlab/webpack-report/에 있으나 배포에 문제가 있습니다).
  • notify: 이 stage에는 여러 실패를 Slack으로 알리는 job이 포함됩니다.

Dependency Proxy#

일부 job은 Docker Hub의 이미지를 사용하며, 이때 이미지 경로 앞에 ${GITLAB_DEPENDENCY_PROXY_ADDRESS}를 접두사로 사용해 Dependency Proxy에서 이미지를 가져옵니다. 기본적으로 이 변수는 ${GITLAB_DEPENDENCY_PROXY}의 값으로 설정됩니다.

  • CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX는 Dependency Proxy를 통해 이미지를 가져올 최상위 그룹 이미지 접두사를 제공하는 GitLab 사전 정의 CI/CD 변수입니다.
  • GITLAB_DEPENDENCY_PROXY는 gitlab-org 그룹과 gitlab-com 그룹의 CI/CD 변수입니다. ${CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX}/로 정의되어 있습니다.
  • GITLAB_DEPENDENCY_PROXY_ADDRESS는 gitlab-org/gitlab 프로젝트에 정의되어 있습니다. 기본값은 "${GITLAB_DEPENDENCY_PROXY}"이지만 일부 경우에는 재정의됩니다(아래 워크어라운드 섹션을 참고합니다).

gitlab-org/gitlab에서는 워크어라운드 때문에 GITLAB_DEPENDENCY_PROXY_ADDRESS를 사용합니다. gitlab-org 그룹과 gitlab-com 그룹의 그 밖의 모든 곳에서는 Dependency Proxy를 사용하기 위해 GITLAB_DEPENDENCY_PROXY를 사용해야 합니다. 그 외의 프로젝트에서는 CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX 사전 정의 CI/CD 변수로 Dependency Proxy를 활성화할 수 있습니다.

# In the gitlab-org/gitlab project
image: ${GITLAB_DEPENDENCY_PROXY_ADDRESS}alpine:edge

# In any other project in gitlab-org and gitlab-com groups
image: ${GITLAB_DEPENDENCY_PROXY}alpine:edge

# In projects outside of gitlab-org and gitlab-com groups
image: ${CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX}/alpine:edge

그 밖의 개인 네임스페이스나 그룹에 있는 포크는 해당 위치에 GITLAB_DEPENDENCY_PROXY도 정의되어 있지 않으면 Docker Hub로 폴백합니다.

프로젝트 액세스 토큰 사용자가 파이프라인을 시작할 때의 워크어라운드#

프로젝트 액세스 토큰 사용자가 파이프라인을 시작하면(예를 들어 메인 프로젝트에서 사용하는 Gitaly 버전을 자동으로 업데이트하는 release-tools approver bot 사용자입니다) Dependency proxy에 액세스할 수 없어 Preparing the "docker+machine" executor 단계에서 job이 실패합니다. 이를 우회하기 위해 ${GITLAB_DEPENDENCY_PROXY_ADDRESS} 변수를 재정의하는 특별한 워크플로 규칙을 두어, 이 경우에는 Dependency proxy를 사용하지 않도록 합니다.

- if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH && $GITLAB_USER_LOGIN =~ /project_\d+_bot\d*/'
  variables:
    GITLAB_DEPENDENCY_PROXY_ADDRESS: ""
Note

그룹 수준 변수가 .gitlab-ci.yml 변수보다 우선순위가 높기 때문에 ${GITLAB_DEPENDENCY_PROXY} 변수를 직접 재정의하지는 않습니다.

외부 CI/CD 시크릿#

https://gitlab.com/groups/gitlab-org/quality/engineering-productivity/-/epics/46의 일환으로 2024년 2월에 ADD_JH_FILES_TOKEN CI 변수를 저장하기 위해 GCP Secret Manager 사용을 도그푸딩하기 시작했습니다.

이 작업의 일환으로 qual-ci-secret-mgmt-e78c9b95 GCP 프로젝트가 생성되었습니다.

공통 job 정의#

대부분의 job은 .gitlab/ci/global.gitlab-ci.yml에 정의된 몇 개의 CI 정의를 확장합니다. 이 정의들은 각각 하나의 구성 키워드로 범위가 한정됩니다.

Job 정의 설명
.default-retry unknown_failure, api_failure, runner_system_failure, job_execution_timeout, stuck_or_timeout_failure 발생 시 job이 재시도할 수 있게 합니다.
.default-before_script 데이터베이스가 실행 중이어야 할 수 있는 Ruby/Rails 작업(예를 들어 테스트입니다)에 적합한 기본 before_script 정의를 job이 사용할 수 있게 합니다.
.repo-from-artifacts job이 클론하는 대신 clone-gitlab-repo의 아티팩트에서 리포지터리를 가져올 수 있게 합니다. 이렇게 하면 GitLab.com Gitaly 부하가 줄고, 아티팩트에서 다운로드하는 것이 클론보다 빠르므로 속도도 약간 향상됩니다. 단, needs: []가 있는 job에는 사용하지 않아야 합니다. 그렇지 않으면 더 늦게 시작하는데, 보통은 모든 job이 가능한 한 빨리 시작되기를 원하기 때문입니다. 클론하는 것보다 더 오래 기다리지 않도록, 다른 의존성이 있는 job에만 사용합니다. 이 동작은 CI_FETCH_REPO_GIT_STRATEGY로 제어할 수 있습니다. 자세한 내용은 Gitaly에서 클론/페치하는 대신 아티팩트로 리포지터리 가져오기를 참고합니다.
.setup-test-env-cache 이후 Ruby/Rails 작업을 위한 테스트 환경 설정에 적합한 기본 cache 정의를 job이 사용할 수 있게 합니다.
.ruby-cache Ruby 작업에 적합한 기본 cache 정의를 job이 사용할 수 있게 합니다.
.static-analysis-cache 정적 분석 작업에 적합한 기본 cache 정의를 job이 사용할 수 있게 합니다.
.qa-cache QA 작업에 적합한 기본 cache 정의를 job이 사용할 수 있게 합니다.
.yarn-cache yarn install을 수행하는 프론트엔드 job에 적합한 기본 cache 정의를 job이 사용할 수 있게 합니다.
.assets-compile-cache 에셋을 컴파일하는 프론트엔드 job에 적합한 기본 cache 정의를 job이 사용할 수 있게 합니다.
.use-pg16 job이 postgres 16, redis, rediscluster 서비스를 사용할 수 있게 합니다(서비스의 구체적인 버전은 .gitlab/ci/global.gitlab-ci.yml을 참고합니다).
.use-pg16-ee .use-pg16과 동일하지만 elasticsearch 서비스도 사용합니다(서비스의 구체적인 버전은 .gitlab/ci/global.gitlab-ci.yml을 참고합니다).
.use-pg17 job이 postgres 17, redis, rediscluster 서비스를 사용할 수 있게 합니다(서비스의 구체적인 버전은 .gitlab/ci/global.gitlab-ci.yml을 참고합니다).
.use-pg17-ee .use-pg17과 동일하지만 elasticsearch 서비스도 사용합니다(서비스의 구체적인 버전은 .gitlab/ci/global.gitlab-ci.yml을 참고합니다).
.use-pg18 job이 postgres 18, redis, rediscluster 서비스를 사용할 수 있게 합니다(서비스의 구체적인 버전은 .gitlab/ci/global.gitlab-ci.yml을 참고합니다).
.use-pg18-ee .use-pg18과 동일하지만 elasticsearch 서비스도 사용합니다(서비스의 구체적인 버전은 .gitlab/ci/global.gitlab-ci.yml을 참고합니다).
.use-buildx job이 docker buildx 도구로 Docker 이미지를 빌드할 수 있게 합니다.
.as-if-foss FOSS_ONLY='1' CI/CD 변수를 설정해 FOSS 프로젝트를 시뮬레이션합니다.
.use-docker-in-docker job이 Docker in Docker를 사용할 수 있게 합니다. 자세한 내용은 CI/CD 구성에 관한 핸드북을 참고합니다.

rules, if: 조건과 changes: 패턴#

rules 키워드를 광범위하게 사용합니다.

모든 rules 정의는 rules.gitlab-ci.yml에 정의되어 있고, extends를 통해 개별 job에 포함됩니다.

rules 정의는 if: 조건과 changes: 패턴으로 구성되며, 이들 역시 rules.gitlab-ci.yml에 정의되어 YAML 앵커를 통해 rules 정의에 포함됩니다

if: 조건#

if: 조건 설명 참고
if-not-canonical-namespace 프로젝트가 정규(gitlab-org/, gitlab-cn/) 또는 보안(gitlab-org/security) 네임스페이스에 없으면 일치합니다. 포크용 job을 생성할 때(when: on_success 또는 when: manual 사용) 또는 포크용 job을 생성하지 않을 때(when: never 사용) 사용합니다.
if-not-ee 프로젝트가 EE가 아니면(즉 프로젝트 이름이 gitlab 또는 gitlab-ee가 아니면) 일치합니다. FOSS 프로젝트에서만 job을 생성할 때(when: on_success 또는 when: manual 사용) 또는 프로젝트가 EE이면 job을 생성하지 않을 때(when: never 사용) 사용합니다.
if-not-foss 프로젝트가 FOSS가 아니면(즉 프로젝트 이름이 gitlab-foss, gitlab-ce, gitlabhq가 아니면) 일치합니다. EE 프로젝트에서만 job을 생성할 때(when: on_success 또는 when: manual 사용) 또는 프로젝트가 FOSS이면 job을 생성하지 않을 때(when: never 사용) 사용합니다.
if-default-refs 파이프라인이 master, main, /^[\d-]+-stable(-ee)?$/(스테이블 브랜치), /^\d+-\d+-auto-deploy-\d+$/(auto-deploy 브랜치), /^security\//(보안 브랜치), 머지 리퀘스트, 태그에 대한 것이면 일치합니다. 이 기본 구성에서는 브랜치에 대해 job이 생성되지 않습니다.
if-master-refs 현재 브랜치가 master 또는 main이면 일치합니다.
if-master-push 현재 브랜치가 master 또는 main이고 파이프라인 소스가 push이면 일치합니다.
if-master-schedule-maintenance 현재 브랜치가 master 또는 main이고 파이프라인이 2시간 간격 스케줄로 실행되면 일치합니다.
if-master-schedule-nightly 현재 브랜치가 master 또는 main이고 파이프라인이 야간 스케줄로 실행되면 일치합니다.
if-auto-deploy-branches 현재 브랜치가 auto-deploy 브랜치이면 일치합니다.
if-master-or-tag 파이프라인이 master 또는 main 브랜치나 태그에 대한 것이면 일치합니다.
if-merge-request 파이프라인이 머지 리퀘스트에 대한 것이면 일치합니다.
if-merge-request-title-as-if-foss 파이프라인이 머지 리퀘스트에 대한 것이고 해당 MR에 ~"pipeline:run-as-if-foss" 레이블이 있으면 일치합니다.
if-merge-request-title-update-caches 파이프라인이 머지 리퀘스트에 대한 것이고 해당 MR에 ~"pipeline:update-cache" 레이블이 있으면 일치합니다.
if-merge-request-labels-run-all-rspec 파이프라인이 머지 리퀘스트에 대한 것이고 해당 MR에 ~"pipeline:run-all-rspec" 레이블이 있으면 일치합니다.
if-security-merge-request 파이프라인이 보안 머지 리퀘스트에 대한 것이면 일치합니다.
if-security-schedule 파이프라인이 보안 스케줄 파이프라인이면 일치합니다.
if-nightly-master-schedule 파이프라인이 $NIGHTLY가 설정된 master 스케줄 파이프라인이면 일치합니다.
if-dot-com-gitlab-org-schedule job 생성을 GitLab.com의 gitlab-org 그룹에 대한 스케줄 파이프라인으로 한정합니다.
if-dot-com-gitlab-org-master job 생성을 GitLab.com의 gitlab-org 그룹에 대한 master 또는 main 브랜치로 한정합니다.
if-dot-com-gitlab-org-merge-request job 생성을 GitLab.com의 gitlab-org 그룹에 대한 머지 리퀘스트로 한정합니다.
if-dot-com-ee-schedule job을 GitLab.com의 gitlab-org/gitlab 프로젝트에 대한 스케줄 파이프라인으로 한정합니다.

changes: 패턴#

changes: 패턴 설명
ci-patterns CI 구성 관련 변경에 대해서만 job을 생성합니다.
ci-build-images-patterns build-images stage와 관련된 CI 구성 변경에 대해서만 job을 생성합니다.
ci-review-patterns review stage와 관련된 CI 구성 변경에 대해서만 job을 생성합니다.
ci-qa-patterns qa stage와 관련된 CI 구성 변경에 대해서만 job을 생성합니다.
yaml-lint-patterns YAML 관련 변경에 대해서만 job을 생성합니다.
docs-patterns 문서 관련 변경에 대해서만 job을 생성합니다.
frontend-dependency-patterns 프론트엔드 의존성이 업데이트될 때(예를 들어 package.json, yarn.lock 변경입니다)만 job을 생성합니다.
frontend-patterns-for-as-if-foss FOSS에 영향을 주는 프론트엔드 관련 변경에 대해서만 job을 생성합니다.
backend-patterns 백엔드 관련 변경에 대해서만 job을 생성합니다.
db-patterns DB 관련 변경에 대해서만 job을 생성합니다.
backstage-patterns 백스테이지 관련 변경(즉 Danger, 픽스처, RuboCop, 스펙입니다)에 대해서만 job을 생성합니다.
code-patterns 코드 관련 변경에 대해서만 job을 생성합니다.
qa-patterns QA 관련 변경에 대해서만 job을 생성합니다.
code-backstage-patterns code-patterns와 backstage-patterns의 조합입니다.
code-qa-patterns code-patterns와 qa-patterns의 조합입니다.
code-backstage-qa-patterns code-patterns, backstage-patterns, qa-patterns의 조합입니다.
static-analysis-patterns Static Analytics 구성 관련 변경에 대해서만 job을 생성합니다.

커스텀 종료 코드#

아래 표는 자동 재시도에 사용하는 커스텀 종료 코드를 나열합니다(정의는 GitLab 전역 CI 구성의 재시도 규칙을 참고합니다).

종료 코드 설명
112 알려진 불안정 테스트가 감지되었습니다. job 전체를 재시도합니다.
201 디스크 공간 부족입니다.

새로운 실패 패턴이 나타나면 이 목록을 확장할 수 있습니다. 충돌을 피하기 위해 201-255 범위의 종료 코드를 사용합니다.

모범 사례#

extends:, <<: *xyz(YAML 앵커), !reference 사용 시기#

참고 자료

핵심 요점#

  • 해시를 확장해야 하면 extends를 사용합니다
  • 배열을 확장해야 하면 !reference를 사용하고, 최후의 수단으로 YAML anchors를 사용합니다
  • 더 복잡한 경우(예를 들어 배열 안의 해시 확장, 해시 안의 배열 확장 등입니다)에는 !reference 또는 YAML anchors를 사용해야 합니다

extends와 YAML anchors로 할 수 있는 것#

extends#
  • 해시에 대한 딥 머지입니다
  • 배열은 머지되지 않습니다. 덮어씁니다(출처)
YAML 앵커#
  • 해시에 대한 딥 머지는 되지 않지만, 해시를 확장하는 데 사용할 수 있습니다(아래 예시를 참고합니다)
  • 배열은 머지되지 않지만, 배열을 확장하는 데 사용할 수 있습니다(아래 예시를 참고합니다)

좋은 예시#

이 예시는 !reference와 YAML anchors로 복잡한 YAML 데이터 구조를 확장하는 방법을 보여 줍니다.

.strict-ee-only-rules:
  # `rules` is an array of hashes
  rules:
    - if: '$CI_PROJECT_NAME !~ /^gitlab(-ee)?$/ '
      when: never

# `if-security-merge-request` is a hash
.if-security-merge-request: &if-security-merge-request
  if: '$CI_PROJECT_NAMESPACE == "gitlab-org/security"'

# `code-qa-patterns` is an array
.code-qa-patterns: &code-qa-patterns
  - "{package.json,yarn.lock}"
  - ".browserslistrc"
  - "babel.config.js"
  - "jest.config.{base,integration,unit}.js"

.qa:rules:as-if-foss:
  rules:
    # We extend the `rules` array with an array of hashes directly
    - !reference [".strict-ee-only-rules", rules]
    # We extend a single array entry with a hash
    - <<: *if-security-merge-request
      # `changes` is an array, so we pass it an entire array
      changes: *code-qa-patterns

qa:selectors-as-if-foss:
  # We include the rules from .qa:rules:as-if-foss in this job
  extends:
    - .qa:rules:as-if-foss

.fast-no-clone-job job 확장#

정규 프로젝트의 브랜치를 다운로드하는 데는 20초에서 30초가 걸립니다.

일부 job은 제한된 수의 파일만 필요하며, 이 파일들은 GitLab API로 다운로드할 수 있습니다.

job에 다음 패턴을 추가하면 job의 git clone/git fetch를 건너뛸 수 있습니다.

시나리오 1: job에 before_script가 정의되어 있지 않은 경우#

이는 해당 job이 확장하는 상위 섹션에도 적용됩니다.

.fast-no-clone-job을 확장하기만 하면 됩니다.

변경 전:

  # Note: No `extends:` is present in the job
  a-job:
    script:
      - source scripts/rspec_helpers.sh scripts/slack
      - echo "No need for a git clone!"

변경 후:

  # Note: No `extends:` is present in the job
  a-job:
    extends:
      - .fast-no-clone-job
    variables:
      FILES_TO_DOWNLOAD: >
        scripts/rspec_helpers.sh
        scripts/slack
    script:
      - source scripts/rspec_helpers.sh scripts/slack
      - echo "No need for a git clone!"

시나리오 2: job(또는 그 job이 확장하는 job)에 before_script 블록이 이미 정의되어 있는 경우#

이 시나리오에서는 다음을 수행해야 합니다.

  1. 첫 번째 시나리오와 같이 .fast-no-clone-job을 확장합니다(이렇게 하면 FILES_TO_DOWNLOAD 변수가 다른 변수들과 머지됩니다)
  2. .fast-no-clone-job의 before_script 섹션이 이 job에 사용하는 before_script에서 참조되도록 합니다.

변경 전:

  .base-job:
    before_script:
      echo "Hello from .base-job"

  a-job:
    extends:
      - .base-job
    script:
      - source scripts/rspec_helpers.sh scripts/slack
      - echo "No need for a git clone!"

변경 후:

  .base-job:
    before_script:
      echo "Hello from .base-job"

  a-job:
    extends:
      - .base-job
      - .fast-no-clone-job
    variables:
      FILES_TO_DOWNLOAD: >
        scripts/rspec_helpers.sh
        scripts/slack
    before_script:
      - !reference [".fast-no-clone-job", before_script]
      - !reference [".base-job", before_script]
    script:
      - source scripts/rspec_helpers.sh scripts/slack
      - echo "No need for a git clone!"

주의 사항#

  • 스크립트가 리포지터리에 액세스하기 위해 git에 의존하는 경우 이 패턴은 동작하지 않습니다. 클론이나 페치를 하지 않으면 리포지터리가 없기 때문입니다.
  • 이 패턴을 사용하는 job에는 curl을 사용할 수 있어야 합니다.
  • FILES_TO_DOWNLOAD에 scripts/utils.sh를 나열하지 않습니다. .fast-no-clone-job이 항상 before_script에서 이 파일을 다운로드하므로, 다시 나열하면 같은 파일을 두 번 다운로드합니다.
  • job에서 bundle install을 실행해야 하면(BUNDLE_ONLY를 사용하는 경우에도) 다음을 수행해야 합니다.
    • gitlab-org/gitlab 프로젝트에 저장된 젬을 다운로드합니다.
      • 이를 위해 download_local_gems 셸 명령을 사용할 수 있습니다.
    • Gemfile과 Gemfile.lock을 포함합니다

이 패턴이 사용되는 곳#

  • 현재 이 패턴은 다음 job에 사용하며, 이 job들은 비공개 리포지터리를 차단하지 않습니다.
    • set-pipeline-name은 다음에 사용합니다.
      • scripts/pipeline/set_pipeline_name.rb
    • pre-merge-checks는 다음에 사용합니다.
      • scripts/pipeline/pre_merge_checks.rb
    • prepare-as-if-foss-env는 다음에 사용합니다.
      • scripts/setup/generate-as-if-foss-env.rb
    • retrieve-tests-metadata는 다음에 사용합니다.
      • scripts/setup/tests-metadata.rb

또한 이 패턴을 사용하면 scripts/utils.sh는 항상 API에서 다운로드됩니다(이 파일에는 .fast-no-clone-job의 코드가 들어 있습니다).

러너 태그#

GitLab.com에서는 비특권 러너와 특권 러너를 모두 사용할 수 있습니다. gitlab-org 그룹의 프로젝트와 그 프로젝트의 포크에는 job에 다음 태그 중 하나만 추가해야 합니다.

  • gitlab-org: 기본값이지만, job을 특권 모드로 실행할 필요가 없는 경우에만 사용합니다.
  • gitlab-org-docker: job을 특권 모드로 실행해야 하는 경우에 사용합니다. Docker-in-Docker 지원이 필요하면 gitlab-org 대신 gitlab-org-docker를 사용합니다.

gitlab-org-docker 태그는 위의 .use-docker-in-docker job 정의가 추가합니다.

포크와의 호환성을 보장하기 위해 gitlab-org와 gitlab-org-docker를 동시에 사용하지 않습니다. gitlab-org 태그와 gitlab-org-docker 태그를 모두 가진 인스턴스 러너는 없습니다. gitlab-org 프로젝트의 포크에서는 두 태그를 모두 지정하면 일치하는 러너가 없어 job이 멈춥니다.

자세한 내용은 GitLab Repositories 핸드북 페이지를 참고합니다.

정규 프로젝트에서 gitlab Ruby 젬 사용#

정규 프로젝트에서 require 'gitlab'을 호출하면, $LOAD_PATH에 lib가 있을 때 lib/gitlab.rb 파일을 require합니다. 이는 애플리케이션(config/application.rb)이나 테스트(spec/spec_helper.rb)를 로드할 때 발생합니다.

즉 위 조건에서는 gitlab 젬을 로드할 수 없고, 로드할 수 있다 해도 상수 이름이 충돌해 내부 가정이 깨지고 임의의 오류가 발생합니다. gitlab Ruby 젬을 사용하는 스크립트를 작업한다면 몇 가지 예방 조치를 취해야 합니다.

1 - 젬의 조건부 require#

충돌 가능성을 피하기 위해 Gitlab 상수가 정의되어 있지 않은 경우에만 gitlab 젬을 require합니다.

# Bad
require 'gitlab'

# Good
if Object.const_defined?(:RSpec)
  # Ok, we're testing, we know we're going to stub `Gitlab`, so we just ignore
else
  require 'gitlab'

  if Gitlab.singleton_class.method_defined?(:com?)
    abort 'lib/gitlab.rb is loaded, and this means we can no longer load the client and we cannot proceed'
  end
end

2 - 스펙에서 gitlab 젬 전체를 모킹#

스펙에서 require 'gitlab'은 lib/gitlab.rb 파일을 참조합니다.

# Bad
allow(GitLab).to receive(:a_method).and_return(...)

# Good
client = double('GitLab')
# In order to easily stub the client, consider using a method to return the client.
# We can then stub the method to return our fake client, which we can further stub its methods.
#
# This is the pattern followed below
let(:instance) { described_class.new }

allow(instance).to receive(:gitlab).and_return(client)
allow(client).to receive(:a_method).and_return(...)

예를 들어 job을 쿼리해야 하는 경우에는 다음 스니펫이 유용합니다.

# Bad
allow(GitLab).to receive(:pipeline_jobs).and_return(...)

# Good
#
# rubocop:disable RSpec/VerifiedDoubles -- We do not load the Gitlab client directly
client = double('GitLab')
allow(instance).to receive(:gitlab).and_return(client)

jobs = ['job1', 'job2']
allow(client).to yield_jobs(:pipeline_jobs, jobs)

def yield_jobs(api_method, jobs)
  messages = receive_message_chain(api_method, :auto_paginate)

  jobs.inject(messages) do |stub, job_name|
    stub.and_yield(double(name: job_name))
  end
end
# rubocop:enable RSpec/VerifiedDoubles

3 - bundle exec로 스크립트를 호출하지 않기#

bundle exec로 실행하면 Ruby의 $LOAD_PATH가 바뀌고, require 'gitlab'을 호출할 때 lib/gitlab.rb를 로드합니다.

# Bad
bundle exec scripts/my-script.rb

# Good
scripts/my-script.rb

CI 구성 테스팅#

이제 업데이트된 YAML 파일로 파이프라인 생성을 시뮬레이션해 CI 구성 변경을 검증하는 RSpec 테스트가 있습니다. 이 테스트와 현재 테스트 커버리지에 관한 문서는 spec/dot_gitlab_ci/job_dependency_spec.rb에서 확인할 수 있습니다.

테스트 작동 방식#

Ci::CreatePipelineService를 활용하면 브랜치 이름, MR 레이블, 파이프라인 소스(스케줄 대 푸시), 파이프라인 유형(머지 트레인 대 머지 결과) 등 다양한 속성으로 파이프라인 생성을 시뮬레이션할 수 있습니다. 이는 GitLab CI Lint API가 CI/CD 구성을 검증할 때 사용하는 것과 동일한 서비스입니다.

이 테스트는 CI 구성을 업데이트하는 머지 리퀘스트에 대해 자동으로 실행됩니다. 다만 팀 구성원은 머지 리퀘스트에 ~"pipeline:skip-ci-validation" 레이블을 추가해 이 테스트를 건너뛸 수 있습니다.

이 테스트는 가장 빠른 피드백을 제공하므로 로컬에서 실행하는 것을 권장합니다.

CI 구성 내부 구조

GitLab v19.4
원문 보기

요약

GitLab 프로젝트의 파이프라인은 GitLab CI/CD의 workflow:rules 키워드 기능으로 생성됩니다. 다음 시나리오에서는 항상 파이프라인이 생성됩니다. 파이프라인 생성은 다음 CI/CD 변수에도 영향을 받습니다.

워크플로 규칙#

GitLab 프로젝트의 파이프라인은 GitLab CI/CD의 workflow:rules 키워드 기능으로 생성됩니다.

다음 시나리오에서는 항상 파이프라인이 생성됩니다.

  • 스케줄, 푸시, 병합 등을 포함한 main 브랜치입니다.
  • 머지 리퀘스트입니다.
  • 태그입니다.
  • 스테이블, auto-deploy, 보안 브랜치입니다.

파이프라인 생성은 다음 CI/CD 변수에도 영향을 받습니다.

  • $FORCE_GITLAB_CI가 설정되어 있으면 파이프라인이 생성됩니다. 사용을 권장하지 않습니다. $FORCE_GITLAB_CI 사용 지양을 참고합니다.
  • $GITLAB_INTERNAL이 설정되어 있지 않으면 파이프라인이 생성되지 않습니다.

그 밖의 경우에는 파이프라인이 생성되지 않습니다(예를 들어 MR이 없는 브랜치를 푸시하는 경우입니다).

이 워크플로 규칙의 단일 진실 공급원(Single Source Of Truth, SSOT)은 .gitlab-ci.yml에 정의되어 있습니다.

$FORCE_GITLAB_CI 사용 지양#

파이프라인은 매우 복잡하므로 어떤 종류의 파이프라인을 트리거하려는지 명확히 이해해야 합니다. 어떤 job을 실행해야 하고 어떤 job을 실행하지 않아야 하는지 알아야 합니다.

$FORCE_GITLAB_CI로 파이프라인을 강제로 트리거하면 그 파이프라인이 어떤 종류인지 정확히 알 수 없습니다. 그 결과 원하는 job을 실행하지 못하거나, 관심 없는 job을 너무 많이 실행할 수 있습니다.

더 많은 컨텍스트와 배경은 다음에서 확인할 수 있습니다. 예상치 못한 실행을 막기 위한 일괄 변경 지양

현재 이를 사용하는 곳의 목록은 다음과 같으며, $FORCE_GITLAB_CI 사용에서 벗어나도록 노력해야 합니다.

$FORCE_GITLAB_CI를 사용하지 않고 파이프라인을 활성화하는 방법은 다음 섹션을 참고합니다.

$FORCE_GITLAB_CI의 대안#

기본적으로 서로 다른 파이프라인을 활성화하기 위해 서로 다른 변수를 사용합니다. 그 예가 $START_AS_IF_FOSS입니다. 크로스 프로젝트 FOSS 파이프라인을 트리거하려면 $START_AS_IF_FOSS와 함께 $ENABLE_RSPEC_UNIT, $ENABLE_RSPEC_SYSTEM 등의 다른 변수들을 설정해, as-if-foss 크로스 프로젝트 다운스트림 파이프라인에서 실행하려는 각 job을 활성화합니다.

$FORCE_GITLAB_CI보다 이 방식이 나은 점은 $START_AS_IF_FOSS가 이 목적으로만 사용되기 때문에 파이프라인을 실행할 방식을 완전히 제어할 수 있고, 이 변수 아래에서 파이프라인이 동작하는 방식을 바꾸어도 다른 유형의 파이프라인에는 영향을 주지 않는다는 점입니다. 반면 $FORCE_GITLAB_CI는 여러 목적으로 사용되기 때문에 그 파이프라인이 정확히 무엇인지 알 수 없습니다.

기본 이미지#

기본 이미지는 .gitlab-ci.yml에 정의되어 있습니다.

여기에는 Ruby, Go, Git, Git LFS, Chrome, Node, Yarn, PostgreSQL, Graphics Magick이 포함됩니다.

파이프라인에서 사용하는 이미지는 gitlab-org/gitlab-build-images 프로젝트에서 구성되며, 이 프로젝트는 중복성을 위해 gitlab/gitlab-build-images로 푸시 미러링됩니다.

빌드 이미지의 현재 버전은 "Used by GitLab section"에서 확인할 수 있습니다.

기본 변수#

사전 정의된 CI/CD 변수 외에도 각 파이프라인에는 .gitlab-ci.yml에 정의된 기본 변수가 포함됩니다.

변수 명명 규칙#

2025년 3월부터 모놀리스 CI 파이프라인에만 사용되는 새 환경 변수에는 GLCI_ 접두사를 붙이기 시작했습니다.

이를 통해 환경 변수가 CI용(GLCI_)인지, 제품용(GITLAB_)인지, GitLab이 소유하지 않은 도구 및 시스템용인지 추적할 수 있습니다. 이는 파이프라인 구성에서 환경 변수 변경의 영향을 더 잘 평가하는 데 도움이 됩니다.

필수 CI 변수#

일부 CI/CD job은 실행에 특정 변수가 필요합니다. 선택적 변수와 달리 이 변수들이 정의되어 있지 않으면 해당 job은 전부 건너뜁니다.

GLCI_MEDIUM_RUNNER_REQUIRED#

이 변수는 최소 4코어와 16GB RAM을 갖춘 러너가 필요한 시스템(기능) 테스트 job을 활성화합니다. Chrome 버전 133 이상은 안정적으로 실행되기 위해 추가 컴퓨팅 리소스가 필요합니다. 그렇지 않으면 PostgreSQL 데이터베이스와 Rails 애플리케이션에 리소스가 부족해 시스템 테스트 job이 예측할 수 없게 불안정해집니다.

CI/CD 설정에서 이 변수를 정의하고, 최소 4코어와 16GB RAM을 갖춘 러너 태그로 설정합니다. 전체 구성 세부 정보는 이 변수를 도입한 MR의 테스트 섹션을 참고합니다.

정의되어 있지 않으면(기본값은 빈 문자열) 시스템 테스트 job이 실행되지 않습니다. 이렇게 하면 기여자의 개인 포크처럼 러너 용량이 충분하지 않은 환경에서 리소스를 많이 소모하는 테스트가 실행되는 것을 방지합니다.

시스템 테스트를 실행해야 하는 환경에는 다음이 포함되며, 이 변수가 필요합니다.

  • 정규 gitlab-org/gitlab 프로젝트
  • GitLab Community 포크
  • 보안 포크
  • dev.gitlab.org 포크

환경별 세부 정보는 같은 MR의 테스트 환경 섹션을 참고합니다.

Stage#

현재 Stage는 다음과 같습니다.

  • sync: 이 stage는 gitlab-org/gitlab의 변경 사항을 gitlab-org/gitlab-foss로 동기화하는 데 사용됩니다.
  • prepare: 이 stage에는 이후 stage의 job에 필요한 아티팩트를 준비하는 job이 포함됩니다.
  • build-images: 이 stage에는 이후 stage의 job이나 다운스트림 파이프라인에 필요한 Docker 이미지를 준비하는 job이 포함됩니다.
  • fixtures: 이 stage에는 프론트엔드 테스트에 필요한 픽스처를 준비하는 job이 포함됩니다.
  • lint: 이 stage에는 린팅과 정적 분석 job이 포함됩니다.
  • test: 이 stage에는 대부분의 테스트와 DB/마이그레이션 job이 포함됩니다.
  • post-test: 이 stage에는 test stage의 job에서 데이터를 수집하거나 리포트를 빌드하는 job이 포함됩니다(예를 들어 커버리지, Knapsack 메타데이터 등입니다). Docs Review App job도 포함됩니다.
  • qa: 이 stage에는 review stage에서 배포된 Review App에 대해 QA 작업을 수행하는 job이 포함됩니다.
  • post-qa: 이 stage에는 qa stage의 job에서 데이터를 수집하거나 리포트를 빌드하는 job이 포함됩니다(예를 들어 Review App 성능 리포트입니다).
  • pages: 이 stage에는 여러 리포트를 GitLab Pages로 배포하는 job이 포함됩니다(예를 들어 coverage-ruby와 webpack-report가 있습니다. webpack-report는 https://gitlab-org.gitlab.io/gitlab/webpack-report/에 있으나 배포에 문제가 있습니다).
  • notify: 이 stage에는 여러 실패를 Slack으로 알리는 job이 포함됩니다.

Dependency Proxy#

일부 job은 Docker Hub의 이미지를 사용하며, 이때 이미지 경로 앞에 ${GITLAB_DEPENDENCY_PROXY_ADDRESS}를 접두사로 사용해 Dependency Proxy에서 이미지를 가져옵니다. 기본적으로 이 변수는 ${GITLAB_DEPENDENCY_PROXY}의 값으로 설정됩니다.

  • CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX는 Dependency Proxy를 통해 이미지를 가져올 최상위 그룹 이미지 접두사를 제공하는 GitLab 사전 정의 CI/CD 변수입니다.
  • GITLAB_DEPENDENCY_PROXY는 gitlab-org 그룹과 gitlab-com 그룹의 CI/CD 변수입니다. ${CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX}/로 정의되어 있습니다.
  • GITLAB_DEPENDENCY_PROXY_ADDRESS는 gitlab-org/gitlab 프로젝트에 정의되어 있습니다. 기본값은 "${GITLAB_DEPENDENCY_PROXY}"이지만 일부 경우에는 재정의됩니다(아래 워크어라운드 섹션을 참고합니다).

gitlab-org/gitlab에서는 워크어라운드 때문에 GITLAB_DEPENDENCY_PROXY_ADDRESS를 사용합니다. gitlab-org 그룹과 gitlab-com 그룹의 그 밖의 모든 곳에서는 Dependency Proxy를 사용하기 위해 GITLAB_DEPENDENCY_PROXY를 사용해야 합니다. 그 외의 프로젝트에서는 CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX 사전 정의 CI/CD 변수로 Dependency Proxy를 활성화할 수 있습니다.

# In the gitlab-org/gitlab project
image: ${GITLAB_DEPENDENCY_PROXY_ADDRESS}alpine:edge

# In any other project in gitlab-org and gitlab-com groups
image: ${GITLAB_DEPENDENCY_PROXY}alpine:edge

# In projects outside of gitlab-org and gitlab-com groups
image: ${CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX}/alpine:edge

그 밖의 개인 네임스페이스나 그룹에 있는 포크는 해당 위치에 GITLAB_DEPENDENCY_PROXY도 정의되어 있지 않으면 Docker Hub로 폴백합니다.

프로젝트 액세스 토큰 사용자가 파이프라인을 시작할 때의 워크어라운드#

프로젝트 액세스 토큰 사용자가 파이프라인을 시작하면(예를 들어 메인 프로젝트에서 사용하는 Gitaly 버전을 자동으로 업데이트하는 release-tools approver bot 사용자입니다) Dependency proxy에 액세스할 수 없어 Preparing the "docker+machine" executor 단계에서 job이 실패합니다. 이를 우회하기 위해 ${GITLAB_DEPENDENCY_PROXY_ADDRESS} 변수를 재정의하는 특별한 워크플로 규칙을 두어, 이 경우에는 Dependency proxy를 사용하지 않도록 합니다.

- if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH && $GITLAB_USER_LOGIN =~ /project_\d+_bot\d*/'
  variables:
    GITLAB_DEPENDENCY_PROXY_ADDRESS: ""
Note

그룹 수준 변수가 .gitlab-ci.yml 변수보다 우선순위가 높기 때문에 ${GITLAB_DEPENDENCY_PROXY} 변수를 직접 재정의하지는 않습니다.

외부 CI/CD 시크릿#

https://gitlab.com/groups/gitlab-org/quality/engineering-productivity/-/epics/46의 일환으로 2024년 2월에 ADD_JH_FILES_TOKEN CI 변수를 저장하기 위해 GCP Secret Manager 사용을 도그푸딩하기 시작했습니다.

이 작업의 일환으로 qual-ci-secret-mgmt-e78c9b95 GCP 프로젝트가 생성되었습니다.

공통 job 정의#

대부분의 job은 .gitlab/ci/global.gitlab-ci.yml에 정의된 몇 개의 CI 정의를 확장합니다. 이 정의들은 각각 하나의 구성 키워드로 범위가 한정됩니다.

Job 정의 설명
.default-retry unknown_failure, api_failure, runner_system_failure, job_execution_timeout, stuck_or_timeout_failure 발생 시 job이 재시도할 수 있게 합니다.
.default-before_script 데이터베이스가 실행 중이어야 할 수 있는 Ruby/Rails 작업(예를 들어 테스트입니다)에 적합한 기본 before_script 정의를 job이 사용할 수 있게 합니다.
.repo-from-artifacts job이 클론하는 대신 clone-gitlab-repo의 아티팩트에서 리포지터리를 가져올 수 있게 합니다. 이렇게 하면 GitLab.com Gitaly 부하가 줄고, 아티팩트에서 다운로드하는 것이 클론보다 빠르므로 속도도 약간 향상됩니다. 단, needs: []가 있는 job에는 사용하지 않아야 합니다. 그렇지 않으면 더 늦게 시작하는데, 보통은 모든 job이 가능한 한 빨리 시작되기를 원하기 때문입니다. 클론하는 것보다 더 오래 기다리지 않도록, 다른 의존성이 있는 job에만 사용합니다. 이 동작은 CI_FETCH_REPO_GIT_STRATEGY로 제어할 수 있습니다. 자세한 내용은 Gitaly에서 클론/페치하는 대신 아티팩트로 리포지터리 가져오기를 참고합니다.
.setup-test-env-cache 이후 Ruby/Rails 작업을 위한 테스트 환경 설정에 적합한 기본 cache 정의를 job이 사용할 수 있게 합니다.
.ruby-cache Ruby 작업에 적합한 기본 cache 정의를 job이 사용할 수 있게 합니다.
.static-analysis-cache 정적 분석 작업에 적합한 기본 cache 정의를 job이 사용할 수 있게 합니다.
.qa-cache QA 작업에 적합한 기본 cache 정의를 job이 사용할 수 있게 합니다.
.yarn-cache yarn install을 수행하는 프론트엔드 job에 적합한 기본 cache 정의를 job이 사용할 수 있게 합니다.
.assets-compile-cache 에셋을 컴파일하는 프론트엔드 job에 적합한 기본 cache 정의를 job이 사용할 수 있게 합니다.
.use-pg16 job이 postgres 16, redis, rediscluster 서비스를 사용할 수 있게 합니다(서비스의 구체적인 버전은 .gitlab/ci/global.gitlab-ci.yml을 참고합니다).
.use-pg16-ee .use-pg16과 동일하지만 elasticsearch 서비스도 사용합니다(서비스의 구체적인 버전은 .gitlab/ci/global.gitlab-ci.yml을 참고합니다).
.use-pg17 job이 postgres 17, redis, rediscluster 서비스를 사용할 수 있게 합니다(서비스의 구체적인 버전은 .gitlab/ci/global.gitlab-ci.yml을 참고합니다).
.use-pg17-ee .use-pg17과 동일하지만 elasticsearch 서비스도 사용합니다(서비스의 구체적인 버전은 .gitlab/ci/global.gitlab-ci.yml을 참고합니다).
.use-pg18 job이 postgres 18, redis, rediscluster 서비스를 사용할 수 있게 합니다(서비스의 구체적인 버전은 .gitlab/ci/global.gitlab-ci.yml을 참고합니다).
.use-pg18-ee .use-pg18과 동일하지만 elasticsearch 서비스도 사용합니다(서비스의 구체적인 버전은 .gitlab/ci/global.gitlab-ci.yml을 참고합니다).
.use-buildx job이 docker buildx 도구로 Docker 이미지를 빌드할 수 있게 합니다.
.as-if-foss FOSS_ONLY='1' CI/CD 변수를 설정해 FOSS 프로젝트를 시뮬레이션합니다.
.use-docker-in-docker job이 Docker in Docker를 사용할 수 있게 합니다. 자세한 내용은 CI/CD 구성에 관한 핸드북을 참고합니다.

rules, if: 조건과 changes: 패턴#

rules 키워드를 광범위하게 사용합니다.

모든 rules 정의는 rules.gitlab-ci.yml에 정의되어 있고, extends를 통해 개별 job에 포함됩니다.

rules 정의는 if: 조건과 changes: 패턴으로 구성되며, 이들 역시 rules.gitlab-ci.yml에 정의되어 YAML 앵커를 통해 rules 정의에 포함됩니다

if: 조건#

if: 조건 설명 참고
if-not-canonical-namespace 프로젝트가 정규(gitlab-org/, gitlab-cn/) 또는 보안(gitlab-org/security) 네임스페이스에 없으면 일치합니다. 포크용 job을 생성할 때(when: on_success 또는 when: manual 사용) 또는 포크용 job을 생성하지 않을 때(when: never 사용) 사용합니다.
if-not-ee 프로젝트가 EE가 아니면(즉 프로젝트 이름이 gitlab 또는 gitlab-ee가 아니면) 일치합니다. FOSS 프로젝트에서만 job을 생성할 때(when: on_success 또는 when: manual 사용) 또는 프로젝트가 EE이면 job을 생성하지 않을 때(when: never 사용) 사용합니다.
if-not-foss 프로젝트가 FOSS가 아니면(즉 프로젝트 이름이 gitlab-foss, gitlab-ce, gitlabhq가 아니면) 일치합니다. EE 프로젝트에서만 job을 생성할 때(when: on_success 또는 when: manual 사용) 또는 프로젝트가 FOSS이면 job을 생성하지 않을 때(when: never 사용) 사용합니다.
if-default-refs 파이프라인이 master, main, /^[\d-]+-stable(-ee)?$/(스테이블 브랜치), /^\d+-\d+-auto-deploy-\d+$/(auto-deploy 브랜치), /^security\//(보안 브랜치), 머지 리퀘스트, 태그에 대한 것이면 일치합니다. 이 기본 구성에서는 브랜치에 대해 job이 생성되지 않습니다.
if-master-refs 현재 브랜치가 master 또는 main이면 일치합니다.
if-master-push 현재 브랜치가 master 또는 main이고 파이프라인 소스가 push이면 일치합니다.
if-master-schedule-maintenance 현재 브랜치가 master 또는 main이고 파이프라인이 2시간 간격 스케줄로 실행되면 일치합니다.
if-master-schedule-nightly 현재 브랜치가 master 또는 main이고 파이프라인이 야간 스케줄로 실행되면 일치합니다.
if-auto-deploy-branches 현재 브랜치가 auto-deploy 브랜치이면 일치합니다.
if-master-or-tag 파이프라인이 master 또는 main 브랜치나 태그에 대한 것이면 일치합니다.
if-merge-request 파이프라인이 머지 리퀘스트에 대한 것이면 일치합니다.
if-merge-request-title-as-if-foss 파이프라인이 머지 리퀘스트에 대한 것이고 해당 MR에 ~"pipeline:run-as-if-foss" 레이블이 있으면 일치합니다.
if-merge-request-title-update-caches 파이프라인이 머지 리퀘스트에 대한 것이고 해당 MR에 ~"pipeline:update-cache" 레이블이 있으면 일치합니다.
if-merge-request-labels-run-all-rspec 파이프라인이 머지 리퀘스트에 대한 것이고 해당 MR에 ~"pipeline:run-all-rspec" 레이블이 있으면 일치합니다.
if-security-merge-request 파이프라인이 보안 머지 리퀘스트에 대한 것이면 일치합니다.
if-security-schedule 파이프라인이 보안 스케줄 파이프라인이면 일치합니다.
if-nightly-master-schedule 파이프라인이 $NIGHTLY가 설정된 master 스케줄 파이프라인이면 일치합니다.
if-dot-com-gitlab-org-schedule job 생성을 GitLab.com의 gitlab-org 그룹에 대한 스케줄 파이프라인으로 한정합니다.
if-dot-com-gitlab-org-master job 생성을 GitLab.com의 gitlab-org 그룹에 대한 master 또는 main 브랜치로 한정합니다.
if-dot-com-gitlab-org-merge-request job 생성을 GitLab.com의 gitlab-org 그룹에 대한 머지 리퀘스트로 한정합니다.
if-dot-com-ee-schedule job을 GitLab.com의 gitlab-org/gitlab 프로젝트에 대한 스케줄 파이프라인으로 한정합니다.

changes: 패턴#

changes: 패턴 설명
ci-patterns CI 구성 관련 변경에 대해서만 job을 생성합니다.
ci-build-images-patterns build-images stage와 관련된 CI 구성 변경에 대해서만 job을 생성합니다.
ci-review-patterns review stage와 관련된 CI 구성 변경에 대해서만 job을 생성합니다.
ci-qa-patterns qa stage와 관련된 CI 구성 변경에 대해서만 job을 생성합니다.
yaml-lint-patterns YAML 관련 변경에 대해서만 job을 생성합니다.
docs-patterns 문서 관련 변경에 대해서만 job을 생성합니다.
frontend-dependency-patterns 프론트엔드 의존성이 업데이트될 때(예를 들어 package.json, yarn.lock 변경입니다)만 job을 생성합니다.
frontend-patterns-for-as-if-foss FOSS에 영향을 주는 프론트엔드 관련 변경에 대해서만 job을 생성합니다.
backend-patterns 백엔드 관련 변경에 대해서만 job을 생성합니다.
db-patterns DB 관련 변경에 대해서만 job을 생성합니다.
backstage-patterns 백스테이지 관련 변경(즉 Danger, 픽스처, RuboCop, 스펙입니다)에 대해서만 job을 생성합니다.
code-patterns 코드 관련 변경에 대해서만 job을 생성합니다.
qa-patterns QA 관련 변경에 대해서만 job을 생성합니다.
code-backstage-patterns code-patterns와 backstage-patterns의 조합입니다.
code-qa-patterns code-patterns와 qa-patterns의 조합입니다.
code-backstage-qa-patterns code-patterns, backstage-patterns, qa-patterns의 조합입니다.
static-analysis-patterns Static Analytics 구성 관련 변경에 대해서만 job을 생성합니다.

커스텀 종료 코드#

아래 표는 자동 재시도에 사용하는 커스텀 종료 코드를 나열합니다(정의는 GitLab 전역 CI 구성의 재시도 규칙을 참고합니다).

종료 코드 설명
112 알려진 불안정 테스트가 감지되었습니다. job 전체를 재시도합니다.
201 디스크 공간 부족입니다.

새로운 실패 패턴이 나타나면 이 목록을 확장할 수 있습니다. 충돌을 피하기 위해 201-255 범위의 종료 코드를 사용합니다.

모범 사례#

extends:, <<: *xyz(YAML 앵커), !reference 사용 시기#

참고 자료

핵심 요점#

  • 해시를 확장해야 하면 extends를 사용합니다
  • 배열을 확장해야 하면 !reference를 사용하고, 최후의 수단으로 YAML anchors를 사용합니다
  • 더 복잡한 경우(예를 들어 배열 안의 해시 확장, 해시 안의 배열 확장 등입니다)에는 !reference 또는 YAML anchors를 사용해야 합니다

extends와 YAML anchors로 할 수 있는 것#

extends#
  • 해시에 대한 딥 머지입니다
  • 배열은 머지되지 않습니다. 덮어씁니다(출처)
YAML 앵커#
  • 해시에 대한 딥 머지는 되지 않지만, 해시를 확장하는 데 사용할 수 있습니다(아래 예시를 참고합니다)
  • 배열은 머지되지 않지만, 배열을 확장하는 데 사용할 수 있습니다(아래 예시를 참고합니다)

좋은 예시#

이 예시는 !reference와 YAML anchors로 복잡한 YAML 데이터 구조를 확장하는 방법을 보여 줍니다.

.strict-ee-only-rules:
  # `rules` is an array of hashes
  rules:
    - if: '$CI_PROJECT_NAME !~ /^gitlab(-ee)?$/ '
      when: never

# `if-security-merge-request` is a hash
.if-security-merge-request: &if-security-merge-request
  if: '$CI_PROJECT_NAMESPACE == "gitlab-org/security"'

# `code-qa-patterns` is an array
.code-qa-patterns: &code-qa-patterns
  - "{package.json,yarn.lock}"
  - ".browserslistrc"
  - "babel.config.js"
  - "jest.config.{base,integration,unit}.js"

.qa:rules:as-if-foss:
  rules:
    # We extend the `rules` array with an array of hashes directly
    - !reference [".strict-ee-only-rules", rules]
    # We extend a single array entry with a hash
    - <<: *if-security-merge-request
      # `changes` is an array, so we pass it an entire array
      changes: *code-qa-patterns

qa:selectors-as-if-foss:
  # We include the rules from .qa:rules:as-if-foss in this job
  extends:
    - .qa:rules:as-if-foss

.fast-no-clone-job job 확장#

정규 프로젝트의 브랜치를 다운로드하는 데는 20초에서 30초가 걸립니다.

일부 job은 제한된 수의 파일만 필요하며, 이 파일들은 GitLab API로 다운로드할 수 있습니다.

job에 다음 패턴을 추가하면 job의 git clone/git fetch를 건너뛸 수 있습니다.

시나리오 1: job에 before_script가 정의되어 있지 않은 경우#

이는 해당 job이 확장하는 상위 섹션에도 적용됩니다.

.fast-no-clone-job을 확장하기만 하면 됩니다.

변경 전:

  # Note: No `extends:` is present in the job
  a-job:
    script:
      - source scripts/rspec_helpers.sh scripts/slack
      - echo "No need for a git clone!"

변경 후:

  # Note: No `extends:` is present in the job
  a-job:
    extends:
      - .fast-no-clone-job
    variables:
      FILES_TO_DOWNLOAD: >
        scripts/rspec_helpers.sh
        scripts/slack
    script:
      - source scripts/rspec_helpers.sh scripts/slack
      - echo "No need for a git clone!"

시나리오 2: job(또는 그 job이 확장하는 job)에 before_script 블록이 이미 정의되어 있는 경우#

이 시나리오에서는 다음을 수행해야 합니다.

  1. 첫 번째 시나리오와 같이 .fast-no-clone-job을 확장합니다(이렇게 하면 FILES_TO_DOWNLOAD 변수가 다른 변수들과 머지됩니다)
  2. .fast-no-clone-job의 before_script 섹션이 이 job에 사용하는 before_script에서 참조되도록 합니다.

변경 전:

  .base-job:
    before_script:
      echo "Hello from .base-job"

  a-job:
    extends:
      - .base-job
    script:
      - source scripts/rspec_helpers.sh scripts/slack
      - echo "No need for a git clone!"

변경 후:

  .base-job:
    before_script:
      echo "Hello from .base-job"

  a-job:
    extends:
      - .base-job
      - .fast-no-clone-job
    variables:
      FILES_TO_DOWNLOAD: >
        scripts/rspec_helpers.sh
        scripts/slack
    before_script:
      - !reference [".fast-no-clone-job", before_script]
      - !reference [".base-job", before_script]
    script:
      - source scripts/rspec_helpers.sh scripts/slack
      - echo "No need for a git clone!"

주의 사항#

  • 스크립트가 리포지터리에 액세스하기 위해 git에 의존하는 경우 이 패턴은 동작하지 않습니다. 클론이나 페치를 하지 않으면 리포지터리가 없기 때문입니다.
  • 이 패턴을 사용하는 job에는 curl을 사용할 수 있어야 합니다.
  • FILES_TO_DOWNLOAD에 scripts/utils.sh를 나열하지 않습니다. .fast-no-clone-job이 항상 before_script에서 이 파일을 다운로드하므로, 다시 나열하면 같은 파일을 두 번 다운로드합니다.
  • job에서 bundle install을 실행해야 하면(BUNDLE_ONLY를 사용하는 경우에도) 다음을 수행해야 합니다.
    • gitlab-org/gitlab 프로젝트에 저장된 젬을 다운로드합니다.
      • 이를 위해 download_local_gems 셸 명령을 사용할 수 있습니다.
    • Gemfile과 Gemfile.lock을 포함합니다

이 패턴이 사용되는 곳#

  • 현재 이 패턴은 다음 job에 사용하며, 이 job들은 비공개 리포지터리를 차단하지 않습니다.
    • set-pipeline-name은 다음에 사용합니다.
      • scripts/pipeline/set_pipeline_name.rb
    • pre-merge-checks는 다음에 사용합니다.
      • scripts/pipeline/pre_merge_checks.rb
    • prepare-as-if-foss-env는 다음에 사용합니다.
      • scripts/setup/generate-as-if-foss-env.rb
    • retrieve-tests-metadata는 다음에 사용합니다.
      • scripts/setup/tests-metadata.rb

또한 이 패턴을 사용하면 scripts/utils.sh는 항상 API에서 다운로드됩니다(이 파일에는 .fast-no-clone-job의 코드가 들어 있습니다).

러너 태그#

GitLab.com에서는 비특권 러너와 특권 러너를 모두 사용할 수 있습니다. gitlab-org 그룹의 프로젝트와 그 프로젝트의 포크에는 job에 다음 태그 중 하나만 추가해야 합니다.

  • gitlab-org: 기본값이지만, job을 특권 모드로 실행할 필요가 없는 경우에만 사용합니다.
  • gitlab-org-docker: job을 특권 모드로 실행해야 하는 경우에 사용합니다. Docker-in-Docker 지원이 필요하면 gitlab-org 대신 gitlab-org-docker를 사용합니다.

gitlab-org-docker 태그는 위의 .use-docker-in-docker job 정의가 추가합니다.

포크와의 호환성을 보장하기 위해 gitlab-org와 gitlab-org-docker를 동시에 사용하지 않습니다. gitlab-org 태그와 gitlab-org-docker 태그를 모두 가진 인스턴스 러너는 없습니다. gitlab-org 프로젝트의 포크에서는 두 태그를 모두 지정하면 일치하는 러너가 없어 job이 멈춥니다.

자세한 내용은 GitLab Repositories 핸드북 페이지를 참고합니다.

정규 프로젝트에서 gitlab Ruby 젬 사용#

정규 프로젝트에서 require 'gitlab'을 호출하면, $LOAD_PATH에 lib가 있을 때 lib/gitlab.rb 파일을 require합니다. 이는 애플리케이션(config/application.rb)이나 테스트(spec/spec_helper.rb)를 로드할 때 발생합니다.

즉 위 조건에서는 gitlab 젬을 로드할 수 없고, 로드할 수 있다 해도 상수 이름이 충돌해 내부 가정이 깨지고 임의의 오류가 발생합니다. gitlab Ruby 젬을 사용하는 스크립트를 작업한다면 몇 가지 예방 조치를 취해야 합니다.

1 - 젬의 조건부 require#

충돌 가능성을 피하기 위해 Gitlab 상수가 정의되어 있지 않은 경우에만 gitlab 젬을 require합니다.

# Bad
require 'gitlab'

# Good
if Object.const_defined?(:RSpec)
  # Ok, we're testing, we know we're going to stub `Gitlab`, so we just ignore
else
  require 'gitlab'

  if Gitlab.singleton_class.method_defined?(:com?)
    abort 'lib/gitlab.rb is loaded, and this means we can no longer load the client and we cannot proceed'
  end
end

2 - 스펙에서 gitlab 젬 전체를 모킹#

스펙에서 require 'gitlab'은 lib/gitlab.rb 파일을 참조합니다.

# Bad
allow(GitLab).to receive(:a_method).and_return(...)

# Good
client = double('GitLab')
# In order to easily stub the client, consider using a method to return the client.
# We can then stub the method to return our fake client, which we can further stub its methods.
#
# This is the pattern followed below
let(:instance) { described_class.new }

allow(instance).to receive(:gitlab).and_return(client)
allow(client).to receive(:a_method).and_return(...)

예를 들어 job을 쿼리해야 하는 경우에는 다음 스니펫이 유용합니다.

# Bad
allow(GitLab).to receive(:pipeline_jobs).and_return(...)

# Good
#
# rubocop:disable RSpec/VerifiedDoubles -- We do not load the Gitlab client directly
client = double('GitLab')
allow(instance).to receive(:gitlab).and_return(client)

jobs = ['job1', 'job2']
allow(client).to yield_jobs(:pipeline_jobs, jobs)

def yield_jobs(api_method, jobs)
  messages = receive_message_chain(api_method, :auto_paginate)

  jobs.inject(messages) do |stub, job_name|
    stub.and_yield(double(name: job_name))
  end
end
# rubocop:enable RSpec/VerifiedDoubles

3 - bundle exec로 스크립트를 호출하지 않기#

bundle exec로 실행하면 Ruby의 $LOAD_PATH가 바뀌고, require 'gitlab'을 호출할 때 lib/gitlab.rb를 로드합니다.

# Bad
bundle exec scripts/my-script.rb

# Good
scripts/my-script.rb

CI 구성 테스팅#

이제 업데이트된 YAML 파일로 파이프라인 생성을 시뮬레이션해 CI 구성 변경을 검증하는 RSpec 테스트가 있습니다. 이 테스트와 현재 테스트 커버리지에 관한 문서는 spec/dot_gitlab_ci/job_dependency_spec.rb에서 확인할 수 있습니다.

테스트 작동 방식#

Ci::CreatePipelineService를 활용하면 브랜치 이름, MR 레이블, 파이프라인 소스(스케줄 대 푸시), 파이프라인 유형(머지 트레인 대 머지 결과) 등 다양한 속성으로 파이프라인 생성을 시뮬레이션할 수 있습니다. 이는 GitLab CI Lint API가 CI/CD 구성을 검증할 때 사용하는 것과 동일한 서비스입니다.

이 테스트는 CI 구성을 업데이트하는 머지 리퀘스트에 대해 자동으로 실행됩니다. 다만 팀 구성원은 머지 리퀘스트에 ~"pipeline:skip-ci-validation" 레이블을 추가해 이 테스트를 건너뛸 수 있습니다.

이 테스트는 가장 빠른 피드백을 제공하므로 로컬에서 실행하는 것을 권장합니다.