Jenkins でバッチを運用する

#はじめに
私達は php で書かれたバッチ、500 個ほどを Jenkins で運用しています。
Jenkins でバッチを運用するようになった理由と移行したときに行った設定等をここにまとめます。

# 発生していた問題
私達はバッチ実行用のサーバー上の cron にすべてバッチを登録することで定期実行させていました。
当時のバッチの数はだいたい 100 個ほどだったと思います。（現在は 500 個ほど）

長い間 cron で管理されいましたが、問題を多数抱えていたので Jenkins でを抱えていたため移行しました。

* バッチが正しく動作したのかわからない
 * ログを見るための仕組みがなかったので、バッチのほぼすべてログを書き出さないものとなっていて正しく動作しているのか分かりにくい
 * ログを書き出す仕組みがあってもそれを管理するのは大変
    
* 権限周りの管理の問題
 * 管理の都合からルート権限持つ人が cron の設定を行うという形式になっていた。これが特に問題を増加させていた。
 * 定期実行するものについては実行時間の変更・一時停止・新規登録等の操作を頻繁に行うので操作ミスが多発していた。（cronの設定が間違っていて動作しない、昔の設定に戻って登録が消え去る等）
 * バッチを止めたいときはバッチの処理の先頭に exit(); と書いてデプロイするなど、謎のオペレーションが常態化した。
* 実行時間が長くなりすぎて問題が起こっているケースに気づけない
    * 徐々に実行時間が長くなっているようなケースはなかなか気が付けないので、原因調査に無駄な時間がかかることがあった

他にも問題はありましたが、これぐらいで。
これらのすべてを Jenkins を利用することで解決できました。

# Jenkins によるバッチの運用
## ジョブの表示のさせ方など
 ![all__Jenkins_.png](https://qiita-image-store.s3.amazonaws.com/0/7869/5fd527ab-7aff-b45c-edbb-5e0945fa30cf.png)

* 上の画像のように基本的に1ジョブ、１バッチとしています。こうしないとどのジョブがどのファイルに対応しているのかパッと見でわからないからです。
* myservice1--import.php のように myservice1-- などのプレフィックをつけるようにしています。
* これは種類ごとにリストビュー（タブ）を切り替えられるようにするためにこのようにしています。
* プレフィックとの間にハイフン２つを採用しているのは、ジョブ名はディレクトリ名に対応しているため、使える文字が限られていて選択肢があまりなかったからです。
* all 以外のリストビュー（タブ）は正規表現で "myservice1--" から始まるものをマッチさせるなどの設定をすることで種類別に表示できるようにしています。cron で運用していた時も種類別にファイル分割していたので、それがそのままこれに反映する形で移行しました。
* 一番右に実行時間の設定をプラグインで表示させています。

## 各ジョブの設定
![myservice1--import_php_Config__Jenkins_.png](https://qiita-image-store.s3.amazonaws.com/0/7869/d6abc4d4-78d3-f6fc-b7b2-afb37fb08799.png)

* APサーバーへのデプロイと同じ仕組みでバッチサーバー上にでもデプロイするようにし、上の画像のように環境変数を通じて参照するようにしています。
* この環境変数は「Jenkins の管理」-> 「システムの設定」のグローバルプロパティで設定した環境変数です。この環境変数を利用して各ジョブからバッチファイルを実行させています。


各ジョブは以下のような形で使用しています。普通の Jenkins の使い方ですね。

 ![myservice2--export_php__Jenkins_.png](https://qiita-image-store.s3.amazonaws.com/0/7869/a3c0ba93-458a-80be-f0b3-a7cbfbd3a4c4.png)

## ビルドにメモを残す
各ビルドのボタン（上の画像だと「各ビルドの実行開始時刻」と書いたところ）の時間をクリックした後に「説明の編集」をおしてフォームに適当に入力すると以下のように表示されます。

![myservice2--export_php__Jenkins_.png](https://qiita-image-store.s3.amazonaws.com/0/7869/f5640df1-c4bb-b918-aba5-6b1dce94dec6.png)

こんな感じでメモを残しておくことができます。「エラーで止まってしまったので手動で実行しました」など他に人に伝えたい事柄があるときに便利です。

## エラーの通知
バッチがコケたときに通知するため「Jenkinsの管理」-> 「システムの設定」 から以下のような設定を行っています。
これは後述の global-shell-hook plugin によるもので、各ジョブが終了した時に実行するシェルスクリプトを設定できるので、ここでジョブが失敗したときだけチャットツールに通知するようにしています。

![システムの設定__Jenkins_.png](https://qiita-image-store.s3.amazonaws.com/0/7869/be8cf6d6-d6b4-fe71-eb95-64d355044b06.png)

## エラー時の対応
![myservice1--import_php__Jenkins_.png](https://qiita-image-store.s3.amazonaws.com/0/7869/ba9df102-5329-16d0-676c-7c8ea14b0d0b.png)

* まずは丸ボタンを押してログを確認するところから。
 * Jenkins はスクリプトの標準出力・標準エラー出力の結果をすべて残してくれるので、スクリプト側ではとにかく何か出力しておけばいいので簡単。
* もう一つはジョブ画面の「推移」というボタンを押した先にあるグラフです。実行時間が長くなってしまってトラブルを起こしているケースがありますが、それを視覚的に確認できます。

## デプロイパスを Jenkins の環境設定として保存する
ジョブの設定それぞれにバッチへのパスをフルパスで記述してしまうと後から変更するのが大変になるので、Jenkins の環境変数として各ジョブではそれを利用してバッチへのパスを指定するようにしています。
これは「Jenkins の管理」->「システムの設定」->「グローバルプロパティ」の下の環境変数のチェックボックにチェックを入れると設定できます


## 常に更新処理を走らせたい
サーバー上のサービスとして立ち上げてリアルタイムにデータを更新していたような処理も、バッチをJenkins で運用し始めるとともに一定時間ごとに終了し、またすぐに実行されるバッチへと変更していきました。

このような構成のほうが以下の理由から運用しやすいので、このようにしていきました。

 * 一つ一つサービスにされるとサービスごとに実装や運用方法が異なって大変
 * サービスの立ち上げ、終了など基本的な操作の把握、ログの確認方法、トラブルが発生した際にの対応方法、デプロイ方法の仕組みの整備などが必要になる
 * ただのスクリプトを定期実行しているだけなのでメンテナンスが楽 

Jenkins で運用していれば統一的な操作で基本的な運用はできるので、とても楽になりました。Jenkins で運用できない理由がなければ、こちらのほうが運用が楽なのでオススメです。

## 利用しているプラグイン

### [Configuration Slicing](https://plugins.jenkins.io/configurationslicing)
これは各ジョブを横断的にまとめて設定変更できるプラグイン。
すべてジョブに設定されているスクリプト設定を同時に編集したりすることができます。

### [Cron Column](https://plugins.jenkins.io/cron_column)
jenkins に設定されている実行時間設定をカラムとして表示できるプラグイン
実行時間を一覧表示できて便利

### [SCM Sync configuration](https://plugins.jenkins.io/scm-sync-configuration)
このプラグインを利用すると jenkins の設定を git リポジトリにバックアップしておくことができます。
動きとしては設定画面から何らの変更を行って反映ボタンを押した瞬間に設定内容がリポジトリに push されるような動きをします

これはとても便利で操作履歴代わりにもなります。gitリポジトリを見るといつどのような設定変更を jenkins したかというのがすべて残るので、何か操作をミスしたけど元に戻す方法を忘れてしまったというときも安心です。

### [Extra Columns](https://plugins.jenkins.io/extra-columns)
様々なものをカラムとして追加できるようになります。ビルドボタンをカラムとして表示するために使ってます。

### [Console Tail](https://plugins.jenkins.io/console-tail)

ジョブの最終ビルドがエラーだったときにジョブ画面でビルドログの最後のほうだけ表示してくれるプラグインです。役にたっているかどうかは微妙だけど、一応入れてます

### [Timestamper](https://plugins.jenkins.io/timestamper)
ジョブが残した実行ログの隣に行が出力された時の日付・時刻を表示することができるようになります。（経過時間表示に切り替えることもできる）とても便利。

![mytestjob__2_Console__Jenkins_.png](https://qiita-image-store.s3.amazonaws.com/0/7869/bea1fcb3-2ca0-f587-ac41-e4e25124bbb8.png)


今は推奨設定で Jenkins をセットアップすると自動的に入るので特に意識しなくても入っている事が多いと思います。

### [global-shell-hook](https://github.com/notona/global-shell-hook)
これは自作のプラグイン。https://github.com/notona/global-shell-hook
自作のプラグインで、失敗した時にチャットツール(Slack)に通知するなどを Jenkins 全体の設定でしたかったので、それができるプラグインを作りました。

ジョブ完了した直後に実行するシェルスクリプトを記述できるプラグインです。これを利用してシェルスクリプトでバッチが失敗した時だけ Slackに通知するようにしました。

シェルスクリプトなので、何か新たに追加で挟みたいときも簡単に入れられます。

## すべて(all) タブの表示するカラムを変更したい
デフォルトだと「すべて」タブで表示する項目は変更できません。

![すべて__Jenkins_.png](https://qiita-image-store.s3.amazonaws.com/0/7869/6d26fb0d-c1d6-3d0e-041c-12fcbeaa9e96.png)


これだと実行間隔を「すべて」タブでチェックできなくてとても不便なので、以下の方法でタブを作り直してできるようにします。

1. Jenkins のトップページの「すべて」タブの隣にある「+」ボタンを押します
2. 「リストビュー」を all2 という名称で作成します
2. リストビューのジョブフィルターの設定で「正規表現でジョブを指定」選び、「.*」と入力して保存します
3. 「Jenkinsの管理」->「システムの設定」の上のほうにある「デフォルトビュー」から all2 を選び保存します。
4. デフォルトビューではなくなった「すべて」タブをクリックすると、「ビューの削除」というボタンが出ているのでこれを押して消します。
5. すると、タブが「all2」だけになるので、「ビューの変更」を押してこのビューの名前を「すべて」に変更して完了です

このバッドノウハウっぽいやり方は Jenkins の wiki に書いてあるやり方です。
https://wiki.jenkins-ci.org/display/JENKINS/Editing+or+Replacing+the+All+View

## Jenkins のセットアップを自動化する
プラグインを手動でポチポチ作業を毎回やるのは面倒なのでプラグインのインストールについても自動化することを考えてみました。

以下を実行して jenkins を cli で実行するための executable jar を取得することができます。

```
wget -O jenkins-cli.jar http://localhost:8080/jnlpJars/jenkins-cli.jar
```

これを使用すると cli で jenkins に様々な操作を行うことができます。
その一つでプラグインのインストール機能があるので、今回はそれを利用します。


debian なら以下の様な感じでスクリプトを組めばインストールするところまで自動化できます。
注意点はコメントとして記載しておきました。

```bash

#!/bin/bash -eux                                                                                                                                                                                                                                                                  

# Jenkins 本体のインストール
wget -q -O - http://pkg.jenkins-ci.org/debian/jenkins-ci.org.key | apt-key add -
cp ${base_dir}/etc/apt/sources.list.d/pkg.jenkins-ci.org.list /etc/apt/sources.list.d/
aptitude update
aptitude install -y jenkins

# install が終わった直後に jenkins の restart されるので、
# しばらくの間 jenkins が http リクエストを受け取ることができず、下のwget に失敗する。
# そのためにここでしばらく待たせている
# ここはどれぐらい待ったらいいかよくわからない。
sleep 60

# jenkins の cli クライアントを取得する
wget -O jenkins-cli.jar http://localhost:8080/jnlpJars/jenkins-cli.jar                                                                                                                                                                                                          

# jenkins プラグインのインストール処理
java -jar jenkins-cli.jar -noKeyAuth -s http://localhost:8080 install-plugin https://updates.jenkins-ci.org/latest/git.hpi                                                                                                                                                      
java -jar jenkins-cli.jar -noKeyAuth -s http://localhost:8080 install-plugin https://updates.jenkins-ci.org/latest/cron_column.hpi                                                                                                                                              
java -jar jenkins-cli.jar -noKeyAuth -s http://localhost:8080 install-plugin https://updates.jenkins-ci.org/latest/scm-sync-configuration.hpi                                                                                                                                   

# /etc/init.d/jenkins restart でも jenkins の再起動はできるが、この操作ではプラグインが取り込まれないので、
# 以下のようにしてsafe-restart する必要がある
java -jar jenkins-cli.jar -s http://localhost:8080 safe-restart               

```

## Jenkins サーバーを移転したいとき
以下の手順だけでできます。シンプル。

1. 「Jenkins の管理」->「シャットダウンの準備」を押す。すべてのジョブが終了するのを待つ。
2. Jenkins を終了する /etc/init.d/jenkins stop
3. JENKINS_HOME（デフォルトは /var/lib/jenkins） を移転先のサーバーに rsync する。
4. 移転先の Jenkins を起動 /etc/init.d/jenkins start

このやり方の問題点は設定されているジョブ数が多いサーバーだとすべてのジョブが同時に移動することになってしまうので、そこはちょっと怖いというところですね。。

## Jenkins をWeb上で再起動したい
何故かボタンが用意されていないですが、例えば localhost で運用しているなら、以下のような形で /safeRestart にアクセスするとボタンが用意されています。

```
http://localhost:8080/safeRestart  
```

![Safely_Restart_Jenkins__Jenkins_.png](https://qiita-image-store.s3.amazonaws.com/0/7869/7ffb7621-a4ce-8688-c800-411a14dfc48b.png)


## Jenkins のログイン画面をなくす
デフォルトだと以下のような形でログインを求められる設定になっていますが、特にこのあたりが不要なら無効化することができます。
![Jenkins.png](https://qiita-image-store.s3.amazonaws.com/0/7869/39f1f099-0e63-716e-4ff0-2c4a5f455f49.png)

JENKINS_HOME（Debian のデフォルトは /var/lib/jenkins）にある config.xml の useSecurity を false にすることで無効化できます

```xml
<?xml version='1.0' encoding='UTF-8'?>
<hudson>
  <disabledAdministrativeMonitors/>
  <version>2.73</version>
  <numExecutors>2</numExecutors>
  <mode>NORMAL</mode>
  <useSecurity>false</useSecurity> <!-- ここを false に。 -->
  ...
</hudson>
```

## 最後に
* 問題を解決するのに必要だったのは便利なバッチ管理画面だったということでしょうか。
* この記事の6割ぐらいは4年前に書かれたものです。加筆・修正を加えた上で公開しています。