Curso Metodologías Ágiles y TDD, parte II

Published on Tuesday, 08 July 2014

Curso de Metodologías Ágiles y TDD, Parte II

Entornos higiénicos con Vagrant

Introducción

Cuando un programador empieza su andadura en el desarrollo del software, no suele pasar mucho tiempo hasta que escucha por primera vez la expresión "en mi ordenador funciona. Tanto es así que la frase se ha convertido en objeto de chistes, memes e incluso camisetas.

Works on my machine meme

El problema surge de la disparidad en los distintos entornos del staging, desde los equipos de desarrollo locales hasta el servidor de producción, sin olvidar los servidores de integración y testing. Antes de la irrupción de la cultura DevOps se solía considerar la parte de operaciones como un área independiente de desarrollo. Los DevOps persiguen crean puentes entre Desarrollo y Operaciones para dar respuestas a las interdependencias y resolver los problemas de integración.

Por la extensión del universo DevOps y por la carencia de conocimientos específicos para tratar el tema en profundidad, en este curso nos limitaremos a rascar la superficie y a exponer prácticas que ayudarán a mantener un flujo de desarrollo más continuo y confiable.

Qué es Vagrant

Vagrant es una herramienta que permite configurar entornos de desarrollo mediante el control y provisionamiento de Máquinas virtuales.

El corazón de Vagrant es el Vagrantfile, archivo que define las propiedades de la máquina virtual (sistema operativo, IP pública o privada, métodos de provisionamiento y otros).

El siguiente es un ejemplo de Vagrantfile muy básico:

# -*- mode: ruby -*-
        # vi: set ft=ruby :
        
        VAGRANTFILE_API_VERSION = "2"
        
        Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
        
          config.vm.box = "hashicorp/precise64"
        
          config.vm.network :private_network, ip: "192.168.33.14"
        
          config.vm.hostname = "dev.my-project.com"
        
          config.vm.provider "virtualbox" do |vb|
            vb.customize ["modifyvm", :id, "--memory", "2048"]
            vb.customize ["modifyvm", :id, "--cpus", 4]
          end
        
          config.vm.provision "ansible" do |ansible|
            ansible.playbook = "provisioning/playbook.yml"
            ansible.extra_vars = {private_interface: "192.168.100.1"}
          end
        
        end
        

Una vez escrito el Vagrantfile y habiendo instalado Vagrant en nuestro equipo, cuando ejecutemos vagrant up se iniciará la descarga de la imagen virtual (hashicorp/precise64) para una máquina con 2048 MB de RAM y 4 núcleos. Una vez descargada se ejecutarán los mecanismos de provision establecidos en config.vm.provision. En este caso hemos escogido el provisionamiento con ansible, aunque la libertad de elección es amplia.

Vagrant Boxes

El parámetro config.vm.box define la box a utilizar. Una box es una imagen ya creada de un entorno para la máquina virtual, y que servirá de base para nuestro provisionamiento específico. La web Vagrant Cloud dispone de un amplio y creciente catálogo de máquinas virtuales para escoger.

Las boxes pueden ser instaladas localmente mediante el comando vagrant box add:

$ vagrant box add hashicorp/precise32
        

Providers

Los providers permiten a Vagrant comunicarse con distintos gestores de máquina virtual. Los más comunes son VirtualBox, VMware o Docker.

Provisioning

El provisioning consiste en la adecuación de la máquina virtual a las necesidades concretas del proyecto, incluyendo la creación de directorios, configuración de permisos, servidores web, librerías y cualquier otra operación automatizable.

Existen varias alternativas de provisioning. Puphpet es una aplicación web que ofrece un wizard para configurar un entorno y genera un Vagrantfile completo provisionando con puppet.

Ejemplo de provisioning con ansible

Comandos de Vagrant

Una vez terminados nuestros scripts de provisioning, levantaremos la máquina con vagrant up. El entorno se creará entonces de forma automática.

Con vagrant ssh accederemos a la máquina virtual con una conexión segura.

Para parar la máquina ejecutaremos vagrant halt, y para eliminarla definitivamente vagrant destroy.

Integración Continua

Cuando un desarrollador aporta el código que ha desarrollado al flujo común de trabajo pueden surgir problemas de integración. Por ejemplo, pueden producirse conflictos en el repositorio o provocar una inestabilidad en el código de un tercero que dependía de una clase modificada por ellos.

La integración continua es un conjunto de buenas prácticas que trata de combatir los problemas de integración integrando más a menudo.

El repositorio

El repositorio de control de versiones permite mantener un historial actualizado y compartir código con comodidad. En ningún caso es un vertedero para compartir código. Debemos tratar el repositorio como si del código de producción se tratara, manteniéndolo en todo momento limpio y estable. Con Git y flujos de trabajo como Github, la buena salud del repositorio es aún más importante.

Principios para el desarrollador

En el capítulo segundo del libro Continuous Integration: Improving Software Quality and Reducing Risk, sus autores enumeran siete buenas prácticas para los desarrolladores. Me he permitido reducirlas a las 5 que considero más importantes.

Tests continuos

Los desarrolladores deben ejecutar tests a medida que van desarrollando código. Normalmente es responsabilidad del desarrollador ejecutar constantemente los tests unitarios del código que está modificando. Además de estos tests y como hemos visto, existen otros tests de mayor nivel que aseguran la estabilidad del sistema. Como estos otros tests son bastante más lentos y ejecutar toda la batería de tests del proyecto podría resultar verdaderamente engorroso, la ejecución de los tests del build completo se deja en manos de servidores de integración continua.

Existen varios sistemas de integración continua disponibles en el mercado, como Jenkins o CruiseControl. Estas aplicaciones pueden instalarse en máquinas dedicadas al control del código. Cuando un desarrollador contribuye código, el servidor de integración continua se pone en marcha para levantar el build y ejecutar el set de tests. En función de la configuración del sistema, el servidor puede rechazar el commit o enviar un aviso al equipo. Cuando los tests son demasiado pesados como para ser ejecutados en cada commit pueden seguirse otras estrategias como ejecutar los tests de manera periódica.

Jenkins. Captura de pantalla

Hay muchas maneras de avisar al equipo de un test fallido, desde las lámparas de lava, los e-mails e incluso los lanzamisiles. Que cada equipo elija la que considere más apropiada.

Staging

Separación de entornos

El staging consiste en la separación de entornos de trabajo. Permite aislar el entorno de desarrollo del de pruebas, y éste del de producción. Un flujo de staging habitual consiste en la separación de al menos los siguientes cuatro entornos: Desarrollo, Integración, Pre-producción y Producción.

El entorno de desarrollo es aquel en el que el desarrollador trabaja diariamente. El desarrollador hace un checkout del repositorio para trabajar en su máquina local y evitar conflictos con los desarrolladores. Si la aplicación necesita una base de datos, el desarrollador tendrá que configurarse un clon de la misma en su propia máquina. Es recomendable actualizar la copia de la base de datos a menudo.

El entorno de integración es el que utilizan todos los desarrolladores para integrar y probar el software en su conjunto. Como hemos comentado con anterioridad, al tratarse del espacio de trabajo común debe tenerse especial cuidado con mantenerlo limpio y estable.

El entorno de preproducción es un entorno aislado que se utiliza antes de los despliegues para realizar las pruebas finales. También puede servir para llevar a cabo las demos con cliente o testing con usuarios reales (beta-testing). Es importante que las características de configuración de este entorno, tanto en hardware como en software, sean tan parecidas como sea posible a las del entorno de producción. De este modo evitaremos problemas provocados por diferencias de versiones de software, hardwares incompatibles y problemas similares.

El entorno de producción, finalmente, es donde los clientes o usuarios dan uso a la aplicación. Puede ser un servidor web en el caso de sites online u ordenadores personales en aplicaciones comerciales.

Utilizar estos entornos de un modo ordenado nos permitirá desplegar siempre código estable y estar listos en todo momento para poder responder a necesidades del cliente. Además de entornos separados necesitaremos la ayuda del repositorio en forma de branches.

Automatización de procesos

La integración continua consiste en realizar los procesos cotidianos (como contribuir código, pasar tests o desplegar código) de forma constante, y para ello es recomendable introducir automatismos. La regla de oro es "si lo vas a hacer dos veces, automatízalo". Seguramente termines haciéndolo muchas más. Así, los sistemas de integración continua no solo sirven para ejecutar tests y avisar a los desarrolladores en el caso de que el build se haya roto. También sirven para ejecutar todo tipo de scripts, como movimientos en ramas del repositorio, descargas de bases de datos o ejecución de analizadores de código.