Jenkinsで複雑な処理をするときのjob構成について

変数やビルド後の通知設定等が違うために、
ほぼ同じ内容のjobを15個ぐらい使っていたら、
だんだんと運用が死んできたのでメモ。

まとめ

Jenkinsはプラグインの挙動を変えるのが難しいため、
ほぼ同じだけれど若干違う手順を行う必要がある場合、
似たようなjobが大量に並び、
手順の変更時などに全てのjobを変えきるのが辛くなります。

そこで、可能な限りビルド手順はスクリプトにするのと、
jobを種類別に細かく分け、
最上位のjobにはどの下流jobを実行するかだけを管理させることで、
複雑なjobでも変更に強くすることができるようになります。

前提条件

  • 設定が全部で7種類ぐらいある
  • 1ビルドはCPUをフルパワーで使って30分程度
  • 一部の設定は定期実行やマージ毎でもビルドしたい
  • 設定ごとに用途や使う頻度が違う為、定期実行タイミングは個別に設定したい
  • ビルド後のデプロイ先や通知先、通知条件がいくつかある
  • ビルド手順はバージョン/デプロイ先によって変化する

問題点

大きな問題として、
Jenkins pulginで提供されている機能を切り替えるのが難しいという問題があります。

たとえば、Jenkinsの拡張メールプラグインでは、
そのjobの結果によって通知先や通知内容等を変更することができます。
ですが、その定義自体を切り替えることが難しく、
この場合は成功時にメールしないけど、
この場合は成功時にメールするといった切り替えができませんでした。

また、定期的に変更チェックするブランチや、チェックする間隔が設定毎に異なりますが、
JenkinsのGit プラグインではそのあたりをうまく設定することが難しいようです。

他にもdeploy先によって使うプラグインが微妙に違うなどの問題があり、
ほぼ同じだけれど若干違うjobを15個並べるといった運用をしていました。

こんな感じに、ほぼ同じだけれど若干違うビルドが並びます。
こんな感じ

結果として、ビルド手順の変更時などに全てのビルドを変更しきることが難しかったり、
古い手順のバージョンのマイクロアップデートと、
新しい手順のメジャーアップデートが重なったときに、
ビルド前にJenkinsの設定を必ず変更する必要がある等、
運用がかなり辛くなってきました。

これに対する対策を考えましたが、あまり良いのが無い感じですが、
とりあえずまとまったのでメモをしておきます。

解決案

1.ビルド手順や通知を全てをシェルスクリプトにし、Jenkinsはシェルを叩くだけにする

Jenkinsのプラグインをほぼ使用せず、全てスクリプトで解決する手法です。
ビルド手順や通知がSCMに保存されるため、ビルド手順とバージョンが完全に紐付き、
常に正しい手順で実行できるという利点があります。

だだし、Jenkinsプラグインの恩恵を得られないため、
今プラグインでやっている処理を全て置き換える必要があります。
また、新たな手順の追加が大変と言った問題もあります。

2.ワークスペース共有を使ってうまくやる

チェックアウトとビルドジョブを切り離し、
SCMのポーリング&チェックアウトだけを行うjobから、
ビルドジョブを下流ビルドとして呼び出す方式です。

こんな感じですね。
ワークスペース共有

ワークスペース共有を使い、
下流ビルドは上流のワークスペース上でビルド作業を行うようにすることで、
ビルドjobの数を大幅に減らすことができます。

だだし、下流が実行中は上流ビルドが動かない事を保証しなければなりません。
(ビルド途中に次のキューによってワークスペースが書き換えられる可能性がある) Parameterize pluginで下流が終わるまで終了を待つ事ができますが、
複数のビルドが同時に走った場合にデッドロックに陥る可能性があります。

  • A-Cとビルドするjobと、B-Cとビルドするjobがあり、jenkinsの同時実行が2の場合、
    2つのビルドを同時に実行するとCが2つともキューに積まれて進まない場合がある

3.jobを疎結合にして成果物を使ってうまくやる

2のデッドロックを回避するために、疎結合にした版になります。
この方式ではワークスペースを共有せず、
コミットのSHA1を渡してビルドジョブ側でcheckout, buildします。
また、Remote APIを利用することで、シェルスクリプトから次に実行するビルドの指定もできます。

2の利点をそのまま受け継ぎ、ワークスペース共有しないのでブロックしなくなります。
ビルド結果などは成果物の保存と、Copy Artifact Pluginを利用して受け渡します。
そのため、後続のjobの実行前にワークスペースが変更されても問題なく進められます。

ただし、ポーリング用とビルド用とで二重にSCMをチェックアウトする必要があるのと、
ビルドログが完全に細切れになるため追いにくく、
またいつ何が正常に終了したかを把握しにくいという問題があります。

4.master-slaveにして、masterからslaveのjobを呼び出すようにする

3のやりかたを踏まえつつ、個別のjobでは無く下流のjobとして運用する方法です。
そのために、2で問題となっていたキューが埋まる問題への解決策を入れました。

具体的には、ポーリング等を実行する最上流のjobをJenkinsのmasterでのみ実行し、
ビルドや通知を下流jobとしてslaveでビルドするようにします。
このように設定することで、masterが全て上流jobで埋まったとしても、
slaveで下流jobが実行されるため、デッドロックが発生しません。

これにより、上流jobはビルドが完全に終了するまで終了を待つことができ、
終了タイミングを把握しやすくなります。

だたし、問題点としてmaster-slave構成にするため、稼働するマシンが増える、
jobをmasterとslaveどっちでやるかを考えないといけない、
下流jobとして紐付けはされるが、ログはまだ細切れになっているといった問題があります。

おわりに

とりあえず今のところは、4番が一番いい方法ではないかと考えています。
そのためしばらくこれで運用し、また問題が起きた場合、
次なる手を考えるとして、今のところは4番で行く予定です。