Servidores CI/CD: diferencias de modelos, Jenkins vs CircleCI

Servidores CI/CD: diferencias de modelos, Jenkins vs CircleCI

Jenkins es una de las herramientas de CI/CD más difundidas, posiblemente por su potencia y también por su larga historia (su primera versión es de 2005). Yo empecé a utilizarlo allá por 2008 cuando aún era Hudson. Inicialmente era un servidor de integración continua. Luego con el auge de la entrega continua se lo empezó a utilizar para hacer deployments. Esta última característica cobró más impulso a partir de la versión 2.

CircleCI es una herramienta de aparición mucho más reciente y viene a resolver el mismo problema que Jenkins pero con un modelo distinto.

En este post decidí hablar concretamente de Jenkins y Circle porque hoy en día estoy trabajando en dos proyectos, cada uno con una de estas herramientas. Pero en realidad es que en lugar de Circle podría referirme a Travis o a los Builders de Gitlab, pues conceptualmente representan el mismo modelo. Del mismo modo, en lugar de Jenkins podría hablar de Bamboo.

Veamos entonces algunas diferencias de estos dos modelos. CircleCI funciona atado a un repositorio Git (puede ser GitHub, BitBucket o algún otro). Al mismo tiempo, el funcionamiento de la herramienta está definido por un archivo (en formato yaml) donde uno define sus Jobs/Workflows. Si bien la herramienta provee una interface gráfica web, la misma es principalmente para visualización y solo permite ajustar algunos pocos settings, pero no permite la creación de Jobs. La situación es análoga al utilizar Travis o GitLab. Este modelo va muy bien con las estrategias del tipo GitOps, donde todo el pipeline de delivery se articula a partir de operaciones/eventos Git.

Por otro lado, Jenkins ofrece un modelo distinto, uno puede crear jobs asociados a distintos tipos de repositorios o incluso permite crear jobs sin asociación alguna a repositorios. Al mismo tiempo ofrece una interface de usuario web que permite manipular completamente la herramienta, de hecho, durante mucho tiempo esta era la única opción. Luego fueron apareciendo distintas opciones/plugins que posibilitaron tener un manejo similar al de Circle/Travis.

En otra dimensión de análisis podríamos decir que el modelo de Circle/Travis es en cierto modo “CI/CD as a Service”. Son herramientas muy enfocadas en CI/CD con toda una serie de cuestiones de diseño ya tomadas de antemano. De hecho en general el modelo de extensibilidad de estas herramientas es bastante limitado.

Por su parte Jenkins surgió inicialmente en modelo más On-Premises/Producto, contando desde su inicio con un modelo muy potente de extensibilidad y luego fue evolucionando incorporando características de los modelo más SaaS. La arquitectura extensible de Jenkins ha posibilitado la aparición de nuevos proyectos montados sobre Jenkins como ser JenkinsX.

Personalmente me siendo muy a gusto trabajando con Jenkins, creo que es un producto muy versátil y lo he utilizado para algunas cosillas más allá de CI/CD. Al mismo tiempo que creo en un contexto organizacional Jenkins ofrece varias características y posibilidades de extensibilidad no presentes en el otro modelo. Entre las bondades de Jenkins podría nombrar la integración con muchísimas herramientas, las capacidades de templating, slicing configuration y la posibilidad de generar plugins en caso que uno lo necesite (lo he hecho y no pareció complicado). Sin embargo y a pesar de mi gusto por Jenkins, creo que la característica de Pipeline as Code (jenkinsfile) no está lo suficientemente lograda. Perdón, creo que la funcionalidad está bien, pero personalmente me siento más a gusto utilizando un set de plugins tradicionales (como JobDSL, el JobBuilder y BuildPipeline) que usando los Jenkinsfile.

Taller de Continuous Delivery

Por estos días me encuentro trabajando en la preparación de un Taller sobre Continuous Delivery. El taller surgió a partir del pedido concreto de un cliente, pero obviamente armar un curso para dictarlo solo una vez no es negocio, así que seguramente agende para volver a dictarlo en un futuro (interesados contactarme ;-)).

El taller es de índole de práctica, o sea los participantes ponen manos en la masa. Para ello he preparado una imagen de máquina virtual para que los alumnos hagan los ejercicios. Hay algunos ejercicios de carácter más teórico (por ejemplo diseño de ambientes) pero la mayoría son de índole práctica orientados a herramientas (por ejemplo configurar Jenkins para hacer deploy automatizado de una aplicación en diversos ambientes).

Comparto aquí un video con un poco más de información.

Hacia un esquema de entrega continua

Cuando me uní al proyecto el equipo venia trabajando con un esquema ágil “clásico” pero con algunas particularidades:

  • Iteraciones time-boxed de 2 semanas
  • Congelamiento y regresión/estabilización los últimos dias de la iteración
  • Pruebas unitarias automatizadas
  • Regresión manual
  • Release al final de la iteración

Esta forma de trabajo ocasionaba de vez en cuando que se llegara al final de iteración con funcionalidades “inestables” porque no se llegaba a tiempo a completar la regresión/estabilización. Personalmente ya me había cruzado con este tipo de situaciones en el pasado y por ello en una retrospectiva propuse al equipo hacer un experimento en la siguiente iteración: tomar una user story, implementarla, testearla y releasearla apenas se la termine sin esperar al final de la iteración. De esta forma:

  • El flujo de entrega sería más frecuente bajando la ansidad de cliente
  • Obtendríamos feedback más temprano del usuario
  • El esfuerzo de estabilización estaría distribuido a lo largo de toda la iteración (y posiblemente sería menor en términos generales)
  • Se evitaría el incremento del WIP (work in progress) ya que las user stories completas no quedarían esperando al final de la iteración para su release.

Cuando hice esta propuesta algunos miembros del equipo me miraron con cierta desconfianza pero a pesar de eso todos estuvimos de acuerdo en hacer el experimento.

Continuará…

Continuous Delivery en Agiles 2013

Durante la conferencia hubo varias sesiones sobre continuous delivery, una de ellas fue la mía. Dado que fue en el último turno del día intenté que fuera bastante movida, por eso arranqué con música y palmas, lo cual atrajo algunos curiosos que estaban en la cercanía de la sala.

Una vez terminó el ingreso del público hicimos tribus para así conocer el perfil de la audiencia. Resulto que había una audiencia dividida, la mitad no conocía continuous delivery y de los que sí conocían, sólo uno pocos tenían experiencia.

Yo personalmente quedé muy conforme con cómo salió la sesión y según el feedback que obtuve parece que los asistentes también se fueron conformes.

Durante la sesión conté con la colaboración de mi colega Luis Mulato.

Aquí están los slides que utilicé en la sesión y para quienes me preguntaron, el tema que sonó al comienzo de la sesión se llama Tapa de los sesos y es de Los Visitantes.

Sobre la estructura de repositorio del código

Existen diversas posibilidades a la hora de armar la estructura de un repositorio de código. La elección de una u otra depende de diversos factores como ser: el tamaño del equipo, la forma de trabajo, la frecuencia de despliegue de la aplicación etc.

Quiero compartir dos estructuras posibles con las que he trabajado.

Estructura minimalista para Continuous Delivery

Contexto:

  • Tu aplicación es “standalone”, en el sentido que tiene mínimas dependencias con aplicaciones externas
  • Tu equipo de desarrollo es chico (menos de 4 programadores)
  • Cuando debes agregar cambios/nuevas funcionalidades lo haces en pequeños incrementos
  • Cuentas con un alto porcentaje de cobertura
  • La regresión manual (en que caso que la necesites) te lleva menos de 30 minutos

Si se dan los puntos anteriores, entonces puedes ir fácilmente a un contexto de Continuous Delivery para lo cual recomiendo una estructura de repositorio basada en dos branches permanentes:

  • Develop: es el branch sobre el que se hace el desarrollo
  • Master: es el branch que contiene lo que se encuentra en producción

Cuando se está desarrollando una nueva funcionalidad, se va commiteando a develop. Cuando dicha funcionalidad está completa y ha sido vista por el responsable de producto en el ambiente de tests, entonces, dicha funcionalidad es mergeada en master y desplegada al ambiente productivo.

En caso de detectarse un error en producción, se crea un branch temporal (hot-fix) para desarrollar el fix que una vez completo es mergeado en master y develop.

Estoy asumiendo un ecosistema de trabajo que cuenta con un ambiente desarrollo (máquina del developer), una ambiente de tests (similar al de producción) y un ambiente de producción.

Estructura tradicional para desarrollo iterativo

Contexto:

  • Trabajas en un esquema iterativo con salidas a producción programadas en cada X días/semanas
  • Tu aplicación depende de otras aplicaciones/servicios externos
  • Tu equipo de desarrollo no es tan chico (más de 4 desarrolladores)
  • No tienes una buena cobertura, lo cual te lleva a ejecutar regresiones que llevan horas

En este caso también tendremos los 3 branches mencionados anteriormente (develop, master y hot-fixes temporales) pero adicionalmente tendremos otros branches:

  • Feature branch: cada funcionalidad es desarrollada en un branch aparte que luego es mergeado a develop
  • Release branch: es este branch se van a acumulando las funcionalidades listas para pasaje a producción. Puntualmente se hace un merge de develop a release branch

Este esquema es conocido como GitFlow y tiene cierta popularidad en el mundo Git, pero a pesar de ello es posible utilizarlo con otro controladores de versiones.

Charla: Estrategias para la adopción de Continuous Delivery

El próximo Jueves 19 de Septiembre, en el contexto del encuentro de la comunidad ágil de Buenos Aires, estaré dando la charla de referencia.

La cita es a partir de las 18.30 en las instalaciones del MUG, como siempre la entrada es gratuita pero requiere registración.

Algunos detalles más sobre la charla los pueden encontrar en la página de registración.

Continuous Delivery, aplicaciones legacy, TestNG y SeleniumIDE

Una cuestión clave en todo contexto de continuous delivery son las pruebas automatizadas. De poco sirve pasar “rápido” entre ambientes, si no podemos garantizar cierta calidad de lo que estamos poniendo en cada ambiente.

Cuando uno construye aplicaciones desde cero, es relativamente fácil generar pruebas automatizadas. El uso de técnicas como BDD y TDD junto con algunos patrones de diseño permiten generar aplicaciones testeables y con alto nivel de cobertura.

Pero no siempre trabajamos sobre aplicaciones nuevas. En ocasiones nos toca trabajar con aplicaciones ya existentes, que muchas veces no cuentan con pruebas y que han sido desarrolladas sin tener en cuenta la testeabilidad. En los contextos ágiles a este tipo de aplicaciones se les suele llamar aplicaciones legacy.

Por suerte son pocos los proyectos, en los que trabajando como programador, he tenido aplicaciones legacy. Sin embargo, trabajando como consultor, me he cruzado bastante a menudo con clientes llenos de este tipo.

Mi estrategia para trabajar con aplicaciones legacy es comenzar haciendo algunas pruebas funcionales que cubran los principales flujos de la aplicación. Y luego a gradualmente comenzar a modificar la aplicación haciendola testeable y agregando distintos tipos de tests según sea posible.

Una combinación de herramientas que me ha resultado interesante para hacer pruebas funcionales es SeleniumIDE + TestNG.

Selenium IDE es una herramienta de automatización de pruebas del tipo Record & Play que funciona integrada con el explorador FireFox y permite que uno vaya recorriendo la aplicación y poniendo aserciones en determinados puntos. Luego la misma herramienta permite reproducir la prueba grabada. Si bien a primera vista la estrategia puede parecer muy interesante, la realidad es que no es tan simple. De hecho este tipo de herramientas de automatización de pruebas ha tenido varias críticas (algunas de las cuales comparto), por diversos motivos que han sido tratados, entre otros, por George Meszaros.

Por su parte TestNG, es una herramienta al estilo JUnit, pero a diferencia de esta, tiene un foco más amplio que la prueba unitaria y por ello provee algunas funcionalidades muy útiles para hacer pruebas funcionales.

A partir de estas dos herramientas, mi estrategia es:

  1. Diseñar la prueba y crear la “cáscara” de la prueba con TestNG
  2. Grabar la prueba con Selenium IDE
  3. Exportar el script resultante a Java
  4. Tomar el código del script Java generado y usarlo para completar la cáscara creada con TestNG
  5. Ajustar la configuración del driver de test (pues si bien por default viene FireFox, uno podría querer/necesitar probar con otro browser)
  6. Ajustando el código de prueba en TestNG tomando en cuenta la particularidades de la prueba
  7. Ejecutar las pruebas con TestNG

Dos cuestiones importantes a considerar para utilizar este enfoque y que son las que abarca el punto 5 son:

  1. Es posible que para ejecutar las pruebas sea necesario contar con un estado particular de la aplicación que puede no ser trivial de generar
  2. Es muy común que los datos de tests requieran cierto grado de “dinamismo”. Aquí puede resultar muy útil la funcionalidad de TestNG que permite definir sets de datos de prueba.

Bien, esto es todo por el momento.

Como siempre, si tienen alguna consulta/inquietud, no duden en escribirme.