TL;DR
クロスプラットフォーム開発や後方互換性のために,複数の環境でビルド・テスト・デプロイを実行したいことがよくあります.GitLab の CI/CD でこれを実現するためのパイプライン戦略は3つあります.それぞれ一長一短あるため,要求に応じて使い分ける必要があります.
- 単一パイプライン・単一ステージ:すべての環境を1つのパイプラインで処理する.ビルド,テスト,デプロイを1つのステージにまとめて処理する.
- 単一パイプライン・複数ステージ:すべての環境を1つのパイプラインで処理する.ビルド,テスト,デプロイを3つのステージに分けて処理する.
- 複数パイプライン・複数ステージ:それぞれの環境を異なるパイプラインで処理する.ビルド,テスト,デプロイを3つのステージに分けて処理する.
なお,理論上は「複数パイプライン・単一ステージ」も考えられますが,あまり使いどころがないので本稿では扱いません.
それぞれの長所短所を以下の表にまとめます.
単一パイプライン 単一ステージ |
単一パイプライン 複数ステージ |
複数パイプライン 複数ステージ |
|
---|---|---|---|
所要時間 | 〇 | × | △ |
ワークフローの視認性 | × | 〇 | △ |
ワークフローの安全性 | × | 〇 | △ |
デバッグのしやすさ | △ | × | 〇 |
メンテナンス性 | △ | 〇 | × |
それぞれの観点で3つの戦略を比べて,最も優れているものを〇,2番目を△,3番目を×としました.あくまでも相対的な順位ですので,△や×だからといって必ずしも使い物にならないわけではありません.また,〇だからといって必ずしも実用的というわけでもありません.
動作環境・予備知識
GitLab のバージョンは2018年9月11日現在の GitLab.com の最新版 (11.3.0-rc4-ee) を想定します.基本的な GitLab CI の仕組みと gitlab-ci.yml
の書き方は既知とします.より具体的には,GitLab CI の Getting started の節は既読とします.
以下,2つのバージョンのコンパイラでそれぞれバイナリをビルドし,テストし,配布する,というワークフローを想定して説明します.
戦略1: 単一パイプライン・単一ステージ
gcc73:
image: gcc:7.3.0
script:
- make build
- make test
- make deploy
gcc82:
image: gcc:8.2.0
script:
- make build
- make test
- make deploy
項目 | 評価 | 理由 |
---|---|---|
所要時間 | 〇 | 環境構築が1度きりで,各ジョブが独立に処理されるので,オーバーヘッドはほぼゼロ. |
ワークフローの視認性 | × | Pipeline 画面でワークフローが可視化されない. |
ワークフローの安全性 | × | ビルド後の環境を使いまわしてテスト,デプロイするため,「CI では動いたのにクリーンな環境では動かない」タイプのバグが紛れ込みやすい. |
デバッグのしやすさ | △ | 1つのジョブが担う役割が大きいので問題の切り分けが難しい. |
メンテナンス性 | △ | 本来 CI が担うべきワークフロー制御を自前構築することになるため,他者が理解しにくい. |
設定ファイルの記述が簡単なので,ファーストステップにはいい感じです.でも使い続けてみると,うっかりバグのあるコードをデプロイしてしまったり,CI では暗黙的になっているワークフローを他人に説明するのが大変だったりと辛い面が見えてきます.
戦略2: 単一パイプライン・複数ステージ
build-gcc73:
stage: build
image: gcc:7.3.0
script: make build
artifacts:
paths:
- binaries/
build-gcc82:
stage: build
image: gcc:8.2.0
script: make build
artifacts:
paths:
- binaries/
test-gcc73:
stage: test
image: gcc:7.3.0
script: make test
dependencies:
- build-gcc73
test-gcc82:
stage: test
image: gcc:8.2.0
script: make test
dependencies:
- build-gcc82
deploy-gcc73:
stage: deploy
image: gcc:7.3.0
script: make deploy
dependencies:
- build-gcc73
deploy-gcc82:
stage: deploy
image: gcc:8.2.0
script: make deploy
dependencies:
- build-gcc82
項目 | 評価 | 理由 |
---|---|---|
所要時間 | × | 依存関係にないジョブの完了を待機するため,オーバーヘッドが大きい. |
ワークフローの視認性 | 〇 | Pipeline 画面ですべてのワークフローが一覧できる. |
ワークフローの安全性 | 〇 | ビルド,テスト,デプロイをそれぞれクリーンな環境で行うため,意図しない依存関係が紛れ込みにくい.問題があるかぎり何もデプロイしない. |
デバッグのしやすさ | × | どれか1つでもジョブが失敗するとパイプライン全体が停止するため,問題点が一度に洗い出せない. |
メンテナンス性 | 〇 | GitLab CI のドキュメントでも紹介されているデファクトスタンダードな方法であるため,他人にも理解しやすい. |
おそらくこれが標準的な方法だと思います.ワークフローが GitLab の UI から見えるし,設定ファイルの書き方も自然です.しかしとにかくデバッグが大変.1つのジョブが失敗すると本来は依存関係にない後段のジョブまですべて止まってしまうので,問題が1つずつしか見つかりません.潜在的に複数の問題があり,それらを適切な順番で修正しないといけないケースもあります.そのようなとき,このパイプラインで見つかった問題を順次修正していく方法では堂々巡りに陥ることがあります.さらに,各ステージですべてのジョブの完了を待つせいで,ターンアラウンドタイムが長くなり修正が捗りません.
戦略3: 複数パイプライン・複数ステージ
そもそも複数パイプラインとは何かというと,あるパイプラインの実行中に別のパイプラインを起動する機能です.GitLab EE の PREMIUM 版,もしくは GitLab.com の SILVER プラン以上でのみ利用できる機能なので,あまり知られていないのも無理はありません.GitLab 上では,複数パイプラインを使ったパイプライングラフは下記のような感じに表示されます.Upstream / Downstreamという見慣れないステージが見えますが,これらが,このパイプラインの上流 / 下流にあたるパイプラインです.
この機能を使うと,複数ステージからなるパイプラインを並列に実行することができます.
entry-point:
except:
- pipelines
image: erikvdbergh/alpine-curl:latest
script:
- curl --request POST --form "variables[CXX]=gcc73" --form "token=$CI_JOB_TOKEN" --form "ref=$CI_COMMIT_REF_NAME" https://gitlab.com/api/v4/projects/$CI_PROJECT_ID/trigger/pipeline
- curl --request POST --form "variables[CXX]=gcc82" --form "token=$CI_JOB_TOKEN" --form "ref=$CI_COMMIT_REF_NAME" https://gitlab.com/api/v4/projects/$CI_PROJECT_ID/trigger/pipeline
build-gcc73:
only:
variables:
- $CXX == "gcc73"
stage: build
image: gcc:7.3.0
script: make build
artifacts:
paths:
- binaries/
build-gcc82:
only:
variables:
- $CXX == "gcc82"
stage: build
image: gcc:8.2.0
script: make build
artifacts:
paths:
- binaries/
test-gcc73:
only:
variables:
- $CXX == "gcc73"
stage: test
image: gcc:7.3.0
script: make test
dependencies:
- build-gcc73
test-gcc82:
only:
variables:
- $CXX == "gcc82"
stage: test
image: gcc:8.2.0
script: make test
dependencies:
- build-gcc82
deploy-gcc73:
only:
variables:
- $CXX == "gcc73"
stage: deploy
image: gcc:7.3.0
script: make deploy
dependencies:
- build-gcc73
deploy-gcc82:
only:
variables:
- $CXX == "gcc82"
stage: deploy
image: gcc:8.2.0
script: make deploy
dependencies:
- build-gcc82
項目 | 評価 | 理由 |
---|---|---|
所要時間 | △ | 余分な同期をしないため,ほぼ単一パイプライン・単一ステージと同じ.環境を作り直すぶんだけ若干遅い. |
ワークフローの視認性 | △ | Pipeline 画面でワークフローが確認できるが,パイプラインごとに別ページに別れてしまうため一覧性は若干劣る.しかし,ジョブの依存関係がパイプラインとして明示されるという点では優れる. |
ワークフローの安全性 | △ | ビルド,テスト,デプロイをそれぞれクリーンな環境で行うため,意図しない依存関係が紛れ込みにくい.一部のジョブが失敗しても,それらに依存しないデプロイは実行される. |
デバッグのしやすさ | 〇 | 一部のジョブが失敗しても,それらに依存しないジョブは継続するため,問題点を一度に洗い出せる. |
メンテナンス性 | × | 高度な機能を駆使するため,設定ファイルの可読性が低い.α版の機能を使っているため,将来の動作は保証されない. |
表では△が多くなっていますが,いずれも〇と比べてほぼ遜色ないです.つまり,この方法は高速で安全,視認性が高く,デバッグも容易と機能面ではいいことづくめです.その反面,設定ファイルがトリッキーになり,メンテナンス性が低くなります.果たしてどれくらいの人が以下の説明なしにこの CI の挙動を読み取れるでしょうか?このメンテが障害にならないほど GitLab に習熟したチームにならオススメですが,そうでなければやめておいた方が無難です.実際, GitLab.com のプロジェクト一覧を眺めても,この戦略を採用しているプロジェクトはほとんどありません.
この CI の挙動: はじめにこの CI が起動されると,ジョブ entry-point
のみからなるパイプラインが作成されます.他のジョブは only: variables
条件を満たさないため(この時点では変数 $CXX
は空です),パイプラインから除外されます.ジョブ entry-point
は GitLab Trigger API を使って2つのパイプラインを起動します.それぞれ,変数 $CXX
の値を gcc73
と gcc82
に設定しています.それぞれのパイプラインでは,今度はジョブ entry-point
は except: pipelines
条件により除外されます.残る only: variables
条件を満たすジョブのみがそれぞれのパイプラインにおいて逐次実行されます.
なお, only: variables
はα版の機能なので,予告なく仕様変更されることがあります.
複数パイプライン機能の動向
機能的には便利な複数パイプライン・複数ステージをもっと簡単に書けるようにしたいという要望はたびたび Issue に挙がっており,2年前から議論されていますが,正式機能として取り込まれるにはまだ時間がかかりそうです.まだしばらくはプロジェクトの状況を考えてその都度判断することになりそうです.