APC 技術ブログ

株式会社エーピーコミュニケーションズの技術ブログです。

株式会社 エーピーコミュニケーションズの技術ブログです。

GitLab rulesを理解してCI/CD Jobの起動を制御する

こんにちは、クラウド事業部 CI/CDサービスメニューチームの山路です。

今回はGitLab CI/CDを扱う上で重要なキーワードである rules について、その使い方を整理しました。

docs.gitlab.com

背景

GitLabは .gitlab-ci.yml にCI/CDの実行内容を定義します。 .gitlab-ci.yml は多くのキーワードを組み合わせて利用しますが、その中でも重要なものの一つが rules です。

rules はGitLab CI/CDのWorkflowやJobに対して設定し、いつ、どのような条件でWorkflow/Jobを実行するかを定義します。CI/CDのワークフローで実行する処理は多岐にわたり、それらを実行するタイミングや対象のブランチ・ファイルは、条件に応じて異なる処理を実行することが殆どです。そのためGitLab CI/CDにおいて rules を理解することは重要と言えます。

一方、GitLab CI/CDには数多くのキーワードが存在し、 rules も多くのキーワードを含んでいます。そのため、 rules を理解するには、ある程度調査や検証をする時間が必要です。私も以前 rulesを簡単に試してみたことがありますが、ドキュメントを眺めると思った以上に多くのキーワードや使い方が紹介されており、なかなか全容をつかむのが難しいと感じました。

今回はそんな rules を一度整理し、GitLab CI/CDを使いこなすための一歩を踏みたい!という気持ちで書き始めました。何かの参考になれば幸いです。

rulesの紹介

GitLabのrulesはWorkflow/Jobで設定できますが、今回はJobに設定することを前提に紹介します。

WorkflowはGitLabのPipeline全体の動きを定義するために使います。workflow で定義したrulesは、GitLabのPipeline作成後、およびJobの実行前に評価されるため、ワークフロー自体を実行するか否か制御します。一方、実際の現場ではワークフロー全体より、個々のJobをいつ実行するか制御する場面のほうが多いだろうと考え、今回はworkflowの紹介はしません。

workflow の内容を知りたい方は、公式ドキュメントをご覧ください。

前提

rules の各キーワードの紹介の前に、 rules を扱う上での前提を紹介します。

  • .gitlab-ci.yml に定義した rules は、GitLabのPipelineが作成されたときに評価され、最初に条件にマッチするまで順番に評価されます。条件にマッチすると、該当したJobをPipelineに追加するか除外するか、 rulesの設定によって決定されます。
  • 上述の通りrulesはPipelineが作成されたときに評価されるので、Pipeline作成後に生成される変数等をrulesの条件に使うことはできません。例えばGitLabは artifacts:reports:dotenv というキーワードでJob間の変数の受け渡しが可能ですが、あるJobが作成したdotenv変数をrulesに渡して評価する、という使い方はできません。
  • rulesonly/except という別のキーワードと併用することはできません。 only / exceptrules 以前に使われ、似たような機能を提供するキーワードでしたが、現在はdeprecated扱いとなり将来的には削除される予定です。

rules は以下のキーワードを使用します。各キーワードは組み合わせて使うことも可能です。

  • if
  • changes
  • exists
  • when
  • allow_failure
  • needs
  • variables
  • interruptible

以降は、各キーワードの紹介、及びその他rulesに関する情報の紹介になります。

rules:if

docs.gitlab.com

if キーワードは、主にGitLabのCI/CD変数を対象に、特定のステータスやイベントに合致した場合にのみJobを追加します。よく使われるCI/CD変数には以下のようなものがあります。

  • CI_PIPELINE_SOURCE : Pipelineが何によって開始されたか (push イベント、スケジュール実行、ChatOpsなど) 、そのイベント名が挿入されます。イベントに応じてJobの起動条件を分ける場合に使います。
  • CI_COMMIT_BRANCH : あるブランチからPushされた場合に、コミット先のブランチ名が挿入されます。例えば develop / main ブランチのような寿命の長いブランチでテスト・デプロイ用のJobを起動するときに使います。
  • CI_COMMIT_TAG : あるタグからPushされた場合に、コミット先のタグ名が挿入されます。例えばGit Flowを採用するリポジトリで v1.x のようなタグを対象に、リリース用のドキュメントを作成するようなJobを起動することができます。
  • ユーザーの定義したカスタム変数 : 例えば when:manual キーワードと組み合わせ、手動でJobを実行するときに入力する変数の値に応じて、Jobの実行を制御できます。

docs.gitlab.com

rules:if を使ったいくつかの例を載せておきます。

# Merge request作成時に起動する
at-merge-request:
  script: echo "Hello, Rules!"
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"

# featureブランチからMerge requestを作成したときに起動する
at-merge-request-from-feature-branch:
  script: echo "Hello, Rules!"
  rules:
    - if: $CI_MERGE_REQUEST_SOURCE_BRANCH_NAME =~ /^feature/

# mainブランチにコミットしたときに起動する
at-main-branch-commit:
  script: echo "Hello, Rules!"
  rules:
    - if: $CI_COMMIT_BRANCH == "main"

# 何かしらタグがPushされたときに起動する
at-tag-push:
  script: echo "Hello, Rules!"
  rules:
    - if: $CI_COMMIT_TAG

# 手動実行時、CUSTOM_VARという変数が“sample value”を含むときに起動する
at-manual:
  script: echo "Hello, Rules!"
  rules:
    - if: $CUSTOM_VAR == "sample value"
      when: manual

rules:if キーワードの追加情報は以下の通りです。

  • 後ほど紹介する when というキーワードは、Jobの中だけでなく rules の中に定義することもできます。Jobとrulesの両方にwhenを設定した場合、 rules に定義された when の設定が優先されます。
  • CI/CD変数との等号に =~ !~ を使うと、右辺は正規表現として評価されます。前述の例の中では、feature ブランチからMerge requestを作成した時が該当します。

rules:changes

docs.gitlab.com

changes キーワードは、リポジトリ中の特定のファイルを指定し、そのファイルに変更があるか否かを検出して、Jobの起動を制御します。対象のファイルは rules:changes 配下に記載することもできますが、 rules:changes:paths 配下に記載することもできます。

rules:changes rules:changes:paths は、ファイル名を直接指定するほか、ファイル名・拡張子に対するワイルドカードも指定できます。

rules:changes:pathsrules:changes:compare_to と組み合わせて使える点でrules:changes と異なります。rules:changes:compare_to に参照先のブランチやタグ・コミットを指定し、どの時点のものと比較して変化があったかを指定することもできます。

なお、 rules:changes は新しいブランチの作成やMerge requestの作成など、ブランチに変更があったときにJobを制御するのが主な用途です。例えばタグのPushやスケジュール・手動実行されたPipelineの場合、ブランチの作成などがなければ思ったようにJobが起動しない場合もあります。そのため該当のPipelineの場合は rules:changes:compare_to を使って特定のブランチと比較することが推奨されます。

また rules:changes のみを設定すると、例えば新しいブランチを作成したときも rules:changes を設定したJobは起動します。これを抑制するには rules:changes を利用する時に rules:if など別のキーワードと組み合わせることも良いでしょう。

以下に rules:changes の例を載せておきます。

# Dockerfile変更時に起動する
at-dockerfile-changes:
  script: echo "Hello, Rules!"
  rules:
    - changes: 
        - Dockerfile

# Merge request作成時、かつDockerfileに変更があったときに起動する
at-merge-request-with-dockerfile-change:
  script: echo "Hello, Rules!"
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
      changes:
        - Dockerfile

# dir/ ディレクトリ配下のいずれかのファイルが更新されたときに起動する
at-changes-any-files-under-directory:
  script: echo "Hello, Rules!"
  rules:
    - changes:
        - dir/*

# mainブランチと比較してDockerfileに変更があったときに起動する
at-changes-compare-to-main-branch:
  script: echo "Hello, Rules!"
  rules:
    - changes:
        paths:
          - Dockerfile
        compare_to: 'main'

rules:changes キーワードの追加情報は以下の通りです。

  • 1つの rules:changes につき、最大50のパターン・ファイルパスを定義できます。
  • rules:changesonly:changes / except:changes キーワードと同じように働きます。
  • changes は、設定したパターンやファイルパスと一致するいずれかのファイルが変更されている場合に Trueとなります。

rules:exists

docs.gitlab.com

※GitLab 15.6で導入

exists キーワードは、特定のファイルが存在するか否かを判定し、存在する場合のみJobを起動しますexists 配下は、ファイル名を直接記載するか、GlobパターンやCI/CD変数を利用できます。

また existschanges と似たような rules:exists:paths というキーワードが存在します (Self-managed版でのみ利用可能) 。 rules:exists:pathsrules:exists:project と合わせて使うことで、どのProjectにファイルが存在するかを指定できます。また rules:exists:ref と組み合わせることで、検索するタグやブランチ名を指定できます。

以下に rules:exists の例を載せます。

# Dockerfileが存在するときに起動する
at-dockerfile-exists:
  script: echo "Hello, Rules!"
  rules:
    - exists:
        - Dockerfile

# testというProject上にDockerfileが存在するときに起動する
at-dockerfile-exists-in-test-project:
  script: echo "Hello, Rules!"
  rules:
    - exists:
      paths:
        - Dockerfile
      project: username/test-project

rules:exists キーワードの追加情報は以下の通りです。

  • exists は、一致するいずれかのファイルが存在する場合に trueとなります。
  • rules:exists には、最大50のファイルパス・パターンを指定できます。
  • パフォーマンス上の理由から、GitLabはexists のファイルパスやパターンに対して最大10000回のチェックを行います。また10000回目以降のチェックについては注意が必要で、Projectに10000個以上のファイルがある場合、または10000回以上のチェックを行う場合、 exists は常に条件に一致すると解釈します。

rules:when

docs.gitlab.com

when キーワードは Job / rules のほか workflow:rules でも定義可能で、Pipeline全体の中でいつJobを起動するか、実行する条件や遅延時間などを制御しますwhen の条件をなにも設定しない場合、デフォルトでは on_success という値が設定されます。

when には以下の6つの値のどれかが設定されます。

  • on_success: 設定したJobより前の時点で失敗したJobが存在しない、もしくは allow_failure: true が設定されている場合に起動します。
  • on_failure: 設定したJobより前の時点で、いずれかのJobが失敗した場合に起動します。
  • never: 設定より前のJobの状態に依らず、Jobを実行しません。
  • always: 設定より前のJobの状態に依らず、Jobを実行します。
  • manual: 手動実行でのみJobを実行します。
  • delayed: 指定した時間だけJobの実行を遅らせます。start_inと組み合わせる

rules の中で when:never を使う場合は rules:if など別の条件と組み合わせ、「ある特定の条件下ではJobを実行しない (それ以外の条件では起動する)」よう制御するために使われることが多いようです。

when:manual はすでに何度か登場していますが、Pipelineの中で手動実行したいJob、例えば本番環境向けのJobだけは人の手で実行したい場合などに利用できます。

以下に when の例を載せます。

# これ以前のstageのJobが失敗したら起動する
at-job-failed:
  script: echo "Hello, Rules!"
  rules:
    - when: on_failure

# Merge request時はJobは実行されない
job-is-not-started-at-merge-request:
  script: echo "Hello, Rules!"
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
      when: never

# このJobは必ず実行される
always:
  script: echo "Hello, Rules!"
  rules:
    - when: always

# 30秒後にJobを開始する
delayed-30seconds:
  script: echo "Hello, Rules!"
  rules:
    - when: delayed
      start_in: 30 seconds

rules:allow_failure

docs.gitlab.com

allow_failure キーワードは、allow_failure: true とすることで、そのJobが失敗してもPipelineを停止しないよう制御します

allow_failurewhen:manual と組み合わせることも可能です。 when:manual は手動実行でJobを起動するよう制御するキーワードですが、 allow_failure: false と組み合わせることで、手動実行したJobが失敗した場合は後続のJobを実行しないよう設定できます。

なお、allow_failure はJobレベルでも定義可能ですが、rules と Jobの両方を定義した場合は rules のほうが優先されます。

以下に allow_failure の例を載せます。

# Jobが失敗してもPipelineを続行する
pipeline-continues-even-job-failed:
  script: echo "Hello, Rules!"
  rules:
    - allow_failure: true

# 手動実行したJobが失敗したらPipelineを停止する
pipeline-stops-when-manual-job-failed:
  script: echo "Hello, Rules!"
  rules:
    - when: manual
      allow_failure: false

rules:needs

docs.gitlab.com

※GitLab 16.1で導入

needs は Jobレベルでも設定可能なキーワードであり、Job間の依存関係を宣言することでJobの実行順を制御できます (Job/rules両方とも設定した場合はrulesのほうを優先します)。 rules:needs も同様の機能を持ちますが、 needsrules とJobの両方で定義した場合は rules のほうが優先されます。GitLabのドキュメントではこれを利用し、開発環境向けと本番環境向けのJobを切り替える方法が紹介されています。

build-dev:
  stage: build
  rules:
    - if: $CI_COMMIT_BRANCH != $CI_DEFAULT_BRANCH
  script: echo "Feature branch, so building dev version..."

build-prod:
  stage: build
  rules:
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
  script: echo "Default branch, so building prod version..."

tests:
  stage: test
  needs: ['build-dev']
  rules:
    - if: $CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH
      needs: ['build-prod']
    - when: on_success # Run the job in other cases
  script: echo "Running tests for dev by default, or prod when default branch..."

※公式ドキュメントより

needs はJob名のほか [] と設定することも可能です。 [] の場合、依存するJobがないと判定され、即座にJobを開始します。

また needsartifacts optional というキーワードを利用できます。 artifactsneeds を使うJobがArtifactsをダウンロードするか否かを制御します。optionaloptional:true と設定することで、依存先のJobが存在する場合はその終了を待機し、存在しない場合は無視する動きになります。

rules:variables

docs.gitlab.com

rules:variables は特定条件下での変数を設定するのに利用します。 rules:variables にはKey:Value形式で変数を定義し、すでに定義されている場合も値を上書きできます。

rules:interruptible

docs.gitlab.com

※GitLab 16.10で導入

interruptible キーワードは、新しいコミットに対して同じref上のPipelineが開始しているときに、Jobの完了前にJobをキャンセルするよう制御しますrules:interruptible はJobの interruptible の値を更新するのに利用します。

なお本機能を有効にするには、GitLab CI/CDの設定から Auto-cancel redundant pipelines (冗長なパイプラインを自動キャンセル) を有効にするか、 workflow:auto_cancel:on_new_commit キーワードを使う必要があります。

docs.gitlab.com

docs.gitlab.com

複数のキーワードを組み合わせる例

rules は複数のキーワードを組み合わせることで、より複雑な条件を設定できます。複数のキーワードを組み合わせた場合、全ての条件を満たさなければ対象のJobは起動しません

複数のキーワードを使った例はすでにいくつか紹介済みですが、GitLabの公式ドキュメントにはさらに複雑な条件の例も紹介されています。

docker build:
  script: docker build -t my-image:$CI_COMMIT_REF_SLUG .
  rules:
    - if: $VAR == "string value"
      changes:  # Include the job and set to when:manual if any of the follow paths match a modified file.
        - Dockerfile
        - docker/scripts/**/*
      when: manual
      allow_failure: true

※公式ドキュメントより

ここでは if changes when allow_failure を組み合わせており、以下の条件を満たした場合にのみJobが起動します。

  • Project中の Dockerfile 、もしくは docker/scripts 配下のいずれかのファイルが変更されている
  • ユーザーの設定した変数 VARstring value という値が格納されている

上記条件を満たしたとき、このJobは手動実行でのみ起動が可能です。そしてこのJobが失敗してもPipelineは停止せず、後続にJobがある場合はそれらを準に実行します。

また、 rules で複数条件を組み合わせる場合、 () && || などを使うことで1行に収めることも可能です。ただし .gitlab-ci.yml の可読性は低くなることもあるので、使う場面は選ぶべきかと思います。

job1:
  script:
    - echo This rule uses parentheses.
  rules:
    - if: ($CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH || $CI_COMMIT_BRANCH == "develop") && $MY_VARIABLE

rulesを再利用する

docs.gitlab.com

rules!reference タグや extends キーワードと合わせることで、他のJobに定義された rules を再利用できます。 !reference extendsrules だけでなく script といったJobの設定に対しても利用できます。また !reference extendsinclude で呼び出した他のテンプレートの内容も参照できます。

さらにGitLab CI/CDのJobは、Job名の先頭に . をつけることでJobを実行不能にしつつ (hidden job) 、テンプレートとして利用できます。 !reference extends はhidden jobと組み合わせることで、テンプレートの設定を再利用することも可能です。

なお rules を使う場合、使用者は !reference extends で呼び出した rules と通常の rules を組み合わせて利用できます。 !reference を使うことで同じrulesを繰り返し定義することなく、 .gitlab-ci.yml の管理と見通しの向上を期待できます。

ここでは !reference extends を使った例をいくつか載せます。

# hidden jobで設定されたrulesを再利用する 
.default_rules:
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
      when: manual

Job1-reference:
  script: 
    - echo "This is from Job1."
  rules:
    - !reference [.default_rules, rules]

Job1-extends:
  script:
    - echo "This is from Job1."
  extends: [.default_rules]

# 他のJobのrulesを再利用する
Job-with-rules:
  script:
    - echo "This is from Job-with-rules."
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"

Job2-reference:
  script:
    - echo "This is from Job2."
  rules:
    - !reference [Job-with-rules, rules]

Job2-extends:
  script:
    - echo "This is from Job2."
  extends: [Job-with-rules]

# 別のテンプレートからrulesを再利用する
include:
  - local: test.yml

Job3-reference:
  script:
    - echo "This is from Job3."
  rules:
    - !reference [.test, rules]

Job3-extends:
  script:
    - echo "This is from Job3."
  extends: [.test]

test.yml

.test:
  script:
    - echo “This is from .test”
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"

includeとの組み合わせ

docs.gitlab.com

include.gitlab-ci.yml からテンプレートファイルを呼び出すのに使うキーワードです。 includerules と組み合わせることで、 rules によるJobの実行条件を上書きできます。これにより、他テンプレートの実行条件を上書きし、より柔軟にJobの再利用と実行を制御できます。

なお include と組み合わせ可能なのは rules:if rules:exists rules:changes の3つです。

いくつかの例を載せます。

include:
# mainブランチにコミットしたときにmain.ymlを使用する
 - local: main.yml
    rules:
      - if: $CI_COMMIT_BRANCH == "main"

# exception-file.mdが存在するときはexcept.ymlを使用しない
 - local: except.yml
    rules:
      - exists:
          - exception-file.md
        when: never

# Dockerfileに変更があったときはdocker.ymlを使用する
 - local: docker.yml
    rules:
      - changes:
        - Dockerfile

includeと組み合わせたときの補足情報を記載します。

  • includerules:existsを組み合わせたとき、異なるProjectから includeで参照した場合は注意が必要です。includeで参照したテンプレートファイル上で rules:existsを使用した場合、GitLabはPipelineを実行したProjectやrefではなく、 includeで指定した別のProject情を検索するよう動きます。これを避けるには rules:existsでなく rules:exists:paths rules:exists:project を使用する必要があります。

さいごに

今回はGitLabの rules について紹介しました。 本記事で rules の使い方については一通りさらったんじゃないかと思いますので、rules でどんなことができるか、自分たちのやりたいことを rules で実現できるか等知りたい方は参考にしていただければと思います。

最後に、弊社はGitLabオープンパートナー認定を受けております。 また以下のようにCI/CDの導入を支援するサービスも行っているので、何かご相談したいことがあればお気軽にご連絡ください。

www.ap-com.co.jp