インフラブログ

とあるWEBサイトのインフラを構築運用するメモ

unicorn/resqueのプロセス管理にeyeを使う

unicorn,resqueのプロセス管理に今回はeyeを使うことにします。 はじめBluepillで設定していたのですが、Ruby2.0環境だとBluepillのプロセスがゾンビで溜まりまくるという問題があり、代替をさがしていたところeyeというのがありました。God、Bluepillを参考に作られているということで設定も似た感じかな?と思い採用しています。今のところRuby2.x環境でも問題は起きていません。

EyeとMonitで監視対象のプロセスをわける

デプロイの際に再起動するプロセスをeyeで、それ以外をmonitで監視するようにします。

インストール

bundleでは無くgemで入れてます。 $gem install eye eyeの起動方法はeye load 設定ファイルとなります。 設定ファイルはサーバの役割毎に用意しておきます。 各サーバに環境変数SERVER_ROLEというのを設定していて、この環境変数から読み込む設定ファイルが決まるようします。 これでappsサーバはunicornを起動する設定を読み込み、resqueサーバはresque関連を起動する設定を読み込むことになります。

eyeの設定ファイル

Railsプロジェクトのconfig/eye/以下に設定ファイルをそれぞれ配置しておきました。 以下はunicornとresque-workerプロセスを管理する設定例になります。 各プロセスの起動停止コマンドや、監視内容を設定しています(プロセスのメモリが300m超えたら再起動するとか)

  • config/eye/staging-app.eye
# -*- coding: utf-8 -*-

ENV['RAILS_ROOT'] ||= File.expand_path(File.join(File.dirname(__FILE__), "../.."))
rails_root = ENV['RAILS_ROOT']
rails_env = ENV['RAILS_ENV']
stage = ENV['STAGE']

current_path = "/home/ec2-user/example/#{stage}/current"
shared_path = "/home/ec2-user/example/#{stage}/shared/"

# load submodules, here just for example
Eye.load("./eye/*.rb")

# Eye self-configuration section
Eye.config do
  logger "/var/log/eye/eye.log"
end

Eye.application "#{stage}-app" do

  working_dir current_path

  process("unicorn") do
    pid_file "tmp/pids/unicorn.pid"
    start_command "bundle exec unicorn -Dc config/unicorn/#{stage}.rb -E #{rails_env}"
    stdall "log/unicorn.log"

    # stop signals:
    # http://unicorn.bogomips.org/SIGNALS.html
    stop_signals [:TERM, 10.seconds]

    # soft restart
    restart_command "kill -USR2 {PID}"

    check :cpu, :every => 30, :below => 99, :times => 3
    check :memory, :every => 30, :below => 300.megabytes, :times => [3,5]

    start_timeout 120.seconds
    restart_grace 120.seconds

    monitor_children do
      stop_command "kill -QUIT {PID}"
      check :cpu, :every => 30, :below => 99, :times => 3
      check :memory, :every => 30, :below => 300.megabytes, :times => [3,5]
    end
  end

  process ("resque_worker") do
    pid_file "tmp/pids/resque_worker.pid"
    start_command "bundle exec rake -t -f Rakefile environment resque:work"
    stop_signals [:QUIT, 5.seconds,:TERM, 5.seconds,:KILL]

    start_timeout 120.seconds
    restart_grace 120.seconds

    daemonize false
    env "RAILS_ENV" => rails_env,
        "QUEUE" => "*",
        "INTERVAL" => 1,
        "BACKGROUND" => "yes",
        "PIDFILE" => "tmp/pids/resque_worker.pid"

    stdall "log/resque_worker.log"

    monitor_children do
      stop_command "kill -QUIT {PID}"
      check :cpu, :every => 30, :below => 99, :times => 3
      check :memory, :every => 30, :below => 300.megabytes, :times => [3,5]
    end
  end

end

デプロイタスクも用意する

デプロイ時にunicorn,resqueの再起動が必要になるので、eyeを通して再起動を行えるようにタスクを書いておきます。 なお、unicornの再起動にはUSR2でのリスタートとプロセスを落として起動するの2つの方法で再起動できるようにそれぞれタスクを用意しました。 (ファイルアップロードのデプロイ時はUSR2リロードで良く、currentシンボリックリンクが切り替わる場合は停止、起動を行います)

  • cap staging deploy:reload - eye restartが実行される(unicornのUSR2リロード)
  • cap staging deploy:restart - eye stop,startが実行される)

lib/capistrano/tasks/eye.cap

namespace :eye do

  desc "eye start"
  task :start do
    on roles(:app), in: :parallel do
      execute "sudo /etc/init.d/eye start || true"
    end
  end

  desc "eye stop"
  task :stop do
    on roles(:app), in: :parallel do
      execute "sudo /etc/init.d/eye stop || true"
    end
  end

  desc "eye restart"
  task :restart do
    on roles(:app), in: :sequence, wait: 10 do
      execute "sudo /etc/init.d/eye restart || true"
    end
  end

  desc "eye stop and start"
  task :stop_start do
    on roles(:app), in: :sequence, wait: 20 do |host|
      puts host
      if host.roles.include?(:web)
        execute "ssh gateway elb_remove.rb #{fetch(:stage)} #{host} || true"
      end
      execute "sudo /etc/init.d/eye stop || true"
      execute "sudo /etc/init.d/eye start || true"
      puts "sleep 30"
      sleep 30
      if host.roles.include?(:web)
        execute "ssh gateway elb_add.rb #{fetch(:stage)} #{host} || true"
      end
    end
  end

  desc "eye info"
  task :info do
    on roles(:app), in: :parallel do
      count = 0
      while capture("sudo /etc/init.d/eye info || true").match(/starting/) && count < 18
        puts "loop"
        count = count + 1
        sleep 10
      end
    end
  end

  after :restart, "eye:info"
  after :stop_start, "eye:info"
end