CI/CD Con Jenkins DSL y Capistrano

<3

Pablo Fredrikson

  • DevOps Manager @ invisionapp
  • Nerd
  • 10 años con Linux

@pablokbs

Devs

  • Escribían el código
  • Probaban el código
  • Probaban todo en su entorno local

Ops

  • Compilaban el código
  • Empaquetaban el código
  • Desplegaban el código
  • Probaban todo en producción

En la prehistoria:

  • 15 desarrolladores
  • 5 servicios
  • 5 entornos
  • 10 cookbooks

En el comienzo

El horror

  • Entornos empezaron a crecer
  • Más gente empezaba a usar Jenkins
  • Más gente cometía errores
  • Jenkins se hacía más lento
  • Jefes se quejaban
  • Caos, muerte y destrucción

El ecosistema

  • Código para las aplicaciones
  • Código para crear entornos de desarrollo (docker)
  • Código para crear la infraestructura y configurar servicios externos (terraform)
  • Código para configurar servidores y mantenerlos (chef)
  • Jenkins se mantenía a mano

Job DSL Plugin

  • Groovy
  • Wiki con documentación
  • Spec tests
  • Soporte para muchos plugins

Job DSL Lang

Job DSL Plugin

job('MiTrabajo') {
  scm {
    github("git@github.com:pablokbs/myapp.git")
  }
  triggers {
    scm('*/15 * * * *')
  }
  steps {
    shell('deploy.sh')
  }
}

Ejemplo simple

Job DSL Plugin

import groovy.json.jsonSlurper

def project = 'pablokbs/myapp'
def github = 'https://api.github.com'
def api = new URL("${github}/repos/${project}/branches")
def branches = new JsonSlurper().parse(api.newReader())

branches.each {
  def branchName = it.name
  job {
    name "${project}-${branchName}".replaceAll('/','-')
    scm {
      git("git://github.com/${project}.git", branchName)
    }
    steps {
      shell("deploy.sh ${project} ${branchName}")
    }
  }
}

Ejemplo más complejo

Job DSL Plugin

Integración github

Muuuuuuchos trabajos

1225 trabajos

Desventajas

  • Nuevo lenguaje, no es muy fácil
  • Se puede complicar
  • No tiene soporte para el 100% de los plugins

1200 trabajos son muchos trabajos

jenkins-master:~# docker -H tcp://127.0.0.1:3241 ps | wc -l
64

1200 trabajos son muchos trabajos

Capistrano

Capistrano

├── current -> /var/www/myapp/releases/20150120114500/
├── releases/
│   ├── 20150080072500
│   ├── 20150090083000
│   ├── 20150100093500
│   ├── 20150110104000
│   └── 20150120114500
├── revisions.log
└── shared/
    └── myapp.conf
    └── temp/
        └── plugins/
        └── uploads/

Capistrano

# basic details
set :application, 'app_name'
set :deploy_user, 'jenkins'

# setup repo details
set :scm, :git
set :repo_url, 'git@github.com:pablokbs/myapp.git'

# how many old releases do we want to keep
set :keep_releases, 5

# files we want symlinking to specific entries in shared.
set :linked_files, %w{config/database.yml}

# dirs we want symlinking to shared
set :linked_dirs, %w{bin log tmp/pids tmp/cache tmp/sockets}

namespace :deploy do
  on roles(:web) do |host|
    execute "sudo service nginx reload"
    info "Nginx reloaded on #{host.hostname}"
  end
end

Capistrano

$ cap production deploy
$ cap production deploy:rollback
Running /usr/bin/env mkdir -p /var/myapp/releases/20160421215335 
Running tar xf /var/myapp/shared/129.tgz -C /var/myapp/releases/20160421215335 
Running /usr/bin/env echo "129.tgz" >> REVISION 
Running /usr/bin/env mkdir -p /var/myapp/releases/20160421215335 
Running /usr/bin/env ln -s /var/myapp/shared/.env /var/myapp/releases/20160421215335/.env 
Running /usr/bin/env ln -s /var/myapp/releases/20160421215335 /var/myapp/releases/current 
Running sudo service myapp restart 
Running echo "Branch master deployed as release 20160421215335 by jenkins" >> revisions.log

Capistrano

s3://yourbucket/somedirectory/
                  |- 201506011200.zip
                  |- 201506011500.zip
                  ...
                  |- 201506020100.zip
                  `- 201506030100.zip
gem 'capistrano-elb-mgmt'
gem 'capistrano-s3_archive'

Plugins

Capistrano

gem 'chef'

Plugins

# Grab the servers

q = "roles:#{fetch(:application)} \
     AND roles:#{fetch(:chef_role)} \
     AND chef_environment:#{fetch(:environment)}"

servers = query.search(
                        :node,
                        q,
                        :filter_result =>
                          {
                            :hostname => ["hostname"],
                            :ip_address => ["ipaddress"],
                            :roles => ["roles"]
                          }
                        ).first

Capistrano

gem 'dogapi'

Plugins

Flow

  • PR en Github dispara el test en Jenkins
  • Merge
  • Se dispara el trabajo en Jenkins (compilación)
  • Jenkins envia el zip a S3 y dispara deploys
  • Capistrano busca en chef por los nodos y ejecuta el deploy
  • Capistrano baja el zip de S3 y crea el release en cada nodo en paralelo
  • Se quita el nodo del balanceador
  • Se reinician los servicios
  • Se agrega el nodo al balanceador

Resultados

Gracias

¿Preguntas?

@pablokbs

@invisionapp

slides.com/pablokbs

Made with Slides.com