Skip navigation

NEED HELP?|

Currently Being Moderated

Trainer 2 moves to Unicorn

Posted by RobCameron.2.16b on May 5, 2011 4:05:18 PM

https://github.com/images/error/angry_unicorn.png

The general internet discovered Unicorn after a blog post over at GitHub. I've been wanting to use it for a while now. Early this week we switched over Trainer 2 to this awesome web server.

 

The idea behind Unicorn is that instead of having a separate load balancer with all kinds of fancy rules to determine which workers are busy and which are available to do some work, you let the tried and true process management built into your OS do that work for you. Unicorn consists of a master process that spawns workers. The master monitors the workers and kills and restarts any that are behaving badly. When a request comes in the master hands it off to the operating system to determine which worker process has the resources available to work on it. The stack looks like this:

 

https://d3nwyuy0nl342s.cloudfront.net/img/a91702456cf39acf5548524593fb19c6d1a84c42/687474703a2f2f696d672e736b697463682e636f6d2f32303039313030392d6e686b787072726e7463346b39753178346b62653134783631742e706e67

You stil want to keep your web server (nginx in our case, or Apache) serving static content. You just let the server know where to pass all the other requests to. In nginx this looks something like:

 

upstream trainer2 {
  server unix:/tmp/unicorn.sock fail_timeout=0;
}
server {
  listen 80;
  server_name trainer.active.com;
  root /var/www/current/public;

  location / {
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $http_host;
    proxy_max_temp_file_size 0;

    # serve static content with a far-future expires header
    location ~ ^/(images|stylesheets|javascripts)/ {
      expires 10y;
    }

    # if the file exists (static stuff) serve it right away
    if (-f $request_filename) {
      break;
    }

    # otherwise pass it on to unicorn
    if (!-f $request_filename) {
      proxy_pass http://trainer2;
      break;
    }
  }
}

 

 

That's it for nginx. Now to start up Unicorn you need a config file (ours is in /config/unicorn.rb):

 

# Sample verbose configuration file for Unicorn (not Rack)
#
# This configuration file documents many features of Unicorn
# that may not be needed for some applications. See
# http://unicorn.bogomips.org/examples/unicorn.conf.minimal.rb
# for a much simpler configuration file.
#
# See http://unicorn.bogomips.org/Unicorn/Configurator.html for complete
# documentation.

root = '/var/www/current'

worker_processes(ENV['RACK_ENV'] == 'production' ? 8 : 2)
working_directory root # available in 0.94.0+
listen "/tmp/unicorn.sock"
timeout 120

pid "#{root}/tmp/pids/unicorn.pid"

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

preload_app true
GC.respond_to?(:copy_on_write_friendly=) and GC.copy_on_write_friendly = true

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

  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
      # someone else did our job for us
    end
  end
end

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

 

 

Now we just start up Unicorn like so (from our application's root directory):

 

unicorn -E production -c config/unicorn.rb -D

 

And that's it! You should be able to hit your server and have Unicorn respond. The true magic starts when you want to do a deploy: you don't have to have any downtime because Unicorn will start up a new master while the old one continues to serve clients. Once the new master is up and running, the first worker it spawns will turn off the old master. Users never saw a delay and you were able to upgrade your app transparently. Using this technique you can upgrade Unicorn itself, or even Ruby with no downtime! To trigger this restart just send the USR2 signal to Unicorn. First, run a ps aux | grep unicorn to get the process list and send the signal to the existing master process:

 

ubuntu@qa:~$ ps aux | grep unicorn

USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
ubuntu   23511  0.0  4.0  80216 71040 ?        Sl   May04   0:15 unicorn master -E qa -c config/unicorn.rb -D                                    
ubuntu   23641  0.0  5.0  99108 88576 ?        Sl   May04   0:51 unicorn worker[0] -E qa -c config/unicorn.rb -D                                 
ubuntu   23644  0.0  5.0  98004 87632 ?        Sl   May04   1:14 unicorn worker[1] -E qa -c config/unicorn.rb -D

ubuntu@qa:~$ kill -USR2 23511

 

After a few seconds (however long it takes for your Rails app to start) you'll begin serving your new code. Done!

Comments (0)