Javier Jerónimo


Push to deploy desde BitBucket

22 Mar 2015 » secdevops

En Genexies Mobile estamos lanzando un nuevo servicio (lo estamos presentando a varios concursos, entre ellos al TechCrunch Startup Battlefield de mayo de 2015… ya iremos contando…) y para agilizar las pruebas en pre-producción hemos configurado un sistema de “push to deploy” desde BitBucket hacia los servidores de pruebas que tenemos en Google Compute Engine.

La solución ha sido bastante sencilla de configurar y voy a explicar un poco cómo lo hemos hecho.

Configuración del servidor de pre-producción

Como he dicho, tenemos un servidor de pre-producción en Google Compute Engine. De hecho, tenemos dos servidores: el servidor backend (con el API Spring + Jersey); y el servidor MongoDB.

El servidor en cuestión, el del backend, tiene una estructura montada que nos permite usarlo para PRE/PRO, es decir, nos permite usarlo en dos entornos simultáneamente:

  • www.example.com: Aquí tenemos publicado un banner, mientras terminamos la versión beta.
  • www-pro.example.com: Aquí tendremos la versión beta desplegada. Al disponer de este entorno y del anterior, podemos alternar en el dominio principal entre el banner y la beta (por si hubiera problemas graves que nos obligaran a tener que parar el servicio).
  • www-pre.example.com: La versión pre-producción del producto.

Como de momento sólo tenemos montado un grupo de servidores, tenemos los tres entornos montados compartiendo el mismo espacio:

# ##########
# Banner: sitio de Apache sirviendo HTML estático.
/etc/apache2/sites-enabled/www-banner.conf
/srv/example/www-banner


# ##########
# PRO: desactivado (alternamos su publicación en el mismo dominio que el banner anterior). Consta de dos partes (servicio) y una adicional ("push-to-deploy"):
# - Sitio sirviendo interfaz HTML5
/etc/apache2/sites-available/www-pro.conf
/srv/example/www-pro # DocumentRoot

# - Proxy inverso a la aplicación en Tomcat7
/etc/apache2/sites-available/api-pro.conf
/srv/example/api-pro

# - Sitio sirviendo script PHP para "push-to-deploy"
/etc/apache2/sites-enabled/deploy-pro.conf
/srv/example/deploy-pro


# ##########
# PRE: Consta de las mismas tres partes:

# - Sitio sirviendo interfaz HTML5
/etc/apache2/sites-enabled/www-pre.conf
/srv/example/www-pre

# - Proxy inverso a la aplicación en Tomcat7
/etc/apache2/sites-enabled/api-pre.conf
/srv/example/api-pre

# - Sitio sirviendo script PHP para "push-to-deploy"
/etc/apache2/sites-enabled/deploy-pre.conf
/srv/example/deploy-pre

Push to deploy

BitBucket, el servicio de repositorios que usamos para algunos proyectos y para este también, permite hacer despliegues mediante una integración sencilla entre los servidores que ejecutan las aplicaciones y los servidores que alojan el código.

En el caso de este servicio, permite configurar anclas (hooks) que no son más que puntos en el flujo de ejecución del servicio (repositorio Git) a los que añadir acciones adicionales a realizar. En general, acciones que se ejecutarán en servicios remotos.

Push to deploy consiste en:

  • Cada vez que se realice un push al repositorio git alojado en BitBucket
  • Ejecuta este ancla…

¿Qué tipos de anclas permite BitBucket? Bastante, entre otras: integración con servicios Bamboo, Campfire, Jenkins, enviar un email, enviar un tweet… y la que nos interesa a nosotros: en cada push al repositorio, envía una petición HTTP POST a una URL concreta (https://confluence.atlassian.com/display/BITBUCKET/POST+hook+management).

La implementación es sencilla: los sitios Apache2deploy-xxx.conf” que listaba anteriormente contienen un script PHP que es invocado remotamente por BitBucket cada vez que hacemos un PUSH al repositorio git.

Estos scripts PHP realizan lo siguiente:

  • Comprobaciones del “git push” que ha servido de disparador para este proceso. Las explico más abajo.
  • Actualizan la copia del repositorio en el servidor (git fetch).
  • Exportan el contenido al punto de instalación.
  • Tareas propias de la generación de los artefactos en el punto de instalación: mvn installgrunt build… etc.
  • DocumentRoot de los sitios de Apache2 apuntan al lugar donde se generan los artefactos en el paso anterior.

Algunas preguntas importantes sobre el proceso

¿Siempre se despliegan los cambios cuando ha habido un_ push_ al repositorio central? ¿E independientemente de la rama sobre la que se hayan hecho los cambios? No, las comprobaciones iniciales de los scripts PHP sirven para esto mismo:

  • En www-pre y api-pre sólo se desplieguen cambios (git push) realizados en la rama integration.
  • En www-pro y api-pro sólo cambios en la rama master.

¿Y si cualquier tunante invoca el POST en vuestros servidores? Es posible, pero improbable. Aunque es una implementación inicial, sencilla, para agilizar las pruebas durante el desarrollo, tiene dos puntos importantes de seguridad:

  • HTTPS en la URL del POST (tampoco hay que hacer el tonto sólo con HTTP).
  • El nombre del script PHP tiene una componente aleatoria (por ejemplo: deploy-script-abcabc.php).

¿Y si algún tunante interno hace un push de código roto? Pues efectivamente… pero somos tres los ingenieros que estamos lanzando el nuevo servicio, así que en ese caso nos tiramos una pelota de goma o damos un pequeño alarido dirigido y listo… 😉 Cuando esté publicada la beta, será una máquina (Jenkins, CodeShip, …) la que despliegue, sólo si las pruebas pasan 100% ok.

Como se puede ver, tiene puntos de mejora, pero nos sirve para esta fase del proyecto.

Resumen del proceso push-to-deploy

El proceso completo quedaría de la siguiente forma:

  1. Ingeniero – realiza un git push al repositorio central BitBucket. bien de código de pruebas (rama integration) o bien algo ya probado y consensuado para subida (rama master).
  2. Servidor BitBucket – ejecuta el ancla: POST https://deploy-pre.example.com/deploy-script-abcabc.php
  3. Script PHP despliegues – realizar comprobaciones iniciales.
  4. Script PHP despliegues – actualizar el código en el servidor: git fetch.
  5. Script PHP despliegues – exportar el código a la ruta de instalación.
  6. Script PHP despliegues – generar los artefactos en la ruta de instalación (mvn clean package tomcat7:redeploy en un caso y grunt build en otro).

Conclusiones

Es una integración sencilla, pero nos permite tener dos entornos: (1) pre-producción para pruebas en entorno-real-no-localhost y (2) casi-producción para la beta. También nos proporciona una agilidad brutal ya que cualquier miembro del equipo puede desplegar una nueva versión de código en cuestión de segundos. Y como git impide que hagas un push si no has mezclado previamente tus cambios con los más recientes en la rama en el repositorio… pues perfecto…

Otro punto importante: esta implementación del mecanismo push-to-deploy es independiente de la infraestructura sobre la que despleguemos finalmente el servicio. Google App Engine, Heroku y otros servicios de plataforma tienen sus propios sistemas de push-to-deploy, pero en nuestro caso estamos usando infraestructura como servicio (IaaS) así que la solución es más casera, pero también compatible con cualquier servicio de infraestructura, porque al fin y al cabo es un script PHP invocado remotamente.

La evolución de este sistema será:

  • Para los despliegues en producción: tener un repositorio central específico para este fin. Sólo el sistema de integración continua, o el maestro de la mazmorra si es una persona, tendrá permisos para hacer push a este repositorio, y lo hará después de que las pruebas pasen 100% ok. O si terminamos usando un servicio externo de integración continua como CodeShip, quitaremos el ancla de BitBucket y dejaremos que sea CodeShip quien lance las pruebas y realice el POST para el despliegue sólo si las pruebas salen 100% ok.
  • Para los despliegues en pre-producción: probablemente esta solución es suficiente, ya que cualquier ingeniero con acceso de escritura al repositorio central puede lanzar un despliegue del contenido de la rama integration en este entorno de pruebas en la nube.
  • Mejorar el sistema de seguridad, dejando de usar una componente aleatoria en el nombre del script, y pasando a usar un parámetro (query-string) que nos permita ir cambiando fácilmente dicho secreto con cierta frecuencia para mayor seguridad.