GitLab CI/CD 구성 파일 최적화하기

GitLab CI/CD 구성 파일 최적화하기

GitLab CI/CD 구성 파일의 복잡성과 중복 구성을 줄이려면 다음을 사용할 수 있어요.

  • 앵커(&), 별칭(*), 맵 병합(<<) 같은 YAML 고유 기능. 다양한 YAML 기능에 대해 더 읽어보세요.
  • 더 유연하고 가독성이 좋은 extends 키워드. 가능하면 extends를 사용해야 해요.

변수 값만 다르고 비슷한 여러 잡을 만들려면 parallel:matrix를 사용해요.

출처: 문서

본문

앵커 (Anchors)

YAML에는 문서 전체에서 내용을 복제하는 데 사용할 수 있는 '앵커(anchor)'라는 기능이 있어요.

앵커를 사용해 속성을 복제하거나 상속할 수 있어요. 앵커를 숨겨진 잡(hidden jobs)과 함께 사용해 잡의 템플릿을 제공할 수 있어요.

& 문자는 앵커 이름을 표시하고, * 문자는 앵커를 참조하는 별칭이에요. YAML 파일에서 앵커를 참조하는 별칭보다 위에 앵커를 정의해야 해요.

중복 키가 있으면 가장 마지막에 포함된 키가 이기고 다른 키들을 덮어써요.

특정 경우(스크립트용 YAML 앵커 참고)에는 YAML 앵커를 사용해 여기저기에 정의된 여러 구성 요소로 배열을 만들 수 있어요. 예를 들어:

.default_scripts: &default_scripts
  - ./default-script1.sh
  - ./default-script2.sh

job1:
  script:
    - *default_scripts
    - ./job-script.sh

[include](/ci/yaml/#include) 키워드를 사용할 때는 여러 파일에 걸쳐 YAML 앵커를 사용할 수 없어요. 앵커는 정의된 파일에서만 유효해요. 다른 YAML 파일의 구성을 재사용하려면 [!reference 태그](/ci/yaml/yaml_optimization/#reference-tags)extends 키워드를 사용해요.

다음 예시는 앵커와 맵 병합을 사용해요. .job_template 구성을 상속하고 각각 자체 커스텀 script를 정의한 test1test2 두 잡을 만들어요.

.job_template: &job_configuration  # Hidden yaml configuration that defines an anchor named 'job_configuration'
  image: ruby:2.6
  services:
    - postgres
    - redis

test1:
  <<: *job_configuration           # Add the contents of the 'job_configuration' alias
  script:
    - test1 project

test2:
  <<: *job_configuration           # Add the contents of the 'job_configuration' alias
  script:
    - test2 project

&는 앵커 이름(job_configuration)을 설정하고, <<는 "주어진 해시를 현재 해시에 병합"을 의미하며, *는 이름 있는 앵커(다시 job_configuration)를 포함해요. 이 예시의 확장된 버전은:

.job_template:
  image: ruby:2.6
  services:
    - postgres
    - redis

test1:
  image: ruby:2.6
  services:
    - postgres
    - redis
  script:
    - test1 project

test2:
  image: ruby:2.6
  services:
    - postgres
    - redis
  script:
    - test2 project

앵커를 사용해 두 세트의 서비스를 정의할 수 있어요. 예를 들어 test:postgrestest:mysql.job_template에 정의된 script를 공유하지만, .postgres_services.mysql_services에 정의된 다른 services를 사용해요.

.job_template: &job_configuration
  script:
    - test project
  tags:
    - dev

.postgres_services:
  services: &postgres_configuration
    - postgres
    - ruby

.mysql_services:
  services: &mysql_configuration
    - mysql
    - ruby

test:postgres:
  <<: *job_configuration
  services: *postgres_configuration
  tags:
    - postgres

test:mysql:
  <<: *job_configuration
  services: *mysql_configuration

확장된 버전은:

.job_template:
  script:
    - test project
  tags:
    - dev

.postgres_services:
  services:
    - postgres
    - ruby

.mysql_services:
  services:
    - mysql
    - ruby

test:postgres:
  script:
    - test project
  services:
    - postgres
    - ruby
  tags:
    - postgres

test:mysql:
  script:
    - test project
  services:
    - mysql
    - ruby
  tags:
    - dev

숨겨진 잡이 편리하게 템플릿으로 사용되고, tags: [postgres]tags: [dev]를 덮어쓰는 것을 볼 수 있어요.

스크립트용 YAML 앵커

YAML 앵커script, before_script](/ci/yaml/#before_script), [after_script와 함께 사용해 여러 잡에서 사전 정의된 명령을 사용할 수 있어요.

.some-script-before: &some-script-before
  - echo "Execute this script first"

.some-script: &some-script
  - echo "Execute this script second"
  - echo "Execute this script too"

.some-script-after: &some-script-after
  - echo "Execute this script last"

job1:
  before_script:
    - *some-script-before
  script:
    - *some-script
    - echo "Execute something, for this job only"
  after_script:
    - *some-script-after

job2:
  script:
    - *some-script-before
    - *some-script
    - echo "Execute something else, for this job only"
    - *some-script-after

extends로 구성 섹션 재사용하기

[extends 키워드](/ci/yaml/#extends)를 사용해 여러 잡에서 구성을 재사용할 수 있어요. 이는 YAML 앵커와 비슷하지만 더 단순하고, include와 함께 extends를 사용할 수 있어요.

extends는 다중 레벨 상속을 지원해요. 추가 복잡성 때문에 세 단계를 넘는 것은 피해야 하지만, 최대 열한 단계까지는 사용할 수 있어요. 다음 예시에는 두 단계의 상속이 있어요.

.tests:
  rules:
    - if: $CI_PIPELINE_SOURCE == "push"

.rspec:
  extends: .tests
  script: rake rspec

rspec 1:
  variables:
    RSPEC_SUITE: '1'
  extends: .rspec

rspec 2:
  variables:
    RSPEC_SUITE: '2'
  extends: .rspec

spinach:
  extends: .tests
  script: rake spinach

extends에서 키 제외하기

확장된 내용에서 키를 제외하려면 그 키에 null을 할당해야 해요. 예를 들어:

.base:
  script: test
  variables:
    VAR1: base var 1

test1:
  extends: .base
  variables:
    VAR1: test1 var 1
    VAR2: test2 var 2

test2:
  extends: .base
  variables:
    VAR2: test2 var 2

test3:
  extends: .base
  variables: {}

test4:
  extends: .base
  variables: null

병합된 구성:

test1:
  script: test
  variables:
    VAR1: test1 var 1
    VAR2: test2 var 2

test2:
  script: test
  variables:
    VAR1: base var 1
    VAR2: test2 var 2

test3:
  script: test
  variables:
    VAR1: base var 1

test4:
  script: test
  variables: null

extendsinclude 함께 사용하기

다른 구성 파일의 구성을 재사용하려면 extends[include](/ci/yaml/#include)를 결합해요.

다음 예시에서 scriptincluded.yml 파일에 정의돼요. 그리고 .gitlab-ci.yml 파일에서 extendsscript의 내용을 참조해요.

  • included.yml:
.template:
  script:
    - echo Hello!
  • .gitlab-ci.yml:
include: included.yml

useTemplate:
  image: alpine
  extends: .template

병합 세부 사항

extends를 사용해 해시는 병합할 수 있지만 배열은 병합할 수 없어요. 중복 키가 있으면 GitLab이 키를 기준으로 역방향 심층 병합을 수행해요. 마지막 멤버의 키가 항상 다른 레벨에 정의된 모든 것을 덮어써요. 예를 들어:

.only-important:
  variables:
    URL: "http://my-url.internal"
    IMPORTANT_VAR: "the details"
  rules:
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
    - if: $CI_COMMIT_BRANCH == "stable"
  tags:
    - production
  script:
    - echo "Hello world!"

.in-docker:
  variables:
    URL: "http://docker-url.internal"
  tags:
    - docker
  image: alpine

rspec:
  variables:
    GITLAB: "is-awesome"
  extends:
    - .only-important
    - .in-docker
  script:
    - rake rspec

결과는 이 rspec 잡이에요.

rspec:
  variables:
    URL: "http://docker-url.internal"
    IMPORTANT_VAR: "the details"
    GITLAB: "is-awesome"
  rules:
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
    - if: $CI_COMMIT_BRANCH == "stable"
  tags:
    - docker
  image: alpine
  script:
    - rake rspec

이 예시에서:

  • variables 섹션은 병합되지만, URL: "http://docker-url.internal"URL: "http://my-url.internal"을 덮어써요.
  • tags: ['docker']tags: ['production']을 덮어써요.
  • script는 병합되지 않지만, script: ['rake rspec']script: ['echo "Hello world!"']를 덮어써요. 배열을 병합하려면 YAML 앵커를 사용할 수 있어요.

!reference 태그

!reference 커스텀 YAML 태그를 사용해 다른 잡 섹션의 키워드 구성을 선택해 현재 섹션에서 재사용할 수 있어요. YAML 앵커와 달리, !reference 태그는 포함된 구성 파일의 구성도 재사용할 수 있어요.

!reference 태그로 포함된 파일의 구성을 재정의하는 경우 CI/CD inputs를 사용하는 것을 고려해 보세요.

!reference 태그와 inputs는 결합할 수 없어요.

  • !reference 태그의 경로 안에서 input을 사용할 수 없어요. 예를 들어 !reference [.settings, "$[[ inputs.key ]]"]. 경로 세그먼트는 inputs가 보간되기 전에 YAML 파일이 파싱될 때 읽히므로, input은 리터럴 텍스트로 취급되고 참조를 찾지 못해요.
  • input 값에서 !reference 태그를 사용할 수 없어요. 태그는 inputs가 보간된 후에 해석되기 때문이에요.

다음 예시에서 서로 다른 두 위치의 scriptafter_scripttest 잡에서 재사용돼요.

  • configs.yml:
.setup:
  script:
    - echo creating environment
  • .gitlab-ci.yml:
include:
  - local: configs.yml

.teardown:
  after_script:
    - echo deleting environment

test:
  script:
    - !reference [.setup, script]
    - echo running my own command
  after_script:
    - !reference [.teardown, after_script]

다음 예시에서 test-vars-1.vars의 모든 변수를 재사용하고, test-vars-2는 특정 변수를 선택해 새 MY_VAR 변수로 재사용해요.

.vars:
  variables:
    URL: "http://my-url.internal"
    IMPORTANT_VAR: "the details"

test-vars-1:
  variables: !reference [.vars, variables]
  script:
    - printenv

test-vars-2:
  variables:
    MY_VAR: !reference [.vars, variables, IMPORTANT_VAR]
  script:
    - printenv

여러 !reference 태그를 사용해 rules, script 또는 스테이지로 배열을 만들 수 있어요. 예를 들어:

.rules_prod:
  - if: $CI_PIPELINE_SOURCE == "merge_request_event"
  - if: $CI_PIPELINE_SOURCE == "schedule"

.rules_staging:
  - if: $CI_COMMIT_BRANCH =~ /^wip-.*/
  - if: $CI_PIPELINE_SOURCE == "push"

deploy_job:
  script: echo test
  rules:
    - !reference [.rules_prod]
    - !reference [.rules_staging]

다른 모든 키워드와 함께 사용하면 config should be an array of 검증 오류가 나요.

script, before_script, after_script!reference 태그 중첩하기

script, before_script, after_script 섹션에서 !reference 태그를 최대 10단계까지 중첩할 수 있어요. 더 복잡한 스크립트를 만들 때 재사용 가능한 섹션을 정의하려면 중첩 태그를 사용해요. 예를 들어:

.snippets:
  one:
    - echo "ONE!"
  two:
    - !reference [.snippets, one]
    - echo "TWO!"
  three:
    - !reference [.snippets, two]
    - echo "THREE!"

nested-references:
  script:
    - !reference [.snippets, three]

이 예시에서 nested-references 잡은 세 개의 echo 명령을 모두 실행해요.

!reference 태그를 지원하도록 IDE 구성하기

파이프라인 에디터!reference 태그를 지원해요. 하지만 !reference 같은 커스텀 YAML 태그에 대한 스키마 규칙이 기본적으로 에디터에서 유효하지 않은 것으로 취급될 수 있어요. 몇몇 에디터는 !reference 태그를 받아들이도록 구성할 수 있어요. 예를 들어:

  • VS Code에서는 settings.json 파일에 vscode-yamlcustomTags를 파싱하도록 설정할 수 있어요.
"yaml.customTags": [
   "!reference sequence"
]
  • Sublime Text에서 LSP-yaml 패키지를 사용한다면 LSP-yaml 사용자 설정에 customTags를 설정할 수 있어요.
{
  "settings": {
    "yaml.customTags": ["!reference sequence"]
  }
}

더 알아보기

구성 최적화의 실제 효과를 확인하고 싶다면 파이프라인 에디터의 전체 구성 보기로 앵커나 extends가 확장된 결과를 볼 수 있어요. 더 복잡한 파이프라인을 만들고 싶다면 [parallel:matrix](/ci/jobs/job_control/#run-a-matrix-of-parallel-trigger-jobs)를 함께 살펴보는 걸 추천해요.