バッチ処理を定期的に実行したくなると、まず最初に選択肢としてあがるのは cron です。しかし cron は基本的に指定された時間に指定されたコマンドを実行するだけの仕組みです。以下のような要望はバッチ実行ではよくあることですが、cron 単体では実現できません。
- 多重実行を防止したい
- 詳細なログを出力したい
- 実行結果を Slack などに通知したい
- 時間がかかるなら強制終了したい
システムが大きくなってきたら Rundeck などのジョブ管理システムを導入するでしょう。ジョブ管理システムなら先ほどの要望は実現可能です。この記事は、ジョブ管理システムを導入までには至っていないけれど、単純な cron だけでは厳しくなっている場合に、もうちょっと頑張るためにはどうしたらいいかというものです。
ではどうするのか。cron で指定できるのはコマンドです。Unix/Linux には多様なコマンドが用意されていますので、その組み合わせで実現しましょうということです。
多重実行を防止する
flock コマンドを使用してロックファイルによる排他制御を行うのが一般的です。
* * * * * flock -n /tmp/job.lock /home/hoge/bin/job.sh
-n オプションを指定してロック取得できない場合は即時終了していますが、待ちたい場合は-n オプションを取り除きます(デフォルトで待つ)。
なお flock は util-linux で提供されるコマンドです。Linux 以外の OS では別途インストールが必要になります。
詳細なログを出力する
cron 自体にもログはあります。
# cron のログの例
Jan 13 13:42:01 myhost CRON[1324]: (hoge) CMD (/home/hoge/bin/job.sh)
しかし、cron のログに出力されるのは、いつ、誰の権限で、なんというコマンドを実行したかでしかありません。それはそれで有用なのですが、コマンドの詳細な実行結果が欲しい場合はコマンド側で出力と保存をしなければなりません。
# ファイルに出力する例
* * * * * /home/hoge/bin/job.sh >> /home/hoge/log/job.log 2>&1
# syslog に出力する例
* * * * * /home/hoge/bin/job.sh 2>&1 | logger
実行結果を Slack などに通知する
curl コマンドなどを使って Slack 等に通知するスクリプトを作って、通知内容はパイプで渡します。
# 実行結果を通知する例
* * * * * /home/hoge/bin/job.sh 2>&1 | /home/hoge/bin/notice.sh
時間がかかるなら強制終了する
timeout コマンドを使うと、時間が過ぎたら強制終了出来ます。
# 指定時間で強制終了する例
* * * * * timeout 3600 /home/hoge/bin/job.sh
ラッパースクリプト化する
これまで紹介してきた方法を使えば cron だけでも、もうちょっと頑張れます。しかし、全てのコマンドを crontab に記述すると、非常に長くなって見通しが悪いです。
# 見通しが悪くなっている crontab の例
* * * * * flock -n /tmp/job.lock timeout 3600 /home/hoge/bin/job.sh 2>&1 | tee -a /home/hoge/log/job.log | /home/hoge/bin/notice.sh
コマンド一つでも見通しが悪いですが、crontab では通常様々なコマンドを実行させますので、この長い記述が何行も続くことになってしまいます。
同じことを何度も記述するわけですから、スクリプトにまとめてしまえばいいわけです。そんなラッパースクリプトの例です。
#!/bin/bash
LOCK_FILE="/tmp/$(basename "$1").lock"
exec 9>"$LOCK_FILE"
flock -n 9 || exit 1
timeout 3600 "$1" 2>&1 | tee -a /home/hoge/log/job.log | /home/hoge/bin/notice.sh
# 見通しが良くなった crontab の例
* * * * * /home/hoge/bin/kicker.sh /home/hoge/bin/job.sh