Less is Best

rubyが好き。技術の話とスタートアップに興味があります。

Unicorn+Rails入門

Railsでよく使われているサーバーUnicornについて勉強しておこうと思います。以前Unicorn+Nginxで動かそうとしたらうまく動かせなかったので、とりあえずゼロからテストアプリ作って、Unicornで動かすまでを1つづつ勉強して仕組みを理解して行こうと思います。

rails new unicorn_sample
cd unicorn_sample
rails g scaffold article title:string content:string
rake db:migrate
vim config/routes.rb

routes.rb

+ root "articles#index"

で動くアプリを一応作成

Unicornを導入

Gemfile

+ gem 'unicorn'
bundle install
touch config/unicorn.rb

https://github.com/herokaijp/devcenter/wiki/Rails-unicorn を参考に

config/unicorn.rb

worker_processes Integer(ENV["WEB_CONCURRENCY"] || 3) # 子プロセスいくつ立ち上げるか
timeout 15 #15秒Railsが反応しなければWorkerをkillしてタイムアウト
preload_app true #後述

# 同一マシンでNginxとプロキシ組むならsocketのが高速ぽい(後述ベンチ)
# listen /path/to/rails/tmp/unicorn.sock
listen 3000

# pid file path Capistranoとか使う時は要設定か
# pid /path/to/rails/tmp/pids/unicorn.pid

# ログの設定方法.
#stderr_path File.expand_path('log/unicorn.log', ENV['RAILS_ROOT'])
#stdout_path File.expand_path('log/unicorn.log', ENV['RAILS_ROOT'])

before_fork do |server, worker|
  old_pid = "#{ server.config[:pid] }.oldbin"
  if File.exists?(old_pid) && server.pid != old_pid
    begin
      # 古いマスターがいたら死んでもらう
      Process.kill("QUIT", File.read(old_pid).to_i)
    rescue Errno::ENOENT, Errno::ESRCH
      # someone else did our job for us
    end
  end

  defined?(ActiveRecord::Base) and
    ActiveRecord::Base.connection.disconnect!
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

  defined?(ActiveRecord::Base) and
    ActiveRecord::Base.establish_connection
end

def rails_root
  require "pathname"
  Pathname.new(__FILE__) + "../../"
end

Rakefile

namespace :unicorn do

  ##
  # Tasks

  desc "Start unicorn"
  task(:start) {
    config = rails_root + "config/unicorn.rb"
    env = ENV['RAILS_ENV'] || "development"
    sh "bundle exec unicorn_rails -D -c #{config} -E #{env}"
  }

  desc "Stop unicorn"
  task(:stop) { unicorn_signal :QUIT }

  desc "Restart unicorn with USR2"
  task(:restart) { unicorn_signal :USR2 }

  desc "Increment number of worker processes"
  task(:increment) { unicorn_signal :TTIN }

  desc "Decrement number of worker processes"
  task(:decrement) { unicorn_signal :TTOU }

  desc "Unicorn pstree (depends on pstree command)"
  task(:pstree) do
    sh "pstree '#{unicorn_pid}'"
  end

  ##
  # Helpers

  def unicorn_signal signal
    Process.kill signal, unicorn_pid
  end

  def unicorn_pid
    begin
      File.read(rails_root + "tmp/pids/unicorn.pid").to_i
    rescue Errno::ENOENT
      raise "Unicorn doesn't seem to be running"
    end
  end

  def rails_root
    require "pathname"
    Pathname.new(__FILE__) + "../"
  end

end

で、UnicornからRailsアプリを起動

rake unicorn:start

再起動

rake unicorn:restart

終了

rake unicorn:stop

preload_appの挙動についてのまとめ.良い記事があったのでそのまま引っ張らせて頂きます。 unicornのpreload_app

preload_appの設定によって、SIGHUP(workerの処理の完了をもって再起動)を送ったときに挙動が違ってくる。

true

unicornの設定:再読み込みされる アプリのリロード:されない。アプリはマスタでロード済みのものを使う。アプリをリロードしたければ、SIGUSR2を送って新マスタ&workerを立ち上げる方法をとる。 ダウンタイム:発生しない(マスタのアプリをそのまま使うので、workerがあがった時から処理が可能)

false

unicornの設定:再読み込みされる アプリのリロード:される ダウンタイム:発生する。(workerがそれぞれアプリをロードするので、ロード完了まで処理できない)

ダウンタイム無しでデプロイが可能という特徴に繋がっているようです。


参考

設定を参考にしてみた

http://qiita.com/corona6@github/items/cfac19432532d261912d

https://gist.github.com/honjo2/5023446

https://github.com/herokaijp/devcenter/wiki/Rails-unicorn

githubチームのunicornの事例(tcpとsocketベンチとってる記事)

https://github.com/blog/517-unicorn

Unicorn自体についての日本語の詳細な解説 http://w.koshigoe.jp/study/?ruby-unicorn-intro

デプロイ時の処理に付いていろいろ https://gist.github.com/falcon8823/3804095