Capistrano Deploy to dynamic AWS EC2

At Metal Toad we use Capistrano to deploy our projects to their respective servers. Normally this is done with a configuration file for each stage (Dev, Staging, QA, and Production) that contains a list of servers. Below is an example of one of those files.

# Set the deployment directory on the target hosts.
set :deploy_to, "/var/www/sites/virtual/<client>-stage.metaltoad-sites.com"
 
# The hostnames to deploy to.
role :web, "stg01-<client>.ec2.metaltoad.net", “stg02-<client>.ec2.metaltoad.net”
 
# Specify one of the web servers to use for database backups or updates.
# This server should also be running Drupal.
role :db, "stg01-<client>.ec2.metaltoad.net", :primary => true
 
# The path to drush
set :drush, "cd #{current_path}/#{app_root} ; /usr/local/bin/drush"
 
# The username on the target system, if different from your local username
ssh_options[:user] = 'deploy'

This process has worked great, but now we are creating Custom Clouds in AWS, we need a more dynamic solution.

The biggest feature of AWS is the ability to pay for the servers you need, and only when you need them. That means we need to constantly change the lists of servers in our Capistrano configuration. These changes lead to deploys failing when a server that was there isn’t or when a server that should have gotten a deploy, didn’t.

Below is my solution to dynamically populate the server lists:

# Set the deployment directory on the target hosts.
set :deploy_to, "/var/www/sites/virtual/#{application}"
 
# The hostnames to deploy to.
set :access_key, ""
set :secret_key, ""
set :ec2_servers, ""
set :ec2_gateway, ""
set :ec2_db_gateway, ""
set :client, ""
run_locally "echo `ec2-describe-instances -O #{access_key} -W #{secret_key} --filter \"instance-state-code=16\"| grep #{client} | grep Name | grep web | awk -F' ' '{ print $5\".ec2.metaltoad.net  \" }'` | sed ':a;N;$!ba;s/\n/, /g' > /tmp/#{client}-servers"
File.open("/tmp/#{client}-servers", 'r') do |f1|  
  while line = f1.gets  
    puts line
    ec2_servers=line.split(" ")
  end  
 
puts ec2_servers  
 
role(:web) { ec2_servers }
end
 
run_locally "echo `ec2-describe-instances -O #{access_key} -W #{secret_key} --filter \"instance-state-code=16\" --filter \"tag-key=Gateway\" | grep #{client} | grep Name  | awk -F' ' '{ print $5\".ec2.metaltoad.net \" }'` | sed ':a;N;$!ba;s/\n/, /g' > /tmp/#{client}-gateway"
File.open("/tmp/#{client}-gateway", 'r') do |f1|  
  while line = f1.gets  
    puts line
    ec2_gateway=line.split(" ")
  end 
set :gateway, ec2_gateway
end
 
# Specify one of the web servers to use for database backups or updates.
# This server should also be running Drupal.
run_locally "echo `ec2-describe-instances -O #{access_key} -W #{secret_key} --filter \"instance-state-code=16\" --filter \"tag-key=DB-Gateway\"| grep #{client} | grep Name | awk -F' ' '{ print $5\".ec2.metaltoad.net  \" }'` | sed ':a;N;$!ba;s/\n/, /g' > /tmp/#{client}-database"
File.open("/tmp/#{client}-database", 'r') do |f1|  
  while line = f1.gets  
    puts line
    ec2_db_gateway=line.split(" ")
  end  
 
role(:db) { ec2_db_gateway }
end
 
# The username on the target system, if different from your local username
ssh_options[:user] = 'deploy'
 
# The path to drush
set :drush, "cd #{current_path}/#{app_root} ; /usr/local/bin/drush"
 
namespace :deploy do
 desc "Notify New Relic"
 task :newrelic do
   run_locally 'curl -H "<newrelic key>" -d "deployment[application_id]=4214617" -d "deployment[user]=`whoami`" https://rpm.newrelic.com/deployments.xml'
# end
#end
 
after "deploy",
    "deploy:newrelic"

This new script uses the AWS CLI to get a list of servers for our EC2 instances. It uses a combination of host names, and tags to identify the correct servers for the specific client. It then identifies our gateway, builds a list of web servers, and a list of servers with access to the database.

I hope other people will find this as useful as I do.

Filed under:

Comments

I love this idea, but I'm curious what you do when you need to spawn a new server. How do you get the code there? Do you have to go into the application and run 'cap deploy' again?

I would really like to spawn new servers on EC2 and have them provision the app without having to touch the application code/git repo.

Currently we do need to do a 'cap deploy' again.

I have plans to create a local environment so on boot it will run 'cap local deploy' and get the latest version of the code from a production branch.

Add new comment

Restricted HTML

  • Allowed HTML tags: <a href hreflang> <em> <strong> <cite> <blockquote cite> <code> <ul type> <ol start type> <li> <dl> <dt> <dd> <h2 id> <h3 id> <h4 id> <h5 id> <h6 id>
  • You can enable syntax highlighting of source code with the following tags: <code>, <blockcode>, <cpp>, <java>, <php>. The supported tag styles are: <foo>, [foo].
  • Web page addresses and email addresses turn into links automatically.
  • Lines and paragraphs break automatically.

About the Author

Nathan Wilkerson, Cloud Operations Manager

Nathan started building computers, programming and networking with a home IPX network at age 13. Since then he has had a love of all things computer; working in programming, system administration, devops, and Cloud Computing. Over the years he's enriched his knowledge of computers with hands on experience and earning his AWS Certified Solutions Architect – Professional.

Recently, Nathan has transitioned to a Cloud Operations Manager role. He helps clients and internal teams interface with the Cloud Team using the best practices of Kanban to ensure a speedy response and resolution to tickets.

Ready for transformation?