Skip to content

Instantly share code, notes, and snippets.

@jamiew
Created October 6, 2010 07:36
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save jamiew/612958 to your computer and use it in GitHub Desktop.
Save jamiew/612958 to your computer and use it in GitHub Desktop.
bundler, memcached, chef, log tailing and delayed job workers
# capistrano deployment
require "bundler/capistrano"
require "capistrano/ext/multistage"
set :stages, %w(canary production)
set :default_stage, "canary"
set :application, "lolcats"
set :deploy_to, "/srv/#{application}"
set :repository, "git@github.com:/lolcats/#{application}.git"
set :branch, "master" # can be overriden by set_branch
set :scm, "git"
set :git_enable_submodules, 1
set :deploy_via, :checkout
ssh_options[:forward_agent] = true
set :migrate_target, :latest
set :keep_releases, 10
set :use_sudo, false
# Deployment hooks
after "multistage:ensure", "set_branch"
before "deploy:update_code", "tag_release"
after "bundle:install", "deploy:migrate"
before "deploy:migrate", "memcached:flush_if_pending_migrations"
after "deploy:restart", "deploy:cleanup"
after "deploy:restart", "delayed_job:restart"
# BRANCH=experimental cap canary deploy
task :set_branch do
set :branch, (ENV["BRANCH"] && fetch(:stage) != :production) ? ENV["BRANCH"].to_s : fetch(:stage).to_s
set :rails_env, stage
end
task :tag_release do
system "cd #{File.dirname(__FILE__)}/.. && ./script/tag_release #{stage} #{release_name}"
raise "Error running script/tag_release" unless $? == 0
end
namespace :deploy do
desc "Create shared directories, setup symlinks and adjust permissions"
task :finalize_update, :except => { :no_release => true } do
run "chmod -R g+w #{latest_release}" if fetch(:group_writable, true)
run <<-CMD
rm -rf #{latest_release}/log &&
rm -rf #{latest_release}/tmp &&
mkdir -p #{latest_release}/public &&
mkdir -p #{shared_path}/log &&
mkdir -p #{shared_path}/tmp/pids &&
mkdir -p #{shared_path}/tmp/sockets &&
mkdir -p #{shared_path}/.bundle &&
mkdir -p #{shared_path}/vendor/bundler_gems
CMD
run <<-CMD
ln -s #{shared_path}/tmp #{latest_release}/tmp &&
ln -s #{shared_path}/log #{latest_release}/log &&
ln -s #{shared_path}/.bundle #{latest_release}/.bundle &&
ln -s #{shared_path}/vendor/bundler_gems #{latest_release}/vendor/bundler_gems &&
cd #{latest_release}/public &&
ln -s javascripts javascripts.#{real_revision} &&
ln -s stylesheets stylesheets.#{real_revision} &&
ln -s images images.#{real_revision} &&
cd #{shared_path} && rm -f html && ln -s app/public html
CMD
if fetch(:normalize_asset_timestamps, true)
stamp = Time.now.utc.strftime("%Y%m%d%H%M.%S")
asset_paths = %w(images stylesheets javascripts).map { |p| "#{latest_release}/public/#{p}" }.join(" ")
run "find #{asset_paths} -exec touch -t #{stamp} {} ';'; true", :env => { "TZ" => "UTC" }
end
end
# Reloading the app server(s)
# For now, execute both unicorn *and* passenger restart actions for back-compat
desc "Restart the application"
task :restart, :roles => :app, :except => { :no_release => true } do
# deploy::restart_passenger
deploy::restart_unicorn
end
desc "Restart Unicorn-powered Application (via USR2 signal)"
task :restart_unicorn, :roles => :app, :except => { :no_release => true } do
unicorn::restart
end
desc "Restart Passenger-powered Application (via 'touch tmp/restart.txt')"
task :restart_passenger, :roles => :app, :except => { :no_release => true } do
run "mkdir -p #{current_path}/tmp && touch #{current_path}/tmp/restart.txt"
end
# Database migrations
desc "Run the db:migrate rake task"
task :migrate, :roles => :db, :only => { :primary => true } do
rake = fetch(:rake, "rake")
rails_env = fetch(:stage, "production")
directory = case migrate_target.to_sym
when :current then current_path
when :latest then current_release
else raise ArgumentError, "unknown migration target #{migrate_target.inspect}"
end
run "cd #{directory}; RAILS_ENV=#{rails_env} #{rake} db:migrate"
end
end
# Bring the application offline using system/maintenance.html, which gets served up by nginx/apache
namespace :downtime do
desc "Installs the maintenance.html file into public"
task :start, :roles => :app, :except => { :no_release => true } do
run "cd #{current_release} && RAILS_ENV=#{rails_env} /usr/bin/rake static_ads:generate[#{current_path}/public]"
end
desc "Removes the maintenance.html file from public"
task :end, :roles => :app, :except => { :no_release => true } do
run "cd #{current_release} && RAILS_ENV=#{rails_env} /usr/bin/rake static_ads:delete[#{current_path}/public]"
end
end
# Pay attention to various logfiles on all the remote servers
namespace :tail do
desc "tail rails application logs"
task :app, :roles => :app do
tail_logs("#{shared_path}/log/#{rails_env}.log #{shared_path}/log/unicorn.stderr.log")
end
desc "tail unicorn stderr log"
task :unicorn, :roles => :app do
tail_logs("#{shared_path}/log/unicorn.stderr.log")
end
desc "tail nginx access logs"
task :web, :roles => :app do
tail_logs("/var/log/nginx/#{application}-access.log")
end
desc "tail nginx error logs"
task :errors, :roles => :app do
tail_logs("/var/log/nginx/#{application}-error.log /var/log/nginx/error.log")
end
desc "tail delayed_job worker logs"
task :jobs, :roles => :worker do
tail_logs("#{shared_path}/log/delayed_job.log")
end
end
def tail_logs(files)
set :user, ENV['USER']
run("tail -n0 -f #{files}") do |channel, stream, data|
puts # for an extra line break before the host name
print "#{channel[:host]}: #{data}"
end
end
namespace :delayed_job do
desc "Start delayed job workers"
task :start, :roles => :worker do
worker_count = (stage == :canary ? 1 : 4)
run "cd #{current_path} && RAILS_ENV=#{rails_env} NEWRELIC_DISPATCHER=delayed_job script/delayed_job start -n #{worker_count}"
end
desc "Stop delayed job workers"
task :stop, :roles => :worker do
run "cd #{current_path} && RAILS_ENV=#{rails_env} NEWRELIC_DISPATCHER=delayed_job script/delayed_job stop"
end
desc "Restart delayed jobs (in the background)"
task :restart, :roles => :worker do
delayed_job::stop
delayed_job::start
end
end
namespace :unicorn do
desc "Start all unicorns using /etc/init.d/unicorn"
task :start, :roles => :app do
run "invoke-rc.d unicorn start"
end
desc "Stop all unicorns using /etc/init.d/unicorn"
task :stop, :roles => :app do
run "invoke-rc.d unicorn stop"
end
desc "Restart running unicorns using SIGUSR2 (NOT /etc/init.d/unicorn)"
task :restart, :roles => :app do
run <<-CMD
if [ -e #{current_path}/tmp/pids/unicorn.pid ] && kill -0 $(cat #{current_path}/tmp/pids/unicorn.pid); then \
echo 'Upgrading Unicorn...'; \
kill -USR2 $(cat #{current_path}/tmp/pids/unicorn.pid); \
else \
echo 'Unicorn not running! Starting manually...'; \
cd #{current_path}; \
rm -f tmp/pids/unicorn.pid; \
/usr/bin/bundle exec unicorn -c config/unicorn.rb -E #{rails_env} -D #{current_path}/config.ru; \
fi
CMD
end
desc "Display unicorn statuses"
task :status, :roles => :app do
run("ps auxf | egrep '([u]nicorn|USER)' | egrep -v '(tail|grep)'")
end
end
namespace :chef do
desc "chef reprovision all of the servers"
task :provision do
run "cd /var/local/spark && rake provision:run"
end
desc "tail the chef_client logs"
task :log do
tail_logs("/var/log/chef_client.log")
end
end
namespace :memcached do
desc "Flush memcached"
task :flush, :roles => [:app] do
run("cd #{current_release} && RAILS_ENV=#{rails_env} /usr/bin/rake memcached:flush")
end
desc "Flush memcached if there are any pending migrations (installs hook, run before db:migrate)"
task :flush_if_pending_migrations, :roles => [:app] do
output = capture("cd #{current_release} && RAILS_ENV=#{rails_env} /usr/bin/rake db:pending_migration_count")
count = /(\d+) pending migrations/.match(output)
if count[0] && count[0].to_i > 0
puts "#{count[0].to_i} migrations will be run! Installing memcached:flush hook"
after "deploy:migrate", "memcached:flush"
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment