cron ジョブを監視したい。監視したい項目は以下。
- cron ジョブの実行が開始されたかどうか
- cron ジョブが正常終了したかどうか
- cron ジョブの所要時間
このような課題を解決するソリューションとして Dead Man’s Snitch を使ったことがあるが、他にも複数の類似サービスが存在する。 Cron Job Monitoring というジャンルのようだ*1。
Dead Man’s Snitch は 1 の監視に主眼をおいたサービス(だと思う)。 Cronitor は 3 の監視もカバーしているっぽい(が試してはいない)。他のサービスもおそらく似たようなものだろう。
cron ジョブ監視専門のサービスを導入するのも良いが、 mackerel で実現できないか、と思ったので考えてみた。
1 と 3 は、
- cron ジョブの所要時間を計測してサービスメトリックとして投稿する
- そのメトリックに対する監視ルールとしてサービスメトリック監視を作成し、サービスメトリックの途切れ監視を有効化する
という組み合わせで監視できる。
2 はmkr wrap コマンドで監視できる。 cron ジョブとして実行するアプリケーションの実装次第で Sentry のようなエラー監視システムに通知することもできる(そうした方が良いだろう)。
試してみた設定やプログラムは以下。 Production 環境で運用する場合は、計測とメトリック投稿を担う wrapper コマンドを用意したり、 cron ジョブの「突き抜け」が発生しないようにロックをかけたりする必要があるだろう。
crontab
* * * * * /usr/bin/time -f '\%e' -o /tmp/elapsed.txt mkr wrap -- ruby cronjob.rb >/tmp/cronjob.log 2>&1; ruby metric.rb
cronjob.rb
ランダムに数十秒かかるコマンド、ということでこんな風にした。余談だがdd(1) で空ファイルを作るときは /dev/zero より seek - flatlineの日記らしい。
#!/usr/bin/env ruby exec "dd if=/dev/zero of=/tmp/a.dat bs=1MB count=#{[1,1,2,3,5].sample}K"
metric.rb
#!/usr/bin/env ruby require 'json' elapsed = File.read('/tmp/elapsed.txt').to_f.ceil data = [{name: "Cronjob.elapsed", time: Time.now.to_i, value: elapsed}] File.write('/tmp/metric.json', data.to_json) apikey = File.read('/etc/mackerel-agent/mackerel-agent.conf').scan(/^apikey\s+=\s+"(.*)"$/)[0][0] system %!curl https://api.mackerelio.com/api/v0/services/test/tsdb -H 'X-Api-Key: #{apikey}' -H 'Content-Type: application/json' -X POST -d @/tmp/metric.json!
サービスメトリックのグラフの例。縦軸の単位は秒。
アラート画面の例。
1 と 2 は同じことだともいえるし、違うことだともいえる。 Cron Job Monitoring サービスは * * * * * command && curl https://example.com/a78e9dd/complete
のように、コマンド実行後に所定の URL へリクエストを送信することで cron ジョブの正常終了を通知する API インタフェースを用意している。これは簡単に使えるが、 &&
を使うので 1 と 2 の監視を厳密に区別できない(ジョブが kick されなかったことと、 kick されたが異常終了したことの差がわからないはず)。 mkr wrap
は 2 の監視をより精緻に行う方法だといえる。
所要時間を監視したい動機は、例えば外部システムと通信するプログラムなど、所要時間の見積もりがしづらい場合がある、というのが一つ。次に、データ量に応じて徐々に所要時間が伸びる場合がある、というのが一つ。いずれも実行開始から一定時間を過ぎたら graceful shutdown させる仕組みを用意した方が良いが、そこまで作り込むのか?という論点はあるし、所要時間を減らすチューニングをするにせよ、 cron の実行間隔を伸ばすにせよ、一回の実行にどのくらい時間がかかるのか?を計測してからの方が良い解決方法を判断できるのは間違いない。
*1:正直、ジャンルがあるほど盛り上がってるとは思わなかった