Como configurar Buildbot
En el post anterior sobre django-yabe escribí sobre que es la Continuos Integration y como, de las distintas herramientas que existen, mi preferida para el caso era Buildbot.
Buildbot esta programado en Python y fue presentado en la PyCon 2003 de Estados Unidos. Desde entonces cada commit que se hace al SVN de Python dispara un proceso que compila el core del lenguaje y corre los tests cases de la stdlib en todos los sistemas operativos soportados. En caso de error notifica a quien halla hecho el commit y aparte lo publica en una pagina web.
Para poder realizar la compilación en varios sistemas operativos Buildbot se separa entre un servicio “Maestro” y muchas instancias “Esclavos”.
La instancia “Maestro” tiene varias responsabilidades:
- Saber cuando hay que iniciar el proceso de build.
- Avisarle a los nodos “Esclavos” que hay que realizar el build.
- Darle a estos las instrucciones de como realizar el build.
- Notificar del resultado del mismo.
En cambio las instancias “Esclavo” son mucho mas bobas: No llevan configuración sobre como compilar el código ni cuando deben hacerlo: Reciben notificaciones del maestro con esta información.
Este es un gráfico (tomado de la documentación oficial) que ilustra esta arquitectura:

Instalación
La instalación es muy fácil, hay tres opciones:
Usando setuptools:
easy_install buildbot
Utilizando el package manager de nuestra distribución (Recomendada):
# Gentoo
emerge buildbot
# Debian y Ubuntu
apt-get install buildbot
# Arch
yaourt -S buildbot
Siguiendo la guiá de instalación en la documentación oficial.
En cualquiera de los tres casos es necesario tener instalado Twisted (el framework para aplicaciones de red asincronicas en Python) y Zope-Interface.
Al finalizar la instalación es necesario crear un directorio donde vallamos a crear las instancias maestro y esclavo del Buildbot:
bitfactory ~ # mkdir /var/lib/buildbot
Configuración del Maestro:
En el diagrama que mostraba la arquitectura vimos que el Maestro prácticamente hace todo, de hecho los esclavos no llevan configuración (solamente se les dice a que maestro deben conectarse).
Por esto es que primer configuramos la instancia del maestro: La instalación de buildbot nos dio un nuevo comando “buildbot” con una serie de opciones, la que nos interesa ahora es “create-master”:
bitfactory ~ # buildbot create-master /var/lib/buildbot/django-yabe-master
mkdir /var/lib/buildbot/django-yabe-master
chdir /var/lib/buildbot/django-yabe-master
creating master.cfg.sample
populating public_html/
creating Makefile.sample
buildmaster configured in /var/lib/buildbot/django-yabe-master
Si miramos /var/lib/buildbot/django-yabe-master veríamos algo así:
bitfactory ~ # ls -l /var/lib/buildbot/django-yabe-master
total 20
-rw-r--r-- 1 root root 434 Nov 20 16:21 Makefile.sample
-rw-r--r-- 1 root root 736 Nov 20 16:21 buildbot.tac
-rw------- 1 root root 7471 Nov 20 16:21 master.cfg.sample
drwxr-xr-x 2 root root 4096 Nov 20 16:21 public_html
El archivo Makefile es legacy de cuando Buildbot se iniciaba y paraba utilizando make. El archivo buildbot.tac es utilizado por Twisted para iniciar el servicio (no hace falta que lo editemos), y master.cfg.example es un archivo de configuración de ejemplo, yo no lo voy a utilizar porque es muy completo, y prefiero algo mas minimalista que se ajuste a lo que necesito, pero es bueno como referencia.
Así es que creamos un nuevo archivo master.cfg:
bitfactory ~ # vim /var/lib/buildbot/django-yabe-master/master.cfg
(Si no les gusta vim usen su editor de texto preferido para editarlo)
Y empezamos. La primer linea es medio confusa y merece aclaración:
c = BuildmasterConfig = {}
Ahí estamos creando un diccionario de Python accesible a través de dos nombres “BuildmasterConfig” y “c”. Cuando Buildbot lea la configuración va a buscar “BuildmasterConfig”, pero nosotros podemos ahorrar letras guardando los valores que queramos en “c”.
Siguiendo con la configuración:
from buildbot.buildslave import BuildSlave
c["slaves"] = [BuildSlave("Esclavo01", "sarasa")]
c["slavePortnum"] = 4484
Ahí definimos una lista con los esclavos y definimos el puerto donde el maestro va a esperar por conexiones de estos.
Lo que sigue es lograr que Buildbot se entere cuando hay una actualización en el repositorio:
from buildbot.changes.pb import PBChangeSource
c["change_source"] = PBChangeSource()
PBChangeSource utiliza el puerto que se declaro para escuchar respuestas de los esclavos para escuchar por “avisos” de que se cambio el código fuente. Estos avisos se pueden enviar con el comando buildbot sendchange, pero también se pueden configurar hooks en Mercurial o Subversion que avisen al Buildbot de cambios en el repositorio.
Con esa configuración Buildbot ya sabe cuando se cambio el código fuente, ahora tenemos que decirle cuando queremos que lo compile:
from buildbot.scheduler import Scheduler
c["schedulers"] = []
c["schedulers"].append(
Scheduler(
name="EveryCommit",
branch=None,
treeStableTimer=5,
builderNames=["Builder01"]
)
)
Un scheduler define cuando debe hacerse la compilación y quienes deben hacerla. En este caso en particular le estamos diciendo que el Scheduler llamado “EveryCommit” va a ver los cambios que se le hagan al trunk (branch=None), y cuando halla un cambio en el repositorio y no halla habido cambios en los últimos 5 segundos va a hacer que el builder “Builder01” haga un build.
Ya tenemos definidos quienes van a compilar (el esclavo que definimos), y cuando van a hacerlo (cuando le avisemos al maestro), ahora tenemos que definir que es lo que van a hacer.
from buildbot.process import factory
from buildbot.steps.source import Mercurial
from buildbot.steps.shell import ShellCommand
f1 = factory.BuildFactory()
f1.addStep(Mercurial, baseURL="/var/lib/hg/django-yabe/", defaultBranch="", mode="clobber")
f1.addStep(ShellCommand(command=["./manage.py", "test"]))
Ahí definimos un Factory: una serie de instrucciones sobre como compilar el código.
En el caso de un proyecto en Django es muy simple: Se baja el código y corre “./manage.py test”.
Para bajarse el código le decimos que tipo de repositorio es y los parámetros que necesita para hacer el checkout, hay una lista completa con los SCM y sus parámetros en la documentación de buildbot.
Sobre este primer paso hay un parámetro que merece una mención especial: mode=”clobber”
Mode no es un parámetro que utiliza el SCM, si no una instrucción que le damos a Buildbot para que reutilice o no los viejos checkouts. En este caso le estamos diciendo “clobber” que para Buildbot significa “cada ves que recibo la instrucción de hacer un build borro mi directorio de trabajo y hago un checkout limpio”.
El segundo paso se explica solo: Ejecuta el comando “./manage.py” con el parámetro “test”, si lleva mas de un parámetro seguimos agregando elementos a la lista command.
Cada Factory va dentro de un Builder:
b1 = {
"name": "Builder01",
"slavename": "Esclavo01",
"builddir": "django-yabe",
"factory": f1
}
c["builders"] = [b1]
Los Builders asocian Factory con esclavos. Esto es porque el Factory necesario para compilar nuestro código en Windows puede ser distinto que en Linux, pero por ahí hay algunos Builds que se tienen que hacer siempre igual en todas las plataformas. Eh ahí la flexibilidad que nos da la herramienta.
Lo único que nos falta es “como avisar” si algo sale mal:
c["status"] = []
from buildbot.status.html import WebStatus
c["status"].append(WebStatus(http_port=8101))
Listo, con eso le dijimos que levante un webserver en el puerto 8101 donde va a estar la información sobre los distintos builds.
Finalmente necesitamos algo de información sobre el proyecto:
c["projectName"] = "Django-yabe"
c["projectURL"] = "http://bitbucket.org/cuerty/django-yabe/"
c["buildbotURL"] = "http://cuerty.com:8101/"
Con todo esto tenemos configurado el buildmaster. Con el siguiente comando levantamos el mismo para probarlo:
bitfactory ~ # buildbot start /var/lib/buildbot/django-yabe-master/
/usr/lib64/python2.6/site-packages/twisted/internet/_sslverify.py:4: DeprecationWarning: the md5 module is deprecated; use hashlib instead
import itertools, md5
Following twistd.log until startup finished..
/usr/lib64/python2.6/site-packages/buildbot/scripts/logwatcher.py:48: PotentialZombieWarning: spawnProcess called, but the SIGCHLD handler is not installed. This probably means you have not yet called reactor.run, or called reactor.run(installSignalHandler=0). You will probably never see this process finish, and it may become a zombie process.
env=os.environ,
/usr/lib64/python2.6/site-packages/twisted/persisted/sob.py:12: DeprecationWarning: the md5 module is deprecated; use hashlib instead
import os, md5, sys
/usr/lib64/python2.6/site-packages/twisted/python/filepath.py:12: DeprecationWarning: the sha module is deprecated; use the hashlib module instead
import sha
/usr/lib64/python2.6/site-packages/twisted/mail/smtp.py:32: DeprecationWarning: the MimeWriter module is deprecated; use the email package instead
import MimeWriter, tempfile, rfc822
2009-11-20 17:16:58-0800 [-] Log opened.
2009-11-20 17:16:58-0800 [-] twistd 8.1.0 (/usr/bin/python2.6 2.6.2) starting up
2009-11-20 17:16:58-0800 [-] reactor class:
2009-11-20 17:16:58-0800 [-] Creating BuildMaster -- buildbot.version: 0.7.11p3
2009-11-20 17:16:58-0800 [-] loading configuration from /var/lib/buildbot/django-yabe-master/master.cfg
2009-11-20 17:16:58-0800 [-] adding new builder Builder01 for category None
2009-11-20 17:16:58-0800 [-] trying to load status pickle from /var/lib/buildbot/django-yabe-master/django-yabe/builder
2009-11-20 17:16:58-0800 [-] no saved status pickle, creating a new one
2009-11-20 17:16:58-0800 [-] added builder Builder01 in category None
2009-11-20 17:16:58-0800 [-] adding IStatusReceiver
2009-11-20 17:16:58-0800 [-] twisted.web.server.Site starting on 8101
2009-11-20 17:16:58-0800 [-] Starting factory
2009-11-20 17:16:58-0800 [-] WebStatus using (/var/lib/buildbot/django-yabe-master/public_html)
2009-11-20 17:16:58-0800 [-] adding 1 new schedulers, removed 0
2009-11-20 17:16:58-0800 [-] notifying downstream schedulers of changes
2009-11-20 17:16:58-0800 [-] adding 1 new changesources, removing 0
2009-11-20 17:16:58-0800 [-] twisted.spread.pb.PBServerFactory starting on 4484
2009-11-20 17:16:58-0800 [-] Starting factory
2009-11-20 17:16:58-0800 [-] BuildMaster listening on port tcp:4484
2009-11-20 17:16:58-0800 [-] configuration update started
2009-11-20 17:16:58-0800 [-] configuration update complete
The buildmaster appears to have (re)started correctly.
¡Errores! No hay que preocuparnos. La mayoría de lo que vemos es porque parte del código de Twisted imprime DeprecationWarning dado que no se actualizo para Python 2.6. Si vamos a http://127.0.0.1:8101 (o la dirección de donde este el buildbot) vemos una pagina que dice “Welcome to the Buildbot!” y nos da distintas opciones, una es el waterfall es quizá la mas interesante, donde vamos a poder ver si falla algo, o si todo esta perfecto.
Esto fue la configuración del maestro, ahora hagamos un esclavo que se conecte a este.
Configuración del Esclavo:
bitfactory ~ # buildbot create-slave /var/lib/buildbot/django-yabe-slave 127.0.0.1:4484 Esclavo01 sarasa
mkdir /var/lib/buildbot/django-yabe-slave
chdir /var/lib/buildbot/django-yabe-slave
creating Makefile.sample
mkdir /var/lib/buildbot/django-yabe-slave/info
Creating info/admin, you need to edit it appropriately
Creating info/host, you need to edit it appropriately
Please edit the files in /var/lib/buildbot/django-yabe-slave/info appropriately.
buildslave configured in /var/lib/buildbot/django-yabe-slave
Listo, tenemos el esclavo configurado. Los parámetros que le pasamos son para que sepa donde conectarse:
- 127.0.0.1 es la IP del Buildmaster
- 4484 es el puerto que configuramos en el Buildmaster para escuchar conexiones de los esclavos.
- El usuario (Esclavo01) y password (sarasa) son los parámetros que le dimos al BuildSlave en el master.cfg.
Ahora puedo arrancar el buildslave:
bitfactory ~ # buildbot start /var/lib/buildbot/django-yabe-slave
/usr/lib64/python2.6/site-packages/twisted/internet/_sslverify.py:4: DeprecationWarning: the md5 module is deprecated; use hashlib instead
import itertools, md5
Following twistd.log until startup finished..
/usr/lib64/python2.6/site-packages/buildbot/scripts/logwatcher.py:48: PotentialZombieWarning: spawnProcess called, but the SIGCHLD handler is not installed. This probably means you have not yet called reactor.run, or called reactor.run(installSignalHandler=0). You will probably never see this process finish, and it may become a zombie process.
env=os.environ,
ERR: 'tail: cannot open `twistd.log' for reading: No such file or directory
'
/usr/lib64/python2.6/site-packages/twisted/persisted/sob.py:12: DeprecationWarning: the md5 module is deprecated; use hashlib instead
import os, md5, sys
/usr/lib64/python2.6/site-packages/twisted/python/filepath.py:12: DeprecationWarning: the sha module is deprecated; use the hashlib module instead
import sha
The buildmaster took more than 10 seconds to start, so we were unable to
confirm that it started correctly. Please 'tail twistd.log' and look for a
line that says 'configuration update complete' to verify correct startup.
¡Mas errores! En principio son parecidos a los anteriores, pero al final no dice que “inicio el servicio”, si no que no sabe si inicio, y que deberíamos hacer un tail del log para ver si arranco bien, así que hacemos eso:
bitfactory ~ # tail /var/lib/buildbot/django-yabe-slave/twistd.log
2009-11-20 17:40:38-0800 [-] Log opened.
2009-11-20 17:40:38-0800 [-] twistd 8.1.0 (/usr/bin/python2.6 2.6.2) starting up
2009-11-20 17:40:38-0800 [-] reactor class:
2009-11-20 17:40:38-0800 [-] Starting factory
2009-11-20 17:40:38-0800 [Broker,client] message from master: attached
2009-11-20 17:40:38-0800 [Broker,client] SlaveBuilder.remote_print(Builder01): message from master: attached
2009-11-20 17:40:38-0800 [Broker,client] sending application-level keepalives every 600 seconds
Lo que nos importa aca es el mensaje “message from master: attached”, eso quiere decir que funciono. Si volvemos a ver el waterfall del buildmaster vamos a ver que el buildslave se conecto:
connect
Esclavo01
¡Excelente! Tenemos un buildbot configurado, ¿Ahora que? Lo integramos con Mercurial.
Configurando Mercurial
Yo tengo en /var/lib/hg/django-yabe un clone del repositorio del proyecto, voy a configurar para que cualquier commit a ese repositorio dispare un build en el buildbot.
bitfactory ~ # cd /var/lib/hg/django-yabe
bitfactory django-yabe # vim .hg/hgrc
(Devuelta, no usen vim si no quieren, elijan la herramienta que prefieran)
Agrego las siguientes lineas:
[hooks]
changegroup.buildbot = python:buildbot.changes.hgbuildbot.hook
[hgbuildbot]
master = 127.0.0.1:4484
Las primeras dos un hook que se activa ante un push o un commit a este repositorio. Las ultimas dos son la configuración para ese hook, ahí tenemos configurada la IP y el puerto del buildmaster.
Con eso basta para hacer que Mercurial se comunique con el buildbot.
El clone del repositorio que tengo en /var/lib/hg/django-yabe esta ahí para que lo acceda remotamente (utilizando el servidor web), solamente para simular una prueba voy a hacer un clone local del repositorio y probarlo:
bitfactory ~ # hg clone /var/lib/hg/django-yabe django_yabe
Pongo mi proyecto dentro del directorio django_yabe y “programo” hasta que “./manage.py test” corra sin problemas:
bitfactory django_yabe # ./manage.py test
Creating test database...
----------------------------------------------------------------------
Ran 0 tests in 0.000s
OK
Destroying test database...
Entonces agrego los archivos al repositorio:
bitfactory django_yabe # hg add __init__.py manage.py settings.py urls.py
Commiteo a mi repositorio local:
bitfactory django_yabe # hg commit
Y finalmente hago un push al repositorio en /var/lib/hg/django-yabe:
bitfactory django_yabe # hg push
pushing to /var/lib/hg/django-yabe
searching for changes
adding changesets
adding manifests
adding file changes
added 1 changesets with 4 changes to 4 files
/usr/lib64/python2.6/site-packages/twisted/spread/pb.py:64: DeprecationWarning: the md5 module is deprecated; use hashlib instead
import md5
rev fe9688cab329fb43eb6169a541c663dac8c134b5 sent
change sent successfully
Las lineas interesantes son las siguientes:
rev fe9688cab329fb43eb6169a541c663dac8c134b5 sent
change sent successfully
Eso quiere decir que Mercurial le aviso al buildbot sobre el cambio, si voy a ver el Waterfall de nuevo:
django-yabe
last build build
successful
Se puede editar el css del buildmaster para hacer un poco mas amigable la vista a través del Waterfall, el mismo esta en /var/lib/buildbot/django-yabe-master/public_html/buildbot.css (En mi caso, reemplacen el path en el suyo).
Eso es todo, ya hay un build por cada push que hagamos al repositorio de Mercurial. Despues un detalle extra que se puede agregar al master.cfg es que aparte de notificar en el waterfall nos envie un reporte de los builds que fallan por email:
c["status"].append(
MailNotifier(
fromaddr="buildbot@cuerty.com",
extraRecipients=[“cuerty@gmail.com”],
sendToInterestedUsers=False,
mode="failing"
)
)
mode=”failing” significa que solamente me envie un email cuando el build falla.
Esto fue todo por ahora, espero que en lo próximo que empiece a trabajar ya sea el código del blog.
Noviembre 22nd, 2009 at 23:24
Excelente documentacion, hacia falta.