docker上のrails7でwhenever

# 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の方が優先される

https://sleepless-se.net/2018/05/26/docker%E3%81%A7%E8%A4%87%E6%95%B0cmd%E3%82%92%E5%AE%9F%E8%A1%8C%E3%81%99%E3%82%8B%E6%96%B9%E6%B3%95/

# docker上でwheneverを動かす
### とりあえずここから
- dockerでrails7が起動するところまで

https://qiita.com/devzooiiooz/items/bd58b1c3178341fa363f

#### 動作確認のためのscaffoldを実行
```sh:
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して確認
```sh:
docker compose up -d
```
http://localhost:3000/cronchecks
(確認終わったらcompose down)
# rake taskを作成
　DBにチェック時刻を追加するだけの処理
- batchという名前でrake taskを作成する
```sh:
docker compose run --rm --no-deps webapp rails g task batch
```
lib/tasks/batch.rakeができるので編集

```ruby: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
```sh:
docker compose run --rm webapp rails batch:test
```

# wheneverを追加
railsapp/Gemfileを編集してwheneverを追加（ルートにあるGemfileではない）
```ruby:railsapp/Gemfile
# cron
gem 'whenever', require: false
```
require: falseについては以下

https://qiita.com/hryau6/items/c327d444fd364a8a1c9b

webappコンテナのみを起動させ、bundle install
```sh:
docker compose run --rm --no-deps webapp bundle install
```
# whenever初期化
./config/schedule.rbを作成
```sh:
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分毎に動かす設定を書く。
```sh:./config/schedule.rb
# 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に追加
```sh: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]はこれで正常
```sh:
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の編集に必要。
```sh:Dockerfile
RUN apt update -qq \
 && apt install -y build-essential mariadb-client nodejs \
 && apt install -y cron nano \
 && npm install --global yarn
```
#### build
```sh:
docker compose build
```
### cronがインストールされたか確認
```sh:
docker compose run --rm --no-deps webapp which cron
/usr/sbin/cron
```
#### この時点でdocker runしてもcronは起動していない。
```sh:
docker compose run --rm --no-deps webapp service cron status
cron is not running ... failed!
```
### startup.shを作成
```sh:
touch startup.sh
```
### startup.shを編集
startup.shの中で
1. wheneverの設定とcronの起動
2. ./bin/devの実行（puma httpdの起動)
```sh:
#!/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
```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が優先されるため
```yaml:compose.yaml
    #command: bash -c "rm -f tmp/pids/server.pid && ./bin/dev"
```
## 再ビルド
```sh:
docker compose build
```
# 晴れてdocker compose up
```sh:
docker compose up -d
```

## 確認
- crontabがserviceで動いているか
cron is runnning.が返ってくればOK。
- cron is not running ... failed!だと起動に失敗している
```sh:
docker compose exec webapp service cron status
cron is running.
```
- whenever --update-crontabが動いているか
```sh:
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
を１分ごとにリロードすると、登録されたデータが増えていることでwheneverが動いていることが確認できる。

## おまけ
- cronから削除
※当たり前だけどバッチ処理はrails webサーバとは関係なく動くので
開発中に止めたい場合は以下のコマンドでcronから削除する

```sh:
docker compose run --rm --no-deps webapp bundle exec whenever --clear-crontab
```
#### 参考
+ CMDとentrypoint

https://tech.enechange.co.jp/entry/2022/10/25/100000