こんにちは。テクマトリックスの長久保です。
ようやく花粉も少しずつ落ち着き始めてきたようで、ホッと胸をなでおろしています。

CIを構築する際に、基本的に1つのソースコードに対して、Jenkinsの場合Jenkinsfile、GitHub Actionsの場合YAMLファイルを1つ作成し、ビルドスクリプトやパイプラインを指定するということが多いかと思います。
今回は1つのソースコードに対して、複数のビルドスクリプトやパイプラインを指定する場合について考えてみたいと思います。

はじめに

CI環境を構築する際に、ほとんどの場合、1つのソースコード群から同じビルドツールやテストツールを使って1つのビルド成果物(製品)が作成できます。また、多くのCIツールは、このプロセスに最適化されており、パイプラインを簡単に作成できるように設計されています。
CIツールとしてJenkinsを用いて説明します。

いわゆるパイプラインの図

ブランチが分かれている場合でも、ブランチに応じた成果物が作成されますが、バッチのビルド引数が変わったり、デプロイの有無が変化したりと大枠は変わらず、部分的な変化しかないことが多いです。

ブランチが分かれてもそこまで大きく変化しない場合が多い

ただ、最近製造業のお客様から「同じソースコードに対して複数環境でのビルドや、異なる成果物を作成するバッチが複数ある」ということを伺う機会が増えてきました。
その「複数」が「WindowsとLinux環境」といった2種類程度クロスコンパイルであれば並列ステージを作成し、条件を組めば解決します。

ビルド環境のOSが異なった場合、このようにパイプライン内を並列にしておいて、切り替えながら実行していくパイプライン

ただ、1つのリポジトリに対してのビルド方法が100種類あったら?1つのリポジトリに対してのバッチスクリプトが100種類あったら?

同一ソースコードに対して、ビルドバッチが変更になったり、異なるステージを実行しながら、異なる製品を作成する。


今日はその場合の方法について考えてみたいと思います。

1.個別にJenkinsfileを作成する

力業です。必要に応じて毎回個別にパイプラインジョブを作成し、Jenkinsfileを作成していきます。

pipeline {
  agent {
    label 'windows'
  }
  stages {
    stage('ビルド') {
      steps {
        bat 'hoge.bat'
      }
      post {
        success {
          script {
            def buildDate = new Date().format("yyyyMMdd")
            def buildPath = "C:\\hoge\\" + buildDate
            bat "mkdir ${buildPath}"
            archiveArtifacts artifacts: 'build\\*', fingerprint: true
            bat "move build\\* ${buildPath}"
          }
        }
      }
    }
  }
}
pipeline {
  agent {
    label 'windows'
  }
  stages {
    stage('ビルド') {
      steps {
        bat 'fuga.bat'
      }
      post {
        success {
          script {
            def buildDate = new Date().format("yyyyMMdd")
            def buildPath = "C:\\fuga\\" + buildDate
            bat "mkdir ${buildPath}"
            archiveArtifacts artifacts: 'build\\*', fingerprint: true
            bat "move build\\* ${buildPath}"
          }
        }
      }
    }
  }
}

メリットとしては取り回しが楽になることかと思います。共通部分がないため、個別のジョブごとに最適な条件を追加できます。また影響範囲も小さいため、修正が頻発しやすい導入時には修正時の影響範囲が少ないため便利です。

Jenkinsではジョブをフォルダー形式で管理もできるため、ある程度まではジョブの管理も可能かと思います。


ただ、その一方メンテナンスコストは増加します。例えば半年後、静的解析ツールを全社的に導入するといった場合、100個のJenkinsfileを変更することになります。
また、フォルダーを用いればジョブの整理はできるとはいえ、100ジョブ作成されるので、ジョブ自体の管理も手間といえば手間になります。

2.部分的に共有ライブラリを利用する

共有ライブラリを利用します。共有ライブラリの書き方自体はリンク先を参照ください。
影響のあるパラメータをJenkinsfileに切り出し、共通化できる部分については共有ライブラリとして定義します。
どこまで共有ライブラリとして切り出すかは、ほかのプログラム言語の共通化(関数化)と似ています。

@Library('jenkins-common') _
corporatePipelineTypeA {
  //ビルドタイプ
  buildType = “Windows”
  //バッチファイル名
  buildBatName = “hoge.bat“
  //成果物の保存先
  buildPath = “C:\\hoge\\“
  //メール送信対象
  sendMailAddress = “hoge@example.com"
}
@Library('jenkins-common') _
corporatePipelineTypeB {
  //ビルドタイプ
  buildType = “Windows”
  //バッチファイル名
  buildBatName = “fuga.bat“
  //成果物の保存先
  buildPath = “C:\\fuga\\“
  //メール送信対象
  sendMailAddress = “fuga@example.com"
}

各要件ごとのJenkinsfileを上記のように作成しておき、実処理部分は共有ライブラリに記載します。
Jenkins側のジョブは作成時にどのJenkinsfileを参照するか切り替えることで、ビルドや利用する共有ライブラリを切り替えます。

1の方法に比べると保守性は高いです。例えば「静的解析のステージを追加したい」「ビルドツールを呼び出す際のオプションを1つ追加したい」となったときには、共有ライブラリで共通化されているので修正箇所が少なくすみます。

とはいえ、Jenkinsのジョブは100個作成する必要があるのは変わりませんし、1の方法とは異なりパラメータのみとはいえ、Jenkinsfileも100個作成されます。
また共有ライブラリとしてパイプラインを共通化しているため、パイプラインに条件分岐を加えようとした場合に考慮する内容は増加します。
変更箇所は少なくなりますが、その分考慮する内容・難易度とのトレードオフにはなります。

3.並列ステージを記載し、条件分岐を作成する

今回の解決すべき課題は同一ソースコードに対して100種類の中からどの方法でのビルドを行うか、設定をどう切り替えるかということになります。
1はJenkinsfile毎に設定を持たせ、2は共通化できる部分は共有ライブラリに切り出し、部分的にJenkinsfileに設定を持たせるということを行いました。
3つ目の方法は、Jenkinsのジョブをパラメータ付きビルドの形式で作成しておき、実行時に受け取ったパラメータをもとにJenkinsfile内で条件分岐を組むことで実現する方法です。

パラメータ付きビルド

入力値に従い、パイプラインが実行されます。ビルドステージが100個縦に並ぶイメージです。

このように組むことでパイプラインやジョブは1つで済みます。
ただし、条件分岐が複雑になります。今回は単体テストをスキップするように記載しましたが、どの用途で実行したときには実行して…ということを実装する必要があります。
ジョブの実行結果も視認性が良いとは言えません。上の画面キャプチャのようにずらずらと縦に並ぶか(Olue Oceanというプラグインで参照)、下のように横にずらずらと並ぶことになります(クラシックUIで参照)。


こちらについては、Pipeline Graph Viewプラグインが役に立つかもしれません。ただ利用者(インストール数)もまだ少なく、α版であるとプラグインのページにも明記されているので、利用前に検証は必要かと思います。

また、Jenkinsのパイプラインはデバッグしながらの実装はできないので、実装自体に苦労するでしょう。パイプラインに限った話ではありませんが、当然条件分岐が増えれば増えるほど可読性は落ちますし、拡張性が高いとは言えません。
とはいえ、開発対象がすでに保守フェーズに入っており、軽微な修正しかなく特に拡張性は必要ない、といったものであればこの方法もよいのかもしれません。

4.MultiBranch Pipelineを用いて、Jenkinsジョブと成果物を自動作成する

「3.並列ステージを記載し、条件分岐を作成する」で記載した内容に似ていますが、パラメータ付きビルドを用いるのではなく、MultiBranch Pipelineのジョブタイプを使い、ブランチにあるJenkinsfileの値を変更することでパイプラインに渡すパラメータを切り替えます。

MultiBranch Pipelineを利用すれば、Jenkins側で100個のジョブを手動で都度作成する必要はなく、ブランチを切ったタイミングでJenkinsがブランチに対応するジョブを自動的に作成してくれます。

例えば次のようなフローが考えられるでしょう。

  1. Jenkinsfileに対象のビルドに必要な設定値を書き入れ、特定のビルド成果物(製品)作成用のブランチを切る。
  2. ブランチが切られたタイミングでJenkinsのジョブが自動的に作成され、パイプラインが実行される。
    パイプラインは変更されたJenkinsfileのパラメータに応じて実行される。
  3. パイプラインを確認し、問題なければ成果物を保存する。
  4. ブランチを削除する。
  5. ブランチが削除されたことに応じて、Jenkinsのジョブが自動的に削除される。

手順1の際に利用する「各成果物作成用のJenkinsfileのパラメータシート」のようなものは必要かと思いますが、目的を達成することはできそうです。「2.部分的に共有ライブラリを利用する」と似ていますが、ジョブの作成分コストが削減でき、都度成果物を作成して、成果物を取得したら過去のジョブの実行履歴が不要といった場合には有用かと思います。

問題としては手順5のようにブランチを削除した場合それに応じてJenkinsのジョブが削除されます。ジョブの実行履歴は残らないので、過去の成果物を利用したい場合はJenkins以外に保存しておく必要があります。
また共有ライブラリを作成するので、その際の共通化の単位や条件分岐などについての考慮は必要です。

まとめ

今回1つのソースコードに対して複数方法のビルドがある場合のJenkinsの活用方法について記載しました。

当然ですが、開発プロセスなどによって最適な方法は異なります。
導入が初めてで手探りで構築する場合やまだ開発対象の製品群の増減がある場合、共通化の想定も難しいでしょうから、「1.個別にJenkinsfileを作成する」が良いですし、運用も製品もある程度固定化されていて、直近変更する予定がない場合は「3.並列ステージを記載し、条件分岐を作成する」の手順でがちがちに作りこんでもよいかもしれません。

また実装方法についても様々な方法があります。
宣言型パイプラインであればwhenディレクティブが活用しやすいですし、matrixという構文もあります。スクリプトステップを用いれば、if文やswitch-case文の利用も可能です。それ以外にも三項演算子をenvironmentディレクティブを利用する…などなど様々な実装方法があります。保守性や実現したいことと照らし合わせて頭を悩ませる必要があるでしょう。

いずれにせよ(厳密にはCIではないですが)Jenkinsで見える化・一元管理できるようになるので十分メリットを得ることができると思います。
また、毎回「この成果物を作成する場合にはどのバッチを実行すればよいのだっけ…?」といった手間からも解放されますし、成果物のダウンロードもJenkinsからできるようになるので、毎回ファイルサーバーを探しに行くということからも解放されます。
Jenkinsを活用したことが無いという方は、ぜひこの機会に検討してみてはいかがでしょうか。

宣伝

興味は沸いたがノウハウが無いのでとっかかりが欲しい

CI/CD環境構築
「スモールスタートしたい」「ちょっと興味はあるんだけどうちの開発は少し特殊でネット上の情報も有益なものが無い」「やりたいことはなんとなくイメージはつくんだけど実現・実装方法について相談したい」など、環境構築についてお困りなことがある方は是非お問合せいただければと思います。

Jenkinsについて詳しく学習したい

このブログ内にでてきた並列でのパイプラインの組み方や、共有ライブラリについて知りたいという方、そもそも現在もフリースタイルジョブでJenkinsを利用しておりパイプラインを使ったことが無いという方は、Jenkinsのトレーニングをお勧めします
Jenkinsトレーニング
バックアップやユーザー管理、セキュリティ等、Jenkinsの管理者向けの「Jenkins 管理-基礎」、パイプライン初心者向けの「パイプライン基礎」、共有ライブラリといったパイプラインの発展的な内容を扱う「パイプライン中級」とレベル別に体系的に学習することができます。
各開発環境ごとに適したパイプラインは異なると思いますので、ぜひ体系的に知識を身に着けて、開発環境の改善に役立ててください。

By nagakubo

主にCI環境構築をメインで担当しています。 Certified CloudBees Jenkins Engineer (CCJE)