Config/multi-deploy.rb

CS290F Fall 2006 - UCSB Computer Science - Thorsten von Eicken

Jump to: navigation, search

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
Personal tools