Deploy a múltiples entornos automatizados con dokku

Introducción

Esta semana estuvimos puliendo una de las herramientas que usamos a diario para realizar el deploy de nuestro proyectos: dokku

Encontramos que si bien dokku es configurable y soporta muchos modos de trabajo (o workflows), en casi todos nuestros proyectos usamos dos ramas de desarrollo:

  • master: en donde realizamos todas las mejoras destinadas a los servicios en producción.
  • staging o develop: donde realizamos mejoras a futuro, pruebas y modificaciones parciales (o en proceso de validación).

Ahora bien, ¿cómo se puede configurar dokku para realizar deploys en este escenario?, ¿qué herramientas se pueden ajustar para simplificar esta tarea?

Idea general del workflow que utilizamos

Una de las ideas que nos gustan de dokku es que propone centrar toda la información de la aplicación dentro de un repositorio git, desde donde todos podemos aportar y reproducir las aplicaciones sin dependencias o configuraciones manuales.

Así que partimos de la idea de tener un solo repositorio para la aplicación, y separar en dos branchs, uno para cada entorno: master y staging.

A su vez, cada uno de estos dos branchs están asociados a un entorno en dokku, cada uno con su propio subdominio:

Con este escenario, si estamos trabajando en un proyecto y queremos iniciar un deploy al entorno que corresponda (dependiendo del branch actual) ejecutamos:

git commit -m "..."  
git push              # Para consolidar en un solo lugar.  
make deploy           # Para realizar el deploy al entorno que corresponde.  

Ese comando make deploy requiere algo de configuración para que funcione la primera vez, pero para ilustrarlo paso a paso veamos la próxima sección:

Trabajando con múltiples entornos

Veamos un ejemplo paso a paso de cómo integrar la idea de múltiples entornos sobre dokku:

Primero tenemos que clonar el repositorio:

git clone git@github.com:EnjambreBit/node-js-sample.git  
cd node-js-sample  

El repositorio tiene un solo remoto, el de github:

~/p/e/node-js-sample (master=)
> git remote -v
origin    git@github.com:EnjambreBit/node-js-sample.git (fetch)  
origin    git@github.com:EnjambreBit/node-js-sample.git (push)  

Ahí se van a subir todos los cambios siempre, ya que es el repositorio principal.

Luego, para hacer deploys a dokku tenemos que agregar la dirección de dos repositorios remotos:

git remote add production dokku@enjambrelab.com.ar:node-js-sample  
git remote add staging dokku@enjambrelab.com.ar:staging-node-js-sample  

y al igual que vimos en un artículo pasado, no hace falta configurar nada en dokku. Cada vez que hagamos push sobre alguno de esos repositorios remotos dokku se va a encargar de iniciar el deploy por nosotros.

Por ejemplo, ¿como haríamos el deploy a producción?, así:

> git checkout master
> git push production master

[...]

-----> Setting config vars
       DOKKU_APP_RESTORE: 1
=====> Application deployed:
       http://node-js-sample.enjambrelab.com.ar

To enjambrelab.com.ar:node-js-sample  
 * [new branch]      master -> master

y para iniciar el deploy a staging, deberíamos tener creado el branch staging (comando git branch staging) y luego ejecutar estos comandos:

> git checkout staging
> git push staging staging:master

[...]

-----> Setting config vars
       DOKKU_APP_RESTORE: 1
=====> Application deployed:
       http://staging-node-js-sample.enjambrelab.com.ar

To enjambrelab.com.ar:staging-node-js-sample  
 * [new branch]      staging -> master

Hasta aquí tendríamos un escenario de deploys en dos entornos, pero quedaría a cargo del desarrollador tener cuidado y elegir exactamente "qué" branch corresponde a "cada" entorno.

Por ejemplo, si está trabajando en master debería hace el deploy a "production".

Esta tarea no es difícil, pero por día puede que hagas varios deploys. Y con las repeticiones, una y otra vez, se convierte en una de esas decisiones triviales que se vuelven como una pequeña piedra en el zapato... un problema que se puede resolver de una vez y listo :P

Simplificando la interfaz

Para simplificar el deploy y no tener que detenernos a decidir en qué branch estamos a la hora de hacer push, podemos crearnos un archivo Makefile que se encargue de realizar el deploy al repositorio que corresponda.

Por ejemplo, si estamos en master se podría ejecutar un deploy así:

make deploy  

y si estamos en staging, se puede ejecutar exactamente lo mismo:

make deploy  

Sí, exactamente el mismo comando:

El comando make simplemente busca reglas dentro de un archivo llamado Makefile, en donde están definidos todos los comandos que se deben ejecutar como si se tratara de sub-programas muy pequeños.

Si querés incorporar este archivo Makefile en tu proyecto ejecuta estos dos comandos y luego make o make deploy:

wget https://raw.githubusercontent.com/EnjambreBit/node-js-sample/master/Makefile  
wget https://raw.githubusercontent.com/EnjambreBit/node-js-sample/master/confirm.sh  

Automatizando para deploys automáticos

Una mejora adicional que podemos agregar a este ejemplo son los deploys automáticos, para que nuestro flujo de trabajo simplemente consista en subir código al repositorio principal, y que otro servicio (como codeship, circleCI o travis) haga el deploy por nosotros.

Lo bueno de esta automatización es que vamos a tener la certeza que nunca se nos olvidará hacer un deploy e incluso permitirá tener otros desarrolladores trabajando en el repositorio (sin que necesiten autenticarse o configurar el accesos a dokku en sus equipos).

La idea general es:

Programador sube código a github -> circleCI detecta el push y lanza el deploy -> dokku recibe el push y hace el deploy.

El primer paso es crear un archivo llamado circle.yml especificando los comandos para realizar el deploy:

deployment:  
  production:
    branch: master
    commands:
      - git remote add production dokku@enjambrelab.com.ar:node-js-sample
      - git push production master
  staging:
    branch: staging
    commands:
      - git remote add staging dokku@enjambrelab.com.ar:staging-node-js-sample
      - git push staging staging:master

El primer paso es configurar nuestro proyecto dentro de CircleCI. Podemos ingresar en https://circleci.com y pulsar el botón Build Project para que el repositorio quede vinculado al servicio:

Ahora, cada vez que hagamos push al repositorio circle.ci va a ser notificado y va a lanzar la ejecución del deploy.

Luego, ingresamos por ssh como root al equipo que tiene dokku instalado y tenemos que crear un par de claves ssh para que circle.ci pueda realizar deploys.

Estos son los comandos a ejecutar:

> ssh-keygen -t rsa       # escribir como
                          # nombre "circleci.id_rsa"
                          # cuando lo solicite 
                          # (sin comillas).

y luego

cat circleci.id_rsa.pub | sudo sshcommand acl-add dokku circleci  

Por último, tenemos que copiar y pegar la clave pública recién generada al administrador de circle.ci, por ejemplo en la vps se puede usar este comando para imprimir la clave en pantalla:

cat circleci.id_rsa  

y el resultado pegarlo en la sección SSH Permissions dentro de las preferencias del proyecto:

Ahora sí, circle.ci va a correr nuestro deploy a dokku:

Incluso hasta puede notificarnos cuando el build finaliza usando las notificaciones de escritorio:

Conclusión

Desde que comenzamos a utilizar dokku cambió radicalmente nuestra forma de encarar proyectos. Podemos dedicarle más tiempo a las desiciones importantes del negocio y nuestros clientes.

Y sin duda esta automatización va a significar otro paso hacia adelante, ya que los deploys tranquilamente van a ser tarea de las computadoras en el futuro, y no de nosotros.