この記事の初出はLiam NewmanによるJeknins.ioへの投稿です。
複数の構成に対して同じアクションを実行しなければならないことがよくあります。そんなとき、これまではパイプラインで同一のステージを何回もコピーするしかありませんでした。変更が必要になると、パイプラインのあちこちに同じ変更を加えなければなりません。パイプラインが大きいと、ほんのいくつかの構成をメンテナンスするだけでも大変でした。
Declarative Pipeline 1.5.0-beta1(Jenkins Experimental Update siteからダウンロードできます。補注:本記事で紹介するmatrix構文は2019年12月リリースの Declarative Pipeline 1.5.0 にてオフィシャルリリースされました)では、新しくmatrix
セクションが追加され、一度ステージのリストを指定するだけで、複数の構成でそのステージのリストを並行実行できるようになりました。どんなものか、さっそく見てみましょう。
単一構成のパイプライン
まず、ビルドステージとテストステージからなるシンプルなパイプラインから始めます。ビルドおよびテストアクションの代わりとしてecho
ステップを使用しています。
Jenkinsfile
pipeline { agent none stages { stage('BuildAndTest') { agent any stages { stage('Build') { steps { echo 'Do Build' } } stage('Test') { steps { echo 'Do Test' } } } } } }
複数のプラットフォームとブラウザーに対応したパイプライン
つぎに、プラットフォームとブラウザーの組み合わせに対してビルドとテストを実行したいと思います。新しいmatrix
ディレクティブを使うと、軸の集まりであるaxes(訳注:axesはaxisの複数形)
を指定できます。個々のaxis
にはname
と1つまたはそれ以上の値を含むvalues
リストがあります。パイプラインが実行されると、Jenkinsはこれらに基づいて、軸の値の可能な組み合わせすべてに対してステージを実行します。マトリクスのすべてのセルは並行して実行されます(唯一の制限は、利用可能なエージェントの数です)。各セル内のステージは順次実行されます。
私のマトリクスにはPLATFORM
とBROWSER
という2つの軸があります。PLATFORM
には3つの値があり、BROWSER
には4つの値があるため、結果として12の異なる組み合わせでステージが実行されます。各セルで軸の値を出力するようecho
ステップを変更しました。
Jenkinsfile
pipeline { agent none stages { stage('BuildAndTest') { matrix { agent any axes { axis { name 'PLATFORM' values 'linux', 'windows', 'mac' } axis { name 'BROWSER' values 'firefox', 'chrome', 'safari', 'edge' } } stages { stage('Build') { steps { echo "Do Build for ${PLATFORM} - ${BROWSER}" } } stage('Test') { steps { echo "Do Test for ${PLATFORM} - ${BROWSER}" } } } } } } }
ログ出力(抜粋)
... [Pipeline] stage [Pipeline] { (BuildAndTest) [Pipeline] parallel [Pipeline] { (Branch: Matrix - OS = 'linux', BROWSER = 'firefox') [Pipeline] { (Branch: Matrix - OS = 'windows', BROWSER = 'firefox') [Pipeline] { (Branch: Matrix - OS = 'mac', BROWSER = 'firefox') [Pipeline] { (Branch: Matrix - OS = 'linux', BROWSER = 'chrome') [Pipeline] { (Branch: Matrix - OS = 'windows', BROWSER = 'chrome') [Pipeline] { (Branch: Matrix - OS = 'mac', BROWSER = 'chrome') [Pipeline] { (Branch: Matrix - OS = 'linux', BROWSER = 'safari') [Pipeline] { (Branch: Matrix - OS = 'windows', BROWSER = 'safari') [Pipeline] { (Branch: Matrix - OS = 'mac', BROWSER = 'safari') [Pipeline] { (Branch: Matrix - OS = 'linux', BROWSER = 'edge') (hide) [Pipeline] { (Branch: Matrix - OS = 'windows', BROWSER = 'edge') [Pipeline] { (Branch: Matrix - OS = 'mac', BROWSER = 'edge') ... Do Build for linux - safari Do Build for linux - firefox Do Build for windows - firefox Do Test for linux - firefox Do Build for mac - firefox Do Build for linux - chrome Do Test for windows - firefox ...
無効な組み合わせを除外する
基本的なマトリクスが作成できたところで、私は無効な組み合わせもあることに気づきました。Microsoft EdgeはWindowsでしか動きませんし、SafariのLinux版はありません。
exclude
を使うと、無効なセルを除くことができます。各exclude
には1つまたはそれ以上のaxis
ディレクティブをname
およびvalues
とともに指定します。exclude
内のaxis
ディレクティブは、(マトリクスのセルを生成するのと同じように)組み合わせのセットを生成します。exclude
の組み合わせに含まれる値に一致するセルはマトリクスから除外されます。複数のexclude
ディレクティブがある場合、個別に評価され、セルが除外されます。
除外する値のリストが長くなる場合、values
ではなくnotValues
を使うと、除外したくない軸の値を指定できます。そうです、二重否定になるため、ちょっとわかりにくいかもしれません。私は、notValue
は本当に必要なときにだけ使用するようにしています。
次のサンプルパイプラインでは、 linux, safari
という組み合わせだけを除外したうえで、さらにedge
ブラウザーでwindows
以外のプラットフォームを除外しています。
このパイプラインは2つの軸を使用していますが、axis
ディレクティブの数に制限はありません。
また、このパイプラインではどちらのexclude
も両方の軸の値を指定していますが、これは必須ではありません。linux
セルだけを実行したい場合、次のようなexclude
を使用します。
exclude { axis { name 'PLATFORM' notValues 'linux' } }
pipeline { agent none stages { stage('BuildAndTest') { matrix { agent any axes { axis { name 'PLATFORM' values 'linux', 'windows', 'mac' } axis { name 'BROWSER' values 'firefox', 'chrome', 'safari', 'edge' } } excludes { exclude { axis { name 'PLATFORM' values 'linux' } axis { name 'BROWSER' values 'safari' } } exclude { axis { name 'PLATFORM' notValues 'windows' } axis { name 'BROWSER' values 'edge' } } } stages { stage('Build') { steps { echo "Do Build for ${PLATFORM} - ${BROWSER}" } } stage('Test') { steps { echo "Do Test for ${PLATFORM} - ${BROWSER}" } } } } } } }
ログ出力(抜粋)
... [Pipeline] stage [Pipeline] { (BuildAndTest) [Pipeline] parallel [Pipeline] { (Branch: Matrix - OS = 'linux', BROWSER = 'firefox') [Pipeline] { (Branch: Matrix - OS = 'windows', BROWSER = 'firefox') [Pipeline] { (Branch: Matrix - OS = 'mac', BROWSER = 'firefox') [Pipeline] { (Branch: Matrix - OS = 'linux', BROWSER = 'chrome') [Pipeline] { (Branch: Matrix - OS = 'windows', BROWSER = 'chrome') [Pipeline] { (Branch: Matrix - OS = 'mac', BROWSER = 'chrome') [Pipeline] { (Branch: Matrix - OS = 'windows', BROWSER = 'safari') [Pipeline] { (Branch: Matrix - OS = 'mac', BROWSER = 'safari') [Pipeline] { (Branch: Matrix - OS = 'windows', BROWSER = 'edge') ... Do Build for linux - firefox ...
実行時にセルの動作を制御する
matrix
ディレクティブの内側には、「セルごと」のディレクティブも追加できます。stage
に追加するのと同じディレクティブでマトリクスの各セルの動作を制御できます。セルごとのディレクティブは、入力としてセルの軸の値を使用できるので、軸の値に合わせて個々のセルの動作をカスタマイズできます。
私のJenkinsサーバーには、対応するOSに合わせてラベル付けしたエージェント(“linux-agent”、“windows-agent”、“mac-agent”)が構成してあります。マトリクスの各セルを適切なOSで実行するため、Groovy文字列テンプレートを使ってセルにラベルを設定します。
matrix { axes { ... } excludes { ... } agent { label "${PLATFORM}-agent" } stages { ... } // ... }
私はときどき、Jenkins Web UIから手動でパイプラインを実行することがあります。その場合、1つのプラットフォームだけを選択して実行できるとよいでしょう。axis
およびexclude
ディレクティブはマトリクスを構成するセルの静的なセットを定義します。組み合わせは実行が始まる前に生成され、このときまだパラメーターは処理されていません。つまり、ジョブが開始された後にマトリクスのセルを追加または削除することはできません。
いっぽう、「セルごと」のディレクティブは実行時に評価されます。 matrix
の内側で「セルごと」にwhen
ディレクティブを指定して実行するマトリクスのセルを制御できます。そこで、プラットフォームのリストを指定したchoice
パラメーターを追加し、when
ディレクティブに条件を追加します。すると、すべてのプラットフォームが実行されるか、選択したプラットフォームに一致するセルだけが実行されます。
pipeline { parameters { choice(name: 'PLATFORM_FILTER', choices: ['all', 'linux', 'windows', 'mac'], description: 'Run on specific platform') } agent none stages { stage('BuildAndTest') { matrix { agent { label "${PLATFORM}-agent" } when { anyOf { expression { params.PLATFORM_FILTER == 'all' } expression { params.PLATFORM_FILTER == env.PLATFORM } } } axes { axis { name 'PLATFORM' values 'linux', 'windows', 'mac' } axis { name 'BROWSER' values 'firefox', 'chrome', 'safari', 'edge' } } excludes { exclude { axis { name 'PLATFORM' values 'linux' } axis { name 'BROWSER' values 'safari' } } exclude { axis { name 'PLATFORM' notValues 'windows' } axis { name 'BROWSER' values 'edge' } } } stages { stage('Build') { steps { echo "Do Build for ${PLATFORM} - ${BROWSER}" } } stage('Test') { steps { echo "Do Test for ${PLATFORM} - ${BROWSER}" } } } } } } }
Jenkins UIからパイプラインを実行し、PLATFORM_FILTER
パラメーターにmac
を指定すると、次のような結果が出力されます。
ログ出力 (抜粋 – PLATFORM_FILTER = ‘mac’ )
... [Pipeline] stage [Pipeline] { (BuildAndTest) [Pipeline] parallel [Pipeline] { (Branch: Matrix - OS = 'linux', BROWSER = 'firefox') [Pipeline] { (Branch: Matrix - OS = 'windows', BROWSER = 'firefox') [Pipeline] { (Branch: Matrix - OS = 'mac', BROWSER = 'firefox') [Pipeline] { (Branch: Matrix - OS = 'linux', BROWSER = 'chrome') [Pipeline] { (Branch: Matrix - OS = 'windows', BROWSER = 'chrome') [Pipeline] { (Branch: Matrix - OS = 'mac', BROWSER = 'chrome') [Pipeline] { (Branch: Matrix - OS = 'windows', BROWSER = 'safari') [Pipeline] { (Branch: Matrix - OS = 'mac', BROWSER = 'safari') [Pipeline] { (Branch: Matrix - OS = 'windows', BROWSER = 'edge') ... Stage "Matrix - OS = 'linux', BROWSER = 'chrome'" skipped due to when conditional Stage "Matrix - OS = 'linux', BROWSER = 'firefox'" skipped due to when conditional ... Do Build for mac - firefox Do Build for mac - chrome Do Build for mac - safari ... Stage "Matrix - OS = 'windows', BROWSER = 'chrome'" skipped due to when conditional Stage "Matrix - OS = 'windows', BROWSER = 'edge'" skipped due to when conditional ... Do Test for mac - safari Do Test for mac - firefox Do Test for mac - chrome
まとめ
この記事では、 matrix
ディレクティブを使い、簡潔で強力な宣言型パイプラインを作成する方法を紹介しました。matrix
を使わずに同等のパイプラインを作成しようとすると、すぐに何倍もの長さになり、理解もメンテナンスもはるかに困難になるでしょう。
現在、マトリクスは試験的アップデートセンターから入手できます。ドキュメントやオンラインヘルプの更新が終わり次第、メインのアップデートセンターにリリースされる予定です。
その他のリソース
(この記事は、CloudBees社 Blog 「Welcome to the Matrix」2020年2月18日 Liam Newman 投稿記事の翻訳です。)