Config/multi-deploy.rb
CS290F Fall 2006 - UCSB Computer Science - Thorsten von Eicken
If you find a problem with this recipe, please fix it right here in the wiki or drop me an email. Thanks! TvE 18:03, 4 December 2006 (PST)
#
# Recipe to deploy a Rails application to multiple EC2 servers
#
require 'capistrano'
require 'mongrel_cluster/recipes'
# Portions of this recipe have been borrowed from various sources
# Special thanks to Slingshot Hosting for their initial version of this.
set :web_name, 'www.aws-console.com' # <====
# Use the first section below for deployment to a single host # <====
# and the second section for deployment to multiple hosts
# Simple set-up with a single host:
aws_host = "domU-12-31-33-00-01-90.usma1.compute.amazonaws.com" # <====
role :web, "#{aws_host}"
role :app, "#{aws_host}"
role :db, "#{aws_host}", :primary => true
# Multi-host set-up: cut&paste the various hosts here...
# app->Rails application servers, web->apache reverse proxy, db->mysql
#role :app, "domU-12-31-34-00-00-69.usma2.compute.amazonaws.com",
# "domU-12-31-34-00-00-69.usma2.compute.amazonaws.com",
# "domU-12-31-34-00-00-69.usma2.compute.amazonaws.com"
#role :web, "domU-12-31-34-00-00-59.usma2.compute.amazonaws.com"
#role :db, "domU-12-31-34-00-00-33.usma2.compute.amazonaws.com", :primary => true
# Accessing the roles in the tasks below:
# retrieving the first host in a list: roles[:db].first[:host]
# retrieving a comma-separated list: roles[:app].map{|r|r[:host]}.join(", ")
# Note the [:host], which gets the host name, there's also a [:options], which
# retrieves things like {:primary=>true}
# Configure the ssh information (we're gonna be bad and use root...)
ssh_options[:keys] = "config/my-ssh-key" # <====
ssh_options[:username] = 'root'
#ssh_options[:keys] = [::EC2::CONFIG['ssh_key_file']]
#ssh_options[:host_key] = 'ssh-dss'
set :use_sudo, false
# Configure user for application processes and database
set :user, ssh_options[:username]
set :dbuser, 'prod'
set :dbpassword, '!my!password!' # <====
# SVN set-up
set :application, "projectname" # <====
set :repository_path, "http://wush.net/svn/project/web/trunk" # <====
set :checkout, "export"
set :svn_user, "project" # <====
set :svn_password, "my!password" # <====
set :repository,
Proc.new { "--username #{svn_user} " +
"--password #{svn_password} " +
"#{repository_path}" }
set :deploy_to, "/home/rails/#{application}"
set :web_server, "httpd"
set :path_to_web_server, "/etc/httpd/"
# set this to the correct db adapter
set :database, "mysql"
# saves space by only keeping last 3 when running cleanup
set :keep_releases, 3
# mongrel_cluster configuration:
set :mongrel_conf, "/etc/mongrel_cluster/#{application}.yml"
set :mongrel_prefix, "/usr/bin/" # path for mongrel_rails executable
set :mongrel_servers, 4
set :mongrel_address, '0.0.0.0'
set :mongrel_start_port, 8000
desc "List the files that would be updated or changed in a deploy."
task :svn_st_up, :roles => :app do
run "cd #{current_path} && " + "svn st -u"
end
desc "Say hello before updating the code so we can tell whether cap managed to log in"
task :before_update_code do
run "perl -e \"print '*'x30, ' Capistrano successfully logged into `hostname -f` ', '*'x30;\""
end
# =============================================================================
# TASKS FOR INITIAL SETUP
# =============================================================================
desc "Tasks to execute before initial setup"
task :before_setup do
sudo "mkdir -p /mnt/rails"
sudo "ln -nfs /mnt/rails /home/rails"
sudo "chown -R #{user}:#{user} /mnt/rails/"
end
desc "Tasks to execute after initial setup: configures the various services"
task :after_setup do
run "mkdir -p #{deploy_to}/#{shared_dir}/config"
mongrel_configure
rails_configure
database_configure
reverse_proxy_configure
end
desc "Configure mongrel cluster"
task :mongrel_configure, :roles => :app do
mongrel_configuration = render :template => <<-EOF
---
cwd: #{deploy_to}/current
user: #{user}
group: #{user}
port: #{mongrel_start_port}
environment: production
address: #{mongrel_address}
pid_file: #{deploy_to}/current/log/mongrel.pid
log_file: #{deploy_to}/current/log/mongrel.log
servers: #{mongrel_servers}
prefix: #{mongrel_prefix}
EOF
run "mkdir -p #{deploy_to}/#{shared_dir}/config"
put mongrel_configuration, "#{deploy_to}/#{shared_dir}/config/mongrel_cluster.yml"
sudo "mkdir -p /etc/mongrel_cluster"
sudo "ln -nfs #{deploy_to}/#{shared_dir}/config/mongrel_cluster.yml /etc/mongrel_cluster/#{application}.yml"
end
desc "Configure Rails"
task :rails_configure, :roles => :app do
run "mkdir -p #{deploy_to}/#{shared_dir}/config"
run "mkdir -p #{deploy_to}/#{shared_dir}/tmp"
run "mkdir -p #{deploy_to}/#{shared_dir}/tmp/cache"
run "mkdir -p #{deploy_to}/#{shared_dir}/tmp/sessions"
run "mkdir -p #{deploy_to}/#{shared_dir}/tmp/sockets"
database_yml = render :template => <<-EOF
# Deployment database.yml
login: &login
adapter: #{database}
host: #{roles[:db].first[:host]}
username: <%= "#{dbuser}" %>
password: <%= "#{dbpassword}" %>
development:
database: <%= "#{application}_development" %>
<<: *login
test:
database: <%= "#{application}_test" %>
<<: *login
production:
database: <%= "#{application}_production" %>
<<: *login
EOF
put database_yml, "#{deploy_to}/#{shared_dir}/config/database.yml"
end
desc "Configure Rails to find the database, runs on roles=>[app,db] (on DB to support migrations..)"
task :rails_configure, :roles => [:app, :db] do
database_yml = render :template => <<-EOF
# Deployment database.yml
login: &login
adapter: #{database}
host: #{roles[:db].first[:host]}
username: <%= "#{dbuser}" %>
password: <%= "#{dbpassword}" %>
development:
database: <%= "#{application}_development" %>
<<: *login
test:
database: <%= "#{application}_test" %>
<<: *login
production:
database: <%= "#{application}_production" %>
<<: *login
EOF
put database_yml, "#{deploy_to}/#{shared_dir}/config/database.yml"
end
desc "Configure database, runs on role=db only"
task :database_configure, :roles => :db do
sudo "perl -p -i -e 's/^skip-networking/#skip-networking/' /etc/my.cnf"
sudo "mkdir -p /mnt/mysql"
sudo "/etc/init.d/mysqld start"
# Only create DB if it doesn't already exist, kind'of a hack...
sudo "bash -c \"if [ ! -d /mnt/mysql/#{application}_production ]; then mysqladmin -u root create #{application}_production; fi\""
sudo "mysql -u root #{application}_production -e \"GRANT ALL ON #{application}_production.* TO '#{dbuser}'@'%' IDENTIFIED BY '#{dbpassword}';\""
#sudo "mysqladmin -u root password '#{dbpassword}'"
end
desc "Configure reverse proxy, runs on role=web only"
task :reverse_proxy_configure, :roles => :web do
# web server configuration (apache specific)
apache2_rails_conf = <<-EOF
<VirtualHost *:80>
Include /etc/rails/#{application}.common
ErrorLog #{path_to_web_server}logs/#{application}_errors_log
CustomLog #{path_to_web_server}logs/#{application}_log combined
</VirtualHost>
<Proxy balancer://#{application}_mongrel_cluster>
EOF
# builds the following as an example with start port 8000 and servers = 3:
# <Proxy balancer://mongrel_cluster>
# BalancerMember http://127.0.0.1:8000
# BalancerMember http://127.0.0.1:8001
# BalancerMember http://127.0.0.1:8002
# </Proxy>
roles[:app].map{|h|h[:host]}.each do |host|
(0..mongrel_servers-1).each do |i|
apache2_rails_conf += " BalancerMember http://#{host}:#{mongrel_start_port + i}\n"
end
end
apache2_rails_conf +=<<-EOF
</Proxy>
Listen #{mongrel_start_port + 80}
<VirtualHost *:#{mongrel_start_port + 80}>
<Location />
SetHandler balancer-manager
Deny from all
Allow from localhost
</Location>
</VirtualHost>
EOF
apache2_rails_configuration = render :template => apache2_rails_conf
apache2_rails_common = render :template => <<-EOF
ServerName #{web_name}
DocumentRoot #{deploy_to}/current/public
<Directory "#{deploy_to}/current/public">
Options FollowSymLinks
AllowOverride None
Order allow,deny
Allow from all
</Directory>
RewriteEngine On
# Uncomment for rewrite debugging
#RewriteLog logs/#{application}_rewrite_log
#RewriteLogLevel 9
# Check for maintenance file and redirect all requests
RewriteCond %{DOCUMENT_ROOT}/system/maintenance.html -f
RewriteCond %{SCRIPT_FILENAME} !maintenance.html
RewriteRule ^.*$ /system/maintenance.html [L]
# Rewrite index to check for static
RewriteRule ^/$ /index.html [QSA]
# Rewrite to check for Rails cached page
RewriteRule ^([^.]+)$ $1.html [QSA]
# Redirect all non-static requests to cluster
RewriteCond %{DOCUMENT_ROOT}/%{REQUEST_FILENAME} !-f
RewriteRule ^/(.*)$ balancer://#{application}_mongrel_cluster%{REQUEST_URI} [P,QSA,L]
# Deflate
AddOutputFilterByType DEFLATE text/html text/plain text/xml application/xml application/xhtml+xml text/javascript text/css
BrowserMatch ^Mozilla/4 gzip-only-text/html
BrowserMatch ^Mozilla/4.0[678] no-gzip
BrowserMatch bMSIE !no-gzip !gzip-only-text/html
EOF
run "mkdir -p #{deploy_to}/#{shared_dir}/config"
put apache2_rails_configuration, "#{deploy_to}/#{shared_dir}/system/#{application}.conf"
put apache2_rails_common, "#{deploy_to}/#{shared_dir}/system/#{application}.common"
sudo "mkdir -p /etc/rails"
sudo "ln -nfs #{deploy_to}/#{shared_dir}/system/#{application}.conf /etc/rails/#{application}.conf"
sudo "ln -nfs #{deploy_to}/#{shared_dir}/system/#{application}.common /etc/rails/#{application}.common"
sudo "cp /etc/httpd/conf.d/ssl.conf /etc/httpd/conf.d/ssl.conf.orig"
sudo "perl -pi -e '/Virtual/&&exit' /etc/httpd/conf.d/ssl.conf"
sudo "apachectl stop"
sudo "apachectl start"
end
# =============================================================================
# TASKS (MISC?)
# =============================================================================
desc "Tasks to execute after code update"
task :after_update_code, :roles => [:app, :db] do
# relink shared deployment database configuration
run "ln -nfs #{deploy_to}/#{shared_dir}/config/database.yml #{release_path}/config/database.yml"
# relink shared tmp dir (for session and cache data)
run "if [ -d #{release_path}/tmp ]; then mv #{release_path}/tmp #{release_path}/tmp.snv; fi"
run "ln -nfs #{deploy_to}/#{shared_dir}/tmp #{release_path}/tmp"
# relink shared mongrel configuration
sudo "ln -nfs #{deploy_to}/#{shared_dir}/config/mongrel_cluster.yml #{release_path}/config/mongrel_cluster.yml"
end
desc "Tasks to execute after a deploy"
task :after_deploy, :roles => [:app, :db] do
# some examples could be:
# restart rails_cron, backgrounDRb
# relink shared user/file dirs
# relink shared ferret_index
end
desc "Restart the web server"
task :restart, :roles => [:web] do
sudo "apachectl stop"
sudo "apachectl start"
# Fails if not running: sudo "/etc/init.d/#{web_server} restart"
end
