wheneverをdockerで動かすには
- rails taskをcronに登録してくれる便利なgem、whenever
- rails taskはActiverecordを使えて便利
- dockerは原則1コンテナ-1daemon
- railsコンテナではpumaでhttpをdaemon化するため、cronをコマンドで常駐できない
- そのためdockerの起動方法を工夫する
dockerのCMD注意点
- DockerfileにCMDを複数行記述しても最後の1行しか実行されない
- docker composeの場合、compose.yamlのcommandの方が優先される
docker上でwheneverを動かす
とりあえずここから
- dockerでrails7が起動するところまで
動作確認のためのscaffoldを実行
docker compose run --rm --no-deps webapp rails g scaffold croncheck check_at:datetime
migrate
docker compose run --rm webapp rails db:migrate
一旦compose upして確認
docker compose up -d
http://localhost:3000/cronchecks
(確認終わったらcompose down)
rake taskを作成
DBにチェック時刻を追加するだけの処理
- batchという名前でrake taskを作成する
docker compose run --rm --no-deps webapp rails g task batch
lib/tasks/batch.rakeができるので編集
namespace :batch do
desc "batch:test"
task :test => :environment do
Croncheck.new(check_at: Time.now).save
end
end
動作確認
エラーが出なければOK
DBに書き込むため、--no-depsは付けないでrun
docker compose run --rm webapp rails batch:test
wheneverを追加
railsapp/Gemfileを編集してwheneverを追加(ルートにあるGemfileではない)
# cron
gem 'whenever', require: false
require: falseについては以下
webappコンテナのみを起動させ、bundle install
docker compose run --rm --no-deps webapp bundle install
whenever初期化
./config/schedule.rbを作成
docker compose run --rm --no-deps webapp bundle exec wheneverize .
[add] writing `./config/schedule.rb'
[done] wheneverized!
wheneverにrake taskを登録
./config/schedule.rbを編集し、先ほどのrake taskを1分毎に動かす設定を書く。
# Use this file to easily define all of your cron jobs.
#
# It's helpful, but not entirely necessary to understand cron before proceeding.
# http://en.wikipedia.org/wiki/Cron
# Example:
#
# set :output, "/path/to/my/cron_log.log"
#
# Rails.rootを使用するために必要
require File.expand_path(File.dirname(__FILE__) + "/environment")
# cronを実行する環境変数(:developmentを初期値とする)
rails_env = ENV['RAILS_ENV'] || :development
# cronを実行する環境変数をセット
set :environment, rails_env
ENV.each { |k,v| env(k,v) } #これが超大事
# cronの標準出力先(必ずしもlogでなくても良い)
set :output, "#{Rails.root}/log/cron.log"
# 1分毎に回す
every 1.minute do
rake "batch:test" #rakeの場合
end
config/application.rbに追加
class Application < Rails::Application
#wheneverのタスクはlibの中に置く
config.autoload_paths += Dir["#{config.root}/lib/**/"] #lib配下でディレクトリ分けする場合
#本番はeagar_load
config.eager_load_paths += Dir["#{config.root}/lib/**/"]
whenever確認コマンド(cronに登録はされない)
※[message]はこれで正常
docker compose run --rm --no-deps webapp bundle exec whenever
* * * * * /bin/bash -l -c 'cd /railsapp && RAILS_ENV=development bundle exec rake batch:test --silent >> /railsapp/log/cron.log 2>&1'
## [message] Above is your schedule file converted to cron syntax; your crontab file was not updated.
## [message] Run `whenever --help' for more options.
cronに登録
ここからcronに登録するが、いきなりwhenever --update-crontabをするとエラーになる。docker image自体にcronがインストールされていないため。
cronのインストール
Dockefileに以下を追記(RUNのところ)し、buildする。
cronとnanoがインストールされる。
nanoはcrontabの編集に必要。
RUN apt update -qq \
&& apt install -y build-essential mariadb-client nodejs \
&& apt install -y cron nano \
&& npm install --global yarn
build
docker compose build
cronがインストールされたか確認
docker compose run --rm --no-deps webapp which cron
/usr/sbin/cron
この時点でdocker runしてもcronは起動していない。
docker compose run --rm --no-deps webapp service cron status
cron is not running ... failed!
startup.shを作成
touch startup.sh
startup.shを編集
startup.shの中で
- wheneverの設定とcronの起動
- ./bin/devの実行(puma httpdの起動)
#!/usr/bin/env bash
cd $APP
bundle exec whenever --update-crontab
cron -f
# service cron startだと起動しないことがあった
rm -f tmp/pids/server.pid
./bin/dev
Dockerfileでentrypoint.shを起動
最終的なDockerfile
FROM ruby:3.2.0
ENV APP /railsapp
ENV LANG C.UTF-8
ENV TZ Asia/Tokyo
RUN curl -fsSL https://deb.nodesource.com/setup_lts.x | bash -
RUN apt update -qq \
&& apt install -y build-essential mariadb-client nodejs \
&& apt install -y cron nano \
&& npm install --global yarn
RUN yarn add @fortawesome/fontawesome-free @fortawesome/fontawesome-svg-core @fortawesome/free-brands-svg-icons @fortawesome/free-regular-svg-icons @fortawesome/free-solid-svg-icons
WORKDIR $APP
COPY Gemfile $APP/Gemfile
COPY Gemfile.lock $APP/Gemfile.lock
RUN bundle install
ADD startup.sh /
RUN chmod +x /startup.sh
CMD ["/startup.sh"]
compose.yamlのcommandをコメントアウトする
DockerfileのCMDよりもcompose.yamlのcommandが優先されるため
#command: bash -c "rm -f tmp/pids/server.pid && ./bin/dev"
再ビルド
docker compose build
晴れてdocker compose up
docker compose up -d
確認
- crontabがserviceで動いているか
cron is runnning.が返ってくればOK。 - cron is not running ... failed!だと起動に失敗している
docker compose exec webapp service cron status
cron is running.
- whenever --update-crontabが動いているか
docker compose exec webapp crontab -l
#環境変数がずらずらっと
* * * * * /bin/bash -l -c 'cd /railsapp && RAILS_ENV=development bundle exec rake batch:test --silent >> /railsapp/log/cron.log 2>&1'
# End Whenever generated tasks for: /railsapp/config/schedule.rb at: 2024-02-14 22:21:19 +0900
確認がOKだと
http://localhost:3000/cronchecks
を1分ごとにリロードすると、登録されたデータが増えていることでwheneverが動いていることが確認できる。
おまけ
- cronから削除
※当たり前だけどバッチ処理はrails webサーバとは関係なく動くので
開発中に止めたい場合は以下のコマンドでcronから削除する
docker compose run --rm --no-deps webapp bundle exec whenever --clear-crontab
参考
- CMDとentrypoint