horensoというcronやコマンドラッパー用のツールを書いた
https://github.com/Songmu/horenso
cron等、バッチジョブを走らせた場合にその結果通知やエラーレポートをどうするかは悩ましい問題です。ラッパースクリプトを統一的に噛ますのが常套手段ですが、そのためのツールとして、horenso というものをGoで作りました。報・連・相。その名の通り、実行ジョブの報告をつかさどってくれる君です。以下のようにして使います。
% horenso -r reporter.pl -- /path/to/job args...
--
以降に指定したコマンドが実行され、その結果がJSONとして標準入力経由でreporterに渡されます。reporterは実行可能なファイル、もしくはコマンドライン文字列であり、記述言語は任意です。reporterに渡されるJSONは以下の様なものです。
{
"command": "perl -E 'say 1;warn \"$$\\n\";'",
"commandArgs": [
"perl",
"-E",
"say 1;warn \"$$\\n\";"
],
"output": "1\n95030\n",
"stdout": "1\n",
"stderr": "95030\n",
"exitCode": 0,
"result": "command exited with code: 0",
"pid": 95030,
"startAt": "2015-12-28T00:37:10.494282399+09:00",
"endAt": "2015-12-28T00:37:10.546466379+09:00"
}
JSONには、実行したコマンドと、その出力、エラー出力、マージされた出力や終了コード、開始時間と終了時間など含まれています。その情報を利用して、ログを残したりチャットなどへの通知処理をおこなうと良いでしょう。
時間のかかるジョブを実行するときなど、ジョブ開始時に通知をしたいこともあるでしょう。そのために -n(--noticer)というオプションがあります。
% horenso -n 'ruby noticer.rb' -r reporter.pl -- /path/to/job args...
ジョブ開始直後にnoticerが呼び出されます。noticerの引数にも、上記のJSONが一部埋まっていない形で渡ってきます。pidは埋まっているため、それを使って開始と終了を突き合わせることもできるでしょう。
また、reporterなどのハンドラーは複数指定することが可能です。その場合、ハンドラーは並行実行されます。Goっぽいですね!
% horenso -r reporter.pl -r reporter2.py -- /path/to/job args...
horenso
をcronで利用する場合、そのままcrontabに書くのではなく、ラッパーシェルを作ってその中で呼び出すのが良いでしょう。例えば以下の様な具合です。
#!/bin/sh
exec /path/to/horenso \
-n /path/to/noticer.py \
-r /path/to/reporter.pl \
-r 'ruby /path/to/reporter.rb' \
-- "$@"
このようなシェルスクリプトを例えば wrapper.sh
という名前で保存し、crontabの中で以下のように利用します。
3 4 * * * /path/to/wrapper.sh /path/to/job --args... 2>&1 | logger -t myjob
horenso
は、コマンド自体の出力を奪うことはせずそのまま出力するため、上記のように logger
コマンドをつかってsyslogに出力している場合でもこれまでどおりログをtailすることが可能です。また、コマンドの終了コードも上書きされることなく、そのまま保持されます。
コマンドの実行結果の通知処理を変更したい場合でも、ラッパースクリプトそのものをいじる必要がなく、reporterやnoticerを変更すれば良いだけなので、ラッパースクリプトをぶっ壊してしまう心配が無いのが安心です。疎結合になっている分、ハンドラーの単体テストもしやすくなっていると言えるでしょう。
reporterを工夫することで、
- ジョブの失敗時にチャットに通知する
- ジョブの実行時間をMackerelにプロットする
- ジョブに失敗するとシーザーが死ぬ
などの処理を柔軟におこなうことが可能になります。
簡単、便利な horenso
、是非お使いください。ビルドバイナリなどはそのうち。
追記と今後の展望
結構反応を頂き嬉しいので、タグ打ってバイナリリリースとかさっさと整えようと思っています。また、若干仕様変更が入る可能性があって、現状、JSONが引数で渡ってくるようになっていますが、引数のサイズ制限などの問題がありそうなので、標準入力経由で渡すように変えようかなーと考えています。
引数の上限は % getconf ARG_MAX
で取得できるようです。勉強になりました。
余談
horenso
は私の代表的プロダクト(自称)であるruncronをGoでreviseしたものです。 runcron
は非常に便利なツールで私ももちろん愛用していましたが、以下の様な課題がありました。
- Perl環境が無いとインストールが手間なので、Perlプロジェクト以外で使いづらい
- プラグインをPerlで書く必要がある
- 理解が必要な概念が幾つかあり若干ゴツい
その点を踏まえて、horenso
は以下のようになりました。
- どんなプロジェクトでも使えるようにGoで書いた
- ハンドラーは任意の言語で記述可能
- コマンドの実行結果がJSONでreporterハンドラーに渡されるだけというシンプル概念
また、Goだと、ハンドラーの並行実行をしたり、出力をパイプして別スレッドでメモリ上にバッファしたりが簡単にできたので、慣れるとこういうツールを書くのには向いてそうだなーと改めて思いました。
もちろん、runcron
は枯れていて安定していますし、プラグイン資産がある場合はその使い回しがやりやすいので、一概にどちらが良いといえるものでもないでしょう。