`needs`로 잡을 더 일찍 시작하기

needs로 잡을 더 일찍 시작하기 (Make jobs start earlier with needs)

파이프라인에서 잡 의존성을 지정하려면 needs 키워드를 사용하세요. 잡은 파이프라인 스테이지가 끝날 때까지 기다리지 않고 의존성이 끝나는 즉시 시작돼요. 이렇게 하면 잡을 더 일찍 실행하고 불필요한 대기를 피할 수 있어요.

사용 사례:

  • 모노레포(monorepo): 독립적인 서비스를 병렬 실행 경로로 빌드하고 테스트.
  • 멀티 플랫폼 빌드: 모든 빌드가 끝날 때까지 기다리지 않고 서로 다른 플랫폼용으로 컴파일.
  • 더 빠른 피드백: 테스트 결과와 오류를 더 일찍 받기.

needs: projectneeds: pipeline 키워드는 잡 의존성을 지정하는 데 사용되지 않아요. 다른 파이프라인에서 아티팩트를 가져오려면 needs: project를 사용하세요. 상위 파이프라인의 파이프라인 상태를 미러링하려면 needs: pipeline을 사용하세요.

출처: 문서

본문

needs 워크플로우

기본적으로 잡은 스테이지 단위로 실행돼요. 한 스테이지의 모든 잡이 성공적으로 끝나야 다음 스테이지의 잡이 시작될 수 있어요. 예를 들어 기본 build, test, deploy 스테이지가 있다면, test의 어떤 잡도 시작되기 전에 build의 모든 잡이 실행되고 끝나야 해요.

needs를 사용하면 잡이 의존하는 특정 잡을 나열해요. 그 잡은 이전 스테이지의 다른 잡이 아직 실행 중이어도 의존성이 끝나는 즉시 시작돼요. 이렇게 하면 일종의 방향성 비순환 그래프(DAG) 구조를 가진 파이프라인이 만들어져요.

같은 파이프라인에서 스테이지 잡과 needs 의존성이 있는 잡을 섞어 쓸 수 있어요.

추가로, needs: []를 사용하면 이전 잡이나 스테이지가 끝날 때까지 기다리지 않고 잡이 즉시 실행되도록 설정할 수 있어요. 소스 코드에서 바로 실행할 수 있고 빌드 결과에 의존하지 않는 lint 잡이나 스캐너를 즉시 실행하는 데 흔히 사용돼요.

needs와 스테이지 잡 비교

needs의 이점을 보여주기 위해 6개 잡이 있는 두 파이프라인을 비교해볼게요.

이 파이프라인은 6개 잡을 스테이지로 구성했어요. needs가 없으면 어떤 잡이 독립적이어도 한 스테이지의 모든 잡이 끝나야 다음 스테이지가 시작돼요:

graph TB
  subgraph build["Build Stage"]
    build_a["build_app_A"]
    build_b["build_app_B"]
  end
  subgraph test["Test Stage"]
    test_a["test_app_A"]
    test_b["test_app_B"]
  end
  subgraph deploy["Deploy Stage"]
    deploy_a["deploy_app_A"]
    deploy_b["deploy_app_B"]
  end
stages:
  - build
  - test
  - deploy

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

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

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

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

deploy_app_A:
  stage: deploy
  script: echo "Deploying A..."

deploy_app_B:
  stage: deploy
  script: echo "Deploying B..."

이 예시에서 build 스테이지의 모든 잡이 완료될 때까지 어떤 test나 deploy 잡도 실행되지 않아요. B 잡이 실행되는 데 오래 걸린다면, A의 test·deploy 잡은 B 잡이 끝나기를 기다리며 지연될 수 있어요.

needs를 사용하면 두 개의 독립 실행 경로를 정의할 수 있어요. 각 잡은 실제로 필요한 잡에만 의존하며, 두 경로에서 병렬 실행이 가능해져요:

graph LR
  subgraph build["Build Stage"]
    build_a["build_app_A"]
    build_b["build_app_B"]
  end
  subgraph test["Test Stage"]
    test_a["test_app_A"]
    test_b["test_app_B"]
  end
  subgraph deploy["Deploy Stage"]
    deploy_a["deploy_app_A"]
    deploy_b["deploy_app_B"]
  end

  build_a --> test_a
  build_b --> test_b
  test_a --> deploy_a
  test_b --> deploy_b
stages:
  - build
  - test
  - deploy

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

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

test_app_A:
  stage: test
  needs: ["build_app_A"]
  script: echo "Testing A..."

test_app_B:
  stage: test
  needs: ["build_app_B"]
  script: echo "Testing B..."

deploy_app_A:
  stage: deploy
  needs: ["test_app_A"]
  script: echo "Deploying A..."

deploy_app_B:
  stage: deploy
  needs: ["test_app_B"]
  script: echo "Deploying B..."

이 예시에서 test_app_Abuild_app_A가 성공적으로 끝나는 즉시 실행돼요. build_app_B가 아직 실행 중이어도 상관없어요. 마찬가지로 deploy_app_Abuild_app_B가 완료되기 전에 실행되고 배포될 수도 있어요.

잡 간 의존성 보기

파이프라인 그래프에서 잡 간 의존성을 볼 수 있어요.

이 보기를 활성화하려면 파이프라인 상세 페이지에서:

  • Job dependencies를 선택하세요.
  • 선택 사항. 어떤 잡이 연결되어 있는지 보여주는 선을 표시하려면 Show dependencies를 토글하세요.

A pipeline graph showing 5 jobs and their dependencies

needs 예시

needs로 잡 간 의존성을 만들고 잡이 시작을 기다리는 시간을 줄이세요. 패턴에는 fan-out, fan-in, 다이아몬드 의존성이 있어요.

Fan-out

fan-out 잡 의존성 그래프를 만들려면 여러 잡이 하나의 잡에 의존하도록 구성하세요.

예를 들면:

graph LR
  subgraph build["Build Stage"]
    build_job["build"]
  end
  subgraph test["Test Stage"]
    test_unit["test_unit"]
    test_integration["test_integration"]
    test_performance["test_performance"]
  end

  build_job --> test_unit
  build_job --> test_integration
  build_job --> test_performance
stages:
  - build
  - test

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

test_unit:
  stage: test
  needs: ["build"]
  script: echo "Unit tests..."

test_integration:
  stage: test
  needs: ["build"]
  script: echo "Integration tests..."

test_performance:
  stage: test
  needs: ["build"]
  script: echo "Performance tests..."

Fan-in

fan-in 의존성 그래프를 만들려면 하나의 잡이 여러 잡이 끝나기를 기다리도록 구성하세요.

예를 들면:

graph LR
  subgraph build["Build Stage"]
    build_frontend["build_frontend"]
    build_backend["build_backend"]
  end
  subgraph test["Test Stage"]
    test_frontend["test_frontend"]
    test_backend["test_backend"]
  end
  subgraph deploy["Deploy Stage"]
    deploy_job["deploy"]
  end

  build_frontend --> test_frontend
  build_backend --> test_backend
  test_frontend --> deploy_job
  test_backend --> deploy_job
stages:
  - build
  - test
  - deploy

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

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

test_frontend:
  stage: test
  needs: ["build_frontend"]
  script: echo "Testing frontend..."

test_backend:
  stage: test
  needs: ["build_backend"]
  script: echo "Testing backend..."

deploy:
  stage: deploy
  needs: ["test_frontend", "test_backend"]
  script: echo "Deploying..."

다이아몬드 의존성

다이아몬드 의존성 그래프를 만들려면 fan-out과 fan-in을 결합하세요. 하나의 잡이 여러 잡으로 퍼지고(fan out), 그 잡들이 다시 하나의 잡으로 모여요(fan in). 예를 들면:

graph LR
  subgraph build["Build Stage"]
    build_job["build"]
  end
  subgraph test["Test Stage"]
    test_unit["test_unit"]
    test_integration["test_integration"]
    test_performance["test_performance"]
  end
  subgraph deploy["Deploy Stage"]
    deploy_job["deploy"]
  end

  build_job --> test_unit
  build_job --> test_integration
  build_job --> test_performance
  test_unit --> deploy_job
  test_integration --> deploy_job
  test_performance --> deploy_job
stages:
  - build
  - test
  - deploy

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

test_unit:
  stage: test
  needs: ["build"]
  script: echo "Unit tests..."

test_integration:
  stage: test
  needs: ["build"]
  script: echo "Integration tests..."

test_performance:
  stage: test
  needs: ["build"]
  script: echo "Performance tests..."

deploy:
  stage: deploy
  needs: ["test_unit", "test_integration", "test_performance"]
  script: echo "Deploying..."

즉시 시작

needs: []를 사용하면 파이프라인이 생성될 때 다른 잡이나 스테이지를 기다리지 않고 즉시 시작하도록 설정할 수 있어요. test처럼 더 나중 스테이지에 나타나야 하지만 즉시 실행할 수 있는 lint나 스캐닝 도구에 사용하세요.

예를 들면:

stages:
  - build
  - test
  - deploy

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

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

lint_yaml:
  stage: test
  needs: []
  script: echo "Linting YAML..."

lint_code:
  stage: test
  needs: []
  script: echo "Linting code..."

deploy_app:
  stage: deploy
  script: echo "Deploying app..."

이 예시에서 lint_yamllint_codeneeds: []build_app이나 test 스테이지가 끝날 때까지 기다리지 않고 즉시 시작돼요. deploy_appneeds를 사용하지 않으므로 시작 전에 앞선 스테이지의 모든 잡이 끝나기를 기다려요.

파이프라인 뷰는 잡을 스테이지별로 그룹화해서 보여줘요:

graph LR
  subgraph build["Build Stage"]
    build_app["build_app"]
  end
  subgraph test["Test Stage"]
    test_app["test_app"]
    lint_yaml["lint_yaml"]
    lint_code["lint_code"]
  end
  subgraph deploy["Deploy Stage"]
    deploy_app["deploy_app"]
  end

  build_app --> test_app
  test_app --> deploy_app

잡은 가능한 한 일찍 실행되기 시작해요:

graph LR
  start["Pipeline Start"]

  start --> build_app["build_app"]
  start --> lint_yaml["lint_yaml"]
  start --> lint_code["lint_code"]

  build_app --> test_app["test_app"]
  test_app --> deploy_app["deploy_app"]

스테이지 없는 파이프라인 (Stageless pipelines)

stagestages 키워드를 생략하고 needs만 사용해 잡 순서를 정의할 수 있어요. stage 키워드가 없는 모든 잡은 기본 test 스테이지에서 실행돼요:

compile:
  script: echo "Compiling..."

unit_tests:
  needs: ["compile"]
  script: echo "Running unit tests..."

integration_tests:
  needs: ["compile"]
  script: echo "Running integration tests..."

package:
  needs: ["unit_tests", "integration_tests"]
  script: echo "Packaging..."

이 파이프라인의 구조를 보려면 파이프라인 상세 페이지에서 Job dependencies를 선택하세요. 기본 뷰를 사용하면 모든 잡이 test 스테이지에 함께 그룹화돼요.

선택적 의존성 (Optional dependencies)

needs에서 optional: true를 사용하면 잡이 파이프라인에 존재하는 경우에만 의존하도록 할 수 있어요. needsrules와 결합할 때 실행될 수도 있고 아닐 수도 있는 잡을 처리하는 데 이 옵션을 사용하세요.

예를 들면:

stages:
  - build
  - test
  - deploy

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

test:
  stage: test
  needs: ["build"]
  script: echo "Testing..."

test_optional:
  stage: test
  rules:
    - if: $RUN_OPTIONAL_TESTS == "true"
  script: echo "Optional tests..."

deploy:
  stage: deploy
  needs:
    - job: "test"
    - job: "test_optional"
      optional: true
  script: echo "Deploying..."

이 예시에서:

  • deploy는 다음에 의존해요:test는 파이프라인에 항상 존재해요.test_optionalRUN_OPTIONAL_TESTStrue일 때만 파이프라인에 존재해요.
  • RUN_OPTIONAL_TESTS가:falsetest_optional은 파이프라인에 없고 deploytest가 끝난 후 실행돼요.truetest_optional이 파이프라인에 존재하고 deploytesttest_optional 둘 다 끝나기를 기다려요.

optional: true가 없으면 deploy 잡이 test_optional을 기대하지만 파이프라인에 존재하지 않으므로 파이프라인 생성이 실패해요.

needsparallel:matrix 결합하기

needs 키워드는 parallel:matrix와 함께 사용해 병렬화된 잡을 가리키는 의존성을 정의할 수 있어요.

문제 해결

오류: 'job' does not exist in the pipeline

needsrules와 결합하면 파이프라인이 생성에 실패하고 이 오류가 표시될 수 있어요:

'unit_tests' job needs 'compile' job, but 'compile' does not exist in the pipeline.
This might be because of the only, except, or rules keywords. To need a job that
sometimes does not exist in the pipeline, use needs:optional.

이 오류는 어떤 잡의 needs가 파이프라인에 존재하지 않는 다른 잡을 가리킬 때 발생해요. 이 문제를 해결하려면 둘 중 하나를 해야 해요:

  • 해당 잡이 파이프라인에 없을 때 무시되도록 잡 의존성에 optional: true를 추가하세요.
  • 필요한 잡이 필요할 때 항상 실행되도록 해당 잡의 rules 구성을 업데이트하세요.

예를 들면:

#
# Method 1: Job with rules that may not exist
#
compile:
  stage: build
  rules:
    - if: $COMPILE == "true"
  script: echo "Compiling..."

unit_tests:
  stage: test
  needs:
    - job: "compile"        # If $COMPILE == "false", the `compile` job is not added
      optional: true        # to the pipeline and this needs is ignored.
  script: echo "Running unit tests..."

#
# Method 2: Job with rules that always matches the dependent job
#
build:
  stage: build
  rules:
    - if: $BUILD == "true"
  script: echo "Building..."

test:
  stage: test
  rules:                    # Both jobs have identical `rules`, and will always exist
    - if: $BUILD == "true"  # in the pipeline together.
  needs: ["build"]
  script: echo "Testing..."

더 알아보기

다음으로는 needs 키워드needs: project 문서를 함께 보면, 다른 파이프라인의 아티팩트를 가져오거나 상위 파이프라인 상태를 미러링하는 고급 의존성 설정까지 익힐 수 있어요.