モノレポ構成のプロジェクトにおける GitLab CI の活用

こんにちは、GMO インターネットグループ株式会社の岩本です。
モノレポ構成のプロジェクトにおける GitLab CI の設定方法について調べましたので紹介します。

はじめに

今回モノレポ構成のプロジェクトを作る機会があり GitLab CI の設定をどのようにすればよいか調べました。
というのも、GitLab CI を動かすためにはプロジェクトルートの .gitlab-ci.yml に CI の設定を書く必要がありますが、複数プロジェクトの CI の設定を 1 つのファイルで管理するのは大変に感じたためです。

trigger:include:local, trigger:include:artifact を使った方法と、include:local を使った方法の計 3 種類の方法についてまとめました。
この記事では GitLab Community Edition 15.3.4, GitLab Runner 15.3.2 を使用しています。

trigger:include:local を使った方法

GitLab CI には trigger というキーワードがあり、これを使うことで別リポジトリのパイプラインを実行したり1,2、同じリポジトリ内の別の .gitlab-ci.yml を実行したりできます3
今回はモノレポ構成のため、同一リポジトリ内にある .gitlab-ci.yml を対象にした trigger:include:local を使います。

ディレクトリ構成は以下のようにしました。
今回は 2 つのプロジェクト project_a, project_b を 1 つのリポジトリで管理する構成とします。
この構成は後述の trigger:include:artifact でも同様です。

.
|-- .gilab-ci.yml
|-- project_a
|   └-- .gilab-ci.yml
└-- project_b
    └-- .gilab-ci.yml

プロジェクトルートの .gitlab-ci.yml, 各プロジェクトの .gitlab-ci.yml は以下のように書きます。

プロジェクトルートの .gitlab-ci.yml

project_a:
  trigger:
    include: project_a/.gitlab-ci.yml
    strategy: depend
  rules:
    - changes:
        - "project_a/**/*"

project_b:
  trigger:
    include: project_b/.gitlab-ci.yml
    strategy: depend
  rules:
    - changes:
        - "project_b/**/*"

各プロジェクトの .gitlab-ci.yml

workflow:
  rules:
    - if: $CI_MERGE_REQUEST_IID
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH

stages:
  - test
  - deploy

job1:
  stage: test
  script: echo test

job2:
  stage: deploy
  script: echo deploy

ポイント

trigger:strategy の設定

trigger:strategy を付けないデフォルトの設定では、子パイプラインが作成された直後に親パイプラインが成功となります4
今回は子パイプラインで起きたエラーを表示してほしいため trigger:strategy を設定しています。

子パイプラインに rules:if: $CI_MERGE_REQUEST_IID を付けないと動作しない場合がある

マージリクエストのタイミングで実行される子パイプラインを、マージリクエストパイプラインであると明示的に設定する必要があります。
これを付けない場合子パイプラインがブランチパイプラインとして認識され、パイプラインが期待したとおりに動かない場合があります5

ブランチパイプラインは git push された内容を、マージリクエストパイプラインはマージリクエストの修正対象全体を見るという違いがあります6

downstream pipeline has no valid jobs
子パイプラインがブランチパイプラインとして認識され正しく動かなかった例

trigger で実行するパイプラインに有効なジョブがなければ失敗する

trigger を発火したはいいものの、子パイプラインの中に実行するジョブが一つも無い場合、親パイプラインは失敗します。実行元でそもそも trigger を発火させないなど何かしら工夫が必要です。

downstream pipeline has no valid jobs
trigger 先のパイプラインに有効なジョブがなかった場合のエラー

trigger:include:artifact を使った方法

今回は project_a, project_b だけでしたがもっとプロジェクト数が多い場合、プロジェクトルートの .gitlab-ci.yml の管理が大変になりそうです。
そこで、各プロジェクトごとに実行するパイプラインを動的に生成し管理の手間を減らせないか検討しました。

具体的には trigger:include:artifact を使った方法7です。

上記で紹介した trigger:include:local を持つ .gitlab-ci.yml を生成します。生成されたファイルをアーティファクトにし、後続のジョブで trigger:include:artifact を使って読み込みます。

.gitlab-ci.yml

image: ubuntu

stages:
  - build
  - triggers

workflow:
  rules:
    - if: $CI_MERGE_REQUEST_IID
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH

variables:
  workflow: >
    workflow:
      rules:
        - if: __CI_MERGE_REQUEST_IID
        - if: __CI_COMMIT_BRANCH == __CI_DEFAULT_BRANCH
  template: >
    PROJECT_NAME:
      trigger:
        include: PROJECT_NAME/.gitlab-ci.yml
        strategy: depend
      rules:
        - changes:
          - "PROJECT_NAME/**/*"

generate-artifact:
  stage: build
  script:
    - echo -e "${workflow}" | sed -i -e 's/__/$/g' > generated.yml
    - >
      find . -name .gitlab-ci.yml -type f -not -path "./.gitlab-ci.yml" | cut -d'/' -f2 |
      while read PROJECT_NAME; do
        export PROJECT_NAME
        echo -e "${template}" | sed -e "s/PROJECT_NAME/$PROJECT_NAME/g" >> generated.yml
      done
  artifacts:
    paths:
      - generated.yml

child-pipeline:
  stage: triggers
  trigger:
    include:
      - artifact: generated.yml
        job: generate-artifact
    strategy: depend

ポイント

パイプラインの生成

各プロジェクトごとに用意している .gitlab-ci.ymlfind コマンドを使って見つけ、trigger:include:local の対象にしています。

これでプロジェクトルートの .gitlab-ci.yml の管理の手間から開放されました。

YAML ファイル生成ジョブが毎回実行される

この方法では毎回ファイルを生成するため、その分時間がかかります。
rules:changes などを使って README.md だけの変更の場合は実行しない、などの工夫もしたほうがよいかもしれません。

子パイプラインが 1 つにまとめられる

個々のマージリクエスト画面で CI ジョブの成功/失敗がチェックマーク等で表示されますが、この方法では複数の子パイプラインが 1 つにまとめられてしまいます。
ぱっと見何を実行したのか分かりにくく、パイプラインが失敗した場合やどのプロジェクトの CI が実行されたか知りたい場合、パイプライン詳細画面にて確認する必要があります。

all child pipelines put together in 1 job
複数のパイプラインが 1 つのジョブにまとめられている

include:local を使った方法

プロジェクトごとの CI の設定は .gitlab/ ディレクトリに配置します。
また、ファイル名は <プロジェクト名>.gitlab-ci.yml とします。

.
|-- .gilab-ci.yml
└-- .gitlab/
    |-- project_a.gilab-ci.yml
    └-- project_b.gilab-ci.yml

プロジェクトルートの .gitlab-ci.yml

stages:
  - test
  - deploy

default:
  image: ubuntu

include:
  - local: .gitlab/*.gitlab-ci.yml

各プロジェクトの .gitlab/*.gitlab-ci.yml

.project_a:rules:
  rules:
    - &mr
      if: $CI_MERGE_REQUEST_IID
      changes:
        - project_a/**/*
    - &main
      if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
      changes:
        - project_a/**/*

project_a:test:
  rules:
    - *mr
    - *main
  stage: test
  script:
    - echo test

project_a:deploy:
  rules:
    - *main
  stage: deploy
  script:
    - echo build
  needs:
    - project_a:build

ポイント

YAML anchors を使ったルールの定義

GitLab には extends を使って共通する設定を再利用できますが、配列の中身はマージされないため8ここでは YAML anchors を使った方法を採用しました9

ジョブ名や変数名が重複しないように

各プロジェクトごとに設定するジョブ名が重複すると、CI がうまく動かない場合があります。
<プロジェクト名>:<ジョブ名> のように、名前が被らないような工夫が必要です。

また、variables を使って変数を定義できますが、こちらも重複しないように気を付ける必要があります。

needs を使った依存関係の整理

GitLab CI のパイプラインは基本的にステージ単位で進みます。上記であればステージは test, deploy があり、test ステージに含まれる全てのジョブを完了させてから、次の deploy ステージに進みます。
複数のプロジェクトを同時に更新していた場合、直接関係のないジョブの完了を待たなければならないかもしれません。

例: project_a:test は完了したが、project_b:test に時間がかかっているため、project_a:deploy が実行できない

この問題は needs を使うことでジョブ間の依存関係を記述でき、無駄な待ち時間を削減できます。
また、ジョブ間の依存関係がグラフで表示されかっこよいです!10

その他

今回 .gitlab-ci.yml を書くにあたり VSCode を使いました。
その際 YAML プラグイン https://marketplace.visualstudio.com/items?itemName=redhat.vscode-yaml を使うと、GitLab CI の設定ファイル内で使えるキーワードを補完してくれたり、マウスホバーした際にそのキーワードの説明文を表示できたりと便利でしたのでおすすめです。

YAML plugin
trigger:include の説明文を表示している例

まとめ

モノレポ構成のプロジェクトで GitLab CI を動かすという 1 つのお題に対し、今回挙げただけでも 3 種類の方法が考えられました。
最終的に 3 つ目の include:local を使った方法を採用することとなりました。CI の結果の見やすさや設定ファイルが一か所にまとまっていること、needs を使った効率的なパイプライン処理ができることなどが決めてです。

また、GitLab CI の設定方法を調査するにあたり、公式ドキュメントの充実さに改めて気づくことができました。感謝 :bow:
今回参考にしたドキュメントやリポジトリの URL を以下に記載します。
中でも GitLab の .gitlab-ci.yml11はモノレポ構成でなくても参考になるかと思いますので、一度目を通すと新しい発見があるかもしれません。

参考資料

脚注

ブログの著者欄

岩本 直大

GMOインターネットグループ株式会社

2018 年 GMO インターネットグループ株式会社 新卒入社 お名前.com KVM, ConoHa VPS等の開発運用に従事

採用情報

関連記事

KEYWORD

採用情報

SNS FOLLOW

GMOインターネットグループのSNSをフォローして最新情報をチェック