capistranoで定期処理を管理する

ウェブサービスを運営していると必ず、「定期的にやるべきこと」ってありますよね。

  • 古くなったログの処理
  • 負荷のかかる処理をバックグラウンドで定期的に
  • 外部データの取得

たいていの場合、手動でやるわけにはいかないので、シェルスクリプトなんかを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]

これで、タスクの実行後に標準出力をメールで送信してくれます。

参考

Comments are closed.