まっしろけっけ

めもてきなやーつ

gemのwheneverを導入してみた

wheneverとは?

javan/whenever · GitHub
簡単に説明するとRubyのコードでcrontabの管理が出来るライブラリです。

導入以前

whenever導入前はbatchを実行するserverに入って「crontab -e」とかで編集する
crontabメンテナンスおじさんが存在していた。

問題点

  • 設定がcrontabメンテナンスおじさんに依存してしまう
  • crontabメンテナンスおじさんとcrontabを設定しているserverがお亡くなりになった場合、再設定出来ない

解決方法

  1. chefで管理する
  2. ansibleで管理する
  3. どこかにまとめておく
  4. whenever導入

等々があった訳ですが
1,2はほぼ同じでプロビジョニングツールで管理するやり方です。

  • crontab変更する為だけにプロビジョニングツールを走らせるのめんどくさい
  • recipeやplaybook変更したけど、すぐには反映したくないけどcrontabだけ変更したいってなったら面倒

3に関して結局メンテナンスおじさんが発生してしまうのを回避出来ない・・・
wheneverではdeploy時にcrontabを更新することが可能なので問題点を解決出来るはず!ということで導入を決定。
経験上crontabを変更するのはdeployのタイミングがほとんどで緊急で編集したいということもほとんどない

gem install

環境は下記

導入自体は結構簡単

Gemfileに下記を追記でbundle install

+ gem 'whenever', require: false

もしくはgem install

$ gem install whenever

設定をcrontabの更新

$ cd rails_root
$ wheneverize . # もしくはbundle exec wheneverize .

これでconfig/schedule.rbというファイルが作成されます。
コメントアウトにてExampleが書いてあります。

schedule.rbを編集
※サンプルなので仮のrake taskを指定しています

# 出力先logの指定
set :output, 'log/crontab.log'
# 実行環境の指定
set :environment, :production

# 1時間毎に実行
every :hour do
  rake 'rake:task1'
end

# 1時間毎に実行
every 1.hour do
  rake 'rake:task2'
end

# 3分毎に実行(crontabと記述方法を合わせる)
every '*/3 * * * *' do
  rake 'rake:task3'
end

crontabを更新してみる

$ bundle exec whenever --update-crontab
# ここでinvalid byte sequence in US-ASCII (ArgumentError)が出たら
$ export LC_ALL=en_US.UTF-8
$ export LANG=en_US.UTF-8
$ bundle exec whenever --update-crontab
$ crontab -l
# Begin Whenever generated tasks for: rails_root/config/schedule.rb
0 * * * * /bin/bash -l -c 'cd rails_root && RAILS_ENV=production bundle exec rake rake:task1 --silent >> log/crontab.log 2>&1'

0 0,2,4,6,8,10,12,14,16,18,20,22 * * * /bin/bash -l -c 'cd rails_root && RAILS_ENV=production bundle exec rake rake:task2 --silent >> log/crontab.log 2>&1'

*/3 * * * * /bin/bash -l -c 'cd rails_root && RAILS_ENV=production bundle exec rake rake:task3 --silent >> log/crontab.log 2>&1'

# End Whenever generated tasks for: rails_root/config/schedule.rb

以上で簡単な設定と使い方はおしまい。
今回はrake taskを指定していますが、runner,commandといったものの指定も可能です。

deploy時に更新する

次にdeploy時にcrontabを更新したいので
whenever/capistranoの設定を行います。

Capfile,deploy.rb等に下記を追記

+ require 'whenever/capistrano'

その他のオプションを設定
追記場所は各自のcapistranoの設定に合わせて行ってください。

+ set :whenever_roles, :batch # 対象のロールを指定
+ set :whenever_environment, :production # 対象の環境を指定

これで対象のロールをdeployすればcrontabが更新されるはずです。
whenever/capistranoの詳細な設定周りは下記を参照してください。
whenever/lib/whenever/capistrano at master · javan/whenever · GitHub

環境毎にcrontabの設定を変更したり

実際に導入するにあたり環境毎に一部のrake taskを実行したくない環境とかもあったので
下記のようにしています。

schedule.rb

# 事故防止の為RAILS_ENVの指定が無い場合にはdevelopmentを使用する
rails_env = ENV['RAILS_ENV'] || :development

set :environment, rails_env

set :output, 'log/crontab.log'

# 全環境での設定


if rails_env.to_sym == :production
  # production環境のみで設定
end

capistranoの設定

+ set :whenever_roles, :batch
+ set :whenever_environment, :production
+ set :whenever_command, "RAILS_ENV=#{whenever_environment} bundle exec whenever  --update-crontab"
# もしくは
+ set :whenever_command, "RAILS_ENV=#{fetch(:whenever_environment)} bundle exec whenever --update-crontab"

こんな感じに設定しておけば環境毎にcrontabの内容を変更することも可能です。
※サンプルのcapistranoに関しては--dry-runオプションで確認しただけになります

まとめ

wheneverの導入によりcrontabメンテナンスおじさん(基本的に自分)の排除に成功しました。
wheneverでの時間指定の記述方法はいくつかありますが、
自分はcrontabと比較した時に分かりやすいのと○時間毎とか設定した場合に冗長になるのが嫌だったので
crontabと同じ記述方法を採用しています。
その他の細かい設定はwheneverのREADMEに載っていますのでご確認ください。

今回のサンプルは下記で公開してます。
shiro16/blog-samples · GitHub