4Trabes Historias de una empresa en 100 metros cuadrados

El blog de Trabe Soluciones

Instalando CruiseControl.rb, una herramienta de Integración continua para aplicaciones Ruby on Rails

|

CruiseControl.rb es el porte a Ruby on Rails de CruiseControl, una conocida herramienta de integración continua, que utilizamos en Trabe Soluciones. En este post os mostraré, a modo de ejemplo, como está montado en nuestras oficinas.

Integración Continua

Martin Fowler define la integración continua (Continuous Integration en inglés) como:

… a software development practice where members of a team integrate their work frequently, usually each person integrates at least daily – leading to multiple integrations per day. Each integration is verified by an automated build (including test) to detect integration errors as quickly as possible. Many teams find that this approach leads to significantly reduced integration problems and allows a team to develop cohesive software more rapidly…

es decir, un proceso automático que verifica que los cambios introducidos en el repositorio de trabajo por los desarrolladores son correctos.

CruiseControl.rb consta de dos partes básicas. Un grupo de builders y una aplicación web. Un builder es un proceso en segundo plano que monitoriza el repositiorio de nuestro proyecto y ante un cambio (una nueva versión del código) ejecuta una acción de Rake, guardando un log y notificando el éxito o fallo de la misma. La aplicación web, por su parte nos permite ver estos logs y forzar nuevos builds. Para entenderlo, lo mejor es jugar con la herramienta.

Montando la herramienta

CruiseControl.rb debe instalarse en un sistema que disponga de Ruby, y sus herramientas asociadas: RDoc, Rake, RubyGem, etc. Además para poder monitorizar aplicaciones Rails debemos instalar los gems de Rails y aquellos gems requeridos por dichas aplicaciones.

Instalando CruiseControl.rb

Para abreviar, pondré los comandos bash necesarios. Son fáciles de seguir. En caso de duda: Google

1
2
3
4
5
6
7
8
9
10
[root@localhost]$ adduser trabe
[root@localhost]$ passwd trabe
[root@localhost]$ su - trabe
[trabe@localhost]$ wget http://rubyforge.org/frs/download.php/19237/cruisecontrolrb-1.1.0.tgz
[trabe@localhost]$ tar xvfz  cruisecontrolrb-1.1.0.tgz
[trabe@localhost]$ mkdir .versions
[trabe@localhost]$ mv cruisecontrolrb-1.1.0 .versions
[trabe@localhost]$ ln -s .versions/cruisecontrolrb-1.1.0 cruisecontrolrb
[trabe@localhost]$ cd cruisecontrolrb
[trabe@localhost]$ ./cruise start

Si todo ha ido bien el servidor arrancará y en el puerto 3333 de la máquina con un navegador veréis una pantalla como la que sigue. Crear un directorio de versiones y un enlace simbólico no es obligatorio (pero es algo que nosotros acostumbramos a hacer para facilitar las actualizaciones del software).

Screenshot de CruiseControl.rb primer arranque

CruiseControl.rb como servicio del sistema

Para este paso necesitamos tener instalado el gem de Mongrel en nuestro sistema (gem install mongrel -y), ya que los scripts que nos ofrece CruiseControl.rb requieren este servidor.

1
2
[trabe@localhost]$ cp daemon/cruise.sample daemon/cruise
[trabe@localhost]$ vi daemon/cruise

Editamos el fichero daemon/cruise (con vi, emacs, nedit o cualquier otro, al gusto) y realizamos algunos cambios. Buscamos el fragmento:

1
2
3
4
5
def cruise_path
  # NOTE: you must specify $CCRB_HOME here
  # "/home/gigix/Projects/cc.rb"
  raise "Please specify your $CCRB_HOME path in this method."
end

… y, como nos insta, lo cambiamos por:

1
2
3
def cruise_path
  "/home/trabe/cruisecontrolrb"
end

Añadimos, a mayores, al comienzo del fichero la información necesaria para que la herramienta chkconfig funcione correctamente. Los runlevels y las prioridades al gusto y adaptados al sistema.

1
2
3
#!/usr/bin/env ruby
# chkconfig: 35 66 34
# description: Controla el servicio de CruiseControl.rb

Opcionalmente podemos modificar la línea que invoca al servidor para que utilice el usuario trabe. De otro modo utilizará root por defecto y eso queda feo, ¿no creeis?.

1
2
3
4
when 'start'
  cd cruise_path
  system "./cruise start -d"
  exit 0

… pasa a ser …

1
2
3
4
when 'start'
  cd cruise_path
  system "su trabe -c '#{cruise_path}/cruise start -d'"
  exit 0

Sólo queda instalar el servicio y arrancarlo por primera vez.

1
2
3
4
[trabe@localhost]$ exit
[root@localhost]$ ln -s /home/trabe/daemon/cruise /etc/init.d/cruise
[root@localhost]$ chkconfig --add cruise
[root@localhost]$ service cruise start

Añadiendo proyectos

Para añadir un proyecto, por ejemplo Queue.it, debemos utilizar la herramienta cruise.

1
[trabe@localhost]$ ./cruise add QueueIt -u svn://svn/projects/queueit

Esto generará un directorio /home/trabe/cruisecontrolrb/projects/QueueIt con el proyecto. Sus contenidos relevantes son los siguientes:

  • work: Copia de trabajo local del proyecto (a partir de la copia remota en SVN)
  • build-*: Logs de cada build
  • cruise_config.rb: Configuración de build del proyecto.

Respecto a la configuración, ya que en principio será idéntica para todos los proyectos, lo que haremos será mantener un fichero de configuración común.

1
2
3
4
5
6
7
# /home/trabe/cruisecontrolrb/projects/cruise_config.rb

Project.configure do |project|
  # Hacer ping a Subversion buscando nuevas versiones cada 30 minutos 
  # (por defecto son 30 segundos)
  project.scheduler.polling_interval = 30.minutes
end

Cada nuevo proyecto utilizará este fichero, a menos que se decida que debe tener una configuración propia.

1
2
3
[trabe@localhost]$ cd QueueIt
[trabe@localhost]$ rm cruise_config.rb
[trabe@localhost]$ ln -s ../cruise_config.rb cruise_config.rb

El dashboard

Una vez añadido el proyecto, se creará el primer build y podremos ver sus resultados en el dashboard.

Screenshot de redultados de un build

Screenshot de redultados de un build

En adelante, cada cambio que haya en el SVN será detectado y se generará un nuevo build. También es posible forzar la creación de estos builds desde el dashboard. Los resultados del proceso pueden obtenerse mediante un feed RSS.

Modificando la tarea de build por defecto

CruiseControl.rb reacciona ante las nuevas versiones del código en el repositorio intentando ejecutar, por este orden:

1. La tarea de Rake definida en el fichero cruise_control.rb

1
project.rake_task = 'custom'

2. Una tarea cruise definida en el Rakefile del proyecto

1
task :cruise => ['test'] do; end

3. Ejecuta la tarea cc:build que CruiseControl.rb inyecta en nuestros proyectos de modo transparente mediante un wrapper no intrusivo. Esta tarea por defecto es equivalente a una tarea cruise tal que :

1
task :cruise => ['db:test:purge', 'db:migrate', 'test'] do; end

En nuestro caso, existe un comportamiento por defecto de todos nuestros proyectos y no queremos, por tanto, tener que escribir en cada uno de ellos una tarea cruise personalizada o configurar la tare a ejecutar. Lo que vamos a hacer es modificar la tarea cc:build con nuestro comportamiento adicional. Añadiremos el siguiente código debajo de la línea 38 del fichero /home/trabe/cruiserb/tasks/cc_build.rake:

1
2
3
4
5
6
7
      CruiseControl::invoke_rake_task 'stats'

      if Rake.application.lookup('trabe:doc')
        CruiseControl::invoke_rake_task 'trabe:doc'
      else
        raise "'trabe:doc' task not found. Cannot build the documentation"
      end

que es equicalente a tener en cada proyecto monitorizado una tarea Rake tal que:

1
task :cruise => ['stats', 'trabe:doc', 'db:test:purge', 'db:migrate', 'test'] do; end

Es decir, lo primero que haremos siempre, será generar las estadísticas de código (que aparecerán en el log del dashboard, para vergüenza de aquellos que descuiden los tests) y la documentación RDoc. Ésto lo hacemos a través de una tarea que tienen todos nuestros proyectos Rails definida en $RAILS_APP_ROOT/lib/tasks/trabe.rake, que genera la documentación con nuestra propia plantilla y en UTF-8 (para aquellos proyectos con documentación en castellano):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
namespace :trabe do
  #desc "Creates the RDOC with UTF-8encoding and Trabe template"
  Rake::RDocTask.new("doc") do |rdoc|

    rdoc.rdoc_dir = 'doc/queueit_doc'
    rdoc.title    = "Queue.it Rdoc"
    rdoc.template = 'trabe'
    rdoc.options << '--charset' << 'utf-8'
    rdoc.options << '--line-numbers'
    rdoc.options << '--inline-source'
    rdoc.options << '--all'

    rdoc.rdoc_files.include('doc/README')
    rdoc.rdoc_files.include('app/**/*.rb')
    rdoc.rdoc_files.include('lib/**/*.rb')
    rdoc.rdoc_files.exclude('lib/fcgi_handler.rb')
  end
end

Truco: Ya que hemos generado la documentación, podemos aprovecahr para publicarla a través de un servidor web (apache en nuestro caso). La configuración es sencilla y la vamos a obviar porque el post ya se está haciendo interminable.

Monitorizando los proyectos

En Trabe monitorizamos los builds a través del dashboard o con un agregador de feeds RSS. Aunque es posible configurar CruiseControl.rb para recibir correos, en Trabe preferimos las otras opciones y evitamos “autoespamearnos”.

Mejoras futuras

Si el tiempo lo permite intentaremos realizar mejoras que aprovechen al máximo la infraestructura, como pueden ser:

  • Integrar CruiseControl.rb en nuestra aplicación de intranet, donde gestionamos toda la información de los proyectos (tareas, tiempos, etc).
  • Desplegar versiones de prueba de las aplicaciones en un servidor al efecto utilizando Capistrano con cada build.

Conclusiones

CruiseControl.rb es una herramienta muy sencilla que todavía puede mejorar mucho, pero su funcionamiento actual es más que suficiente para satisfacer una necesidad real dentro del proceso de desarrollo del software: la integración continua.

Cabe destacar que CruiseControl.rb permite monitorizar proyectos que utilizan otras herramientas a parte de Rake. Es posible, por ejemplo, monitorizar proyectos Java que utilicen Ant o Maven. El control de proyectos no Rails exige un esfuerzo adicional de configuración, pero merece la pena intentar utilizar una única herramienta para controlar todos los proyectos de la organización, sea cual sea la tecnología que uses.

Echadle un vistazo y sacad vuestras propias conclusiones. Sólo necesitais diez minutos para ponerlo en marcha y probar con un par de proyectos.

Lo sentimos, pero los comentarios están cerrados