Cronを簡単に(無料で)スマートにしたい

Cronをなんとかしたい。
いつ走って、いつ終わって、失敗したなら教えてほしい。

Jenkins、RundeckやAzkabanを使えって話かもしれないが、それはやりすぎ。

僕はcrondを置き換えたいわけじゃない。
**ほんの一手間で期待を満たしてくれるテク** を探しているんだ。
単純な話なんだけれど、ベストな方法が見つからないんだ。

せっかくいろいろ調べたので、まとめた。

なお、本書に出てくる手順は、CentOS6ベースのOSで動作確認した際のメモ。

# クラウドサービス系

クラウドサービスであれば、AWSやGCEからでも利用できる。自宅サーバーにだって利用できる。気軽に始められる。
ただし、クラウドサービスということは、有料だということだ。


## Cronitor

Cronの改善を考える上で、基準となる商用サービスだと思う。
サイト: https://cronitor.io/

**既存のCronジョブに一手間加えて便利にしてくれる。**
まさに今回の目的からすれば理想的な設計をしている。

### 使い方

以下は[オフィシャルサイトの説明](https://cronitor.io/)から抜粋するが：

成功を通知させたければ`&&`でcurlに繋げば良い。

```bash
# m h dom mon dow   command
0 1 * * * /path/to/mysqlbackup.sh && 
curl https://cronitor.link/d3x0c1/complete
```

失敗を通知させたければ`||`でcurlに繋げば良い。

```bash
# m h dom mon dow   command
0 1 * * * /path/to/mysqlbackup.sh || curl https://cronitor.link/d3x0c1/fail 
```

以上。

アラートは、成功失敗だけでなく、ジョブが実行されていない/終わっていないことを検知して通知したりもできる。
成功通知は、Cronジョブがコメントアウトされていることの検知にも利用できるだろう。


### 利点・欠点

利点も欠点もシンプルだ。

利点:

- シンプル

欠点:

- 有料 ($24.95/月〜)
- 成功可否くらいしかわからない

商用サービスでの利用ならともかく、気軽に使うには有料(しかもそれなりに高い）というのは気が引ける。


## Dead Man's Snitch

使い方に関して何もCronitorと変わらない。
サイト: https://deadmanssnitch.com/

値段が比較的安いところから始まったり、スマホアプリがあったりするが、それくらいの違いなので割愛。
サービス名で損していると思う。


## healthchecks.io

### 概要
無料版Cronitorと考えて良い。
サイト: https://healthchecks.io/

自らも

> Free alternative to Cronitor and Dead Man's Snitch.

と名乗るくらいに、第三の選択肢だ。
**Freeプランでも無制限に監視できるし、無制限にアラートを送信できる。**　商用利用も可だ。
サービスレベルの担保とかをしない代わりに安くしているような印象。

無料というところに惹かれたので、使用感をまとめておこう。


### 使い方

Cronitorとほとんど同じだ。
対象のCronジョブの後ろに`&&`でcurlを追記するだけだ。

```bash
# m h dom mon dow   command
  8 6 *   *   *     /home/user/backup.sh && curl -fsS --retry 3 https://hchk.io/YOUR-JOB-KEY > /dev/null
```

ジョブごとに一意なURLが払い出されるため、これによりジョブの成功がhealthchecks.ioサーバーに記録される。
**失敗は記録できない作りのようだ。**

実行結果はこのように記録されている。

![healthchecks_io.png](https://qiita-image-store.s3.amazonaws.com/0/82212/476e2bd5-32df-dd72-c5b3-ca1d5ab382a3.png "healthchecks_io.png")


設定したタイムリミットを超えると失敗と判定されてアラートが発生する。

ところで、使い方とは少し違う軸の話だが、感銘を受けたので書いておこう。
healthchecks.ioはアカウント作成やログインもシンプルだ。
メールアドレスを入れたらアカウントが作られ、ログイン用のURLが通知される。
・・・認証用URLではない。 **ログイン用URL**だ。
つまり **パスワードも不要なのだ。**　(パスワードを設定することもできる)



### 利点・欠点

利点: 

- 低コスト、というか **無料**
- シンプル

欠点:

- 失敗時、確認のためにサーバーにログインする必要がある




# オープンソース系
自分でどうにかしなければならないが、自分でどうにかすれば無料。
サーバータイプのものは、どこに設置するかが問題になるかもしれない。


## OSSのCronitor

### 概要
妙にレトロなダッシュボードが目を引くCronジョブ管理ソフト。
サイト: https://github.com/josh-berry/cronitor

cronitor.ioの代替を探していたらすぐに見つかったのだが、名前が同じだけで、cronitor.ioとは関係ないだろうと想像している。


### 使い方
サーバーインストールに関しては、サイトの「Setting up the Server」を読みながら作業すれば良いので割愛。

ジョブ実行ホスト側では、このようなCronを仕込む。

```bash
[ジョブ実行ホストのcron]

CRONITOR_URL=http://cronitor-server:8434/
* * * * * cronitor-run -q -j test -- ls -l
```

`&&`や`||`を用いない、wrapperタイプだ。

これに関する監視設定は、cronitorサーバーの`rules.yaml`に記載する必要がある。
例えば、「上記"test"ジョブは少なくとも2分間隔で実行された記録がなければエラー」とするなら、以下のようなルールになる。

```rules.yaml
- match: "test"
  due_every: 2m
  keep: 10day
```



### 動作確認

上記のような条件で一度でもジョブを実行すると、cronitorサーバーに記録が残り始める。

![ 2016-04-27 17.21.28.png](https://qiita-image-store.s3.amazonaws.com/0/82212/f019db04-b597-0b4c-61ee-e6afc734eaf7.png " 2016-04-27 17.21.28.png")


・・・なんだかいろいろと気になるが、[log]をクリックして詳細なログを追っていこう。

wrapperタイプだけあって、標準出力の結果が取れている。また、環境変数も出力されていて、デバッグに役に立ちそうだ。
![FireShot Capture 9 - Log_ test at 2016-04-27 17_20_40.72089_ - http___192.168.56.102_8434_log_test.png](https://qiita-image-store.s3.amazonaws.com/0/82212/167402f9-3891-7e0e-cb3b-85d2ed44abd2.png "FireShot Capture 9 - Log_ test at 2016-04-27 17_20_40.72089_ - http___192.168.56.102_8434_log_test.png")


ためしにCronジョブを止めてしまうとFailedになる。
![ 2016-05-02 16.06.18.png](https://qiita-image-store.s3.amazonaws.com/0/82212/1c65d637-6269-c3a8-4965-0004c18a9311.png " 2016-05-02 16.06.18.png")

・・・アラート設定がないので、アラートは出ないのだけれど。

### 利点・欠点
開発が止まっているせいなのか、妙にレトロで、RESTなサービスとの連携とかが考えられていないような気がする。

利点:

- ジョブの標準出力結果を通知できる
- ジョブ実行ホストのcronルールの変更が簡単

欠点: 

- ジョブに対するルールを入れるとき、YAMLファイルを編集する必要あり
- アラート機能がない


## minicron

[minicron](https://github.com/jamesrwhite/minicron)はRubyベースのオープンソースのプロジェクト。(先ほどのOSSのcronitorと比べると)モダンなUIが魅力的に見える。クラウドサービスとの連携も用意されているようだ。

他のOSSより複雑で、できることが多いので、僕のメモも量が多めになってしまったが、削減せずにそのままで。

### インストール

ruby gemでもインストールできるらしいが、最新バージョンにするために本家のインストール方法に従うことにする。

```bash
$ bash -c "$(curl -sSL https://raw.githubusercontent.com/jamesrwhite/minicron/master/install.sh)"


### ファイルの確認・・・権限がおかしいので直しておこう
$ ls -l /opt/minicron/
total 8
drwxr-xr-x 4 root games 4096 Apr 28 16:40 lib
-rwxr-xr-x 1 root games  512 Apr 21 06:10 minicron

$ chown -R root:root /opt/minicron/
$ ls -l /opt/minicron/
total 8
drwxr-xr-x 4 root root 4096 Apr 28 16:40 lib
-rwxr-xr-x 1 root root  512 Apr 21 06:10 minicron


$ type minicron
minicron is /usr/local/bin/minicron
```

設定を作る。サンプルファイルはgithubに置かれているので、ダウンロードしてから編集するのが良さそうだ。

```bash
### サンプル設定ファイルの配置
$ curl -o /etc/minicron.toml https://raw.githubusercontent.com/jamesrwhite/minicron/master/config/minicron.toml

### 編集する
$ vim /etc/minicron.toml
```

取り急ぎ変えるべき設定値は下記。
アラート通知先なども設定できるようだが、まずはCron管理の挙動を見るためにシンプルに。

- [server]のhost : 0.0.0.0に変更
- timzone : Asia/Tokyo
- secret : 好きな文字列に


サーバーを起動してみる。

```bash
$ minicron db setup
$ minicron server start

$ minicron server status
minicron is running
```


### ジョブの登録

`http://サーバーのIPアドレス:9292`にブラウザでアクセスしてみる。

![ 2016-05-02 11.44.46.png](https://qiita-image-store.s3.amazonaws.com/0/82212/637a384b-bedf-0e7b-e0dc-19c32f02b1f7.png " 2016-05-02 11.44.46.png")

Welcomeページが表示された。

[Hosts] → [Add New Host]　と辿って、ホストを登録しよう。
![ 2016-05-02 11.45.32.png](https://qiita-image-store.s3.amazonaws.com/0/82212/c90823de-2b64-5ab3-46e6-a1106079f4c5.png " 2016-05-02 11.45.32.png")

モダンなインタフェースで英語ながら説明も書いてあるので、特に悩まないと思うが、下記しておく。

- Name: ニックネームなので自由に
- FQDN: 対象サーバーのFQDN(`hostname -f`で確認可能)
- User: Cronジョブの登録先＝実行ユーザー
- Host: minicron serverから見た時のIPアドレスやFQDN(sshによる管理で使用)
- Port: sshのポート番号

例としてminicron server自分自身を登録してみた。
![ 2016-05-02 11.52.38.png](https://qiita-image-store.s3.amazonaws.com/0/82212/13a1907d-4053-4f3e-3c4c-83a7a67258b8.png " 2016-05-02 11.52.38.png")


追加したホストの指定したユーザーのauthorized_keyに、表示されたPublic Keyをコピペする。

```
$ ジョブ実行サーバーにCron実行ユーザーでログイン

[ジョブ実行サーバーにて]
$ vim ~/.ssh/authorized_keys
```

Connection Testで接続テスト。
![ 2016-05-02 11.59.53.png](https://qiita-image-store.s3.amazonaws.com/0/82212/1dccc49c-4c9d-b151-99b2-c88be5764a59.png " 2016-05-02 11.59.53.png")

OKになった。

このとき[Import Existing Jobs]をクリックすると、既存のCronジョブもminicron管理下にできる。任意で。

ここまでくれば、[Add New Job]から、新しいCronジョブを追加できる。
試しに`ls -l /tmp`を実行するジョブを追加しよう。
![ 2016-05-02 12.01.39.png](https://qiita-image-store.s3.amazonaws.com/0/82212/b29b7565-ffad-c2a7-71ee-ffbb17c22d8f.png " 2016-05-02 12.01.39.png")

さらに[Add New Schedule]で実行スケジュールを指定する。
毎分(every minute)を指定してSaveした。
![ 2016-05-02 12.03.45.png](https://qiita-image-store.s3.amazonaws.com/0/82212/7f825a02-b7b7-b290-27b4-dfd1a8a173c9.png " 2016-05-02 12.03.45.png")


### 動作確認

対象のサーバーで、ジョブ実行ユーザーになって`crontab -l`を打ってみよう。

```bash 
$ crontab -l

#
# This file was automatically generated by minicron at 2016-05-02 03:03:15 UTC, DO NOT EDIT manually!
#

# ENV variables
PATH=/bin:/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
MAILTO=""

# ID:   1
# Name: 元々あったジョブその1
# Status: enabled
	* * * * * minicron run '元々あったジョブその1'

# ID:   2
# Name: 元々あったジョブその2
# Status: enabled
	* * * * * minicron run '元々あったジョブその2'

# ID:   3
# Name: minicron test
# Status: enabled
	* * * * * minicron run 'ls -l /tmp'
```

既存のタスクも変更されて、すっかりminicron仕様になっていることを確認できるだろう。
**これ以降、このcrontabはminicron以外で変更することはお勧めできない。**
※ 勝手に消されてしまう。


設定が正しければ、当該ジョブの実行回数がJobsでカウントアップされていることがわかる。
![ 2016-05-02 12.21.09.png](https://qiita-image-store.s3.amazonaws.com/0/82212/49ddc872-5207-f0a8-0297-066f903fe49c.png " 2016-05-02 12.21.09.png")

さらにそれぞれのExecutionsよりJobをViewで調べていくと、実行結果も出力されている事がわかる。
![ 2016-05-02 12.23.31.png](https://qiita-image-store.s3.amazonaws.com/0/82212/f254fd32-da2f-aeef-ed82-89706a8f10ff.png " 2016-05-02 12.23.31.png")


### うまくいかない時は

時間が経ってもExecutionsに反映されない場合は、ジョブを設定したサーバーにログインして、手動で登録したコマンド(`crontab -l`で表示されるコマンド)を打ってみよう。

私の失敗したパターンでは、以下の様なメッセージが出力されていた。

```bash
$ minicron run 'ls -l /tmp'
hostname: Unknown host
[API Error] Validation failed: Fqdn can't be blank
```

これ、自分自身のホスト名(FQDN)が引けなかったというオチらしい。
hostsファイルに書いておいて、以下のように確認する。

```bash
$ hostname -f
localhost
```

この時表示された名前と、minicronで登録したFQDN名がマッチしていないとNG、という仕組みのようだ。


### 利点・欠点

SSHを使って管理するというところが判断ポイントだと思う。

利点:

- ジョブの標準出力結果がブラウザで見られる
- SSHを使う点でシンプル
- 中央集権的に管理できるので便利
- ログインしてオペレーションしたくない環境に向いている

欠点:

- 単にジョブの成功・失敗を知りたいだけの用途には、セットアップが煩雑
- ホスト上で直接Cronを変更できなくなる
- SSHで管理という、セキュリティ上の不安感




## horenso

### 概要
horensoはGolangで作られたコマンドのwrapperツールだ。
サイト: https://github.com/Songmu/horenso

これは日本人作者だし、作者本人の[Songmu氏が解説している](http://www.songmu.jp/riji/entry/2016-01-05-horenso.html)ので、細かいことはもうそちらに委ねてしまおう。

これはコンセプトが素晴らしい。
wrapperツールだけというミニマルな作りなので管理が楽だし、実行開始を知らせる「Noticer」と、実行結果を知らせる「Reporter」を作成すれば、やりたいことは何でもできそうだ。


### 使い方

horensoは[ビルドしたパッケージが配られている](https://github.com/Songmu/horenso/releases)ので、インストールはPATHが通ったところに配置するだけでいい。

あとは、ジョブ実行ホストのcronにて、horensoをwrapperとして使う。

```bash
* * * * * horenso -n horenso-noticer-XXXX -r horenso-reporter-XXXX -- ls -l /tmp
```

noticer, reporterをどうするか、というところが、このツールの使いこなしのポイントになりそうだ。


### 利点・欠点


利点:

- ジョブの標準出力結果を通知できる
- Noticer, Reporterを作れば、どんなサービスにも対応できる可能性
- バイナリなのでクライアント側が便利

欠点: 

- ジョブやCronの管理ツールではない
- **report/noticeを送る先を考える必要あり**

現状ではSlackに流すのが一般的だろうか？でも、例えば10分毎に実行しているジョブを通知し続けるのは・・・邪魔だよな・・・　　頻度が多いジョブでは、実質的に「失敗通知」にしか利用できないだろう。

この欠点は単に **公開されて間もないツールだから** ということに起因している気がする。Noticer, Reporterの充実と、上手な連携先を閃きさえすれば、非常に優秀なツールになりそうだ。



# まとめ

僕の結論として、2016年5月の今、こう思っている。

- ジョブの成功・失敗を知れたら十分なら、healthchecks.ioが魅力的だ。
- ジョブの出力が必要であれば、horensoで頑張ろう。

・・・何かのサーバー管理下にあるなら、結局はログ監視が万能だろうか。