#373 Zero-Downtime Deployment pro
Here I show how to accomplish zero-downtime deployment using Unicorn. I also cover gotchas when working with migrations and how to put up a maintenance page when you do need to take down the site.
- Download:
- source codeProject Files in Zip (74.5 KB)
- mp4Full Size H.264 Video (28.2 MB)
- m4vSmaller H.264 Video (16.8 MB)
- webmFull Size VP8 Video (17.5 MB)
- ogvFull Size Theora Video (42.6 MB)
Resources
Update February 16, 2013: The cap deploy:web:disable
tasks have been removed from Capistrano. Use the capistrano-maintenance gem instead.
terminal
cap unicorn:setup unicorn:stop unicorn:start rails d migration rename_content_to_body_in_articles rails g migration add_content_to_articles content:text rails g migration copy_article_content rails g migration remove_body_from_articles body:text cap nginx:setup cap deploy:web:disable cap -e deploy:web:disable cap deploy:web:enable
config/application.rb
sleep 10 # to simulate slow loading app
config/recipes/templates/unicorn.rb.erb
working_directory "<%= current_path %>" pid "<%= unicorn_pid %>" stderr_path "<%= unicorn_log %>" stdout_path "<%= unicorn_log %>" listen "/tmp/unicorn.<%= application %>.sock" worker_processes <%= unicorn_workers %> timeout 30 preload_app true before_fork do |server, worker| # Disconnect since the database connection will not carry over if defined? ActiveRecord::Base ActiveRecord::Base.connection.disconnect! end # Quit the old unicorn process 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| # Start up the database connection again in the worker if defined?(ActiveRecord::Base) ActiveRecord::Base.establish_connection end end
config/recipes/templates/unicorn_init.erb
restart|reload) sig USR2 && echo reloaded OK && exit 0
db/migrate/copy_article_content.rb
class CopyArticleContent < ActiveRecord::Migration def up execute "update articles set content=body" end def down execute "update articles set body=content" end end
config/recipes/templates/nginx_unicorn.erb
if (-f $document_root/system/maintenance.html) { return 503; } error_page 503 @maintenance; location @maintenance { rewrite ^(.*)$ /system/maintenance.html last; break; }
config/deploy.rb
set :maintenance_template_path, File.expand_path("../recipes/templates/maintenance.html.erb", __FILE__)
config/recipes/templates/maintenance.html.erb
<!DOCTYPE html> <html> <head> <title>Blog</title> <style type="text/css"> html, body { background-color: #4B7399; font-family: Verdana, Helvetica, Arial; font-size: 14px; } a { color: #0000FF; img { border: none; } } #container { width: 80%; margin: 0 auto; background-color: #FFF; padding: 20px 40px; border: solid 1px black; margin-top: 20px; } </style> </head> <body> <div id="container"> <h1>The system is down for <%= reason ? reason : "maintenance" %></h1> <p>It will be back <%= deadline ? deadline : "soon" %></p> </div> </body> </html>
models/article.rb
attr_accessible :body # ... def body=(body) self.content = body end