Unicorn继续在部署+重启后使用旧代码

时间:2012-11-27 06:38:09

标签: capistrano unicorn

task :restart_unicorn, :except => { :no_release => true } do
  run "#{try_sudo} kill -s USR2 $(cat /var/www/app_name/shared/pids/unicorn.pid)"
end

当我在服务器上执行sudo kill -s USR2 $(cat /var/www/app_name/shared/pids/unicorn.pid)时,会发生的情况是创建一个新的独角兽主人,而旧的独角兽主人员的名字附加(old)。旧的永远不会被杀死,但即使我自己杀了它,新的独角兽实例仍然会在部署之前显示旧代码。新实例与旧实例具有相同的创建时间 - 就好像它只是被复制一样。如果我停止实例,并再次启动它,它可以工作,但我希望能够进行零停机时间部署。

感谢任何帮助。

修改

在追随最高级的Ilya O.之后,我创建了一个执行此操作的capistrano任务:

old_pid = get_pid('/var/www/appname/shared/pids/unicorn.pid')
run "#{try_sudo} kill -s SIGUSR2 $(cat /var/www/appname/shared/pids/unicorn.pid)"
/var/www/app/current/tmp/pids/unicorn.pid)"
run "#{try_sudo} kill -s SIGWINCH #{old_pid}"

这会在pid上运行SIGUSR2,并杀死旧的独角兽进程。问题是我的所有应用程序服务器永远不会更新到我最近部署的代码,这个任务似乎只是将旧的独角兽环境复制到一个新进程中。如果我简单地杀死主进程然后再次启动unicorn,它可以正常工作,但是有一分钟左右的请求被丢弃。

3 个答案:

答案 0 :(得分:6)

你的Unicorn配置中有preload_app设置为true吗?如果是这样,您需要在新进程启动并运行后将SIGUSR2和SIGQUIT发送到原始主进程。

可能还会发生的是原始主进程已经死亡,但是孩子们仍在处理请求。您可以尝试发送SIGUSR2,并在生成新的主进程后,发送SIGWINCH(对旧的master)以杀死旧的unicorn子进程,然后在旧的主进程上发送SIGQUIT。您现在应该只有“新”的独角兽进程来处理请求。

编辑:尝试使用SIGQUIT而不是SIGWINCH。我认为旧流程可能会在SIGWINCH之后产生工作人员。

答案 1 :(得分:3)

我不确定你为什么要把所有的工作都放在capistrano上,通常是在capistrano这样的东西就足够了:

set :unicorn_pid, "unicorn.my_website.pid"

desc "Zero-downtime restart of Unicorn"
task :restart, roles: :app do
  if remote_file_exists?("/tmp/#{unicorn_pid}")
    puts "Killing /tmp/#{unicorn_pid}"
    run "kill -s USR2 `cat /tmp/#{unicorn_pid}`"
  else
    run "cd #{current_path} ; RAILS_ENV=#{rails_env} bundle exec unicorn_rails -c #{unicorn_config} -D"
  end
end

然后用于杀死旧的独角兽进程的真实代码实际上是在unicorn配置上

# config/unicorn.rb

# Set environment to development unless something else is specified
env = ENV["RAILS_ENV"] || "production"

# See http://unicorn.bogomips.org/Unicorn/Configurator.html for complete documentation.
worker_processes 2 # amount of unicorn workers to spin up

APP_PATH = "/u/apps/my_website/current"

listen "/tmp/my_website.socket"

preload_app true

timeout 30         # restarts workers that hang for 30 seconds

pid "/tmp/unicorn.my_website.pid"

# By default, the Unicorn logger will write to stderr.
# Additionally, ome applications/frameworks log to stderr or stdout,
# so prevent them from going to /dev/null when daemonized here:
stderr_path APP_PATH + "/log/unicorn.stderr.log"
stdout_path APP_PATH + "/log/unicorn.stdout.log"

if env == "production"
  # Help ensure your application will always spawn in the symlinked
  # "current" directory that Capistrano sets up.
  working_directory APP_PATH

  # feel free to point this anywhere accessible on the filesystem
  user 'deploy', 'deploy' # 'user', 'group'
  shared_path = "/u/apps/my_website/shared"

  stderr_path "#{shared_path}/log/unicorn.stderr.log"
  stdout_path "#{shared_path}/log/unicorn.stdout.log"
end

before_fork do |server, worker|
  # the following is highly recomended for Rails + "preload_app true"
  # as there's no need for the master process to hold a connection
  if defined?(ActiveRecord::Base)
    ActiveRecord::Base.connection.disconnect!
  end


  # When sent a USR2, Unicorn will suffix its pidfile with .oldbin and
  # immediately start loading up a new version of itself (loaded with a new
  # version of our app). When this new Unicorn is completely loaded
  # it will begin spawning workers. The first worker spawned will check to
  # see if an .oldbin pidfile exists. If so, this means we've just booted up
  # a new Unicorn and need to tell the old one that it can now die. To do so
  # we send it a QUIT.
  #
  # This enables 0 downtime deploys.
  old_pid = "/tmp/unicorn.my_website.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
end

after_fork do |server, worker|

  # Unicorn master loads the app then forks off workers - because of the way
  # Unix forking works, we need to make sure we aren't using any of the parent's
  # sockets, e.g. db connection (since "preload_app true")
  if defined?(ActiveRecord::Base)
    ActiveRecord::Base.establish_connection
  end

  # if preload_app is true, then you may also want to check and
  # restart any other shared sockets/descriptors such as Memcached,
  # and Redis.  TokyoCabinet file handles are safe to reuse
  # between any number of forked children (assuming your kernel
  # correctly implements pread()/pwrite() system calls)
end

我大部分时间都在使用该设置而且我的应用程序重新加载没有任何问题,你可能已经拥有了所有这些,因为它主要是默认配置,但如果你遗漏了什么,试一试;)

答案 2 :(得分:2)

Unicorn的人有一个公共的init.d脚本,你应该在你的系统中添加它并使用它。

您将找到两种不同的方法来重启unicorn:重启和升级。

我个人在/etc/init.d/unicorn中添加此脚本,设置此可执行文件,然后您可以在capistrano配方中使用sudo service unicorn upgrade

初始化脚本:http://unicorn.bogomips.org/examples/init.sh