モノレポにおける効率的なGitLab CIの設定
こんにちは、エンジニアのmackeyです。今回はGitLab CIの設定方法に関して、実際に実装してみてよさそうだと思った記述を紹介したいと思います。
背景
モノレポ構成のプロジェクトにおいて、効率的なCI/CDパイプラインの構築が必要となりました。具体的には以下の要件がありました。
- 各サービスディレクトリで変更があった場合のみ、該当するサービスのCIを実行する
- ブランチとジョブの実行条件を以下のように制御する
- develop/staging/productionブランチへのプッシュ時:デプロイを実行
- 上記ブランチへのマージリクエスト時:フォーマットチェックとテストを実行
この要件を満たすようなGitLab CIの設定を実装したので、紹介したいと思います。
実装例
モノレポのディレクトリ構成は以下を例として進めます。
.
├── .gitlab-ci.yml
├── frontend
│ ├── .gitlab-ci.yml
│ ├── (その他ファイル)
└── backend
├── .gitlab-ci.yml
└── (その他ファイル)
以下がgitlab-ci.ymlの実装例です。backendはfrontendと同様の書き方になるので省略します。今回の要件に必要最低限の部分のみ抜き出しているので、実際は他の記述も必要に応じて加えてください。
.gitlab-ci.yml
.deploy_branch:
rules:
- if: |
$CI_COMMIT_BRANCH != "develop" &&
$CI_COMMIT_BRANCH != "staging" &&
$CI_COMMIT_BRANCH != "production"
when: never
.merge_request:
rules:
- if: $CI_PIPELINE_SOURCE != 'merge_request_event'
when: never
- if: |
$CI_MERGE_REQUEST_TARGET_BRANCH_NAME != "develop" &&
$CI_MERGE_REQUEST_TARGET_BRANCH_NAME != "staging" &&
$CI_MERGE_REQUEST_TARGET_BRANCH_NAME != "production"
when: never
stages:
- fmt
- test
- deploy
include:
- /frontend/.gitlab-ci.yml
- /backend/.gitlab-ci.yml
frontend/.gitlab-ci.yml
frontend-fmt:
stage: fmt
rules:
- !reference [.merge_request, rules]
- changes:
- frontend/**/*
script:
- cd frontend
- ...(fmtのコマンド)
frontend-test:
stage: test
rules:
- !reference [.merge_request, rules]
- changes:
- frontend/**/*
script:
- cd frontend
- ...(testのコマンド)
frontend-deploy:
stage: deploy
rules:
- !reference [.deploy_branch, rules]
- changes:
- frontend/**/*
script:
- cd frontend
- ...(deployのコマンド)
解説
上のコードについて、gitlab-ci.ymlのキーワードごとに解説していきます。
最新の内容はhttps://docs.gitlab.com/ee/ci/yaml/ を参照してください。
stages
stagesセクションではパイプラインの実行順序を定義します。
stages:
- fmt
- test
- deploy
これらのステージは定義された順序で逐次実行されます。前のステージが成功した場合のみ、次のステージに進むことができます。一方で、同じステージに属する複数のジョブは並列で実行されます。この特性を活かし、本実装ではfmtジョブが成功した場合のみtestジョブを実行するようにしています。
include
includeを使用することで、CI/CD設定を複数のファイルに分割できます。
include:
- /frontend/.gitlab-ci.yml
- /backend/.gitlab-ci.yml
各サービスのCI/CD設定を独立したファイルとして管理することによって、設定ファイルの肥大化を防ぎ、メンテナンス性が向上します。また、各チームが担当するサービスのCI/CD設定を独立して管理できるようになります。
includeで指定するパスはリポジトリのルートからの相対パスとなります。また、インクルードされるファイルは親ファイルの変数やアンカーにアクセスできるため、共通の設定を効率的に再利用できます。
rules
今回の記事で一番重要な部分となります。.deploy_branchと.merge_requestで各サービスディレクトリで使用する共通ルールを定義しています。
否定条件を使用する理由
一見すると以下のような肯定的な条件の方が直感的に見えるかもしれません。
rules:
- if: |
$CI_COMMIT_BRANCH == "develop" ||
$CI_COMMIT_BRANCH == "staging" ||
$CI_COMMIT_BRANCH == "production"
しかし、本実装では以下のような否定条件を採用しています。
rules:
- if: |
$CI_COMMIT_BRANCH != "develop" &&
$CI_COMMIT_BRANCH != "staging" &&
$CI_COMMIT_BRANCH != "production"
when: never
この否定条件を使用する理由には、「上から順に評価され、最初にマッチした条件で実行可否が決定される」というrulesの仕様が関係しています。前者のruleの場合、ブランチ条件にマッチした時点でジョブが実行されてしまいます。今回は差分条件と組み合わせたいので、否定条件として書くことによって、ブランチ条件と差分条件をAND条件として扱うことができます。
共通ルールの参照方法
共通ルールの利用には!reference
タグを使用しています。extends
ではなく!reference
を使用する理由は、extends
を使用すると別でrulesを書いた場合に上書きされてしまうためです。!reference
を使用することで、複数のルール(今回の場合は差分のルール)を組み合わせることが可能になります。
注意点
共通ルールのみを使用する場合は、以下のように最後にwhen: always
を追加する必要があります。
rules:
- !reference [.deploy_branch, rules]
- when: always
この設定を忘れると、共通ルールの条件に合致しない場合にジョブが実行されないので、注意する必要があります。
まとめ
モノレポにおけるCIの設定では、以下の点が重要です。
- ディレクトリごとの変更検知による効率的なパイプライン実行
- ブランチとマージリクエストに応じた適切なジョブの制御
- 設定ファイルの分割による管理のしやすさ
今回紹介したような構成により、モノレポ環境でも効率的かつ管理しやすいCI/CDパイプラインを実現できます。