Contenido

Integración contínua: BuildBot

Como prometí en el artículo anterior, es hora de presentar otra alternativa para la Integración Contínua (CI, o Continuous Integration).

No es la única alternativa, pero yo sólo he trasteado con Jenkins y con BuildBot.

Buildbot

Maestro/Esclavo

De la misma manera que Jenkins, BuildBot tiene una arquitectura Maestro/Esclavo. Sin embargo, Jenkins nos gestionaba la instalación de los esclavos, mientras que BuildBot no lo hará. En general, es menos amigable que Jenkins.

Cuando comenzamos un proyecto hay que crear el maestro y después crear los esclavos. Este es un proceso sencillo que veremos mediante un ejemplo.

Instalando

El maestro y el esclavo son paquetes debian separados. Eso nos permite instalarlos sólo allí donde hacen falta. En el ejemplo se considerará que ambos están en la misma máquina, aunque no tendría por qué.

Se puede ejecutar en casi cualquier parte, ya que está escrito en python. Basta con tener el intérprete instalado en la máquina.

El ejemplo

Y como ejemplo, trataré de realizar lo mismo que ya hicimos en Jenkins: Vamos a configurar pyDoubles para ejecutarlo con BuildBot.

Creando el esclavo

Comienzo por el esclavo porque no vamos a configurar nada, así que bastará con ejecutar:

$ buildslave create-slave -r ./example-slave localhost example-slave pass
mkdir ./example-slave
chdir ./example-slave
mkdir ./example-slave/info
Creating info/admin, you need to edit it appropriately
Creating info/host, you need to edit it appropriately
Not creating info/access_uri - add it if you wish
Please edit the files in ./example-slave/info appropriately.
buildslave configured in ./example-slave
$

Como véis, un proceso bastante sencillo. Y ahora sólo hay que seguir las instrucciones y editar los archivos que se indican: info/admin, info/host y uno que no indica claramente, que es el buildbot.tac. Si abrís los archivos, veréis que buildbot.tac es un programa python. Realmente no es necesario saber python para editarlo, ya que la plantilla es autoexplicativa. En nuestro caso, ni siquiera es necesario editarlo porque va a estar todo en local.

La opción ‘-r’ que utilicé en la línea de órdenes sirve para que utilice rutas relativas. Así, podríamos mover de directorio la configuración del esclavo.

En el caso del esclavo está claro qué archivos podemos mantener en un sistema de control de versiones: los tres indicados (info/admin, info/host y buildbot.tac).

Creando el maestro

De manera similar al esclavo, podemos crear el maestro:

$ buildbot create-master -r example-master
mkdir ./example-master
creating master.cfg.sample
populating public_html/
creating database (sqlite:///state.sqlite)
buildmaster configured in ./example-master
$

Como podeis observar ahora tardará un poco más, ya que tiene que crear e inicializar la base de datos. Por defecto utilizará sqlite, pero podemos configurarlo contra un mysql. Para el ejemplo, sqlite será suficiente.

Veamos lo que nos ha creado aquí:

.
├── buildbot.tac
├── master.cfg.sample
├── public_html
│   ├── bg_gradient.jpg
│   ├── default.css
│   ├── favicon.ico
│   └── robots.txt
└── state.sqlite

Lo primero es mover el archivo “master.cfg.sample” a “master.cfg”. Éste será nuestra plantilla a modificar.

¿Qué tendríamos que guardar en un sistema de control de versiones? Pues basta con guardar buildbot.tac y master.cfg. Nada más. El resto de los archivos podemos volver a obtenerlos con la orden (fijáos que lo ejecuto desde dentro del directorio creado):

example-master$ buildbot upgrade-master
checking for running master
checking master.cfg
upgrading basedir
populating public_html/
populating ./public_html/favicon.ico
populating ./public_html/robots.txt
populating ./public_html/bg_gradient.jpg
populating ./public_html/default.css
populating ./master.cfg.sample
upgrading database (sqlite:///state.sqlite)

upgrade complete
example-master$

Evidentemente, perderemos el histórico de datos, pero… ¿es realmente importante? Si es así, recomiendo hacer copias de seguridad como se harían de cualquier otra base de datos. Y, evidentemente, no utilizar sqlite.

Configurando el maestro

Ahora es necesario configurar el maestro. Podéis observar el parecido que hay en el archivo buildbot.tac con el del esclavo. Dejaremos las opciones tal y como están.

Vamos con el complicado: master.cfg.

Sí, es python. Pero para hacer lo básico no es necesario saber python, sino seguir los consejos de los comentarios. La ventaja de utilizar python es que ofrece mucha versatilidad.

Por defecto trae configurado el proyecto pyFlakes, que vamos a cambiar por pyDoubles. Veremos que no es tan complejo. Voy a pegar todo el archivo, pero si lo comparáis, veréis que sólo han cambiado las secciones “ChangeSources”, “Builders” y “Project Identity "

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
# -*- python -*-
# ex: set syntax=python:

# This is a sample buildmaster config file. It must be installed as
# 'master.cfg' in your buildmaster's base directory.

# This is the dictionary that the buildmaster pays attention to. We also use
# a shorter alias to save typing.
c = BuildmasterConfig = {}

####### BUILDSLAVES

# The 'slaves' list defines the set of recognized buildslaves. Each element is
# a BuildSlave object, specifying a unique slave name and password.  The same
# slave name and password must be configured on the slave.
from buildbot.buildslave import BuildSlave
c['slaves'] = [BuildSlave("example-slave", "pass")]

# 'slavePortnum' defines the TCP port to listen on for connections from slaves.
# This must match the value configured into the buildslaves (with their
# --master option)
c['slavePortnum'] = 9989

####### CHANGESOURCES

# the 'change_source' setting tells the buildmaster how it should find out
# about source code changes.  Here we point to the buildbot clone of pyflakes.

from buildbot.changes.gitpoller import GitPoller
c['change_source'] = []
#c['change_source'].append(GitPoller(
#        'git://github.com/buildbot/pyflakes.git',
#        workdir='gitpoller-workdir', branch='master',
#        pollinterval=300))

####### SCHEDULERS

# Configure the Schedulers, which decide how to react to incoming changes.  In this
# case, just kick off a 'runtests' build

from buildbot.schedulers.basic import SingleBranchScheduler
from buildbot.schedulers.forcesched import ForceScheduler
from buildbot.changes import filter
c['schedulers'] = []
c['schedulers'].append(SingleBranchScheduler(
                            name="all",
                            change_filter=filter.ChangeFilter(branch='master'),
                            treeStableTimer=None,
                            builderNames=["runtests"]))
c['schedulers'].append(ForceScheduler(
                            name="force",
                            builderNames=["runtests"]))

####### BUILDERS

# The 'builders' list defines the Builders, which tell Buildbot how to perform a build:
# what steps, and which slaves can execute them.  Note that any particular build will
# only take place on one slave.

from buildbot.process.factory import BuildFactory
from buildbot.steps.source import Mercurial
from buildbot.steps.shell import ShellCommand

factory = BuildFactory()
# check out the source
factory.addStep(Mercurial(repourl='https://bitbucket.org/carlosble/pydoubles', mode='copy'))
# run the tests (note that this will require that 'trial' is installed)
factory.addStep(ShellCommand(command=["nosetests", "pyDoublesTests/unit.py"]))
factory.addStep(ShellCommand(command=["nosetests", "pyDoublesTests/hamcrest_integration.py"]))

from buildbot.config import BuilderConfig

c['builders'] = []
c['builders'].append(
    BuilderConfig(name="runtests",
      slavenames=["example-slave"],
      factory=factory))

####### STATUS TARGETS

# 'status' is a list of Status Targets. The results of each build will be
# pushed to these targets. buildbot/status/*.py has a variety to choose from,
# including web pages, email senders, and IRC bots.

c['status'] = []

from buildbot.status import html
from buildbot.status.web import authz, auth

authz_cfg=authz.Authz(
    # change any of these to True to enable; see the manual for more
    # options
    auth=auth.BasicAuth([("pyflakes","pyflakes")]),
    gracefulShutdown = False,
    forceBuild = 'auth', # use this to test your slave once it is set up
    forceAllBuilds = False,
    pingBuilder = False,
    stopBuild = False,
    stopAllBuilds = False,
    cancelPendingBuild = False,
)
c['status'].append(html.WebStatus(http_port=8010, authz=authz_cfg))

####### PROJECT IDENTITY

# the 'title' string will appear at the top of this buildbot
# installation's html.WebStatus home page (linked to the
# 'titleURL') and is embedded in the title of the waterfall HTML page.

c['title'] = "My BuildBot Installation"
c['titleURL'] = "https://www.magmax.org"

# the 'buildbotURL' string should point to the location where the buildbot's
# internal web server (usually the html.WebStatus page) is visible. This
# typically uses the port number set in the Waterfall 'status' entry, but
# with an externally-visible host name which the buildbot cannot figure out
# without some help.

c['buildbotURL'] = "https://localhost:8010/"

####### DB URL

c['db'] = {
    # This specifies what database buildbot uses to store its state.  You can leave
    # this at its default for all but the largest installations.
    'db_url' : "sqlite:///state.sqlite",
}

Ejecutando el maestro

Ningún misterio: desde el directorio del maestro, basta ejecutar:

example-master$ buildbot start

Ejecutando el esclavo

Tampoco tiene misterio: desde el directorio del esclavo, basta ejecutar:

example-slave$ buildslave start

Y todo debería ir correctamente.

Lanzando una build (cómo usar la GUI)

Si os habéis fijado en los logs o en el archivo de configuración, el maestro está sirviendo una web en https://localhost:8010/. Lo primero que haremos será LogIn. Como no lo hemos modificado en el archivo de configuración, bastará con usar pyflakes/pyflakes (si dudáis, mirad la sección de autenticación del archivo master.cfg).

Una vez autenticados, podemos irnos a la sección “Waterfall” y pulsar sobre “runtests”. Eso nos mostrará la página de ejecución de tests, donde está todo configurado. Basta pulsar el botón “Force Build”. Y, si todo es correcto, debería haberse ejecutado la batería de pruebas.

La ventana más útil para ver resultados es la de Waterfall. A partir de ahí no os costará mucho navegar el resto.

Buildbot grid

Comparativa entre BuildBot y Jenkins

A menudo las comparativas son odiosas. Éste es uno de esos casos. No se puede decir que uno sea mejor que el otro, pero sí que cada cual tiene sus puntos fuertes.

Voy a exponer algunas de estas ventajas de cada uno.

Guardando las configuraciones

Ambos utilizan una base de datos para almacenar los resultados. Sin embargo, la forma de guardar las configuraciones de los trabajos es completamente diferente: Jenkins utiliza archivos XML, mientras que BuildBot utiliza un único archivo Python.

Dado que los archivos de Jenkins no se modificarán a mano, sino utilizando la GUI, resultará dificil mantenerlos en un sistema de control de versiones. Sin embargo, resulta muy sencillo hacerlo con BuildBot.

Configuraciones cambiantes

Si tenemos configuraciones que cambian mucho, entonces es mejor no utilizar BuildBot. En BuildBot las configuraciones son estáticas y se requiere un reinicio del maestro para aplicar los cambios. Por esa razón, en estos casos es mejor utilizar Jenkins.

Si lo único que va a cambiar son algunos parámetros, entonces sí podemos plantearnos usar BuildBot.

Try

BuildBot tiene una característica que Jenkins no tiene: el try. Sirve para “intentar” una build, de manera que coja los cambios locales, aunque no estén en el repositorio, e intente realizar una build con ellos. Esta característica puede resultar de mucha utilidad cuando se quieren probar situaciones que pueden no funcionar correctamente o para probar el propio sistema de CI.

Bonito

Indiscutiblemente, Jenkins es mucho más bonito que BuildBot. Han cuidado mucho más la interfaz.

Además, BuildBot ofrece los resultados sin más, mientras que Jenkins permite procesarlos y presentarlos con una interfaz más bonita.

Ampliable

Jenkins permite la fácil ampliación mediante la creación de plug-ins. Estas extensiones requieren conocimientos de Java, Jelly y, probablemente, un poco de Groovy, además de conocer la API.

Con BuildBot es probable que no necesitemos de ampliaciones, ya que se puede programar sobre la propia build. En caso de necesitarlas, basta con escribir el programa Python adecuado. Para ello, podemos basarnos en el propio código de la clase de la que heredaremos.

Para algunas cosas, será más sencillo el sistema de Jenkins. Para otras, el de BuildBot.

Rápido

Jenkins tarda más de un minuto en reiniciarse. BuildBot breves segundos.

Tras un reinicio, Jenkins pierde el control de los esclavos: no sabe si están ejecutando algo, así que es probable que los trate como desocupados. Esto puede producir que fallen tanto el trabajo en curso como el nuevo. En el caso de que no lance un nuevo trabajo, Jenkins ignorará los resultados del trabajo en curso, por lo que se habrá perdido irremediablemente.

Con BuildBot también se pierde la ejecución en curso. Además, cuando recupera el contacto con el esclavo, relanzará la build que estaba a medias. Sin embargo, el maestro reinicia al esclavo, evitando que haya dos instancias del esclavo corriendo simultáneamente.

La documentación de Jenkins es extensa, pero entrar en el código suele ser complejo. La documentación de BuildBot es bastante buena, aunque la página web está desactualizada: recomiendo descargarse el código y leer la documentación directamente de allí (formato sphinx, que podéis compilar en vuestras máquinas). De todas maneras, es código Python, que suele ser bastante legible.

API

Jenkins tiene API en formatos JSON y XML. Además, ofrece un cliente java bastante potente.

BuildBot tiene API en formato JSON. Es algo más sencilla, pero es normal: la mayor parte de las cosas que queremos hacer estarán escritas en python, en el archivo de configuración.

Jenkins, además, ofrece una interfaz Groovy, sólo disponible para administradores. Sobre esta interfaz debo prevenir que es peligrosa: un descuido en una condición y puedes renombrar todos los trabajos en curso; un System.exit, y se apagará el servidor.

Otras opciones

No son las dos únicas opciones disponibles. Existen muchas otras, de libre disposición, como CruiseControl, Jenkins, BuildBot, Apache Gump o Apache Continuum. También hay aplicaciones propietarias, como Bamboo o Team Foundation Server.

Otro ejemplo es Travis-CI. En este caso, nos venden el servicio, ya que no resulta sencillo encontrar información sobre cómo instalárnoslo. Aún así, es software libre y el código está disponible.

Como véis, todas estas sólo las mencionaré, ya que nunca las he utilizado. Hay muchas otras. Tan sólo tenéis que elegir una y comenzar a usarla.

En producción

Y como no hay nada mejor que un ejemplo, podéis visitar los waterfall de gente que ya lo está usando:

Y fin

Realmente las condiciones para utilizar cualquier sistema de integración contínua son las mismas: deben existir pruebas y debe ser fácil de desplegar.

La elección de un buen servidor de integración contínua es importante. Al principio será más un estorbo que otra cosa, pero poco a poco termina transformándose en un servicio crítico que, cuando está parado, la empresa no avanza.

Puede parecer exagerado, pero la dependencia con el mismo es importante: termina siendo el juez supremo de nuestros cambios. Si el servidor de CI no lo aprueba, nuestros cambios no se aplican. Da igual cómo nos defendamos o cómo queramos interpretarlo: si no hay verde, nuestro trabajo no está terminado.

Además, un buen servidor de CI nos permitirá recoger estadísticas de uso, de manera que podamos optimizar el proceso. Será interesante evaluar la posibilidad de lanzar varios hilos, comprar más hardware (más esclavos), evaluar el impacto de un cambio, comprobar el estilo de nuestro código, etc.

Elegid uno, y usadlo.