리소스 그룹

리소스 그룹 (Resource group)

기본적으로 GitLab CI/CD의 파이프라인은 동시에 실행돼요. 동시성은 머지 리퀘스트의 피드백 루프를 개선하는 중요한 요소예요. 하지만 배포 잡의 동시성을 제한해서 하나씩 순차 실행하고 싶은 상황도 있죠. 리소스 그룹을 사용하면 잡의 동시성을 전략적으로 제어해서, 지속적 배포 워크플로를 안전하게 최적화할 수 있어요.

출처: 문서

본문

리소스 그룹 추가하기

리소스 그룹에는 리소스를 하나만 추가할 수 있어요.

다음과 같은 파이프라인 구성(저장소의 .gitlab-ci.yml 파일)이 있다고 가정해 볼게요.

build:
  stage: build
  script: echo "Your build script"

deploy:
  stage: deploy
  script: echo "Your deployment script"
  environment: production

브랜치에 새 커밋을 푸시할 때마다 builddeploy 두 잡이 있는 새 파이프라인이 실행돼요. 하지만 짧은 간격으로 여러 커밋을 푸시하면 여러 파이프라인이 동시에 실행되기 시작해요. 예를 들어:

  • 첫 번째 파이프라인은 build -> deploy 잡을 실행해요.
  • 두 번째 파이프라인은 build -> deploy 잡을 실행해요.

이 경우 서로 다른 파이프라인의 deploy 잡들이 production 환경에 동시에 실행될 수 있어요. 같은 인프라에 여러 배포 스크립트를 실행하면 인스턴스에 해를 끼치거나 혼란을 줄 수 있고, 최악의 경우 손상된 상태로 남을 수 있어요.

deploy 잡이 한 번에 하나씩만 실행되도록 하려면 동시성에 민감한 잡에 [resource_group 키워드](/ci/yaml/#resource_group)를 지정해요.

deploy:
  # ...
  resource_group: production

이 구성으로 배포의 안전성을 보장하면서도, build 잡은 계속 동시에 실행해 파이프라인 효율을 최대화할 수 있어요.

전제 조건

프로세스 모드

배포 기본 설정에 맞게 잡 동시성을 제어할 수 있는 프로세스 모드를 선택할 수 있어요. 지원되는 모드는 다음과 같아요.

프로세스 모드 설명 사용 시점
unordered 기본 프로세스 모드. 잡이 실행할 준비가 되면 언제든 처리해요. 잡의 실행 순서가 중요하지 않을 때. 가장 쉬운 옵션.
oldest_first 리소스가 비면, 파이프라인 ID 오름차순으로 정렬된 대기 잡 목록에서 첫 번째 잡을 선택해요. 가장 오래된 파이프라인의 잡부터 실행하고 싶을 때. unordered보다 효율은 떨어지지만 지속적 배포에 더 안전해요.
newest_first 리소스가 비면, 파이프라인 ID 내림차순으로 정렬된 대기 잡 목록에서 첫 번째 잡을 선택해요. 가장 새로운 파이프라인의 잡을 실행하고 오래된 배포 잡을 방지하고 싶을 때. 각 잡은 멱등(idempotent)이어야 해요.
newest_ready_first 리소스가 비면, 이 리소스에서 대기 중인 대기 잡 목록에서 첫 번째 잡을 선택해요. 잡은 파이프라인 ID 내림차순으로 정렬돼요. newest_first가 현재 파이프라인을 배포하기 전에 새 파이프라인을 우선시하는 것을 방지하고 싶을 때. newest_first보다 빠르고, 각 잡은 멱등이어야 해요.

프로세스 모드 변경하기

리소스 그룹의 프로세스 모드를 변경하려면 API를 사용해서 process_mode를 지정해 기존 리소스 그룹을 편집하는 요청을 보내야 해요.

  • unordered
  • oldest_first
  • newest_first
  • newest_ready_first

프로세스 모드 간 차이 예시

build 잡과 deploy 잡이 있는 다음 .gitlab-ci.yml을 생각해 볼게요. 각 잡은 자신의 스테이지에서 실행되고, deploy 잡은 production 리소스 그룹으로 설정되어 있어요.

build:
  stage: build
  script: echo "Your build script"

deploy:
  stage: deploy
  script: echo "Your deployment script"
  environment: production
  resource_group: production

짧은 간격으로 프로젝트에 세 개의 커밋이 푸시되면, 세 개의 파이프라인이 거의 동시에 실행돼요.

  • 첫 번째 파이프라인은 build -> deploy 잡을 실행해요. 이 배포 잡을 deploy-1이라고 부를게요.
  • 두 번째 파이프라인은 build -> deploy 잡을 실행해요. 이 배포 잡을 deploy-2라고 부를게요.
  • 세 번째 파이프라인은 build -> deploy 잡을 실행해요. 이 배포 잡을 deploy-3이라고 부를게요.

리소스 그룹의 프로세스 모드에 따라:

  • 프로세스 모드가 unordered면:

    • deploy-1, deploy-2, deploy-3은 동시에 실행되지 않아요.
    • 잡 실행 순서는 보장되지 않아요. 예를 들어 deploy-1deploy-3 앞이나 뒤에 실행될 수 있어요.
  • 프로세스 모드가 oldest_first면:

    • deploy-1, deploy-2, deploy-3은 동시에 실행되지 않아요.
    • deploy-1이 먼저, deploy-2가 그다음, deploy-3이 마지막에 실행돼요.
  • 프로세스 모드가 newest_first면:

    • deploy-1, deploy-2, deploy-3은 동시에 실행되지 않아요.
    • deploy-3이 먼저, deploy-2가 그다음, deploy-1이 마지막에 실행돼요.

크로스 프로젝트/부모-자식 파이프라인으로 파이프라인 수준 동시성 제어하기

동시 실행에 민감한 하위(downstream) 파이프라인에 resource_group을 정의할 수 있어요. [trigger 키워드](/ci/yaml/#trigger)로 하위 파이프라인을 트리거할 수 있고, [resource_group 키워드](/ci/yaml/#resource_group)는 이것과 함께 존재할 수 있어요. resource_group은 배포 파이프라인의 동시성을 제어하는 데 효율적이며, 다른 잡들은 계속 동시에 실행될 수 있어요.

다음 예시는 한 프로젝트에 두 개의 파이프라인 구성이 있는 경우예요. 파이프라인이 실행되기 시작하면 민감하지 않은 잡이 먼저 실행되고, 다른 파이프라인의 동시 실행에 영향받지 않아요. 하지만 GitLab은 배포(자식) 파이프라인을 트리거하기 전에 다른 배포 파이프라인이 실행 중이지 않은지 확인해요. 다른 배포 파이프라인이 실행 중이면, GitLab은 그 파이프라인들이 끝날 때까지 기다렸다가 다른 파이프라인을 실행해요.

# .gitlab-ci.yml (parent pipeline)

build:
  stage: build
  script: echo "Building..."

test:
  stage: test
  script: echo "Testing..."

deploy:
  stage: deploy
  trigger:
    include: deploy.gitlab-ci.yml
    strategy: mirror
  resource_group: AWS-production
# deploy.gitlab-ci.yml (child pipeline)

stages:
  - provision
  - deploy

provision:
  stage: provision
  script: echo "Provisioning..."

deployment:
  stage: deploy
  script: echo "Deploying..."
  environment: production

하위 파이프라인이 끝날 때까지 잠금이 해제되지 않도록 [trigger:strategy](/ci/yaml/#triggerstrategy)를 정의해야 해요.

관련 주제

문제 해결

파이프라인 구성에서 데드락 피하기

[oldest_first 프로세스 모드](/ci/resource_groups/#process-modes)가 파이프라인 순서대로 잡이 실행되도록 강제하기 때문에, 다른 CI 기능과 잘 맞지 않는 경우가 있어요.

예를 들어 부모 파이프라인과 같은 리소스 그룹을 요구하는 자식 파이프라인을 실행하면 데드락이 생길 수 있어요. 나쁜 구성의 예를 볼게요.

# BAD
test:
  stage: test
  trigger:
    include: child-pipeline-requires-production-resource-group.yml
    strategy: mirror

deploy:
  stage: deploy
  script: echo
  resource_group: production
  environment: production

부모 파이프라인에서 test 잡이 실행되고, 그 잡은 다시 자식 파이프라인을 실행해요. [strategy: mirror 옵션](/ci/yaml/#triggerstrategy) 때문에 test 잡은 자식 파이프라인이 끝날 때까지 기다려요. 부모 파이프라인은 다음 스테이지에서 deploy 잡을 실행하는데, 이 잡은 production 리소스 그룹의 리소스를 요구해요. 프로세스 모드가 oldest_first면 가장 오래된 파이프라인의 잡부터 실행되므로 deploy 잡이 다음에 실행돼요.

그런데 자식 파이프라인도 production 리소스 그룹의 리소스를 요구해요. 자식 파이프라인은 부모 파이프라인보다 새롭기 때문에, deploy 잡이 끝날 때까지 기다리는데, 그건 결코 일어나지 않아요.

이 경우 부모 파이프라인 구성에서 resource_group 키워드를 지정해야 해요.

# GOOD
test:
  stage: test
  trigger:
    include: child-pipeline.yml
    strategy: mirror
  resource_group: production # Specify the resource group in the parent pipeline

deploy:
  stage: deploy
  script: echo
  resource_group: production
  environment: production

잡이 Waiting for resource에 걸리는 경우

때로 잡이 Waiting for resource: <resource_group> 메시지와 함께 멈출 수 있어요. 해결하려면 먼저 리소스 그룹이 제대로 동작하는지 확인해요.

  1. 잡 세부 정보 페이지로 이동해요.

  2. 리소스가 잡에 할당되어 있으면 View job currently using resource를 선택하고 잡 상태를 확인해요.

    • 상태가 running 또는 pending이면 기능이 제대로 동작하는 거예요. 잡이 끝나고 리소스를 해제할 때까지 기다려요.
    • 상태가 created이고 프로세스 모드Oldest first 또는 Newest first면 기능이 제대로 동작하는 거예요. 잡의 파이프라인 페이지로 이동해 어떤 업스트림 스테이지나 잡이 실행을 막고 있는지 확인해요.
    • 위 조건 중 어느 것도 해당하지 않으면 기능이 제대로 동작하지 않을 수 있어요. GitLab에 이슈를 보고해요.
  3. View job currently using resource를 사용할 수 없으면 리소스가 잡에 할당되지 않은 거예요. 대신 리소스의 대기 잡을 확인해요.

    1. REST API로 리소스의 대기 잡을 가져와요.
    2. 리소스 그룹의 프로세스 모드Oldest first인지 확인해요.
    3. 대기 잡 목록에서 첫 번째 잡을 찾고, GraphQL로 잡 세부 정보를 가져와요.
    4. 첫 번째 잡의 파이프라인이 더 오래된 파이프라인이면, 파이프라인이나 잡 자체를 취소해 봐요.
    5. (선택) 다음 대기 잡이 여전히 더 이상 실행되지 말아야 할 오래된 파이프라인에 있으면 이 과정을 반복해요.
    6. 문제가 지속되면 GitLab에 이슈를 보고해요.

복잡하거나 바쁜 파이프라인의 경쟁 조건

위의 해결책으로 문제를 해결할 수 없다면 알려진 경쟁 조건(race condition) 문제일 수 있어요. 이 경쟁 조건은 복잡하거나 바쁜 파이프라인에서 발생해요. 예를 들어 다음과 같은 경우 경쟁 조건을 겪을 수 있어요.

  • 여러 자식 파이프라인이 있는 파이프라인.
  • 여러 파이프라인이 동시에 실행되는 단일 프로젝트.

이 문제에 부딪힌 것 같으면 GitLab에 이슈를 보고하고, issue 436988에 새 이슈 링크와 함께 댓글을 남겨요. 문제를 확인하기 위해 GitLab이 전체 파이프라인 구성 같은 추가 세부 정보를 요청할 수 있어요.

임시 해결 방법으로 다음을 할 수 있어요.

  • 새 파이프라인 시작하기.
  • 걸린 잡과 같은 리소스 그룹을 가진 완료된 잡을 다시 실행하기.

예를 들어 setup_jobdeploy_job이 같은 리소스 그룹을 가진다면, setup_job이 끝났는데도 deploy_jobwaiting for resource에 걸려 있을 수 있어요. setup_job을 다시 실행해서 전체 프로세스를 재시작하고 deploy_job이 끝날 수 있게 해요.

GraphQL로 잡 세부 정보 가져오기

GraphQL API에서 잡 정보를 가져올 수 있어요. 크로스 프로젝트/부모-자식 파이프라인으로 파이프라인 수준 동시성 제어를 사용한다면 GraphQL API를 사용해야 해요. 트리거 잡은 UI에서 접근할 수 없기 때문이에요.

GraphQL API에서 잡 정보를 가져오려면:

  1. 파이프라인 세부 정보 페이지로 이동해요.

  2. Jobs 탭을 선택하고 걸린 잡의 ID를 찾아요.

  3. 대화형 GraphQL 탐색기로 이동해요.

  4. 다음 쿼리를 실행해요.

{
  project(fullPath: "<fullpath-to-your-project>") {
    name
    job(id: "gid://gitlab/Ci::Build/<job-id>") {
      name
      status
      detailedStatus {
        action {
          path
          buttonTitle
        }
      }
    }
  }
}

job.detailedStatus.action.path 필드에 리소스를 사용하는 잡의 ID가 포함돼요.

  1. 다음 쿼리를 실행하고 위의 기준에 따라 job.status 필드를 확인해요. pipeline.path 필드에서 파이프라인 페이지를 방문할 수도 있어요.
{
  project(fullPath: "<fullpath-to-your-project>") {
    name
    job(id: "gid://gitlab/Ci::Build/<job-id-currently-using-the-resource>") {
      name
      status
      pipeline {
        path
      }
    }
  }
}

이슈 보고하기

다음 정보를 포함해 새 이슈를 열어요.

  • 영향받은 잡의 ID.
  • 잡 상태.
  • 문제가 발생하는 빈도.
  • 문제를 재현하는 단계.

추가 지원이 필요하거나 개발 팀과 연락하고 싶다면 지원팀에 연락할 수도 있어요.

더 알아보기

리소스 그룹은 배포 안전성과 밀접한 관련이 있어요. 순차 배포가 왜 안전한지 더 알고 싶다면 안전한 배포를 위한 GitLab 문서를, 리소스 그룹을 API로 관리하는 법은 리소스 그룹 API 문서를 함께 보면 좋아요.