HomeRoomで実践中のJenkinsバッチジョブ管理Tip集

初投稿になります。アルゴリズムの探求者です。より現場に近い話題で情報発信していければと思っています。よろしくお願いします!

さて、今回は「HomeRoomで実践中のJenkinsバッチジョブ管理Tip集」というタイトルでお送りします。

Jenkinsと言えば、もう今や知らないエンジニアは居ないだろうという地位を確立したCIツールですが、CIツールとしてではなく、バッチジョブ管理に使おうという活用法があります。WEB上を検索すると、そのような使い方をしているという話は結構聞きますし、明らかにバッチ管理用と思われるJenkinsプラグインも多数あります。しかし、具体的なジョブ設定にまで踏み込んだ情報は意外に少ないなという印象です。

一方、HomeRoomはSpeeeで運営している賃貸情報サイトです。物件情報を毎日最新情報に更新するためのバッチを始め、アクセス分析やデータバックアップなど、様々なバッチが裏で稼働しています。もともとそれらのバッチはcron上に乗っていましたが、2013年8月に、それらのバッチを全てJenkins上に移行し、より見やすいバッチ管理と、より精度の高いログ収集とエラー検知、有事の際の素早いアラート通知を実現しました。

そこで今回は、HomeRoomで運用しているバッチジョブ管理用Jenkinsで実際に使用している設定をいくつか、Tips形式で紹介したいと思います。

Tips1: Jenkinsの見た目を変えよう

バッチジョブ管理用のJenkinsを構築する上で、通常のCIツールとしてのJenkinsと最も異なる点は、「Jenkinsや登録したジョブが止まるとトラブルに繋がる」という点です。システムの観点から安定性を追求することも必要ですが、加えて、Jenkinsを操作する人間がミスを起こしにくい環境を作ることも重要になってきます。

ということで、まずは稼働中のCI用Jenkinsと間違えないよう、見た目を変えましょう。Simple Theme Pluginを使います。

特にデザインにこだわりがなければ、TOPBARの色を変えるだけでも効果的です。色を変えたtopbar.pngを用意して、以下のようなCSSをSimple Theme Pluginに設定するだけです。

#top-panel {
  background-image: url(topbar.png) !important;
}

Tips2: リモートホストでのバッチ起動

バッチ処理を実際に起動するホストにJenkinsも立てる場合は楽なのですが、ネットワーク・セキュリティやリソースの観点から別ホストにJenkinsを立てるケースも多いでしょう。その場合は、Jenkinsからリモートホスト上のバッチを制御する必要があります。その手法として、主に2パターンあります。

  • Jenkinsスレーブをバッチサーバに立てて、スレーブでジョブを実行する
  • マスターからSSHでバッチを直接制御する

どちらが良いかは一概には言えないところかと思います。

スレーブ方式 SSH方式
  • Jenkinsジョブ設定が簡潔
  • バッチサーバを複数構築して負荷分散をしたい場合にジョブ振り分けをJenkinsに任せることができる
  • バッチサーバのリソース(特にメモリ)をJenkinsが奪わない
  • (環境にも依るが)Jenkinsが落ちても実行中のバッチは処理継続する

HomeRoomでは、特に最後の「Jenkinsが落ちても実行中のバッチは処理継続する」という部分を重要視して、SSH方式を採っています。

Jenkinsは元々CIツールですので、自身が作ったプロセスは極力掃除して残さないような動作をします。Jenkins自身が落ちると、スレーブプロセスやスレーブが立ち上げたジョブプロセスもkillを試みます。CIツールとしてみたときにはありがたい機能ですが、バッチジョブ管理としては、バッチプロセスに無条件でシグナルを送るのは好ましくありません。その点、SSH方式を用いると、SSH clientプロセスにはkillが届きますが、SSHリモートで作成されたプロセスは生き残ります(設定にも依ります)。

Tips3: パーミッション制御

特定のユーザ権限でないと動かないバッチも多いかと思いますが、そうなるとJenkinsへの権限委譲についても考慮する必要が出てきます。

一般化すると、「JenkinsはhostAのjenkinsuserで動いていて、hostBのbatchuserでバッチを起動する」という状況を実現するために権限設定をどうするかを決めるということになります。ここではSSH方式を採用しているHomeRoomでのパーミッション制御について紹介します。

図を見ていただければわかるかと思いますが、Jenkinsからjenkinsuser@hostBにログインし、hostB上でsudoによりbatchuser権限を取得します。このとき重要な事は、Jenkinsは画面上からコマンドを記述して実行することができるため、sudo設定で単純にALL権限を付与してしまうと、委譲する権限が強すぎるということです。あくまでJenkinsがbatchuser@hostB権限で実行できるのは特定のバッチの起動のみ、という権限管理をすべきです。これを踏まえ、HomeRoomでは以下のようにsudoer設定をしています。

Defaults: jenkinsuser !requiretty
Cmnd_Alias BATCH_CMND = /bin/sh -xe /path/to/batch.sh
jenkinsuser ALL = (batchuser) NOPASSWD: BATCH_CMND

1行目は、端末からの利用でない場合でもsudoの使用を許可する設定です。デフォルト設定次第ではこの設定がないと動作しません。2,3行目で、特定のバッチ起動コマンドだけ、batchuserとしてパスワードなしでsudoできるよう設定しています。あとは、/path/to/batch.shのWrite権限がjenkinsuserに無いことを確認すれば、最小限の権限移譲を実現できます。

Tips4: ジョブの粒度

バッチ処理をJenkinsジョブに落としこむときに、ジョブの粒度が問題になると思います。

例えば、処理A,B,Cを順番に実行するバッチがあった時に、

  • 1つのJenkinsジョブの中で3つの処理を実行する
  • 各々の処理を実行するジョブを計3つ作り、Jenkins上で依存関係を貼る

という2つの選択肢があります。

この問題に対しては、「バッチがエラーになった場合のリカバリー方法」で決めるのが良いと思います。

もし「処理BやCがエラーになった場合は処理Aからやりなおし」という性質のバッチであれば、1つのJenkinsジョブにまとめてしまうべきです。その方がJenkins設定も簡潔になります。逆に、「処理Bでエラーになったら処理Bからやりなおせばよい」という性質のバッチであれば、処理AとBは別のジョブとして設定すべきです。そうすれば処理Bでエラーになったら処理Bジョブを、処理Cでエラーになったら処理Cジョブを再実行するという運用もJenkinsからワンクリックで可能になりますし、場合によっては「処理Bでエラーになったら処理Bジョブを自動で再実行する」という仕組みを構築することさえ可能です。

Tips5: エラー検知を厳しく

これはJenkins独特のノウハウというよりはバッチジョブ管理ツールを活用するための必須要件かと思いますが、ツールが「ジョブの成否」を判断する必要があります。特に今までcronで起動していたバッチをJenkinsに乗せる場合、Jenkinsが成否を判断できるようになっていない場合がほとんどだと思いますので、必要に応じて修正を入れましょう。

この修正については、コマンドのリターンコードを制御できるよう地道に修正を行うしかないですが、1つコツとしては、

/bin/sh -xe /path/to/batch.sh

この「e」オプションで正常稼働できるように修正していくと良いと思います。

「e」オプションはスクリプト内の各コマンドでリターンコード0以外が返されると、その時点でスクリプトの実行を止め、スクリプト実行自体もそのリターンコードを返すというものです。例外機構の整ったプログラミング言語で書かれたものは想定外のエラーが発生した時点で実行を止めるものが多いと思いますが、「e」オプションはシェルスクリプトもその挙動に近づけるというイメージでしょうか。もちろん途中で止まった場合はリターンコード0以外が返りますので、Jenkinsもエラーと判定してくれます。

「e」オプション付きでJenkinsにバッチを乗せると、「実は今までエラーになっていたけど気づいていなかった」というバッチに出会うケースがかなり高確率であります。ぜひ一度このホラーを体験してみてくださいw

「x」オプションはスクリプト内のコマンドを標準出力に出力してくれるコマンドです。特に「e」オプションをつけてエラーが発生した場合は「どこでエラーになったのか」という情報が重要ですので、「x」オプションも合わせてつけておくのがオススメです。

Tips6: 時刻以外のトリガーでジョブを起動

例えば、「特定のファイルの更新をチェックしておき、更新されたらジョブを起動する」というように、厳密な意味でのトリガーがJenkinsの外にある場合です。HomeRoomでも「不動産会社様から最新の物件情報をFTPで送信していただけるので、ファイルが置かれたことを検知したらDB反映バッチを起動する」という、よくあるパターンのバッチがあります。

Jenkinsは実行されたジョブに対して成否を判断するため、「本来トリガーされるべきジョブがトリガーされていない」というエラーは存在しないのです。そのため、Jenkinsの外にトリガーがある場合は、「トリガーされたか」を確認する必要が別途出てきます。HomeRoomでは、Clone Workspace SCM Pluginを使って「トリガーされたか」チェックジョブを設定しました。

まずファイル更新チェックジョブ側で、更新を確認したら、バッチ本ジョブをキックすると同時に、以下のようにキック時刻を記録します。

date '+%Y-%m-%d %H:%M:%S' > last_triggered

このファイルはClone Workspace SCM Pluginのアーカイブ対象に登録します。

そして、別途、「トリガーされたか」チェック用ジョブを作成し、「定期的に実行」で夜、起動します。このジョブは、「ソースコード管理」で「Clone Workspace」を選択し、先ほどのジョブからファイルを持ってきます。これで、最後にトリガーされた時刻が記載されたファイルがチェック用ジョブに渡ってきますので、あとはその「最後にトリガーされた時刻」が昨日以前のときexit 1してやることにより、チェックジョブがエラーとなり、「今日はまだトリガーされてないよ!」というアラートを飛ばすことができます。

Tips7: タイムアウトを設ける

「エラー検知を厳しく」のところで述べたように、「e」オプションさえつけていれば、エラーが上がればすぐにそれをJenkinsが検知することができますが、これでは「何らかの不具合でコマンドが返ってこない」というケースには対応できません。HomeRoomでも、ディスクが物理的に故障していてプロンプトがずっと返ってこないという状況になり、バッチが終わらないということがありました。

このような状況でもアラートを飛ばすためにはタイムアウトを設けるのが無難です。Build-timeout Pluginを使います。

さいごに

今回はHomeRoomでJenkinsをバッチジョブ管理ツールとして利用する際に実施した設定をいくつか紹介させていただきました。今回紹介した以外にも様々な工夫をしていますので、またの機会に。

Jenkinsをバッチジョブ管理ツールとして利用するというのは、ある意味では本来の使い方から外れた使い方とも言えますので、工夫して解決しなければいけない課題があることも事実ですが、このようにノウハウを共有してより良いJenkins活用の参考にし合えればと思います。