Heroku+Sidekiq-cronを活用してHerokuを眠らせない方法

# local環境
macOS Sierra 10.12.6
ruby 2.5.3p105 (2018-10-18 revision 65156) [x86_64-darwin15]
Rails 5.2.1

# prod環境
Heroku

# 背景
HerokuのFreeプラン利用してます。1ヶ月1000h稼働できるはずだけど（クレカ情報を登録済）、30分何もアクセス無いとSleepしてしまいます。
定期的に何かを公開環境で簡易的に実行したい場合に不便。ということでHerokuに定期的にHerokuから通信をしてみることでSleepしないようにしてみたい。
自分用メモですが便利そうなので公開しました。


# project作成
プロジェクトでsidekiq-cronを利用する機会が多いのでRailsを選びました。
app_nameは任意で

```
% rails new app_name
% cd app_name
```

# Sidekiqを使うためのcode修正
## Gemfile
以下を追記

```gem:GemFile
gem 'sidekiq'
```

## routes.rb
Sidekiqの管理画面を操作するために、以下のようにする

```ruby:config/routes.rb
require 'sidekiq/web'
Rails.application.routes.draw do
  # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
  mount Sidekiq::Web => '/sidekiq'
end
```

# bundle install

```
% bundle install
```

# redis起動
このままrails sしても
`Error connecting to Redis on 127.0.0.1:6379 (Errno::ECONNREFUSED)`

が発生してしまいますので、redisを起動します。

```
% redis-server&
```

redisがinstallされていなければ、 `brew install redis` しておく。

# rails sして、Pumaを起動
```
% rails s
=> Booting Puma
=> Rails 5.2.1 application starting in development
=> Run `rails server -h` for more startup options
Puma starting in single mode...
* Version 3.12.0 (ruby 2.5.3-p105), codename: Llamas in Pajamas
* Min threads: 5, max threads: 5
* Environment: development
* Listening on tcp://0.0.0.0:3000
```


# loacl環境でsidekiqの動作確認
以下へアクセスする
http://localhost:3000/sidekiq

こんな感じで表示される
<img width="1336" alt="Sidekiq管理画面.jpg" src="https://qiita-image-store.s3.amazonaws.com/0/120760/58ad2031-ce50-f08a-0359-cd34965fdc66.jpeg">


# sidekiq-cron の設定
https://github.com/ondrejbartas/sidekiq-cron
を見ながらシコシコ頑張る

## Gemfile
以下を追記

```gem:GemFile
gem 'sidekiq-cron'
```

## workersを作る
```
mkdir app/workers
```

## Workerを作る

```ruby:app/workers/ping_worker.rb
require 'net/http'
class PingWorker
  include Sidekiq::Worker

  sidekiq_options queue: :ping

  def perform(*args)
    # 通信したい先のherokuのURLを指定します
    uri = URI.parse("https://xxxxx.herokuapp.com/xxxxx")
    http = Net::HTTP.new(uri.host, uri.port)
    http.use_ssl = true
    p http.get(uri.request_uri)
  end
end
```
**注意：ここでqueue: に指定した文字列は他と必ず一致させること**


## スケジュール登録してみる
この例では1分毎実行としましたが、実際は20分毎でよい。

```yaml:config/config/schedule.yml
my_first_job:
  cron: "*/1 * * * *"
  class: "PingWorker"
  queue: ping
```

**注意：ここでqueue: に指定した文字列は他と必ず一致させること**


## schedule.ymlを読み込む為の設定
```ruby:config/initializers/sidekiq.rb
schedule_file = "config/schedule.yml"

if File.exist?(schedule_file) && Sidekiq.server?
  Sidekiq::Cron::Job.load_from_hash YAML.load_file(schedule_file)
end
```

## sidekiq/cron/webを requireする
```ruby:config/routes.rb
require 'sidekiq/web'
require 'sidekiq/cron/web'
Rails.application.routes.draw do
  # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
  mount Sidekiq::Web => '/sidekiq'
end
```

## sidekiq.ymlにworkerをセット
```yaml:config/sidekiq.yml
:verbose: false
:pidfile: ./tmp/pids/sidekiq.pid
:logfile: ./log/sidekiq.log
:concurrency:  25
:queues:
  - default
  - ping
```

**注意：ここでqueuesに指定した文字列は他と必ず一致させること**


## bundle install
`bundle install`

## sidekiqを起動する
```
% bundle exec sidekiq -C config/sidekiq.yml
         m,
         `$b
    .ss,  $$:         .,d$
    `$$P,d$P'    .,md$P"'
     ,$$$$$bmmd$$$P^'
   .d$$$$$$$$$$P'
   $$^' `"^$$$'       ____  _     _      _    _
   $:     ,$$:       / ___|(_) __| | ___| | _(_) __ _
   `b     :$$        \___ \| |/ _` |/ _ \ |/ / |/ _` |
          $$:         ___) | | (_| |  __/   <| | (_| |
          $$         |____/|_|\__,_|\___|_|\_\_|\__, |
        .d$$
```



# local環境でsidekiq-cronの動作確認
puma起動してから以下へアクセスする
http://localhost:3000/sidekiq

以下のようにメニューにCronが表示される！
<img width="746" alt="cronメニューがでた_吹き出しつき.jpg" src="https://qiita-image-store.s3.amazonaws.com/0/120760/aed6ecb0-fb0d-ec02-da80-f0dc45fc92c8.jpeg">


そして Cron を押すとジョブがある！
<img width="1180" alt="cronにジョブが見える.jpg" src="https://qiita-image-store.s3.amazonaws.com/0/120760/48ad9b7f-65c1-c2da-ca95-25810c69be13.jpeg">

sidekiqを起動したコンソールではこのように表示されます

```
   $$^' `"^$$$'       ____  _     _      _    _
   $:     ,$$:       / ___|(_) __| | ___| | _(_) __ _
   `b     :$$        \___ \| |/ _` |/ _ \ |/ / |/ _` |
          $$:         ___) | | (_| |  __/   <| | (_| |
          $$         |____/|_|\__,_|\___|_|\_\_|\__, |
        .d$$                                       |_|

#<Net::HTTPOK 200 OK readbody=true>
#<Net::HTTPOK 200 OK readbody=true>
：
：
```

正しく動いていると以下のように完了が増えていきます。
<img width="452" alt="成功の数.jpg" src="https://qiita-image-store.s3.amazonaws.com/0/120760/f07d752b-dacb-fcd1-df56-50b7c3454fb0.jpeg">


# herokuで実行する
local環境で動作確認ができたのでherokuにアップします。

## gitへのcommit

```sh
% git add -A
% git commit -m "First Commit."
```

## sqlite3はherokuアップ時にエラーになるので事前にGemfile修正

コメントアウトする

```makefile:Gemfile
#gem 'sqlite3'
```


そして、`bundle install` を実施する

## herokuにpushする

```sh
# APPNAMEは自身のheroku app名を指定する
% heroku login
% # 既に作ってしまっている場合は以下コマンド heroku create APPNAME
% heroku git:remote --app APPNAME
% git push heroku master
Counting objects: 124, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (108/108), done.
Writing objects: 100% (124/124), 29.05 KiB | 0 bytes/s, done.
Total 124 (delta 6), reused 0 (delta 0)
remote: Compressing source files... done.
remote: Building source:
：
 https://APPNAME.herokuapp.com/ deployed to Heroku
```

**ただここでredisが無かったり、sidekiqが動かなかったりで苦戦**

以下苦戦した後の対処です。


## herokuのredis設定が必要なので設定

### plugin install

```
heroku plugins:install heroku-redis
```

### data.heroku.comにてinstall

以下へアクセスして、heroku-redisをappへinstallする
https://data.heroku.com/


### configを確認

REDIS_URLが表示される

```
% heroku config -a APPNAME | grep REDIS_URL
8:REDIS_URL:                redis://xxxxxxxxxx
```

### initializersで確認したREDIS_URLを指定


```ruby:config/initializers/sidekiq.rb
schedule_file = "config/schedule.yml"

Sidekiq.configure_server do |config|
  if Rails.env.production?
    config.redis = { url: ENV.fetch('REDIS_URL') }
  end
end
Sidekiq.configure_client do |config|
  if Rails.env.production?
    config.redis = { url: ENV.fetch('REDIS_URL') }
  end
end

if File.exist?(schedule_file) && Sidekiq.server?
  Sidekiq::Cron::Job.load_from_hash YAML.load_file(schedule_file)
end
```

## sidekiqは単純には動かないのでunicornを利用

以下を参考にさせていただきました。 :bow:
３年以上前の記事ですが大変参考になりました！

http://cflat-inc.hatenablog.com/entry/2014/12/22/075907

### Gemfile

```makefile:Gemfile
gem 'unicorn'
```

### Procfile

```makefile:Procfile
web: bundle exec unicorn -p $PORT -c ./config/unicorn.rb
```

### config/unicorn.rb

```ruby:config/unicorn.rb
timeout 15
preload_app true

worker_processes 3

# whatever you had in your unicorn.rb file
@sidekiq_pid = nil

before_fork do |server, worker|
  Signal.trap 'TERM' do
    puts 'Unicorn master intercepting TERM and sending myself QUIT instead'
    Process.kill 'QUIT', Process.pid
  end

  if Rails.env.production?
    # tmpフォルダは自分で作らないとherokuは作ってくれません
    spawn("mkdir -p tmp/pids")
    @sidekiq_pid ||= spawn("bundle exec sidekiq -C config/sidekiq.yml")
    Rails.logger.info('Spawned sidekiq #{@sidekiq_pid}')
  end
end


after_fork do |server, worker|
  Signal.trap 'TERM' do
    puts 'Unicorn worker intercepting TERM and doing nothing. Wait for master to send QUIT'
  end

end
```

### sidekiqのconcurrency は小さめにする

これが大きいと、

`Redis::CommandError (ERR max number of clients reached):`

が発生します。

```yaml:config/sidekiq.yml
:concurrency:  2
```

### 再度 heroku へpush

```
git add -A
git commit -m "push heroku!"
git push heroku master
```


### heroku ログ確認

```
% heroku logs --tail

2018-11-01T07:59:55.019594+00:00 app[web.1]: I, [2018-11-01T07:59:55.019486 #4]  INFO -- : reaped #<Process::Status: pid 11 exit 0> worker=0
2018-11-01T07:59:55.019726+00:00 app[web.1]: I, [2018-11-01T07:59:55.019681 #4]  INFO -- : reaped #<Process::Status: pid 16 exit 0> worker=1
2018-11-01T07:59:55.019844+00:00 app[web.1]: I, [2018-11-01T07:59:55.019799 #4]  INFO -- : reaped #<Process::Status: pid 20 exit 0> worker=2
2018-11-01T07:59:55.019939+00:00 app[web.1]: I, [2018-11-01T07:59:55.019890 #4]  INFO -- : master complete
2018-11-01T07:59:55.182263+00:00 heroku[web.1]: Process exited with status 0
2018-11-01T07:59:56.000000+00:00 app[api]: Build succeeded
2018-11-01T07:59:59.734590+00:00 heroku[web.1]: Starting process with command `bundle exec unicorn -p 55787 -c ./config/unicorn.rb`
2018-11-01T08:00:04.109925+00:00 app[web.1]: I, [2018-11-01T08:00:04.109783 #4]  INFO -- : Refreshing Gem list
2018-11-01T08:00:12.210910+00:00 app[web.1]: I, [2018-11-01T08:00:12.210791 #4]  INFO -- : listening on addr=0.0.0.0:55787 fd=9
2018-11-01T08:00:12.233668+00:00 app[web.1]: I, [2018-11-01T08:00:12.233473 #4]  INFO -- : Spawned sidekiq #{@sidekiq_pid}
2018-11-01T08:00:12.240924+00:00 app[web.1]: I, [2018-11-01T08:00:12.240706 #4]  INFO -- : Spawned sidekiq #{@sidekiq_pid}
2018-11-01T08:00:12.247828+00:00 app[web.1]: I, [2018-11-01T08:00:12.247086 #11]  INFO -- : worker=0 ready
2018-11-01T08:00:12.248878+00:00 app[web.1]: I, [2018-11-01T08:00:12.248768 #4]  INFO -- : Spawned sidekiq #{@sidekiq_pid}
2018-11-01T08:00:12.257032+00:00 app[web.1]: I, [2018-11-01T08:00:12.256900 #4]  INFO -- : master process ready
2018-11-01T08:00:12.257692+00:00 app[web.1]: I, [2018-11-01T08:00:12.257631 #4]  INFO -- : reaped #<Process::Status: pid 9 exit 0> worker=unknown
2018-11-01T08:00:12.257880+00:00 app[web.1]: I, [2018-11-01T08:00:12.257807 #4]  INFO -- : reaped #<Process::Status: pid 13 exit 0> worker=unknown
2018-11-01T08:00:12.260889+00:00 app[web.1]: I, [2018-11-01T08:00:12.260717 #15]  INFO -- : worker=1 ready
2018-11-01T08:00:12.280155+00:00 app[web.1]: I, [2018-11-01T08:00:12.280036 #4]  INFO -- : reaped #<Process::Status: pid 17 exit 0> worker=unknown
2018-11-01T08:00:12.291409+00:00 app[web.1]: I, [2018-11-01T08:00:12.291227 #19]  INFO -- : worker=2 ready
2018-11-01T08:00:12.703542+00:00 heroku[web.1]: State changed from starting to up
2018-11-01T08:00:28.187287+00:00 app[web.1]: #<Net::HTTPOK 200 OK readbody=true>
：
：
```


# ブラウザでherokuにアクセスして最終チェック

**動いてる！感動！** :heart_decoration: 

<img width="1124" alt="herokuで動いた！感動.jpg" src="https://qiita-image-store.s3.amazonaws.com/0/120760/af75b47f-8b97-41d2-9b4c-f7b295621aff.jpeg">

対象のherokuへも通信はきちんと送られていたのでこれでやりたいことは一旦できました。
これで通信先増やせば対象Herokuは眠らなくなるはずです。

仕事ではマネージメントばかりで全くエンジニアっぽいことしていなかったのですが、ちょっとエンジニアっぽいことができて楽しかった :heart_eyes:
特にheroku楽しいですね！便利で最高 :heart_eyes_cat: 


# 備考

sidekiqって結構前から停滞中だったんですね。知りませんでした。
https://qiita.com/k5trismegistus/items/9fa92bcf24fce7681ee5#no-longer-maintained