λ Jonas Gorauskas
λ Jonas Gorauskas

Reputation: 6198

Rolling Deployment of Java/Tomcat application using Capistrano

I am using Capistrano 2.15.5 to deploy Java web applications to Tomcat application servers. For the time being, we are tied and committed to this version of Capistrano and we cannot upgrade to v3. The deploy task will call on the other tasks to get executed in parallel on all servers in the role. This works as expected and has been working well for a while.

Now we need to implement a Rolling Deployment when the tasks are executed on one server at a time. So, based on the code below, we would execute shutdown_tomcat, download_bits, deploy_bits, start_tomcat on prdapp01 first and then prdapp02 and so on... So that only one server in the cluster is offline at any given time, thus maximizing our uptime.

What is the best way to implement this Rolling Deployment strategy with Capistrano?

This is what a paired-down version of my Capistrano script looks like currently:

task :production do
  role :app,   "deployuser@prdapp01", "deployuser@prdapp02", "deployuser@prdapp03"
  # ...
end

task :deploy do
  shutdown_tomcat
  download_bits
  deploy_bits
  start_tomcat
end

task :shutdown_tomcat, :roles => :app do
  run "sudo /sbin/service tomcat stop || exit 1;"
end

task :download_bits, :roles => :app do
  run <<-EOS
    [ -f /tmp/app_download ] && rm -rf /tmp/app_download;
    mkdir -p /tmp/app_download;
    cd /tmp/app_download;
    wget -q https://internal.server/path/to/application.war || exit 1;
  EOS
end

task :deploy_bits, :roles => :app do
  run <<-EOS
    cd /tmp/app_download;
    unzip -q -o /tmp/app_download/application.war -d /usr/local/tomcat/webapps/ || exit 1;
    exit 0;
  EOS
end

task :start_tomcat, :roles => :app do
  run "sudo /sbin/service tomcat start || exit 1;"
end

Upvotes: 2

Views: 2453

Answers (1)

λ Jonas Gorauskas
λ Jonas Gorauskas

Reputation: 6198

I believe that I came up with a reasonable solution to the Rolling Deployment strategy I described above. First, I pass in to Capistrano an argument telling it what deployment type I want to perform: Full or Rolling. Then I follow this process:

  1. Capture the server list from the appropriate role into another list.
  2. Iterate over the new list.
  3. Reset the original role to the value of the current server.
  4. Apply all the tasks to that server alone
  5. Leather, rinse, repeat...

Here are the relevant changes to the deploy task:

task :deploy do
  deploy_type = fetch(:deploytype).downcase
  case deploy_type
    when 'full' then
      shutdown_tomcat
      download_bits
      deploy_bits
      start_tomcat
    when 'rolling' then
      server_list = roles[:app].servers    # get the original servers
      server_list.each do |current_server| # apply other tasks in series
        roles[:app].servers = []           # reset original server list
        role :app, "#{current_server}"     # otherwise you'll append to it
        shutdown_tomcat
        download_bits
        deploy_bits
        start_tomcat
      end                                  # each loop
    else
      puts 'Invalid deployment type'
      exit
  end                                      # case deploy_type
end                                        # task :deploy

Then you can invoke it with the following command:

$ cap -f deploy.cap -s deploytype=rolling production deploy

Feedback is appreciated!

Upvotes: 2

Related Questions