LoginSignup
12
7

More than 5 years have passed since last update.

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

Posted at

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

以下を追記

GemFile
gem 'sidekiq'

routes.rb

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

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

こんな感じで表示される
Sidekiq管理画面.jpg

sidekiq-cron の設定

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

Gemfile

以下を追記

GemFile
gem 'sidekiq-cron'

workersを作る

mkdir app/workers

Workerを作る

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分毎でよい。

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

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

schedule.ymlを読み込む為の設定

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する

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をセット

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が表示される!
cronメニューがでた_吹き出しつき.jpg

そして Cron を押すとジョブがある!
cronにジョブが見える.jpg

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

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

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

正しく動いていると以下のように完了が増えていきます。
成功の数.jpg

herokuで実行する

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

gitへのcommit

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

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

コメントアウトする

Gemfile
#gem 'sqlite3'

そして、bundle install を実施する

herokuにpushする

# 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を指定

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:
3年以上前の記事ですが大変参考になりました!

Gemfile

Gemfile
gem 'unicorn'

Procfile

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

config/unicorn.rb

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):

が発生します。

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:

herokuで動いた!感動.jpg

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

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

備考

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

12
7
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
7