cooking with










... and deploying your Rails apps

Coimbra.rb 10/2013

Capistrano

What is it?

Capistrano is a utility and framework for executing commands in parallel on multiple remote machines, via SSH.

on rubygems.org


Capistrano is written in Ruby, but it can easily be used to deploy any language. 
on capistranorb.com

Disclaimer: This presentation is based on Capistrano 2.x

Features


Deploys  web applications directly from 
source control to one or multiple servers

Support for multiple version control systems
(git, svn, mercurial, cvs, ...)

Rollback code to earlier releases, with ease

Support for multiple environments 
(on 2.x via capistrano-multistage, by default on 3.x)

Getting started

cd my_awesome_app/

gem install capistrano gem install capistrano-multistage# (or just include these on your Gemfile)

capify .

deploy.rb W/ Multistage


Generic details about your app, independent 
from the target environment 

require 'capistrano/ext/multistage'

set :stages, %w(production staging)set :default_stage, "staging"set :application, "set your application name here"set :scm, :gitset :repository, "ssh://ourserver/#{application}.git"set :deploy_to, "/var/www/#{application}"set :deploy_via, :remote_cacheset :rails_env, 'production'

Environment Definitions


User, hosts, branches
e.g.: On a staging environment, we would like 
to deploy from a development branch or feature branch

set :user, "your username here"
set :branch, "desired branch to deploy from"
role :web, "your web-server here"
role :app, "your app-server here"
role :db,  "your primary db-server here", :primary => true
role :db,  "your slave db-server here"

Deploying

To deploy our latest code to the target server (already provisioned) we just type









But if this is our first deploy on the target server



Will create our folder structure on the server and deploys and starts a "cold" application 
cap <enviroment> deploy:setupcap <environment> deploy:cold 
 cap <environment> deploy

NOT ONLY FOR DEPLOY

We can also provision servers with it. 

But how?

first, some basics

Capistrano DSL

The syntax is similar to the Rake syntax:





A task should be triggered by a callback, like after or before a certain built-in or custom task


desc "Task description"
task :<task name>, roles: [<desired role(s) which should run the task >] do
  run "apt-get -y update" # Bash commands can be used like this
end
after "deploy:<task>", "<namespace>:<task name>"

MOre BASICS

Built-in tasks

deploy:setup
deploy:start
deploy:stop
deploy:symlink
deploy:update
deploy:update_code
deploy:updload

deploy
deploy:check
deploy:cleanup
deploy:cold
deploy:finalize_update
deploy:migrate
deploy:migrations
deploy:restart
deploy:rollback
deploy:rollback_code

Provisioning


1. Install dependencies 

2. Setup configuration files
    3. Create service lifecycle policies  

    4. Monitoring your stack

    Installing dependencies

    Triggered by the install task



    Run necessary installation commands
    Mind the "-y" to use non-interactive mode
    desc "Install latest stable release of nginx"
    task :install, roles: :web do
    	run "#{sudo} add-apt-repository -y ppa:nginx/stable"
    	run "#{sudo} apt-get -y update"
    	run "#{sudo} apt-get -y install nginx"
    end
    after "deploy:install", "nginx:install"
     cap <environment> deploy:install

    Setup Configuration files

    Triggered by the setup task



    Set up the requires variables for configuration



    So they can be used on the configuration file templates
     set :domain, "myawesomeapp.com"
     cap <environment> deploy:setup

    SETUP CONFIGURATION FILES

    Main Task and auxiliary methods








    Usage of templates to generate our configuration files

    desc "Setup nginx configuration for this application"
    task :setup, roles: :web do
    	template "nginx_unicorn.erb", "/tmp/nginx_conf"
        run "#{sudo} mv /tmp/nginx_conf /etc/nginx/sites-enabled/#{application}"
        run "#{sudo} rm -f /etc/nginx/sites-enabled/default"
        restart
    end
    after "deploy:setup", "nginx:setup"
    
    def template(from, to)
      erb = File.read(File.expand_path("../templates/#{from}", __FILE__))
      put ERB.new(erb).result(binding), to
    end


    Setup COnfiguration FILES

    ERB Templates
    upstream unicorn {
      server unix:/tmp/unicorn.<%= application %>.sock fail_timeout=0;
    }
    
    server {
      listen 80;
      server_name <%= domain %>;
      root <%= current_path %>/public;
    location ^~ /assets/ { gzip_static on; expires max; add_header Cache-Control public; }
    ... }

    Monitoring


    Using Monit (start/stop/restart  scripts)
    Keep track of infrastructure failures
     check process nginx with pidfile /var/run/nginx.pid
      start program = "/etc/init.d/nginx start"
      stop program = "/etc/init.d/nginx stop"
      if children > 250 then restart
      if 5 restarts within 5 cycles then timeout

    On this particular case, nginx already has a startup script, but for most of the recipes I've written, I had to create my own 



    Piece of advice: Mind the pid!

    other utilities

    Real-time server logs





    Clone production database to development

    Access Rails production console from your shell

    Run Rake tasks on production
      desc "Tail all application log files"
      task :tail, :roles => :app do
        run "tail -f #{shared_path}/log/*.log" do |channel, stream, data|
          puts "#{channel[:host]}: #{data}"
          break if stream == :err
        end
      end

    Dry-run Deployments


    I use Vagrant VM's for this

    To run the whole provisioning/deployment process

    Making sure that everything is running nice and clean

    Capistrano 3 now has a "dry-run" mode, 
    through which we can visually confirm every 
    command to be run without ever touching a server

    Pros



    Provisioning and deployment processes
    are done in similar ways

    Learning curve is quite accessible

    Fast deployments for prototyping
    (You can deploy a new app in 5 minutes or less)


    Cons



    OS/Distribution centric
    Currently I just work with Debian/Ubuntu, 
    but with some work we can support multiple distros

    Is it a good practice to keep our
    provisioning/deployment processes coupled? 

    Maintenance can be tricky on complex infrastructures

    Alternatives



    Chef

    Puppet

    Ansible

    Rubber

    Docker

    Recipes so far












    Not yet published but... It will be soon. Suggestions?

    Ongoing work


    Port current recipes to Capistrano v3
    (slightly different DSL, but could save us
    some lines of code)

    Standardize recipes to fit multiple distros 
    and package managers (yum, pacman, ...)

    Distribute as a gem?




    Thanks guys!


    Jorge Santos
    @chomponthis
    github.com/jsantos
    Made with Slides.com