Railsアプリの本番環境での運用 -godとか-

perldaemontools + Server::Starterで面倒見てたあたりをRailsでもしっかりやりたいなぁと。
以外としっかりとやってるサンプルがウェブに落ちてなかったので、いろいろ調べました。
いろいろ検証してみたけど、最終的に以下のように落ち着きそうです。試行錯誤した中身もまとめたい気もする。

やること

  • graceful restart
  • Railsアプリの永続化
  • 永続化してるやつの自動起動とか永続化とか
  • capistranoからアプリの再起動などできるように

終着点

Railsアプリをgod + unicornで上げて、godをinit script置いてchkconfigで自動起動とかの設定。
あと、capistranoからgod監視下のアプリを再起動できるように

unicornをgodで動かす

よくあるやつ。

rails_env  = ENV['RAILS_ENV']  || 'production'
rails_root = ENV['RAILS_ROOT'] || '/path/to/application'

God.watch do |w|
  w.name     = 'application_name'
  w.interval = 30.seconds

  w.uid = 'app'
  w.gid = 'app'

  w.start   = "bundle exec -- unicorn_rails -c #{rails_root}/config/unicorn.rb -E #{rails_env} -D"
  w.stop    = "kill -QUIT `cat #{rails_root}/tmp/pids/unicorn.pid`"
  w.restart = "kill -USR2 `cat #{rails_root}/tmp/pids/unicorn.pid`"

  w.start_grace   = 10.seconds
  w.restart_grace = 10.seconds
  w.pid_file      = "#{rails_root}/tmp/pids/unicorn.pid"
  w.log           = File.expand_path(File.join(File.dirname(__FILE__), '..', 'log', 'god.log'))

  w.behavior(:clean_pid_file)

  w.start_if do |start|
    start.condition(:process_running) do |c|
      c.interval = 5.seconds
      c.running  = false
    end
  end

  w.restart_if do |restart|
    restart.condition(:memory_usage) do |c|
      c.above = 300.megabytes
      c.times = [3, 5]
    end

    restart.condition(:cpu_usage) do |c|
      c.above = 50.percent
      c.times = 5
    end
  end

  w.lifecycle do |on|
    on.condition(:flapping) do |c|
      c.to_state     = [:start, :restart]
      c.times        = 5
      c.within       = 5.minutes
      c.transition   = :unmonitored
      c.retry_in     = 10.minutes
      c.retry_times  = 5
      c.retry_within = 2.hours
    end
  end
end

init script

はこんな感じ。
/etc/init.d/godに置く。

#!/bin/bash
. /etc/rc.d/init.d/functions

RBENV_ROOT=/opt/rbenv
RBENV_VERSION=""
export RBENV_VERSION

PATH=$RBENV_ROOT/shims:$RBENV_ROOT/bin:/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
RUBY_PATH=$RBENV_ROOT/shims/ruby
DAEMON=$RBENV_ROOT/shims/god
PID_FILE=/var/run/god.pid
LOG_FILE=/var/log/god.log
SCRIPT_NAME=/etc/init.d/god
CONFIG_FILE_DIR=/etc/god

#DEBUG_OPTIONS="--log-level debug"
DEBUG_OPTIONS=""

test -x $DAEMON || exit 0

RETVAL=0

god_start() {
  start_cmd="$DAEMON -l $LOG_FILE -P $PID_FILE $DEBUG_OPTIONS"
  echo $start_cmd
  $start_cmd || echo -en "god already running"
  RETVAL=$?
  if [ "$RETVAL" == '0' ]; then
    sleep 2
    if [ -d $CONFIG_FILE_DIR ]; then
      echo "god: loading master config"
      $DAEMON load $CONFIG_FILE_DIR/master.conf
    fi
  fi

  return $RETVAL
}

god_stop() {
  stop_cmd="god terminate"
  echo $stop_cmd
  $stop_cmd || echo -en "god not running"
}

case "$1" in
  start)
    god_start
    RETVAL=$?
    ;;
  stop)
    god_stop
    RETVAL=$?
    ;;
  restart)
    god_stop
    god_start
    RETVAL=$?
    ;;
  status)
    $DAEMON status
    RETVAL=$?
    ;;
  *)
    echo "Usage: god {start|stop|restart|status}"
    exit 1
    ;;
esac

exit $RETVAL

で、/etc/god/master.confに各設定のloadを書く。
ここは、/etc/god以下にconfig fileを複数用意するのもありだけど、
master.confだけ置いて中でloadする形にした。

capistranoのtask.

capistrano 3なので、lib/capistrano/tasks/god.capとかに置く感じで。

namespace :god do
  task :environment do
    set :god_config, "#{current_path}/config/god/#{fetch(:rails_env)}.rb"
  end

  task :start do
    on roles(:web) do
      execute :sudo, "/opt/rbenv/shims/god start #{fetch(:application)}"
    end
  end

  task :restart do
    on roles(:web) do
      execute :sudo, "/opt/rbenv/shims/god restart #{fetch(:application)}"
    end
  end

  task :stop do
    on roles(:web) do
      execute :sudo, "/opt/rbenv/shims/god stop #{fetch(:application)}"
    end
  end
end

これでcapistrano経由でrestartとかできる。
god(というかrbenv)のpathは分離したい感じだ。

capistrano-unicornとか使うとcapistrano経由でunicornでアプリ上げれるけど、永続化とかサーバ再起動時にアプリがあがらないのとか気になってたのでちゃんと調べた。冗長なサーバ構成である程度アプリ落ちても動作する+監視で落ちたのに気づけるというところで少しは大丈夫なところもあるのかもと思いもしたけど、やっぱり気になったので。