介绍如何使用capistrano自动化部署一个unicorn+nginx+rails的项目, 实现zero down time restart
capistrano 是一款能完成自动化部署工作的工具,它能把代码部署到远程服务器上的同时可以执行一些预定义的任务,这些任务可以是capistrano的buildin tasks 也可以是用户自定义的一些任务程序(比如部署完重启服务器
)
本文介绍如何使用capistrano部署一个unicor+nginx+rails的项目,并且实现unicorn的zero down time restart.
$ cap install
# Basic variables
rails_root = File.dirname(File.expand_path("../",__FILE__))
pid "#{rails_root}/tmp/pids/unicorn.pid"
stderr_path "#{rails_root}/log/unicorn.err"
stdout_path "#{rails_root}/log/unicorn.log"
listen "/tmp/unicorn.example_app.sock"
preload_a true
working_directory rails_root
worker_processes 2
timeout 120
# Writing Before_fork
before_fork do |server, worker|
# the following is highly recomended for Rails + "preload_a true"
# as there's no need for the master process to hold a connection
defined?(ActiveRecord::Base) and ActiveRecord::Base.connection.disconnect!
##
# 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 a). When this new Unicorn is completely loadedded
# 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.
#
# Using this method we get 0 downtime deploys.
old_pid = "#{rails_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|
##
# Unicorn master loads the a 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
##
defined?(ActiveRecord::Base) and ActiveRecord::Base.establish_connection
# Redis and Memcached would go here but their connections are established
# on demand, so the master never opens a socket
end
#!/bin/sh
### BEGIN INIT INFO
# Provides: unicorn
# Required-Start: $remote_fs $syslog
# Required-Stop: $remote_fs $syslog
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: Manage unicorn server
# Description: Start, stop, restart unicorn server for a specific alication.
### END INIT INFO
set -e
TIMEOUT=${TIMEOUT-60}
APP_ROOT=your_app_absolute_path
PID=$APP_ROOT/tmp/pids/unicorn.pid
CMD="cd $APP_ROOT && bundle exec unicorn -c $APP_ROOT/config/unicorn.rb -E production -D"
set -u
AS_USER=$USER
OLD_PIN="$PID.oldbin"
sig() {
test -s "$PID" && kill -s $1 `cat $PID`
}
run() {
if [ "$(id -un)" = "$AS_USER" ]; then
eval $1
else
su -c "$1" - $AS_USER
fi
}
case "$1" in
start)
sig 0 && echo >&2 "Already running" && exit 0
run "$CMD"
;;
stop)
sig QUIT && exit 0
echo >&2 "Not running"
;;
force-stop)
sig TERM && exit 0
echo >&2 "Not running"
;;
restart|reload)
sig HUP && echo reloaded OK && exit 0
echo >&2 "Couldn't reload, starting '$CMD' instead"
run "$CMD"
;;
upgrade)
if sig USR2 && sleep 3
then
n=$TIMEOUT
while test -s $OLD_PIN && test $n -ge 0
do
printf '.' && sleep 1 && n=$(( $n - 1 ))
done
if test $n -lt 0 && test -s $OLD_PIN
then
echo >&2 "$OLD_PIN still exists after $TIMEOUT seconds"
exit 1
fi
exit 0
fi
echo >&2 "Couldn't upgrade, starting '$CMD' instead"
run "$CMD"
;;
reopen-logs)
sig USR1
;;
*)
echo >&2 "Usage: $0 <start|stop|restart|upgrade|force-stop|reopen-logs>"
exit 1
;;
esac
lock '3.4.0'
set :alication, 'your_app_name'
set :repo_url, 'your_app_git_url'
set :deploy_to, 'your_deploy_dir'
# Default value for :linked_files is []
# set :linked_file, %w{Gemfile Gemfile.lock}
# Default value for linked_dirs is []
set :linked_dirs, fetch(:linked_dirs, []).push('log', 'tmp/pids', 'tmp/cache', 'tmp/sockets', 'vendor/bundle', 'public/sitemap')
namespace :deploy do
# Restart unicorn when finishing deploying
after :published, :start_unicorn do
on roles(:web), in: :groups, limit: 3, wait: 10 do
execute "cd #{deploy_to}/current && bundle install --gemfile=./Gemfile --path #{deploy_to}/shared/vendor/bundle"
execute "#{deploy_to}/current/config/unicorn.init.sh upgrade"
end
end
end
在项目的根目录下执行
$ cap production deploy