在使用capistrano“零停机”部署后,Unicorn工作人员超时

时间:2015-04-22 17:15:02

标签: ruby-on-rails ruby-on-rails-3 deployment capistrano unicorn

我正在运行Rails 3.2.21应用程序并使用capistrano(nginx和unicorn)部署到Ubuntu 12.04.5框。

我的应用程序设置为零停机部署(至少我),我的配置文件看起来或多或少像these

问题出在这里:当部署工作接近完成并重新启动独角兽时,当我看到我的unicorn.log时,我看到它启动了新工作人员,收获旧工作人员......但随后我的应用程序只挂了2-3分钟。此时对应用程序的任何请求都会到达超时窗口(我设置为40秒)并返回应用程序的500错误页面。

这是 unicorn.log 输出的第一部分,因为独角兽正在重启(我有5名独角兽工人):

I, [2015-04-21T23:06:57.022492 #14347]  INFO -- : master process ready
I, [2015-04-21T23:06:57.844273 #15378]  INFO -- : worker=0 ready
I, [2015-04-21T23:06:57.944080 #15381]  INFO -- : worker=1 ready
I, [2015-04-21T23:06:58.089655 #15390]  INFO -- : worker=2 ready
I, [2015-04-21T23:06:58.230554 #14541]  INFO -- : reaped #<Process::Status: pid 15551 exit 0> worker=4
I, [2015-04-21T23:06:58.231455 #14541]  INFO -- : reaped #<Process::Status: pid 3644 exit 0> worker=0
I, [2015-04-21T23:06:58.249110 #15393]  INFO -- : worker=3 ready
I, [2015-04-21T23:06:58.650007 #15396]  INFO -- : worker=4 ready
I, [2015-04-21T23:07:01.246981 #14541]  INFO -- : reaped #<Process::Status: pid 32645 exit 0> worker=1
I, [2015-04-21T23:07:01.561786 #14541]  INFO -- : reaped #<Process::Status: pid 15534 exit 0> worker=2
I, [2015-04-21T23:07:06.657913 #14541]  INFO -- : reaped #<Process::Status: pid 16821 exit 0> worker=3
I, [2015-04-21T23:07:06.658325 #14541]  INFO -- : master complete

之后,当应用程序挂起2-3分钟时,会发生以下情况:

E, [2015-04-21T23:07:38.069635 #14347] ERROR -- : worker=0 PID:15378 timeout (41s > 40s), killing
E, [2015-04-21T23:07:38.243005 #14347] ERROR -- : reaped #<Process::Status: pid 15378 SIGKILL (signal 9)> worker=0
E, [2015-04-21T23:07:39.647717 #14347] ERROR -- : worker=3 PID:15393 timeout (41s > 40s), killing
E, [2015-04-21T23:07:39.890543 #14347] ERROR -- : reaped #<Process::Status: pid 15393 SIGKILL (signal 9)> worker=3
I, [2015-04-21T23:07:40.727755 #16002]  INFO -- : worker=0 ready
I, [2015-04-21T23:07:43.212395 #16022]  INFO -- : worker=3 ready
E, [2015-04-21T23:08:24.511967 #14347] ERROR -- : worker=3 PID:16022 timeout (41s > 40s), killing
E, [2015-04-21T23:08:24.718512 #14347] ERROR -- : reaped #<Process::Status: pid 16022 SIGKILL (signal 9)> worker=3
I, [2015-04-21T23:08:28.010429 #16234]  INFO -- : worker=3 ready

最终,在2或3分钟后,应用程序再次开始响应,但一切都更加缓慢。您可以在New Relic中清楚地看到这一点(水平线表示部署,浅蓝色区域表示Ruby):

New Relic graph during and after deploy

我有一个相同的登台服务器,我无法在登台中复制这个问题...已授予,暂存是无负载的(我是唯一一个尝试发出页面请求的人)。

这是我的 config / unicorn.rb 文件:

root = "/home/deployer/apps/myawesomeapp/current"
working_directory root

pid "#{root}/tmp/pids/unicorn.pid"
stderr_path "#{root}/log/unicorn.log"
stdout_path "#{root}/log/unicorn.log"

shared_path = "/home/deployer/apps/myawesomeapp/shared"

listen "/tmp/unicorn.myawesomeapp.sock"
worker_processes 5
timeout 40

preload_app true

before_exec do |server|
  ENV['BUNDLE_GEMFILE'] = "#{root}/Gemfile"
end

before_fork do |server, worker|
  if defined?(ActiveRecord::Base)
    ActiveRecord::Base.connection.disconnect!
  end

  old_pid = "#{root}/tmp/pids/unicorn.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

    end
  end
end

after_fork do |server, worker|
  if defined?(ActiveRecord::Base)
    ActiveRecord::Base.establish_connection
  end
end

只是为了描绘一幅完整的图片,在我的capistrano deploy.rb 中,独角兽重启任务如下所示:

namespace :deploy do
  task :restart, roles: :app, except: { no_release: true } do
    run "kill -s USR2 `cat #{release_path}/tmp/pids/unicorn.pid`"
  end
end

为什么独角兽工作人员在部署后立即超时?我认为零停机时间的关键是要保持旧的停机时间,直到新的停机并准备服务?

谢谢!

更新

我做了另一次部署,这一次关注 production.log ,看看那里发生了什么。唯一可疑的是以下几行,这些行与正常请求混在一起:

Dalli/SASL authenticating as 7510de
Dalli/SASL: 7510de
Dalli/SASL authenticating as 7510de
Dalli/SASL: 7510de
Dalli/SASL authenticating as 7510de
Dalli/SASL: 7510de

更新#2

正如下面的一些答案所示,我更改了before_fork块以添加sig = (worker.nr + 1) >= server.worker_processes ? :QUIT : :TTOU,因此工作人员将逐渐被杀死。

同样的结果,非常缓慢部署,我在上图中说明了相同的峰值。仅仅为了上下文,在我的5个工作进程中,前4个发送了一个TTOU信号,第5个发送了QUIT。不过,似乎没有什么区别。

2 个答案:

答案 0 :(得分:3)

我最近在试图在Digital Ocean上设置Rails / Nginx / Unicorn时遇到了类似的问题。在调整了一些事情之后,我能够实现零停机部署。以下是一些尝试:

  1. 减少工作进程数。
  2. 增加服务器的内存。我在512MB RAM液滴上超时了。当我把它增加到1GB时似乎解决了这个问题。
  3. 使用&#34; capistrano3-unicorn&#34;宝石。
  4. 如果preload_app为true,请使用restart(USR2)。如果为false,请使用重新加载(HUP)。
  5. 确保&#34; tmp / pids&#34;在deploy.rb中作为linked_dirs集合。
  6. 使用px aux | grep unicorn确保删除旧流程。
  7. 使用kill [pid]手动停止仍在运行的任何独角兽进程。
  8. 这是我的独角兽配置供参考:

    working_directory '/var/www/yourapp/current'
    pid '/var/www/yourapp/current/tmp/pids/unicorn.pid'
    stderr_path '/var/www/yourapp/log/unicorn.log'
    stdout_path '/var/www/yourapp/log/unicorn.log'
    listen '/tmp/unicorn.yourapp.sock'
    worker_processes 2
    timeout 30
    preload_app true
    
    before_fork do |server, worker|
      old_pid = "/var/www/yourapp/current/tmp/pids/unicorn.pid.oldbin"
      if old_pid != server.pid
        begin
        sig = (worker.nr + 1) >= server.worker_processes ? :QUIT : :TTOU
        Process.kill(sig, File.read(old_pid).to_i)
        rescue Errno::ENOENT, Errno::ESRCH
        end
      end
    end
    

    deploy.rb

    lock '3.4.0'
    
    set :application, 'yourapp'
    set :repo_url, 'git@bitbucket.org:username/yourapp.git'
    set :deploy_to, '/var/www/yourapp'
    set :linked_files, fetch(:linked_files, []).push('config/database.yml', 'config/secrets.yml', 'config/application.yml')
    set :linked_dirs, fetch(:linked_dirs, []).push('log', 'tmp/pids', 'tmp/cache', 'tmp/sockets', 'vendor/bundle', 'public/system')
    set :format, :pretty
    set :log_level, :info
    set :rbenv_ruby, '2.1.3'
    
    namespace :deploy do
      after :restart, :clear_cache do
        on roles(:web), in: :groups, limit: 3, wait: 10 do
        end
      end
    end
    
    after 'deploy:publishing', 'deploy:restart'
    namespace :deploy do
      task :restart do
        #invoke 'unicorn:reload'
        invoke 'unicorn:restart'
      end
    end
    

答案 1 :(得分:1)

您是否正在销售独角兽并且在部署时运行捆绑安装?如果是这样,这可能是一个可执行的问题。

当您执行Capistrano部署时,cap会为您的修订创建一个新的版本目录,并将current符号链接移动到指向新版本。如果您还没有告诉正在运行的独角兽优雅地更新其可执行文件的路径,那么如果您添加此行,它应该可以工作:

Unicorn::HttpServer::START_CTX[0] = ::File.join(ENV['GEM_HOME'].gsub(/releases\/[^\/]+/, "current"),'bin','unicorn')

您可以找到更多信息here。我认为你的before_fork块看起来不错,但我也会在@travisluong's回答中添加sig = (worker.nr + 1) >= server.worker_processes ? :QUIT : :TTOU行;随着新工人的产生,这会逐渐杀死工人。

顺便说一句,我不会删除preload_app true因为它会大大改善工人的生成时间。