Whenever 使用教程

Whenever 是一个 Ruby gem,它提供了清晰的语法用于编写和部署 cron jobs。本文翻译自它的 Github:https://github.com/javan/when...

安装

$ gem install whenever

或者写到 Gemfile,配合Bundle:

gem 'whenever', require: false

开始

$ cd /apps/my-great-project
$ bundle exec wheneverize .

为你创建初始化配置文件 config/schedule.rb (只要你的目录下有 config 文件夹)。

whenever 命令

whenever 命令只是将 schedule.rb 文件的内容输出为 cron 语法结构,并不会读取和写入你的 crontab 文件。

$ cd /apps/my-great-project
$ bundle exec whenever

如果要将这些任务写入到 crontab 文件中去,带参数执行命令:

$ whenever --update-crontab

其他常用命令:

$ whenever --user app # set a user as which to install the crontab
$ whenever --load-file config/my_schedule.rb # set the schedule file
$ whenever --crontab-command 'sudo crontab' # override the crontab command
提示:如果你运行 whenever --update-crontab 没有附带 --user 选项,所生成的 cron 将只能被当前用户执行。这意味着如果你所执行的任务需要其他用户的权限,将失败。

可以使用 crontab -l 列出当前当前系统的任务列表。

运行 whenever --help,输出一些用于调度的选项、调度变量以及其他内容。

schedule.rb 文件例子

every 3.hours do # 1.minute 1.day 1.week 1.month 1.year is also supported
  # the following tasks are run in parallel (not in sequence)
  runner "MyModel.some_process"
  rake "my:rake:task"
  command "/usr/bin/my_great_command"
end

every 1.day, at: '4:30 am' do
  runner "MyModel.task_to_run_at_four_thirty_in_the_morning"
end

every 1.day, at: ['4:30 am', '6:00 pm'] do
  runner "Mymodel.task_to_run_in_two_times_every_day"
end

every :hour do # Many shortcuts available: :hour, :day, :month, :year, :reboot
  runner "SomeModel.ladeeda"
end

every :sunday, at: '12pm' do # Use any day of the week or :weekend, :weekday
  runner "Task.do_something_great"
end

every '0 0 27-31 * *' do
  command "echo 'you can use raw cron syntax too'"
end

# run this task only on servers with the :app role in Capistrano
# see Capistrano roles section below
every :day, at: '12:20am', roles: [:app] do
  rake "app_server:task"
end

定义自己的工作类型

Whenever 内置三种类型的工作:command,runner 以及 rake。你可以通过定义job_type,来定义你自己的工作类型。

举个例子:

job_type :awesome, '/usr/local/bin/awesome :task :fun_level'

every 2.hours do
  awesome "party", fun_level: "extreme"
end

这段例子的意思是,每两个小时,执行一次 /usr/local/bin/awesome party extreme:task 总是作为第一个参数,额外的参数被替换其他传递的内容,或者使用 set 定义的变量。

whenever 中,默认的工作类型定义如下:

job_type :command, ":task :output"
job_type :rake,    "cd :path && :environment_variable=:environment bundle exec rake :task --silent :output"
job_type :runner,  "cd :path && bin/rails runner -e :environment ':task' :output"
job_type :script,  "cd :path && :environment_variable=:environment bundle exec script/:task :output"

Rails 3 之前的应用程序,和不使用 Bundler 的应用程序,将分别重新定义 rake 和runner,以使其正常运行。

如果 :path 没有设置,使用当前whenever执行的目录。:environment_variable 变量默认使用 RAILS_ENV:environment 默认设置为production:outpuot用于配置你的输出重定向,可以在(这里)[http://github.com/javan/whene...],了解到如何配置。

所有的工作默认通过 bash -l -c 'command' 来执行。这会使你的 cron job 加载整个环境变量,而不是 cron 执行的有限环境。这让你的 rvm 工作的更好。了解更多:http://github.com/javan/whene...

你可以通过定义 :job_template 来改变这一默认行为。

set :job_template, "bash -l -c ':job'"

设置 :job_setemplate 为空,来恢复标准行为:

set :job_template, nil

解析日期和时间

Whenever 使用 (Chronic)[https://github.com/mojombo/ch...] gem,来解析指定的日期和时间。

如果默认的 Chronic 配置不适合你,你可以自定义。

举一个例子,使用24小时制,来替换默认的12小时制:

set :chronic_options, hours24: true

# By default this would run the job every day at 3am
every 1.day, at: '3:00' do
  runner "MyModel.nightly_archive_job"
end

可以在这里找到配置选项:
https://github.com/mojombo/chronic/blob/master/lib/chronic/parser.rb

通过 MAILTO 环境变量,自定义 email 接收对象

工作的结果,可以输出到电子邮件地址,通过 MAILTO 环境变量来配置。

多种方式可以配置邮件接受地址:

全局配置例子:

env 'MAILTO', 'output_of_cron@example.com'

every 3.hours do
  command "/usr/bin/my_great_command"
end

只在一个代码块中生效的例子:

every 3.hours, mailto: 'my_super_command@example.com'  do
  command "/usr/bin/my_super_command"
end

只在一个工作中生效的例子:

every 3.hours do
  command "/usr/bin/my_super_command", mailto: 'my_super_command_output@example.com'
end

Capistrano 集成

使用内置的 Capistrano recipe,即可完成简单的 crontab 部署时更新。 Capistrano V3 的使用,将在下一节介绍。

打开 "config/deploy.rb" 文件:

require "whenever/capistrano"

在这里https://github.com/javan/whenever/blob/master/lib/whenever/capistrano/v2/recipes.rb 你可以找到 recipe 可以使用的配置项。比如你想使用 bundler:

set :whenever_command, "bundle exec whenever"
require "whenever/capistrano"

如果你想在不同的环境中使用(如 staging,production),你可以这么用:

set :whenever_environment, defer { stage }
require "whenever/capistrano"

capistrano 变量 :stage 保存着当前环境的名称。这将在 schedule.rb 文件中提供正确的 :environment。

如果你的两个环境,在同一台服务器上,你需要为他们提供命名空间,否则将在部署时相互覆盖:

set :whenever_environment, defer { stage }
set :whenever_identifier, defer { "#{application}_#{stage}" }
require "whenever/capistrano"

如果你要在别的路径开启调度,你需要这样配置:

set :whenever_load_file, defer { "#{release_path}/somewhere/else/schedule.rb" }
require "whenever/capistrano"

Capistrano V3 Integration

打开你的 "Capfile" 文件:

require "whenever/capistrano"

查看文件底部的 load:default task 以了解可以配置的选项。举个例子,crontab 的 application 或 stage 命名空间条目的行为,在 "config/deploy.rb" 文件中配置。

set :whenever_identifier, ->{ "#{fetch(:application)}_#{fetch(:stage)}" }

集成 Capistrano,默认期望 :application 变量被设置,以便在 crontab 中确定作业范围。

Capistrano 权限

关于权限,首先要知道的是它是完全可选的,以及向后兼容。如果你不需要 Crontabs 部署不同的工作,在不同的服务器上,你可以停止阅读,并放心的跳过这块的内容,一切都工作的很好。

当你在 schedule.rb 文件中定义工作的时候,默认它会被分发到所有在 whenever_roles 列表中的服务器。(该列表默认为 [:db]):

very :day, at: '12:20am' do
  rake 'foo:bar'
end

如果我们在 deploy.rb 中,设置 whenever_roles 为 [:db, :app],以及 schedule.rb 中加入工作:

every :day, at: '1:37pm', roles: [:app] do
  rake 'app:task' # will only be added to crontabs of :app servers
end

every :hour, roles: [:db] do
  rake 'db:task' # will only be added to crontabs of :db servers
end

every :day, at: '12:02am' do
  command "run_this_everywhere" # will be deployed to :db and :app servers
end

简单的规则:

  1. 如果 whenever_roles 中,未列出服务器的角色,则永远不会将作业添加到他的 crontab 中。
  2. 如果 whenever_roles 中,包含服务器的角色,那么将在其 crontab 中添加所有作业,如果包含 :roles 参数,这些作业将被列出。
  3. 如果作业有 :roles 参数,但角色不在 whenever_roles 列表中,则作业不会被部署到任何服务器上

RVM Integration

如果你的产品生产环境,使用 RVM(Ruby Version Manager),你将会遇到一个导致你的 cron job 挂起的陷阱。这与 whenever 并无直接关联,调试起来很麻烦。你的 .rvmrc 文件必须被可信,否则 cron jobs 将挂起等待该文件受信任。一个解决办法是,将这一行添加到 ~/.rvmrc 中,来禁止这个提示。

rvm_trust_rvmrcs_flag=1

这表示 rvm 将信任所有的 rvmrc 文件。

Heroku?

Heroku 不支持 cron,作为替代,提供了Heroku Scheduler。如果你要部署到 Herku,你要使用它,而不是 Whenever。

Testing

whenever-test 是一个 Whenever 的扩展,用于测试 Whenever schedule。

Credit

Whenever 的诞生,是为了使用 Inkling(http://inklingmarkets.com/)。可以在这里进行了解:http://blog.inklingmarkets.co...

谢谢所有贡献者,是他们让 Whenever 变得更好:http://github.com/javan/whene...

讨论/反馈/提问/Bugs

一般的讨论和问题,请访问 google group:http://groups.google.com/group/whenever-gem

如果你遇到大 bug 或者问题,可以使用 github 的 issue:http://github.com/javan/whene...

Ryan Bates 创建了一份很好的关于 Whenever 的 Railscast:http://railscasts.com/episode... 可能有些过时,但是个很好的开始。

阅读 8.5k

推荐阅读
胡说八道
用户专栏

一些 Web 开发方面的技术文章,有翻译,有原创

475 人关注
15 篇文章
专栏主页