ウェブサービスを運営していると必ず、「定期的にやるべきこと」ってありますよね。
- 古くなったログの処理
- 負荷のかかる処理をバックグラウンドで定期的に
- 外部データの取得
たいていの場合、手動でやるわけにはいかないので、シェルスクリプトなんかをcronで実行するわけですが、サービスの拡大に伴って、サーバ数が増えた場合、どうするんでしょう?
全てのサーバーにcronを設定すると、全てのサーバーで同じ設定になっていることを確認しなければなりません。 cronの設定自体を自動化すれば、全てのサーバーで同じ設定になっていることが保証されますが。。。
そんなわけで、Ruby on Rails のdeplyツールとして使われているCapistranoを使って、cronをCapistranoサーバーから一括実行することにしてみました。
以下の手順では、ssh接続などの設定は省いています。
(実はこの設定でかなり苦労したところではありますが。。)
1. Capistranoのインストール
gemでインストールします。
> gem install capistrano > cap --version Capistrano v2.4.0
2. capfileの作成
capfile はCapistranoの「レシピ」と呼ばれていて、Capistranoが実行するタスクの内容を記述します。Rubyをベースにした独自言語ですが、Rubyを知っている人にとってはほとんど同じです。
role :app, "app-server1", "app-server2" desc "テスト用のタスク" task "test_for_cron", :roles => :app do run "sh /path/to/cron/script.sh" end
3. cronの設定
cronの設定時には、-f オプションを使って、capfileの場所を明示的に示しましょう。
> crontab -e 0 12 * * * cap -f /path/to/capfile test_for_cron > /dev/null 2> /var/log/capistrano/error.log
12時になったら、2つのアプリケーションサーバー
- app-server1
- app-server2
でスクリプト /path/to/cron/script.sh が実行されるようになりました。
なお、/path/to/cron/script.sh は各サーバー内に存在する必要があります。 サーバーが増えたら、role に足すだけで、そのサーバーでも実行されることになります。
ただ実行するだけでは面白くないので、実行したらメールを送ります。
4. メール送信用タスクの作成
メールはlocalhost で送りますが、Capistranoっぽく、localhostにリモート接続してみます。 (というか、どこにも接続せずにtaskを実行する方法が分からんだけ?)
desc "cronを実行したらメールを送信" task "send_email", :host => "localhost" do fn = "/tmp/filename.txt" addresses = "email@address.com" time = Time.now.strftime("%Y/%m/%d %H:%M:%S") put @result.join("\n"), fn, :mode => 400 run "mail -s 'cron has done on #{time}' #{addresses} < #{fn}" run "rm -f #{fn}" end
5. cron用のタスクで結果を取得するよう変更
先ほど作成したタスクを修正して、実行結果をメール送信用タスクに渡せるようにします。
複数のサーバーで実行されることを想定して、結果は配列で渡します。
desc "テスト用のタスク" task "test_for_cron", :roles => :app do @result = [] @result.push capture("sh /path/to/cron/script.sh") end
6. タスク実行後にメール送信タスクをhookする
Capistranoには start, finish, before, after などのtriggerがあり、task実行の前後などに特定のタスクを実行することができます。 capflieのtaskの前に以下の設定を追加します。
on :finish, :send_email, :except => [:send_email]
これで、タスクの実行後に標準出力をメールで送信してくれます。