GitLabのCI/CDで超重要なrulesの全てを理解する
「あ、あかん、このrulesの意味がぜんぜんわからん…」
ここ一年ぐらい、GitLab.com上での開発をメインでやっているハマコーです。現プロジェクトでもGitLab Runnerを利用したCI/CDを開発サイクルの中で回しているのですが、今までナンチャッテで理解していた.gitlab-ci.yml
にちょっと複雑なジョブ起動条件を設定しようとしてハマってしまいました。
主にこのあたりはrules
キーワードを使って制御していくのですが、正直慣れていないと記述方法や考え方などハマりどころが多かったため、ごく基本的なところからrules
を理解するための情報をまとめてみました。この記事が、これからGitLabで本格的にCI/CDを組もうとされている方にも、すでにばっりばりにCI/CD運用している方にも、何らしか参考になれば幸いです。
この記事はGitLabのカレンダー | Advent Calendar 2021 - Qiitaの、18日目の記事になります。
(祭) ∧ ∧ Y ( ゚Д゚) Φ[_ソ__y_l〉 GitLabマツリダワッショイ |_|_| し'´J
.gitlab-ci.ymlとは?
GitLabで、CI/CDを実現するために、gitリポジトリに格納するファイルです。標準のファイル名は.gitlab-ci.yml
で、ルートディレクトリに格納されCI/CD設定の全てをこのファイルに記述します。
公式情報
.gitlab-ci.yml
自体のマニュアルトップ。利用上の注意点やスコープが記載されています
.gitlab-ci.yml
のキーワードのリファレンス。利用できる予約語に対する詳細な説明が含まれています。
実際にGitLabでCI/CDを実現するためのインフラとしてGitLab Runnerの設定が必要になりますが、本記事ではスコープ外です。もし詳しい情報が必要な方は、こちらを参考に。
ただ、特に各ProjectでGitLab Runnerを設定していなくても、デフォルトではGitLab.comにホストされたShared Runnerを利用してくれるはずなので、今回のサンプルの実行には別途GitLab Runnerの設定は不要かと思います。
何故rulesについての記事を書くのか?
.gitlab-ci.yml
には、CI/CDで必要となる全ての情報を埋め込む必要があるんですが、rulesは頻出の割には、理解がちょっと難しいと感じたからです。
プロジェクトの取り決めによりますが、一つのリポジトリにアプリケーションコードもテストコードもインフラのコードも全て含まれている場合、CI/CDの中で、各ジョブの起動条件を制御することは頻出なのですが、自分の英語力のへぼさも相まって、最初公式マニュアルだけを見てても理解に苦しみました。
そんな頻出設定なrules
をこれから記述する人向けに、この記事が参考になれば幸いです。
rulesではなくて、onlyやexceptを使えばよいのでは?
GitLabのCI/CDでのブランチコミットの起動条件で検索するとonly
やexcept
を利用したサンプルが多数でてきますが、現在はrules
の利用が推奨されています。
only and except are not being actively developed. rules is the preferred keyword to control when to add jobs to pipelines.
引用:Keyword reference for the `.gitlab-ci.yml` file | GitLab
only
は直感的に利用できて便利なのですが、より汎用的にジョブの起動条件を設定するのにrules
を公式が推しているのであれば、rules
を学んでおいて損は無いでしょう。
手元で動かすための公開サンプルプロジェクト
今回、実際に手元で動かして試してみたい方もいると思うので、学習用のsampleプロジェクト公開しています。forkしてらえれば手元でも動きますので、理解を深めるためにこのサンプルを役立ててもらえれば幸いです。
hamako9999-Group / sample-to-understand-rules-in-gitlab-ci · GitLab
ディレクトリ構成。ルートディレクトリの.gitlab-ci.yml
から、各サンプルディレクトリをchild pipeline
として定義。
$ tree -a . . ├── .gitlab-ci.yml ├── README.md └── sample00 └── .gitlab-ci.yml └── sample01 └── .gitlab-ci.yml └── sample02 └── .gitlab-ci.yml └── sample04 └── .gitlab-ci.yml
ルートディレクトリに設定したgitlab-ci.yml
。この内容で全てのサンプルプロジェクトが起動します。
stages: - samples trigger-sample00: stage: samples trigger: include: sample00/.gitlab-ci.yml trigger-sample01: stage: samples trigger: include: sample01/.gitlab-ci.yml ## and more samples
とりあえず、GitLabのWebメニュー、CI/CD -> Pipelinesから、[Run Pipeline]ボタンからPipelineを起動してみてください。おそらく、GitLab.comでホストしているShared Runnerが動くはずですが、うまく動かない場合は、Shared Runnerあたりの設定(GitLab Runner | GitLab)を確認しましょう。
サンプル0「rules学ぶ前の元ネタ」
1ステージ、1ジョブでechoだけするシンプルな元ネタを利用します。rules
も何も含まれていないため、当然ですがPipelineを起動することで、echoが動作します。
stages: - build build-code-job: stage: build script: - echo "無事に動いてますか?"
サンプル1「初めてのrules」
さて、前置き長くなりましたが、ようやく.gitlab-ci.yml
にrules
を組み込んでいきます。全て公式マニュアル
Keyword reference for rulesに記載されている内容ですが、それらをかいつまんで説明していくのが、このブログの趣旨です。
ここで早速、rulesを使う上での注意点。
rules
は、Pipelineが起動されたタイミングで評価され、各ジョブがそのPipelineに含まれるかどうかをrules
に則って評価します。そのため、リポジトリに格納したファイルの内容を読み込んで、rules
の評価対象にすることはできませんrules
は、only
やexcept
キーワードを置換するため、双方のキーワードを同時に利用することはできません。もし設定した場合、key may not be used with rules
というエラーになります
rules
には、以下のキーワードを配列として含めることができます。
- if
- changes
- exists
- allow_failure
- variables
- when
とりあえず、when
のみを利用したシンプルなrulesを見てみます。
stages: - build build-job-work: stage: build script: - echo "これは動く" rules: - when: on_success build-job-work-always: stage: build script: - echo "これも動く" rules: - when: always build-job-never-work: stage: build script: - echo "これは動きません" rules: - when: never
この場合、ジョブbuild-job-never-work:
は、動きません。正確にはPipelineのジョブとして乗りません。build-job-work
とbuild-job-work-always
が動いてますね。
サンプル2「特定ブランチコミットをジョブ追加条件とする」
ここからは、もう少しよくあるユースケースでのサンプルを見ていきます。ここでは、mainブランチへのコミットをのみをジョブの追加条件としたい場合です。
この書き方では、build-job-when-main-branch
ジョブのみがパイプラインに追加され、残りの2つのジョブはパイプラインに追加されません。
stages: - build build-job-when-main-branch: stage: build script: - echo $CI_COMMIT_BRANCH - echo "メインブランチへのコミット時のみ、パイプラインに追加される" rules: - if: '$CI_COMMIT_BRANCH == "main"' build-job-when-main-branch-not-work: stage: build script: - echo $CI_COMMIT_BRANCH - echo "メインブランチへのコミット時だが、when neverがあるため、パイプラインに追加されない" rules: - if: '$CI_COMMIT_BRANCH == "main"' when: never build-job-when-main-branch-not-work2: stage: build script: - echo $CI_COMMIT_BRANCH - echo "ifステートメントにtrueが存在したいため、パイプラインに追加されない" rules: - if: '$CI_COMMIT_BRANCH == "amema"' - if: '$CI_COMMIT_BRANCH == "koryama"' - if: '$CI_COMMIT_BRANCH == "wassyoi"'
rules:ifステートメントの解説
rules:if
ステートメントの公式マニュアル
rules:if
ステートメントの条件です。
- ifステートメントがtrueの場合は、パイプラインにジョブが追加されます
- ifステートメントがtrueだが、when:neverと組み合わされている場合、パイプラインにジョブが追加されません
- ifステートメントにtrueがない場合、パイプラインにジョブが追加されません
何を当たり前のことを言っているんだ?となりそうですが、公式で提供されている以下のサンプルを読み解きながら理解していきます。
job: script: echo "Hello, Rules!" rules: - if: '$CI_MERGE_REQUEST_SOURCE_BRANCH_NAME =~ /^feature/ && $CI_MERGE_REQUEST_TARGET_BRANCH_NAME != $CI_DEFAULT_BRANCH' when: never - if: '$CI_MERGE_REQUEST_SOURCE_BRANCH_NAME =~ /^feature/' when: manual allow_failure: true - if: '$CI_MERGE_REQUEST_SOURCE_BRANCH_NAME'
上記の場合、上から以下の順番で評価されます。
- マージリクエストのソースブランチ名が「feature」で始まっていて、かつマージリクエストのターゲットブランチがデフォルトブランチ(ここではmain)以外の場合は、パイプラインにジョブを追加しない
- マージリクエストのソースブランチ名が「feature」で始まっていて、かつ手動起動の場合、パイプラインにジョブを追加する。その時、ジョブが失敗しても、パイプラインを停止しない
- マージリクエストのソースブランチ名が指定されている場合(マージリクエストの場合)、パイプラインにジョブを追加する
rules:ifステートメントのその他サンプル
このrules:if
で利用できる環境変数周辺の条件式サンプルは、こちらのマニュアルで豊富なサンプルが定義されているので、必ず合わせて参照することをオススメします。
Choose when to run jobs | GitLab
こういった、正規表現を使ったタグのセマンティックバージョニング書式の指定も可能です。
rules: - if: $CI_COMMIT_TAG =~ /^v\d+.\d+.\d+/
CI/CDで利用できる環境変数について
CI/CDで利用できる事前定義の環境変数マニュアルはこちら。
Predefined variables reference | GitLab
この環境変数一覧とニラメッコすることで、どの条件の時にパイプラインにジョブを追加できるかを指定できるようになるはずです。指定方法のイメージは湧いたのではないでしょうか。
サンプル3「特定ブランチのタグ付与をジョブ追加条件とする」
結論から言うと、特定ブランチのタグ付与を条件としてジョブを起動する方法は、GitLabのCIの仕様としてできません。
試しにやってみましょう。main
ブランチにタグが付与されたときだけ、プロダクション環境にデプロイする意図で、build
とdeploy
の2つのステージがある、以下の.gitlab-ci.yml
を作成します。
stages: - build - deploy build-job: stage: build script: - echo $CI_COMMIT_BRANCH - echo $CI_COMMIT_TAG - echo $CI_COMMIT_REF_NAME - echo "アーティファクトのビルド" deploy-job: stage: deploy script: - echo $CI_COMMIT_BRANCH - echo $CI_COMMIT_TAG - echo $CI_COMMIT_REF_NAME - echo "この条件は真にならず、デプロイは実行されない" rules: - if: '$CI_COMMIT_BRANCH == "main" && $CI_COMMIT_TAG != null'
mainブランチへpushしてみると、以下のようにdeployステージのジョブがPipelineに追加されません。まぁここまではタグを付与していないので当たり前の話ですが。
では、mainブランチ上にタグをv1.0.0
として定義し、プッシュしてみます。
$ git tag -a v1.0.0 -m 'test tag push' $ git log commit 629c5edeea0bb360f10fb6cb49db56c36007fdc2 (HEAD -> main, tag: v1.0.0, origin/main, origin/HEAD) $ git push origin v1.0.0
これにより、deployステージが実行されるかと思いきや実行されず。mainブランチへのpushと同じ結果になります。
環境変数の内容を確認
build-job
の中で、下記3種類の環境変数をecho
しているのでそのログを確認すると以下の結果となっています。
mainブランチにpushした場合。
$ echo $CI_COMMIT_BRANCH main $ echo $CI_COMMIT_TAG $ echo $CI_COMMIT_REF_NAME main
mainブランチのコミットにタグを付与してpushした場合。
$ echo $CI_COMMIT_BRANCH $ echo $CI_COMMIT_TAG v1.0.0 $ echo $CI_COMMIT_REF_NAME v1.0.0
このあたりマニュアルに記載されているとおりですが、以下の内容となります。
CI_COMMIT_BRANCH
- ブランチからプッシュした時にブランチ名が入る。タグプッシュの場合は、空白
CI_COMMIT_TAG
- タグプッシュした時にタグ名が入る。ブランチプッシュの場合は、空白
CI_COMMIT_REF_NAME
- ブランチ名かタグ名のいずれかが入る
そもそもGitのタグは、任意のコミットに対するタグ名の付与という概念なので、タグとブランチの間に直接の関係性はないためそういう仕様のようです。つまり、Pipelineの起動時、タグ名とそのタグが付与されたブランチの名前の両方を取得することは不可能です。
自分はこの仕様に気づくまでそれなりに時間かけてしまったので、同じところでハマる人が今後現れないよう願っています。実際にプロダクション環境へのリリースを、タグ付与をトリガーとするか、もしくは例えばプロダクションリリース用のrelease
ブランチへのマージをトリガーにするかは、各組織の運用体制によると思いますが、タグ付与の権限制限や、ブランチのプロテクトなど、リポジトリ側の設定と合わせてこのあたり設計することをおすすめします。
GitLab flowは、Git flowともGitHub flowとも違いますので、GitLab.comでのブランチ運用の検討には、下記GitLab flowの解説を参考にするのも良いと思います。
Introduction to GitLab Flow | GitLab
サンプル4「任意のファイルの変更をジョブ追加条件とする」
一つのリポジトリで、複数種類(アプリコード、インフラコードなど)のファイルを管理する場合、ファイル変更をジョブ追加条件に指定するのは必須です。その場合は、rules:changes
が利用できます。
中身は何でも良いので、新たにapplication.txt
とinfra.txt
のファイルを追加します。
ディレクトリ構成。
$ tree sample04 -a sample04 ├── .gitlab-ci.yml ├── application.txt └── infra.txt
以下の、.gitlab-ci.yml
を用意します。
stages: - build build-application-job: stage: build script: - echo "アプリケーションコードのビルド" - cat sample04/application.txt rules: - changes: - sample04/application.txt build-infra-job: stage: build script: - echo "インフラコードのビルド" - cat sample04/infra.txt rules: - changes: - sample04/infra.txt
プッシュのタイミングによりますが、プッシュ時に各ファイルに変更が入っていれば、各ジョブが実行されます。
ルートディレクトリに置いた親.gitlab-ci.ymlでのchangesの使い方
この記事の冒頭、ルートディレクトリに配置し、child pipeline
としてサンプルプロジェクトのジョブを起動する以下の.gitlab-ci.yml
を紹介しました。ただ、この場合だとchild pipeline
を無条件で起動してしまいます。
stages: - samples trigger-sample00: stage: samples trigger: include: sample00/.gitlab-ci.yml ## more samples
もし、サンプルプロジェクト配下のファイルのみが変更された時に、そのchild pipeline
を起動する場合は、以下のようにrules:changes
を記述することで実現できます。
stages: - samples trigger-sample00: stage: samples trigger: include: sample00/.gitlab-ci.yml rules: - changes: - sample00/**/* ## more samples
child pipeline
は、複数種類のコードをまとめて同じリポジトリで管理する場合に、ほぼ必須の考え方となるので、詳細は以下の公式ドキュメントをしっかり把握することをおすすめします。
Parent-child pipelines | GitLab
公式ドキュメント提供の有用なサンプル
こちらに、workflow
用のサンプルが掲載されています。workflow
は、rules
がジョブのパイプラインへの追加条件を指定するものであったのに対し、パイプラインそのものの起動条件を定義します。が、rules
の書き方のサンプルになるものも多数記載されているので、こちらも見ておくと、利用できるシチュエーションがいろいろ把握できるのでおすすめです。
GitLab CI/CD workflow
keyword | GitLab
まとめ:これからのGitLab環境のCI/CDの整備のお供としての基本的な考え方を知っておこう
今回、rules
の把握のため、公式ドキュメントをひたすら読み漁りながら理解を深めてきました。丁寧に読み込めば、記載の意図や動作の仕組みなどが詳細に記載されているので、めちゃくちゃ有用でした。
CI/CDのユースケースに応じて関連キーワードでググることで、有象無象のブログに引き当たることが多くあると思いますが、エッジケース含めた対応力応用力をつけておこうとするとそれでは足りず、公式ドキュメントを参照する必要が必ずあると思います。
一度、動かしてみてだいたいの感触を掴んだ後は、ジョブやパイプラインそのものの考え方も含めて、下記Gitlab.comのCI/CDのドキュメントをしっかり読み込むことが結局は近道かなと思います。
それでは、今日はこのへんで。濱田(@hamako9999)でした。