Inicio > capistrano > Capistrano y multistaging

Capistrano y multistaging

Sábado, 9 de enero de 2010 Rafa García Dejar un comentario Ir a comentarios

Mientras veía caer los copos de nieve por la ventana, observando como cuajaban en el patio, me vino a la mente que este “viernes de Capistrano” tenía que ser diferente, no estaba/estoy lo suficiente inspirado para corregir bugs. Entonces he recordado que tengo una deuda pendiente con la gente que vino al taller de Capistrano en la Conferencia Rails. Hubo varios temas que quería haber tocado pero que tuve que eliminar por falta de tiempo. Así que vamos a por uno de esos temas, el multistaging.

Un ejemplo de entorno multistage podría ser el siguiente:

  • mi máquina de desarrollo
  • un servidor de aceptación
  • un servidor de producción

Supongamos que mi máquina de desarrollo la actualizo a mano y los otros dos stages los quiero hacer con Capistrano. Capistrano por defecto solo sabe “deployar” y no es capaz de diferenciar entre aceptación y producción. Para ayudarle a diferenciar entre los diferentes stages podemos seguir varias estrategias.

Vamos a empezar por el mas simple de los casos, supongamos que entre aceptación y producción lo único que cambian son los datos de los roles. Una solución muy simple podría ser añadir a nuestro deploy.rb lo siguiente:

  if ENV['DEPLOY'].upcase == 'PRODUCTION'
    puts "*** Deployando en producción"
    role :web, "www.production.com"
    role :app, "app.production.com"
    role :db,  "db.production.com", :primary => true
  else
    puts "*** Deployando en staging"
    role :web, "www.staging.com"
    role :app, "app.staging.com"
    role :db,  "db.staging.com", :primary => true
  end

El código añadido es bastante simple, si la variable de entorno DEPLOY tiene el string ‘PRODUCTION’ ejecutaremos el deploy en la máquina de producción, sino se irá a staging. La ejecución se haría de la siguiente manera:

  $ DEPLOY=production cap deploy

Para probarlo sin tener que subir toda una aplicación a nuestros servidores podemos crear una tarea simple y estúpida como esta:

  task :foo do
    run "uptime"
  end

Entonces ahora lo podemos probar de nuevo:

  $ DEPLOY=production cap foo
  *** Deployando en producción
  ...

  $ cap foo
  *** Deployando en staging
  ...

Vale, funciona pero me chirría un poco, ¿no sería mejor hacerlo en el idioma de Capistrano? -aquí es donde todos al unísono debéis decir: SÍIIIIIIII. Entonces vamos a hacer algo parecido encadenando las tareas.

Si recordáis el día del taller os expliqué que con Capistrano podíamos definir varias tareas y llamarlas una detrás de otra, eso es lo que vamos a hacer. Vamos a considerar la configuración de cada stage una tarea diferente:

  task :production do
    puts "*** Deployando en producción"
    role :web, "www.production.com"
    role :app, "app.production.com"
    role :db,  "db.production.com", :primary => true
  end

  task :staging do
    puts "*** Deployando en staging"
    role :web, "www.staging.com"
    role :app, "app.staging.com"
    role :db,  "db.staging.com", :primary => true
  end

Utilizando este método el deploy o la ejecución de tareas en un stage u otro se hace de manera mas natural(sin usar variables de entorno):

  $ cap staging foo
  * executing `staging'
*** Deployando en staging
  * executing `foo'
  * executing "uptime"
    servers: ["localhost"]
Password:
    [localhost] executing command
 ** [out :: localhost] 01:39:08 up  2:25,  5 users,  load average: 0.01, 0.07, 0.05
    command finished

Si nos fijamos en el log de Capistrano vemos que primero ejecuta la tarea “staging” y luego ejecuta la tarea “foo”, que es lo que comentaba del encadenamiento de tareas.

Esta estrategia a mi me parece muy útil cuando los cambios que se necesitan para desplegar en producción y staging son mínimos. Considerando un cambio mínimo el cambio de variables.

Sin embarbo, esta estrategia no me parece limpia si ya se cambian tareas,callbacks, etc. entre stages. En tal caso cambiaría la estrategía y usaría la gema/plugin capistrano-ext. Esta gema por dentro usa el mismo método que acabamos de utilizar pero llega un poco mas lejos.

Para usar este plugin solo hay que hacer 4 cosillas y una de ellas opcional ;-)

  1. Hacer un require de capistrano-ext
  2. Definir los stages que vamos a usar
  3. Definir el stage por defecto **OPCIONAL**
  4. Escribir nuestras recetas por cada stage dentro de config/deploy/<stage>.rb

Vamos a hacer el require y definir los stages, escribimos en el inicio de nuestro deploy.rb:

  require 'capistrano/ext/multistage'
  set :stages, %w(staging production)

No ha sido tan dificil ¿verdad?

Ahora siguiendo nuestro sencillo ejemplo vamos a escribir la receta de cada stage. En config/deploy/staging.rb escribimos:

  puts "*** Deployando en staging"
  role :web, "localhost"
  role :app, "localhost"
  role :db,  "localhost", :primary => true

Y en config/deploy/production.rb:

  puts "*** Deployando en producción"
  role :web, "localhost"
  role :app, "localhost"
  role :db,  "localhost", :primary => true

Lo probamos y vemos que funciona:

  $ cap production foo
  * executing `production'
*** Deployando en producción
    triggering start callbacks for `foo'
  * executing `multistage:ensure'
  * executing `foo'
  * executing "uptime"
    servers: ["localhost"]
Password:
    [localhost] executing command
 ** [out :: localhost] 02:22:19 up  3:08,  5 users,  load average: 0.11, 0.10, 0.07
    command finished

Si no le pongo stage falla miserablemente:

  $ cap foo
    triggering start callbacks for `foo'
  * executing `multistage:ensure'
No stage specified. Please specify one of: staging, production (e.g. `cap staging foo')

Para definir un stage por defecto entonces añadimos a nuestro deploy.rb:

  require 'capistrano/ext/multistage'
  set :stages, %w(staging production)
  set :default_stage, 'staging'

Y ahora comprobamos que usamos staging por defecto:

  $ cap foo
      triggering start callbacks for `foo'
    * executing `multistage:ensure'
  *** Defaulting to `staging'
    * executing `staging'
  *** Deployando en staging
    * executing `foo'
    * executing "uptime"
      servers: ["localhost"]
  Password:
      [localhost] executing command
   ** [out :: localhost] 02:29:17 up  3:15,  5 users,  load average: 0.03, 0.08, 0.08
      command finished

Esta última estrategia la recomiendo si hay demasiada diferencia a la hora de hacer un deploy entre los diferentes stages, por ej. si en staging tenéis montado el stack con mongrel y en producción con passenger. Otra situación podría ser cuando el deploy tiene muchas variables(además diferentes entre stages) y hace que se pierda la legibilidad de nuestro deploy.rb

Al usar esta estregia siempre procuro que las variables y tareas comunes se queden en el deploy.rb, y el resto vaya al fichero de configuración del stage. Si tuviésemos demasiadas tareas comunes las movería a otro fichero, todo sea por mantener la legibilidad. De todos modos cada cual lo puede hacer como le vaya bien :)

Nota: El deploy.rb que he usado para escribir este post solo contenía lo que os he ido indicado, para las pruebas que hemos hecho no es necesario definir la aplicación, el repositorio, tipo de scm,…

Categories: capistrano Tags: , ,
  1. Sin comentarios aún.
  1. Sin trackbacks aún.