Introducción
El proceso de ejecutar de forma manual builds, test y despliegues puede convertirse en una pesadilla y en un proceso propenso a errores:
- Los errores humanos suceden a menudo, ocurren con facilidad.
- Cuando los proyectos crecen, los procesos de build tienden a complicarse.
- Lidiar con diferentes versiones entre entornos no es algo sencillo.
- Cambiar de versiones no es un proceso fácil en muchos casos.
¿Y si...?:
- Los procesos de build pudieran automatizarse, incluyendo indicadores que nos informen sobre si el proceso se ha completado (por ejemplo, ejecutando baterías de test).
- Los procesos de build pudieran arrancar cuando ocurriera un merge para una rama concreta, o se realizará un push al repositorio remoto.
- Cada proceso de build generase una imagen de Docker, y de este modo no nos tendríamos que preocupar por configuraciones en el servidor, o no tener instaladas las versiones exactas de software.
- No ocupáramos demasiado espacio en disco ya que comenzaríamos desde una imagen base de Docker que tuviera el sistema operativo y software preinstalado.
- Pudiéramos subirlo a un cloud hub registry, permitiendo ser consumido desde cualquier proveedor local o de cloud (Amazon, Azure, Google Cloud...)
- Pudiéramos intercambiar fácilmente distintas versiones de build.
Todos estos son los beneficios que podemos obtener al configurar un servidor de CI/CD con Travis (CI proviene de Continuos Integration, CD significa Continuos Delivery) y mezclarlo con la tecnología de contenedores Docker.
Este es el segundo artículo de la serie Hola Docker, el primer artículo está disponible en este enlace.
TL; DR;
En este artículo usaremos Travis para activar automáticamente los siguientes procesos en cada merge to master o pull request:
- Levantaremos una instancia linux + nodejs limpia.
- Descargaremos el código fuente del repositorio.
- Instalaremos las dependencias necesarias del proyecto.
- Ejecutaremos la batería de test asociados.
- Generaremos la imagen de docker, incluyendo la build de producción.
- Etiquetaremos y publicaremos la imagen generada en el Docker Hub Registry.
Esta configuración la usaremos tanto para el proyecto de Front End como para el proyecto de Back End.
Si quieres profundizar en los detalles, sigue leyendo :)
Agenda
Pasos que vamos a seguir:
- Presentaremos el proyecto de ejemplo.
- Trabajaremos manualmente con Docker Hub.
- Enlazaremos nuestro proyecto de GitHub con Travis.
- Estableceremos los pasos para un proceso de CI.
- Comprobaremos el resultado de nuestras baterías de test.
- Verificaremos si el contenedor de docker puede ser generado.
- Subiremos una imagen a Docker Hub Registry.
- Comprobaremos si nuestro proceso de Continuous Delivery ha tenido éxito, consumiendo nuestras imágenes desde un fichero docker compose.
Proyecto de ejemplo
Tomaremos como ejemplo una aplicación de chat. La aplicación está dividida en dos partes: el cliente (Front End) y el servidor (Back End) que serán encapsulados en contenedores Docker (a esta acción la denominaremos "dockerizar" de ahora en adelante), y desplegados mediante Docker Containers.
Ya tenemos listos un par de repositorios que juntos crean la aplicación de chat:
Siguiendo la aproximación actual de despliegue (revisar artículo previo de esta serie), una tercera parte será incluida también: un balanceador de carga. Su responsabilidad será direccionar el tráfico al front o back dependiendo de la url de la petición. El balanceador de carga también será "dockerizado" y desplegado usando Docker Container.
Docker Hub
En nuestro último artículo tomamos como punto de partida una imagen de Ubuntu + Nodejs; fue estupendo recuperarlas desde Docker Hub y tener el control de que versión estábamos descargando.
¿No sería maravilloso ser capaces de subir nuestras imágenes a Docker Hub Registry, incluyendo la versión? Eso es lo que Docker Hub nos ofrece: puedes crear tu propia cuenta y subir las imágenes de Docker.
Ventajas de usar Docker Hub:
- Podemos mantener varias versiones de las imágenes de nuestros contenedores (genial para soportar distintos entornos, A/B testing, canary deployment, green blue deployment, rolling back, etc...).
- Dado que Docker se crea a partir de una serie de capas, nuestra imagen custom no ocupará más espacio del necesario, es decir, un contenedor construido sobre un contenedor linux no desplegará el SO, sólo apuntará a la imagen previa.
- Como la imagen del contenedor ya esta en la nube, desplegar en el proveedor de cloud es sencillo.
Docker Hub es fantástico para arrancar: podemos crear una cuenta gratis y subir nuestras imágenes Docker (la versión gratuita permite crear repositorios públicos ilimitados y uno sólo privado).
Si más tarde necesitamos usarlo para propósitos de negocio y restringir el acceso, podemos usar un registro de Docker privado, algunos proveedores son:
Travis
Aunque Docker nos ayuda a estandarizar la creación y configuración de un determinado entorno, generar nuevas versiones manualmente puede resultar tedioso y propenso a errores:
- Tenemos que descargar el estado del código adecuado para el proceso de build.
- Ejecutar los tests automatizados y comprobar que pasan en verde.
- Tenemos que generar nuestra imagen de Docker.
- Añadir el versionado adecuado (etiquetar la imagen en los términos de Docker).
- Y por último, debemos subir la imagen manualmente al registro de Docker Hub.
Imaginemos hacer esto en cada proceso de merge to master; quedaríamos agotados con este infierno de despliegue... ¿Existe alguna manera de automatizar esto? ¡Travis al rescate!
Simplemente invirtiendo algo de tiempo en crear una configuración inicial, Travis hará de manera automática lo siguiente:
- Notificar de cualquier PR o merge to master (se pueden establecer políticas de notificación).
- Crear un entorno limpio (por ejemplo, en nuestro caso tomará una imagen de Ubuntu + Nodejs).
- Descargar el estado de código adecuado desde el repositorio.
- Ejecutar los tests.
- Crear la imagen de Docker que contendrá la build de producción.
- En caso de tener éxito, desplegar en el registro de Docker Hub (añadiendo la etiqueta de versión siguiendo la notación de Docker).
Una de las ventajas de Travis es que es bastante sencillo de configurar:
- No necesitas instalar infraestructura (está basado en un entorno cloud).
- La configuración se realiza mediante un fichero yml.
- Ofrece una versión community edition donde podemos jugar con nuestros proyectos de prueba o usarlo para nuestros proyectos open source (sólo proyectos públicos).
- Ofrece una versión enterprise para nuestros proyectos privados.
Configuración de Cuentas
Fork sobre los proyectos de ejemplo
Si quieres seguir este tutorial, puedes empezar por hacer un fork de los repositorios de Front End y Back End:
- Front End: https://github.com/Lemoncode/redux-chat-front
- Back End: https://github.com/Lemoncode/redux-chat-back
Al hacer fork de estos repositorios, una copia se creará en tu cuenta de github y podrás enlazarlos a tu cuenta de Travis y configurar el proceso de CI.
Registro de Docker Hub
En nuestro artículo previo de esta serie, consumíamos una imagen de Docker Hub. Si queremos subir nuestras propias imágenes a Docker Hub (gratuito para imágenes públicas), necesitamos crear una cuenta. Puedes crear tu cuenta en el siguiente enlace.
Registro de Travis
Travis ofrece dos portales:
- travisci.org: Permite añadir CI/CD a tus proyectos open source de manera gratuita.
- travisci.com: Permite añadir CI/CD a tus proyectos privados a coste.
Como estamos usando Travis para aprendizaje, saltemos a travisci.org.
El siguiente paso que tenemos que dar, es enlazar nuestra cuenta de Github con Travis (iniciamos sesión con Github). Al hacer esto:
- Ya tenemos nuestra cuenta creada en Travis.
- Autorizamos a Travis para extraer información de nuestro repositorio (por ejemplo, descargar el repositorio para ejecutar el proceso de CI/CD).
- Autorizamos a Travis para recibir notificaciones desde nuestro repositorio (por ejemplo, si una build se realizó para una rama determinada).
- Github puede verificar el estado de la build desde Travis (por ejemplo, indicando si una pull request ha pasado el proceso de CI).
Comencemos
En este tutorial, aplicaremos la automatización a los repositorios (los cuales hemos hecho previamente un fork para que aparezcan en nuestra cuenta de Github) de la aplicación de chat usando una Travis Pipeline. Travis lanzará una tarea después de cada commit donde se realizarán lss siguientes pasos:
- Se ejecutarán los test.
- La aplicación será "dockerizada".
- La imagen creada será subida al Docker Registry, en nuestro caso Docker Hub.
Subiendo una imagen de forma manual a Docker Hub
Antes de comenzar con la automatización, hagamos este proceso manualmente.
Una vez que tengamos una cuenta en Docker Hub, podemos interactuar con la plataforma desde la shell (abrir un bash terminal, o windows cmd).
Nos autentificamos contra Docker Hub.
$ docker login
Para poder subir nuestras imágenes, tienen que estar etiquetadas siguiendo el siguiente patrón:
<nombre_usuario_Docker_Hub>/<nombre_imagen>:<version>
La versión es opcional. Si no la especificamos, se aplicará latest
.
$ docker tag front <nombre_usuario_Docker_Hub>/front
Y finalmente la subimos
$ docker push <nombre_usuario_Docker_Hub>/front
Desde ahora, la imagen estará disponible para el uso de cualquier usuario.
$ docker pull <nombre_usuario_Docker_Hub>/front
Para crear el resto de las imágenes que necesitamos, debemos seguir exactamente los mismos pasos.
Como hemos dicho anteriormente, todas las tareas que se repiten se pueden llegar a convertir en un proceso tedioso y donde podemos cometer errores. En los siguientes pasos aprenderemos como automatizarlo usando la CI/CD de Travis.
Enlazando el repositorio de Travis y las credenciales de Docker Hub
Primero tenemos que activar nuestros repositorios en Travis
Una vez activados, necesitamos entrar en cada una de las configuraciones del proyecto (Back y Front) y establecer como variables de entorno el usuario y la clave de Docker Hub
Front
Back
Estas variables se usarán después para registrarnos en Docker Hub (nota: la primera vez que introducimos los datos, aparecen como texto plano. Una vez ya establecidas, aparecen como campos de clave).
Posiblemente, el repositorio sobre el que has realizado el fork no está registrado en tu cuenta de Travis. Puedes intentar sincronizar Travis con tu cuenta de Github manualmente, mediante el botón Sync
Finalmente podemos empezar la automatización de nuestras tareas.
Configuración de Travis
Para configurar Travis, necesitamos crear un fichero llamado .travis.yml
en la raíz de nuestro proyecto. Es aquí, donde describimos las acciones que serán ejecutadas por Travis.
Como ya hemos enlazado tanto el repositorio de Back End como el de Front End en Travis, éste automáticamente comprobará los ficheros yml, y en cuanto estén disponibles, los analizará.
Back End
Los pasos para crear el fichero .travis.yml
para la aplicación de back end son los siguientes:
- Escoger lenguaje y versión: en este caso vamos a eligir nodejs (tenemos otras opciones disponibles: ruby, java, python...).
- Docker Service: indicamos a Travis que vamos a hacer uso de Docker (Travis CI puede ejecutar imágenes Docker de build y subir estas imágenes al registro de contenedores).
- Instalar dependencias: como en un entorno en local, podemos ejecutar npm install.
- Testing: en nuestro caso, ejecutará los tests unitarios que han sido implementados en nuestra aplicación.
- Construir la imagen Docker: si los tests pasan, simplemente creamos la imagen del contenedor (buscará un fichero Dockerfile en la raíz del repositorio y seguirá las instrucciones del fichero para crear una build de producción, almacenándola en un contenedor de imagen Docker).
- Autenticación en Docker Hub: antes de subir la imagen generada a Docker Hub Registry necesitamos autenticarnos en Docker Hub.
- Etiquetar las imágenes de Docker: Tenemos que identificar la imagen del contenedor con una etiqueta.
- Subir las imágenes de Docker: subir la imagen generada al Docker Hub registry.
Un resumen del proceso de build:
1. Escoger lenguaje y versión
Vamos a crear nuestro fichero .travis.yml en la raíz del repositorio de backend:
Empezaremos por indicar, que vamos a utilizar nodejs como nuestro language, después estableceremos que estamos usando la versión 12 de nodejs (más información acerca de los lenguajes en éste enlace).
./.travis.yml
+ language: node_js
+ node_js:
+ - "12"
2. Servicio Docker
Primero pediremos ejecutar los comandos con sudo (administrador), sólo por si alguno de los comandos que estamos ejecutando requiere de permisos elevados.
Para utilizar Docker necesitamos solicitarlo como servicio en el fichero yml.
./.travis.yml
language: node_js
node_js:
- "12"
+ sudo: required
+ services:
+ - docker
3. Instalar dependencias
Comenzamos teniendo una máquina Ubuntu + nodejs levantada y en ejecución. Así que indicamos que queremos usar Docker. Travis ya ha descargado el código fuente desde nuestro repositorio de código, así que es el momento de ejecutar npm install antes de comenzar la ejecución de las pruebas unitarias.
./.travis.yml
language: node_js
node_js:
- "12"
sudo: required
services:
- docker
+ before_script:
+ - npm install
4. Testing
Todo la fontanería está lista, por lo que podemos comenzar a definir nuestros scripts principales. Vamos a añadir dentro del fichero Travis yml una sección que se llame script, y dentro de la misma vamos a añadir un comando npm test; este simple comando, hará que se ejecuten nuestra batería de tests.
./.travis.yml
language: node_js
node_js:
- "12"
sudo: required
services:
- docker
before_script:
- npm install
+ script:
+ - npm test
5. Construir la imagen de Docker
Si la ejecución de los tests ha tenido éxito, estamos listos para construir la imagen Docker. En el artículo anterior creamos un Dockerfile configurando los pasos del proceso de build.
./Dockerfile
FROM node WORKDIR /opt/back COPY . . RUN npm install EXPOSE 3000 ENTRYPOINT ["npm", "start"]
Refresquemos la memoria acerca de la configuración reflejada en el Dockerfile:
- FROM node Establecemos la imagen base de node desde Docker Hub image.
- WORKDIR /opt/back Establecemos nuestro directorio de trabajo en /opt/back.
- COPY . . Copiamos el contenido en el contenedor. La ruta donde será copiado el contenido será en el directorio de trabajo seleccionado, en este caso /opt/back.
- RUN npm install Instalamos las dependencias.
- EXPOSE 3000 Notificamos cuál es el puerto que expone nuestra aplicación.
- ENTRYPOINT ["npm", "start"] Comando para arrancar nuestro contenedor.
Volvamos al fichero Travis yml: dentro de la sección script, justo después de npm test, añadimos el comando para construir la imagen del contenedor Docker.
./.travis.yml
language: node_js
node_js:
- "12"
sudo: required
services:
- docker
before_script:
- npm install
script:
- npm test
+ - docker build -t back.
Este comando buscará el fichero Dockerfile, que creamos en la raíz del repositorio de backend, y seguirá los pasos para realizar el proceso de build de la imagen.
¡Un momento! Acabo de caer en la cuenta de que aquí hay algo extraño: estamos utilizando distintos contenedores. Travis ejecuta los tests en una instancia de linux y el Dockerfile usa otra configuración de linux / node extraída del Docker Hub Registry. Esto huele mal, ¿no?
¡Totalmente de acuerdo! Ambas configuraciones, la del fichero Travis yml y la del fichero Dockerfile deberían arrancar desde la misma imagen. Necesitamos asegurarnos de que los tests se ejecutan con la misma configuración que tendremos en nuestro entorno de producción - esto es una limitación de la versión gratuita Travis.org (aquí puedes encontrar algunos workarounds). La versión de pago permite configurar la imagen del contenedor que quieras como punto de partida, más información en éste enlace.
6. Autenticarnos en Docker Hub
Justo después de que todos los scripts hayan sido ejecutados, y la imagen de docker haya sido generada, queremos subir la Docker image al Docker Hub Registry
Travis yml expone una sección denominada _aftersuccess. Esta sección sólo es ejecutada, si todos los pasos de la sección script, se han ejecutado con éxito. Es aquí donde realizaremos las acciones necesarias, para subir la imagen al registro de docker.
El primer paso es autenticarnos en Docker Hub (haremos uso de las variables de entorno que añadimos a la configuración de nuestro proyecto de Travis, ver la sección Enlazando el repositorio de Travis y las credenciales de Docker Hub de este artículo).
./.travis.yml
language: node_js
node_js:
- "12"
sudo: required
services:
- docker
before_script:
- npm install
script:
- npm test
- docker build -t back.
+ after_success:
+ - docker login -u $DOCKER_USER -p $DOCKER_PASSWORD
7. Etiquetar las imágenes de Docker
La imagen de Docker que acabamos de generar tiene el nombre de back. A la hora de subir la imagen a Docker Hub Registry, debemos proveer un nombre más elaborado y único:
- Lo prefijamos con el nombre del usuario de Docker.
- Añadimos un sufijo con un número único de build (en este caso \$TRAVIS_BUILD_NUMBER).
Por otro lado, indicaremos que la imagen que se ha generado es la última imagen de Docker disponible.
En un proyecto real esto puede variar dependiendo de nuestras necesidades.
./.travis.yml
language: node_js
node_js:
- "12"
sudo: required
services:
- docker
before_script:
- npm install
script:
- npm test
- docker build -t back.
after_success:
- docker login -u $DOCKER_USER -p $DOCKER_PASSWORD
+ - docker tag back $DOCKER_USER/back:$TRAVIS_BUILD_NUMBER
+ - docker tag back $DOCKER_USER/back:latest
8. Subir las imágenes de Docker
Ahora que tenemos identificadas nuestras imágenes de Docker con nombres únicos, necesitamos subirlas al Docker Registry. Utilizaremos el comando docker push para este fin.
./.travis.yml language: node_js node_js: - "12" sudo: required services: - docker before_script: - npm install script: - npm test - docker build -t back. after_success: - docker login -u $DOCKER_USER -p $DOCKER_PASSWORD - docker tag back $DOCKER_USER/back:$TRAVIS_BUILD_NUMBER + - docker push $DOCKER_USER/back:$TRAVIS_BUILD_NUMBER - docker tag back $DOCKER_USER/back:latest + - docker push $DOCKER_USER/back:latest
Fíjate que primero estamos subiendo la imagen $DOCKER_USER/back:$TRAVIS_BUILD_NUMBER, y después la imagen $DOCKER_USER/back:latest
¿Significa esto que la imagen será subida dos veces?... La respuesta es no.
Docker es lo suficientemente listo para identificar que la imagen es la misma, así que le asignará "nombres" diferentes a la misma imagen en el Docker Repository
El resultado final
Nuestro fichero .travis.yml
debería ser parecido a este:
language: node_js node_js: - "12" sudo: required services: - docker before_script: - npm install script: - npm test - docker build -t back . after_success: - docker login -u $DOCKER_USER -p $DOCKER_PASSWORD - docker tag back $DOCKER_USER/back:$TRAVIS_BUILD_NUMBER - docker push $DOCKER_USER/back:$TRAVIS_BUILD_NUMBER - docker tag back $DOCKER_USER/back:latest - docker push $DOCKER_USER/back:latest
Ahora, si subimos toda esta configuración a Travis, automáticamente disparará un proceso de build (también puedes lanzar un proceso de build desde la web UI de Travis).
Una vez finalizado, puedes comprobar que la imagen de Docker ha sido generada con éxito (comprobar la consola web de Travis):
Y también podemos comprobar si la imagen está disponible en nuestra cuenta de Docker Hub Registry:
Front End
Los pasos para crear le fichero .travis.yml
son muy parecidos a los anteriores (backend), la única diferencia es que no hemos implementado pruebas unitarias (nos saltaremos ese paso):
- Servicio de Docker.
- Construir la Imagen de Docker.
- Autenticarse en Docker Hub.
- Etiquetar las imágenes de Docker.
- Subir las imágenes de Docker.
1. Servicio de Docker
La aplicación de frontend será "dockerizada". Por lo que, comenzaremos indicado que el servicio es necesario, y sudo es requerido; exactamente igual que en la configuración de backend
./.travis.yml
+ sudo: required
+ services:
+ - docker
2. Construir la imagen de Docker
Generamos la imagen de Docker dentro de la sección script
.
./.travis.yml
sudo: required
services:
- docker
+ script:
+ - docker build -t front .
3. Autenticarnos en Docker Hub
Si la imagen de Docker se construyó con éxito, el siguiente paso es autenticarnos en Docker Hub.
./.travis.yml
sudo: required
services:
- docker
script:
- docker build -t front .
+ after_success:
+ - docker login -u $DOCKER_USER -p $DOCKER_PASSWORD
4. Etiquetar las imágenes de Docker
Como hicimos con la aplicación de backend, vamos a etiquetar la versión actual utilizando Travis Build number y definirla como latest
.
./.travis.yml
sudo: required
services:
- docker
script:
- docker build -t front .
after_success:
- docker login -u $DOCKER_USER -p $DOCKER_PASSWORD
+ - docker tag front $DOCKER_USER/front:$TRAVIS_BUILD_NUMBER
+ - docker tag front $DOCKER_USER/front:latest
8. Subir las imágenes de Docker
Ahora sólo necesitamos subir las imágenes de Docker.
./.travis.yml sudo: required services: - docker script: - docker build -t front . after_success: - docker login -u $DOCKER_USER -p $DOCKER_PASSWORD - docker tag front $DOCKER_USER/front:$TRAVIS_BUILD_NUMBER + - docker push $DOCKER_USER/front:$TRAVIS_BUILD_NUMBER - docker tag front $DOCKER_USER/front:latest + - docker push $DOCKER_USER/front:latest
Y el fichero final .travis.yml
debería ser como éste:
sudo: required services: - docker script: - docker build -t front . after_success: - docker login -u $DOCKER_USER -p $DOCKER_PASSWORD - docker tag front $DOCKER_USER/front:$TRAVIS_BUILD_NUMBER - docker push $DOCKER_USER/front:$TRAVIS_BUILD_NUMBER - docker tag front $DOCKER_USER/front:latest - docker push $DOCKER_USER/front:latest
Ejecutando un sistema de multi contenedores
Vamos a comprobar si nuestra Configuración de CI funciona como esperamos.
Primero nos aseguramos de que Travis ha ejecutado con éxito al menos un proceso de build (en caso contrario, podemos disparar el proceso de build de manera manual o simplemente realizar una subida de código trivial, a los repositorios de backend y frontend)
Deberíamos ver en Travis que el proceso de build ha sido lanzado para los repositorios de Front End y Back End (autenticándote en travis.org):
También deberíamos ver las imágenes disponibles en el docker registry (autenticándote en Docker Hub):
Como hicimos en nuestro artículo previo, podemos arrancar todo nuestro sistema usando Docker Compose. Sin embargo, en este caso para el Front End y Back End vamos a consumir las imágenes que hemos subido a Docker Hub Registry.
Los cambios que vamos a introducir al fichero docker-compose.yml son los siguientes:
version: '3.7' services: front: - build: ./container-chat-front-example + image: <nombre_usuario_Docker_Hub>/front:<version> back: - build: ./container-chat-back-example + image: <nombre_usuario_Docker_Hub>/back:<version> lb: build: ./container-chat-lb-example depends_on: - front - back ports: - '80:80'
Así queda el fichero docker-compose.yml
:
version: "3.7" services: front: image: <nombre_usuario_Docker_Hub>/front:<version> back: image: <nombre_usuario_Docker_Hub>/back:<version> lb: build: ./container-chat-lb-example depends_on: - front - back ports: - "80:80"
Podemos arrancarlo utilizando:
$ docker-compose up
Entonces se descargarán las imágenes de back y front desde Docker Hub Registry (las últimas disponibles). Podemos comprobar cómo funciona abriendo un navegador web e introduciendo http://localhost/ en la barra del navegador (más información acerca de cómo funciona en nuestro artículo previo Hola Docker)
Recursos
- Artículo previo Hola Docker
- Repositorio Front End
- Repositorio Back End
- Travis community
- Travis enterprise
- Docker Hub
En Resumen
Al introducir este proceso de CI/CD, hemos obtenido múltiples ventajas:
- El proceso de build se ha automatizado, de esta manera evitamos los errores manuales.
- Podemos desplegar distintas versiones de la build fácilmente (como de si una máquina de discos se tratará, ver diagrama).
- Podemos dar marcha atrás fácilmente una publicación errónea.
- Podemos aplicar testeo A/B o tener ambientes Canary.
¿Y qué hay del despliegue? En el próximo artículo de esta serie aprenderemos a crear despliegues automatizados usando Kubernetes.
Permanece atento a nuestro blog :)
Puedes seguir leyendo más artículos de esta serie:
- [Hola Docker][0]
¿Te gusta el mundo Devops?
En Lemoncode impartimos un Bootcamp Devops Online, si quieres saber más puedes pinchar aquí para más información.