Search code examples
ruby-on-railsdeploymentunicorn

Currently my unicorn master needs to be killed manually before each deployment


I'm using capistrano in my rails4 app and everytime I'm release a new version (=> cap production deploy) I need to kill the unicorn master on the server so that the capistrano can walk without any failures.

### How can I automate the process of killing the unicorn process? 

This is how my deploy.rb is looking like:

lock '3.4.0'

set :application, 'maalify'
set :repo_url, '[email protected]:iNeedCode/Maalify.git'
set :deploy_to, '/opt/www/maalify'
set :user, 'root'
set :linked_dirs, %w{log tmp/pids tmp/cache tmp/sockets}


set :rbenv_ruby, '2.2.1'
set :rbenv_type, :user
set :rbenv_path, "~/.rbenv"
set :rbenv_prefix, "RBENV_ROOT=#{fetch(:rbenv_path)} RBENV_VERSION=#{fetch(:rbenv_ruby)} #{fetch(:rbenv_path)}/bin/rbenv exec"
set :rbenv_map_bins, %w(rake gem bundle ruby rails)
set :rbenv_roles, :all
set :linked_files, %w{config/database.yml .rbenv-vars} # create these files manually ones on the server



# Capristrano3 unicorn
set :unicorn_pid, "/opt/www/maalify/current/shared/tmp/pids/unicorn.pid"
set :unicorn_config_path, "/opt/www/maalify/current/config/unicorn.rb"

# Clean up all older releases
before :deploy, "unicorn:stop"
after "deploy:publishing", "unicorn:start"
after "deploy:restart", "deploy:cleanup"


namespace :deploy do
  after :restart, :clear_cache do
    on roles(:web), in: :groups, limit: 3, wait: 10 do
      execute :rake, 'cache:clear'
    end
  end

end

Update

Here is the Unicorn.rb

# set path to application
app_dir = "/opt/www/maalify"
shared_dir = "#{app_dir}/shared"
working_directory "#{app_dir}/current"

# Set unicorn options
worker_processes 1
preload_app true
timeout 30

# Set up socket location
listen "#{shared_dir}/tmp/sockets/unicorn.sock", :backlog => 64

# Logging
stderr_path "#{shared_dir}/log/unicorn.stderr.log"
stdout_path "#{shared_dir}/log/unicorn.stdout.log"

# Set master PID location
pid "#{shared_dir}/tmp/pids/unicorn.pid"

After adding the before_fork I'm still getting the same error as previously when I didn't kill unicorn by hand:

/opt/www/maalify/shared/bundle/ruby/2.2.0/gems/unicorn-4.8.3/lib/unicorn/http_server.rb:206:in `pid=': Already running on PID:16268 (or pid=/opt/www/maalify/shared/tmp/pids/unicorn.pid is stale) (ArgumentError)
        from /opt/www/maalify/shared/bundle/ruby/2.2.0/gems/unicorn-4.8.3/lib/unicorn/http_server.rb:135:in `start'
        from /opt/www/maalify/shared/bundle/ruby/2.2.0/gems/unicorn-4.8.3/bin/unicorn:126:in `<top (required)>'
        from /opt/www/maalify/shared/bundle/ruby/2.2.0/bin/unicorn:23:in `load'
        from /opt/www/maalify/shared/bundle/ruby/2.2.0/bin/unicorn:23:in `<main>'

Solution

  • I've noticed in your unicorn config file next directive:

    preload_app true
    

    Here is info from docs:

    HUP - reloads config file and gracefully restart all workers. If the "preload_app" directive is false (the default), then workers will also pick up any application code changes when restarted. If "preload_app" is true, then application code changes will have no effect; USR2 + QUIT (see below) must be used to load newer code in this case.

    You can set directive preload_app to false and then if you need to reload code, send HUP signal to master process.

    But here is another way to reload code without downtime. You need to implement USR2 + QUIT method in config. It means when you send USR2 signal, unicorn starts new instance of application without killing old one. You can hook before_fork callback to which will kill old instance painless for you.

    Here is example, add this to unicorn config:

    before_fork do |server, worker|
      ActiveRecord::Base.connection.disconnect!
    
      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
    end
    
    after_fork do |server, worker|
      ActiveRecord::Base.establish_connection
    end
    

    Now send USR2 signal to master process and check it out!

    Next step is change your deploy.rb. You need to remove all unicorn hooks and add next one:

    after 'deploy:publishing', 'deploy:restart'
    namespace :deploy do
      task :restart do
        invoke 'unicorn:legacy_restart'
      end
    end
    

    And last step: fix pid path in deploy.rb.

    set :unicorn_pid, "/opt/www/maalify**/current/shared/**tmp/pids/unicorn.pid"
    

    Must be:

    set :unicorn_pid, "/opt/www/maalify/shared/tmp/pids/unicorn.pid"