Herokuのappを二つ使って交互起動で常時運用(Rack版)

この記事は2016/5/31までの内容です。



Herokuのappを二つ使って交互起動で常時運用(Sinatra版) - 別館 子子子子子子(ねこのここねこ)
では Sinatra で交互起動させましたが、大したこともしてないので Sinatra を動かすほどでもない気がしてきました。ということで Rack で同様のことをするスクリプトを書きました。

config.ru

先の投稿では alternateapps.rb というスクリプト名にしましたが、今回は Rack アプリケーションなので config.ru にしました。 rackup で起動します。
もしも通常の Ruby スクリプトとして使いたい場合には Sinatra学習の基礎としてRackを学ぶ - 別館 子子子子子子(ねこのここねこ) の[ruckup を使わず ruby スクリプトとして起動]を見てください。
なお、Heroku のデフォルトサーバアプリである WEBrick だと Heroku からの TERM シグナルに対してエラーメッセージ(FATAL SignalException: SIGTERM)が出るので、エラー回避をするために trap(:TERM) 節を入れています。

#!/usr/bin/env ruby
# coding: utf-8

require 'rack'
require 'dotenv'

require 'open-uri'
require 'json'

$stdout.sync = true
$update = nil
Dotenv.load

trap(:TERM) do 
  Rack::Handler::WEBrick.shutdown
end

class HerokuAlternate
  def call(env)
    request = Rack::Request.new(env)
    responce = case request.path_info
    when "/update"
      $update = true
      puts "Recieve access from another app. Stop the cron processes."
      body = JSON.dump({status: 'Update'})
      Rack::Response.new(body, 200, 'Content-Type' => 'text/plain')
    else
      Rack::Response.new("Not Found", 404, 'Content-Type' => 'text/plain')
    end
    responce.finish
  end
end

certs =  [ENV['BASIC_AUTH_USERNAME'], ENV['BASIC_AUTH_PASSWORD']]
begin
  JSON.parse(open(ENV['ANOTHER_SITE_URL'], {:http_basic_authentication => certs}).read)
rescue Errno::ECONNREFUSED, OpenURI::HTTPError
  puts "Another app does not wake."
end
require './schedule' #ここで通常のアプリ(ただしjoinしてないもの)を読み込む

use Rack::Auth::Basic do |username, password|
  username == ENV['BASIC_AUTH_USERNAME'] && password == ENV['BASIC_AUTH_PASSWORD']
end
run HerokuAlternate.new

# rackup config.ru -p 4567 -s webrick

相手からの通信(アクセス)を /update だけにしていますが、その他へのアクセスで機能を追加したい場合には case 節に追加してください。

Procfile

web: bundle exec rackup config.ru -p $PORT

参考

Heroku 24x7 対策 個人的変更手順:

  • 相手側 app を Heroku に作成(app 名は testapp に対して testapp2 など単純に&リモートレポジトリ名は heroku2 に)
$ heroku create testapp2 --remote heroku2
$ heroku config:set TZ=Asia/Tokyo --remote heroku2
$ heroku addons:create papertrail --remote heroku2 (また papertrail 管理画面で Timezone を GMT+09:00 に)
    • 各種環境定数を元の app (heroku config --remote heroku の出力結果)と同様に設定(heroku config:set …
  • config.ru 新規作成
  • Gemfile 変更
    • gem 'rack' を追加
    • bundle update 実行
  • Procfile 変更
    • web: bundle exec rackup config.ru -p $PORT に変更
  • 各種環境定数設定
    • BASIC_AUTH_USERNAME
    • BASIC_AUTH_PASSWORD
    • ANOTHER_SITE_URL(それぞれ適切に:https://testapp2.herokuapp.com/update
    • MY_SITE_WAKE_URL(それぞれ適切に:https://testapp.herokuapp.com/
$ heroku config:set BASIC_AUTH_USERNAME=username BASIC_AUTH_PASSWORD=password ANOTHER_SITE_URL=https://testapp2.herokuapp.com/update MY_SITE_WAKE_URL=https://testapp.herokuapp.com/ --remote heroku
$ heroku config:set BASIC_AUTH_USERNAME=username BASIC_AUTH_PASSWORD=password ANOTHER_SITE_URL=https://testapp.herokuapp.com/update MY_SITE_WAKE_URL=https://testapp2.herokuapp.com/ --remote heroku2
  • 元のスクリプトを変更
    • join 削除
    • 自己サイトへの定期アクセス追加
require 'rufus-scheduler'
require 'open-uri'
 :
scheduler = Rufus::Scheduler.new
scheduler.every '15m' do
  begin
    open(ENV['MY_SITE_WAKE_URL']).read
  rescue OpenURI::HTTPError => ex
    puts 'wake up access.'
  end
end
    • 相手側 app からアクセスを受けたら $updatetrue になるので、各定期作業(scheduler.cron)節の冒頭に次を追加
scheduler.cron '' do
  next if $update
  • git commit する
  • git push heroku master && git push heroku2 master