CI/CD 개발 지침

CI/CD에 특화된 개발 가이드를 여기에 나열했습니다.

CI/CD YAML 참조 문서 가이드를 확인하여 CI/CD YAML 구문 참조 페이지를 업데이트하는 방법을 배워보세요.

CI/CD 사용 예시

우리는 ci-sample-projects 그룹을 유지하고 있으며, GitLab CI/CD의 다양한 사용 사례에 대한 .gitlab-ci.yml의 예시 프로젝트를 쇼케이스합니다. 또한 다양한 시나리오에 사용될 수 있는 특정 구문도 다룹니다.

CI 아키텍처 개요

다음은 CI 아키텍처의 단순화된 다이어그램입니다. 주요 구성 요소에 중점을 두기 위해 일부 세부 정보는 제외되었습니다.

CI 소프트웨어 아키텍처

왼쪽에는 사용자 또는 자동화에 의해 유발된 다양한 이벤트를 기반으로 파이프라인을 유발할 수 있는 이벤트가 있습니다:

이러한 이벤트 중 하나를 유발하면 CreatePipelineService가 호출되어 입력 이벤트 데이터와 이를 유발하는 사용자를 가져와 파이프라인을 생성하려고 시도합니다.

CreatePipelineService는 주로 YAML Processor 구성 요소에 의존하며, 입력으로 YAML 블롭을 받아 파이프라인의 추상 데이터 구조(단계 및 모든 작업 포함)를 반환합니다. 이 구성 요소는 또한 구문 또는 의미 오류가 있는 경우에 해당 YAML의 구조를 유효성 검사하고 반환합니다. YAML Processor 구성 요소는 파이프라인의 구조화에 사용 가능한 모든 키워드를 정의하는 곳입니다.

CreatePipelineServiceYAML Processor에 의해 반환된 추상 데이터 구조를 받아서 이를 영구 모델(파이프라인, 단계, 작업 등)으로 변환합니다. 그러고나면 파이프라인은 처리될 준비가 됩니다. 파이프라인을 처리하는 것은 실행 순서대로(단계 또는 DAG) 작업을 실행하는 것을 의미하며 다음 중 하나가 될 때까지 진행됩니다:

  • 예상된 모든 작업이 실행되었을 때.
  • 실패로 인해 파이프라인 실행이 중단될 때.

파이프라인을 처리하는 구성 요소는 ProcessPipelineService이며, 이는 파이프라인의 모든 작업을 완료 상태로 이동하는 것을 담당합니다. 파이프라인이 생성되면 모든 작업은 초기에 created 상태입니다. 이 서비스는 파이프라인 구조를 기반으로 처리될 수 있는 상태인 created 단계의 작업을 살펴봅니다. 그런 다음 이러한 작업을 pending 상태로 이동시켜(이제 런너에 의해 선택될 수 있음) 실행되기를 기다릴 수 있게 합니다. 작업이 실행된 후에는 성공적으로 완료되거나 실패할 수 있습니다. 파이프라인 내 작업의 각 상태 전환은 이 서비스를 다시 트리거하며, 이 서비스는 완료를 향한 다음 작업을 찾습니다. 이렇게 하면 ProcessPipelineService는 작업, 단계 및 전반적인 파이프라인의 상태를 업데이트합니다.

다이어그램의 오른쪽에는 GitLab 인스턴스에 연결된 런너 목록이 있습니다. 이들은 공유 런너, 그룹 런너 또는 프로젝트 런너가 될 수 있습니다. 러너 및 Rails 서버 간의 통신은 Runner API Gateway로 그룹화된 API 엔드포인트를 통해 발생합니다.

런너를 등록하고 삭제하며, 러너를 확인할 수 있으며, 이로 인해 데이터베이스에 읽기/쓰기 쿼리가 발생합니다. 런너가 연결된 후에는 다음 작업을 실행해야 합니다. 이로 인해 RegisterJobService가 호출되어 다음 작업을 선택하고 러너에 할당합니다. 이 시점에서 작업은 running 상태로 전환되며, 이로 인해 다시 한번 상태 변경이 발생하여 ProcessPipelineService가 트리거됩니다. 자세한 내용은 작업 스케줄링을 참조하세요.

작업이 실행되는 동안 러너는 로그를 서버로 보내고 저장해야 할 아티팩트를 전달합니다. 또한, 작업은 실행에 필요한 이전 작업의 아티팩트에 종속될 수 있습니다. 이 경우 러너는 전용 API 엔드포인트를 통해 이전 작업에서 해당 아티팩트를 다운로드합니다.

아티팩트는 객체 저장소에 저장되며, 메타데이터는 데이터베이스에 유지됩니다. 아티팩트의 중요한 예시는 병합 요청에서 구문 분석 및 렌더링되는 보고서(JUnit, SAST, DAST 등)입니다.

작업 상태 전환은 모두 자동화되는 것은 아닙니다. 사용자는 수동 작업을 실행하거나 파이프라인을 취소하거나 특정 실패한 작업 또는 전체 파이프라인을 다시 시도할 수 있습니다. 작업 상태를 변경하는 모든 요소는 작업의 상태를 추적하는 데 책임이 있는ProcessPipelineService를 트리거합니다.

특수한 유형의 작업은 브릿지 작업으로, pending 상태로 전환될 때 서버 측에서 실행됩니다. 이 작업은 다운스트림 파이프라인(다중 프로젝트 또는 하위 파이프라인과 같은)을 생성하는 책임을 지며, 워크플로 루프는 다운스트림 파이프라인이 트리거될 때마다 CreatePipelineService에서 다시 시작됩니다.

CI Backend Architectural Walkthrough에서 아키텍처 설명 영상을 시청할 수 있습니다.

작업 스케줄링

파이프라인이 생성되면 모든 작업이 모든 단계에 대해 한꺼번에 created 상태로 생성됩니다. 이로써 파이프라인의 전체 내용을 시각화할 수 있습니다.

created 상태의 작업은 아직 실행 대상(runner)에 의해 확인되지 않습니다. 작업을 실행 대상에 할당하려면 작업은 먼저 pending 상태로 전환되어야 합니다. 이는 다음과 같은 경우에 발생할 수 있습니다.

  1. 작업이 파이프라인의 맨 처음 단계에서 생성된 경우.
  2. 작업이 수동으로 시작되고 트리거된 경우.
  3. 이전 단계의 모든 작업이 성공적으로 완료된 경우. 이 경우 다음 단계의 모든 작업을 pending 상태로 전환합니다.
  4. 작업이 needs:를 사용하여 DAG 의존성을 명시하고 모든 의존 작업이 완료된 경우.
  5. 작업이 Ci::PipelineCreation::DropNotRunnableBuildsService에 의해 실행 불가능한 상태로 인해 dropped되지 않은 경우.

실행 대상이 연결되면, 서버는 지속적으로 폴링하여 다음 pending 작업을 실행하도록 요청합니다.

참고: 실행 대상이 GitLab과 상호 작용하기 위해 사용하는 API 엔드포인트는 lib/api/ci/runner.rb에 정의됩니다.

서버가 요청을 수신하면 Ci::RegisterJobService 알고리즘을 기반으로 하여 pending 상태의 작업을 선택하고 실행 대상에 할당하며 작업을 실행 대상에 전송합니다.

실행 대상과 GitLab 서버 간의 통신

실행 대상이 등록 토큰을 사용하여 등록하면, 서버는 실행할 수 있는 작업 유형을 알게 됩니다. 이는 다음에 따라 달라집니다.

  • 등록된 실행 대상의 유형:
    • 공유 실행 대상
    • 그룹 실행 대상
    • 프로젝트 실행 대상
  • 연결된 태그.

실행 대상은 POST /api/v4/jobs/request로 실행할 작업을 요청함으로써 통신을 시작합니다. 몇 초마다 폴링이 발생하지만, 작업 대기열이 변경되지 않으면 서버 측 작업 부하를 줄이기 위해 HTTP 헤더를 통해 캐싱을 활용합니다.

이 API 엔드포인트는 Ci::RegisterJobService를 실행하여 다음을 수행합니다:

  1. pending 상태의 작업 풀에서 실행할 다음 작업 선택
  2. 작업을 실행 대상에 할당
  3. API 응답을 통해 실행 대상에 표시

Ci::RegisterJobService

이 서비스에서 사용하는 주요 상위 쿼리는 등록된 실행 대상의 레벨에 따라 선택되는 다수의 작업을 모으기 위해 사용됩니다:

  • 공유 실행 대상을 위한 작업 선택 (인스턴스 레벨)
    • 실행 중인 빌드가 적은 프로젝트를 우선시하는 공정한 스케줄링 알고리즘 활용
  • 그룹 실행 대상을 위한 작업 선택
  • 프로젝트 실행 대상을 위한 작업 선택

이러한 작업 목록은 작업과 실행 대상 태그 간의 일치에 따라 더 필터링됩니다.

참고: 작업에 태그가 포함된 경우, 실행 대상은 작업에 지정된 모든 태그와 일치하지 않는 경우 작업을 선택하지 않습니다. 실행 대상에는 작업에 정의된 태그보다 더 많은 태그가 있을 수 있지만, 그 반대의 경우는 아닙니다.

마지막으로 실행 대상이 태그가 지정된 작업만 선택해야 하는 경우, 태그가 지정되지 않은 모든 작업이 필터링됩니다.

이 시점에서 남은 pending 작업을 반복하고, 추가 정책을 기반으로 실행 대상이 “선택할 수 있는” 첫 번째 작업을 할당하려고 합니다. 예를 들어, protected로 표시된 실행 대상은 protected 브랜치(예: 프로덕션 배포)에서 실행해야 하는 작업만 선택할 수 있습니다.

풀 내 실행 대상 수를 늘릴수록 동일한 작업을 다른 실행 대상에 할당하는 충돌 가능성이 증가합니다. 이를 방지하기 위해 충돌 오류를 정상적으로 처리하여 목록에서 다음 작업을 할당합니다.

정체된 빌드 삭제

“정체된” 빌드로 표시하고 해당 빌드를 삭제하는 두 가지 방법이 있습니다.

  1. 빌드가 생성될 때, Ci::PipelineCreation::DropNotRunnableBuildsService는 실행할 수없는 빌드에 대한 미리 알려진 조건을 확인합니다:
    • 빌드를 실행하는 데 충분한 CI/CD Minutes가 없는 경우, 빌드는 즉시 ci_quota_exceeded로 삭제됩니다.
    • 미래에, 프로젝트가 빌드에 사용 가능한 실행 대상을 allowed_plans를 통해 요구하는 계획에 없는 경우, 빌드는 즉시 no_matching_runner로 삭제됩니다.
  2. 실행 대상이 빌드를 진행할 수 없는 경우, 1시간 후에 Ci::StuckBuilds::DropPendingService에 의해 해당 빌드가 삭제됩니다.
    • 실행 대상이 24시간 내에 작업을 수행하지 않는 경우, 해당 시간 이후에 처리 대기열에서 자동으로 제거됩니다.
    • 빌드가 정체될 경우, 처리할 수 있는 실행 대상이 없는 경우 1시간 후에 대기열에서 제거됩니다.
    • 양쪽 모두 해당 작업의 상태가 적절한 실패 이유와 함께 failed로 변경됩니다.

이 차이의 이유

CI 분 할당량 메커니즘은 대부분의 경우 상수적인 결정이기 때문에 작업이 생성될 때 조기에 처리됩니다. 프로젝트가 한도를 초과하면, 그에 맞는 다음 작업은 다음 달이 시작될 때까지 적용됩니다. 물론, 프로젝트 소유자는 추가 분을 구매할 수 있지만, 이는 프로젝트가 수동으로 수행해야 하는 작업입니다.

allowed_plans에 대해 동일한 메커니즘이 곧 사용될 예정입니다. 프로젝트가 필요한 계획에 없고 작업이 해당 러너를 대상으로 할 경우, 프로젝트 소유자가 구성을 변경하거나 네임스페이스를 필요한 계획으로 업그레이드할 때까지 계속 실패합니다.

이 두 가지 메커니즘은 SaaS에 매우 특화되어 있으면서도 SaaS의 규모를 고려할 때 상당히 계산 비용이 많이 듭니다. 작업을 보류로 전환하기 전에 검사를 수행하고 조기에 실패시키는 것은 여기에서 많은 의미를 갖습니다.

왜 다른 경우에 대한 보류 및 작업 조기 삭제를 다루지 않는 이유는 무엇인가요? 경우에 따라 작업이 보류 중인 경우, 러너가 작업을 수행하는 데 느린 경우입니다. 이것은 GitLab 레벨에서 알 수없는 것입니다. GitLab의 대기열의 크기에 따라 러너의 구성 및 용량 및 대기열의 크기에 따라 작업이 즉시 수행될 수도 있고 기다릴 수도 있습니다.

또한 다른 이유가 있을 수 있습니다.

  • 러너 유지 관리를 수행하고있고 잠시 사용할 수 없는 경우,
  • 구성을 업데이트하고 실수로 태깅 또는 보호 플래그를 엉망으로 만들었을 수 있습니다(또는 SaaS 인스턴스 러너의 경우 잘못된 비용 요소 또는 allowed_plans 구성을 할당했을 수 있음).

모든 이러한 것들은 일시적인 문제일 수 있으며 대부분 예상되지 않으며 조기에 감지 및 수정되기를 기대합니다. 이러한 경우 중 하나가 발생할 때 작업을 즉시 삭제하고 싶지는 않습니다. 러너가 처리량을 초과했거나 일시적으로 사용할 수 없거나 구성 오류가 있는 경우에만 작업을 삭제하는 것은 사용자에게 매우 해로울 것입니다.

GitLab CI/CD에서 “작업”의 정의

GitLab CI 컨텍스트에서 “작업”은 지속적인 통합, 전달 및 배포를 제어하는 작업을 나타냅니다. 일반적으로 파이프라인에는 여러 단계가 포함되고, 단계에는 여러 작업이 포함됩니다.

Active Record 모델링에서 작업은 CommitStatus 클래스로 정의됩니다. 이에 더해, 다음과 같은 유형의 작업이 있습니다:

  • Ci::Build … 러너에 의해 실행되는 작업.
  • Ci::Bridge … 하위 파이프라인을 트리거하는 작업.
  • GenericCommitStatus … 외부 CI/CD 시스템에서 실행되는 작업, 예를 들어 Jenkins.

코드베이스에서 “작업” 용어를 사용할 때, 독자는 클래스/객체가 위에 언급된 유형 중 어떤 것이든일 것으로 상정합니다. 특별히 Ci::Build 클래스를 참조하는 경우 “작업”으로 객체/클래스의 이름을 지정해서는 안 됩니다. 이는 혼란을 초래할 수 있습니다. 문서에서는 “작업” 대신 “빌드”를 일반적으로 사용해야 합니다.

코드베이스에 몇 가지 일관성이 없는 부분이 있어서 리팩토링이 필요합니다. 예를 들어, CommitStatusCi::Job으로 변경해야 하고 Ci::JobArtifactCi::BuildArtifact로 변경해야 합니다. 전체 리팩토링 계획에 대한 자세한 내용은 이 이슈를 참조하세요.

계산 할당량

  • “CI/CD 분”은 GitLab 16.1에서 “compute quota” 및 “compute minutes”로 이름이 변경되었습니다.

이 다이어그램은 계산 할당량 기능 및 해당 구성 요소의 작업 방식을 보여줍니다.

계산 할당량 아키텍처

다음 비디오에서 이 기능의 자세한 내용을 살펴보세요.