TT Lab
はじめる
学ぶ 学習パス コース

GitLab CI/CD

三つのサービスのうち一つしか変えていないのに全部ビルドされた

TT Labで続きを見る

目標

parallel:matrixで組み合わせの数だけジョブを増やし、除外する組み合わせと特定の組み合わせの待機を扱ったうえで、モノレポをサービスごとの子パイプラインと、リポジトリの構造から生成する動的パイプラインに分けて、gitlab-ci-localで実行します。

なぜ重要なのか

設定が大きくなる仕方は、たいていコピー&ペーストです。Pythonのバージョンが1つ増えるたびにジョブをコピーし、サービスが1つ増えるたびに親ファイルにブロックを貼り付けていくと、1つのファイルにすべてのチームのルールが積み上がり、すべての変更がすべてのサービスを再ビルドします。matrixは組み合わせを1つのブロックにまとめ、子パイプラインはサービスの設定をそのサービスのディレクトリに戻し、changesと動的生成は変わったものだけを動かします。その代わり、ジョブ名・アーティファクト・変数がどう流れるかを知っていてはじめて、needsとルールを正しく掛けられます。

ステップ

  1. /root/glci-monoをgitリポジトリにし(.gitignoreに.gitlab-ci-local/を入れます)、.gitlab-ci.ymlにstages[build, package, trigger, generate, dynamic]とジョブbuild(stageはbuild)を置いてください。parallel: matrixで、PYは"3.11"・"3.12"、OSはlinux・alpineの組み合わせを作り、スクリプトはdist/py$PY-$OS.txtにpy=<PY> os=<OS>を書き、artifactsでdist/をアップロードします。コミットして実行してください。ジョブ名がbuild: [3.11,linux]の形で4つできるはずです。
  2. buildにrulesを追加し、OSがalpineかつPYが"3.11"の組み合わせだけwhen: never、残りはwhen: on_successにしてください。コミットして実行すると、ジョブが3つだけ動くはずです。
  3. ジョブpackage-linux(stageはpackage)を追加し、needsでbuildのPY: "3.12"、OS: linuxの組み合わせ1つだけを指して(needs:parallel:matrix)、スクリプトはls distにしてください。コミットして実行すると、package-linuxのログにpy3.12-linux.txtだけが見えるはずです。
  4. /root/glci-mono/services/api/ci.ymlにジョブapi-unit(echo "api unit svc=$SVC")を置き、親にジョブtrigger-api(stageはtrigger)を追加して、変数SVC: apiとtrigger: include: services/api/ci.ymlを置いてください。コミットして実行すると、子パイプラインのapi-unitのログがapi unit svc=apiになるはずです。
  5. services/web/ci.ymlにジョブweb-unit(echo "web unit svc=$SVC")を置き、親にtrigger-web(SVCはweb)を追加してください。2つのtriggerジョブには、それぞれrules: changes:でservices/api/**/*・services/web/**/*を置きます。/root/glci-mono-origin.gitにbareリポジトリを作ってoriginとして登録し、コミットしたmainをpushしたあとでgit remote set-head origin mainを実行してください。採点ツールは、コピーでwebだけを直したブランチを作り、trigger-webだけが一覧にあるかを確認します。
  6. /root/glci-mono/scripts/generate.shが、services/の下のディレクトリごとにジョブlint-<이름>(echo "lint <이름>")を含むYAMLを標準出力に出すようにしてください(プレースホルダーはディレクトリ名です)。親のジョブgenerate(stageはgenerate)がその出力をgenerated.ymlとして保存してartifactsでアップロードし、ジョブrun-generated(stageはdynamic、needs: [generate])がtrigger: include: - artifact: generated.yml, job: generateでそれを子パイプラインとして動かすようにします。コミット・pushして実行すると、lint-apiとlint-webが動くはずです。
  7. /root/glci-mono/services/billing/に、ci.ymlではなくREADME.md(内容は何でもかまいません)だけを作って、コミット・pushしてください。親の.gitlab-ci.ymlは直しません。実行すると、動的な子パイプラインにlint-billingが新しくできて動くはずです。採点ツールは、コピーにサービスのディレクトリをもう1つ作っても確認します。

参考

Python2種とOS2種、4つのジョブを1つのブロックにまとめる

/root/glci-monoをgitリポジトリにし(.gitignoreに.gitlab-ci-local/を入れます)、.gitlab-ci.ymlにstages[build, package, trigger, generate, dynamic]とジョブbuild(stageはbuild)を置いてください。parallel: matrixで、PYは"3.11"・"3.12"、OSはlinux・alpineの組み合わせを作り、スクリプトはdist/py$PY-$OS.txtにpy=<PY> os=<OS>を書き、artifactsでdist/をアップロードします。コミットして実行してください。ジョブ名がbuild: [3.11,linux]の形で4つできるはずです。

matrixの1つの項目の中に複数の変数を書くと、すべての組み合わせがジョブになります。バージョンの値は引用符で囲んでおけば、3.10が3.1になる心配がありません。一覧はgitlab-ci-local --list-csv-allで見ます。

サポートしない組み合わせを1つだけ除く

buildにrulesを追加し、OSがalpineかつPYが"3.11"の組み合わせだけwhen: never、残りはwhen: on_successにしてください。コミットして実行すると、ジョブが3つだけ動くはずです。

matrixの変数は、rules:ifでたいていCI/CD変数のように使えます。組み合わせを除くためにmatrixを2つの項目に分けると、増えるたびに一覧を計算し直すことになりますが、ルールで除けば例外だけを書けば済みます。

組み合わせ1つのアーティファクトだけを待って受け取る

ジョブpackage-linux(stageはpackage)を追加し、needsでbuildのPY: "3.12"、OS: linuxの組み合わせ1つだけを指して(needs:parallel:matrix)、スクリプトはls distにしてください。コミットして実行すると、package-linuxのログにpy3.12-linux.txtだけが見えるはずです。

matrixで生まれたジョブをneedsに名前のまま(build: [3.12,linux])書く代わりに、parallel:matrixで変数の値を書いて指します。指した組み合わせのアーティファクトだけを受け取ります。

サービスの設定はサービスのディレクトリに置く

/root/glci-mono/services/api/ci.ymlにジョブapi-unit(echo "api unit svc=$SVC")を置き、親にジョブtrigger-api(stageはtrigger)を追加して、変数SVC: apiとtrigger: include: services/api/ci.ymlを置いてください。コミットして実行すると、子パイプラインのapi-unitのログがapi unit svc=apiになるはずです。

triggerジョブは、スクリプトの代わりに別のパイプラインを作ります。includeで指したファイルが子パイプライン全体の設定になり、triggerジョブのvariablesは子に渡されます。サービスチームが自分のディレクトリのファイルだけを直せば済む構造です。

変更があったサービスのパイプラインだけを動かす

services/web/ci.ymlにジョブweb-unit(echo "web unit svc=$SVC")を置き、親にtrigger-web(SVCはweb)を追加してください。2つのtriggerジョブには、それぞれrules: changes:でservices/api/**/*・services/web/**/*を置きます。/root/glci-mono-origin.gitにbareリポジトリを作ってoriginとして登録し、コミットしたmainをpushしたあとでgit remote set-head origin mainを実行してください。採点ツールは、コピーでwebだけを直したブランチを作り、trigger-webだけが一覧にあるかを確認します。

モノレポですべてのサービスを毎回ビルドすると、パイプラインの時間がサービスの数だけ増えます。changesはリモートのデフォルトブランチと比べた変更を見る(gitlab-ci-localの場合)ので、リモートが必要です。

リポジトリの構造を読んで子パイプラインを作る

/root/glci-mono/scripts/generate.shが、services/の下のディレクトリごとにジョブlint-<이름>(echo "lint <이름>")を含むYAMLを標準出力に出すようにしてください(プレースホルダーはディレクトリ名です)。親のジョブgenerate(stageはgenerate)がその出力をgenerated.ymlとして保存してartifactsでアップロードし、ジョブrun-generated(stageはdynamic、needs: [generate])がtrigger: include: - artifact: generated.yml, job: generateでそれを子パイプラインとして動かすようにします。コミット・pushして実行すると、lint-apiとlint-webが動くはずです。

子パイプラインの設定を、ジョブが実行中に作れます。サービスが増えるたびに親の設定を直す代わりに、ルール(ディレクトリ=ジョブ)をスクリプトに置きます。生成された設定の中のincludeにはCI/CD変数を使えないという制約が、公式ドキュメントにあります。

サービスを1つ足しても親の設定はそのまま

/root/glci-mono/services/billing/に、ci.ymlではなくREADME.md(内容は何でもかまいません)だけを作って、コミット・pushしてください。親の.gitlab-ci.ymlは直しません。実行すると、動的な子パイプラインにlint-billingが新しくできて動くはずです。採点ツールは、コピーにサービスのディレクトリをもう1つ作っても確認します。

動的パイプラインのルールがリポジトリの構造にあるので、ディレクトリができるだけでジョブができます。逆に、servicesの下にサービスではないディレクトリを置くと、それもジョブになるということなので、ルールをドキュメントとして残しておく必要があります。