¡Llegamos al final de nuestra serie de Docker!

En este breve post de hoy voy a presentaros los entornos Docker con los que trabajamos en algunos nuestros proyectos reales.

Tenéis el código necesario en este repositorio de Github.

Resumiendo:

  1. Tenemos dos entornos: uno como servidor de aplicaciones con Apache y PHP; otro para una base de datos MariaDB 5.5.
  2. Ambos se basan en el sistema operativo CentOS 7. ¿Por qué? Porque nuestros entornos de stage y producción también están basados en CentOS 7.

Apache + PHP

Si le echáis un vistazo a su Dockerfile, veréis que partimos de la imagen centos:7 y mediante un comando RUN instalamos los binarios que necesitamos: 1. El repositorio EPEL (necesario para instalar PHP en su versión 7). 2. PHP 7.0. 3. Las extensiones FPM, GD, Mcrypt, Mysql, devel, PEAR y Mbstring para PHP 7.0 (el código de nuestros proyectos hacen uso de ellas). 4. Cosas de C: los compiladores gcc y gcc-c++, autoconf, automake y make 5. Xdebug

Añadimos el archivo run-httpd.sh a la raíz de nuestro sistema de directorios de la imagen: éste va a ser el punto de inicio del contenedor, como veremos en un minuto.

Añadimos el archivo php.ini dentro del directorio etc de nuestra imagen. Es decir, sobreescribimos el php.ini original directamente por el que tenemos en el repositorio. Esencialmente, este nuevo php.ini lo que hace es activar las extensiones instaladas anteriormente.

Añadimos también el archivo httpd.conf dentro del directorio /etc/httpd. Es decir, sobreescribimos el httpd.conf original directamente por el que tenemos en el repositorio. En esta nueva configuración tenemos cambiado el puerto de escucha al 80, activamos todas las directivas en los archivos .htaccess y establecemos el manejador de archivos con extensión php al motor PHP instalado.

Le damos permisos de ejecución al archivo run-httpd.sh y por último lo ejecutamos.

Dicho script realiza tres tareas:

  1. Primero, inserta al final del archivo php.ini el contenido de una variable de entorno XDEBUG_REMOTE_HOST, que contendrá la IP de nuestro host (lo veremos a continuación), como valor del parámetro xdebug.remote_host. Es decir, le decimos a PHP que queremos depurar desde nuestra máquina anfitrión, como es evidente.
  2. Elimina archivos temporales que pudiesen haber quedado de una ejecución anterior del contenedor y que evitan que httpd (aka, Apache) casque horriblemente.
  3. Ejecuta el servidor Apache en *foreground*. ¿Por qué en Foreground? Repasad los artículos anteriores y si aún así os quedan dudas, disparad en los comentarios :)

Con este Dockerfile hacemos uso del comando docker build y creamos una imagen (llamémosla moisesvilar/apache, por ejemplo).

Por último, ejecutamos nuestro contenedor con el siguiente comando:

docker run -d -p 80:80 -v /c/Users/moisesvilar/projects:/var/www/html -e XDEBUG_REMOTE_HOST='192.168.51.87' --name apache_container moisesvilar/apache

La IP 192.168.51.87 es la dirección de mi máquina, donde tengo instalado mi IDE con la que realizar la depuración.

Sería la misma máquina anfitrión si utilizamos un entorno con soporte nativo de Docker o bien la máquina Windows si estamos utilizando la Docker Machine.

Dicha IP, evidentemente, es distinta para cada miembro del equipo de desarrollo. Por eso se la pasamos como una variable de entorno a la hora de ejecutar el contenedor, y no la establecemos directamente en el archivo php.ini. Si lo hiciéramos así ¡tendríamos que tener un Dockerfile para cada miembro del equipo! y no serían reutilizables. ¿Lo vemos? Claro que sí. Sois gente más lista que yo :)

MARIA DB

El Dockerfile del contenedor MariaDB parece más complicado de lo que parece.

Pero en resumen lo que hace es:

  1. Parte de la imagen centos:7
  2. Instala los paquetes necesarios para el servidor MariaDB
  3. Copia en la imagen y ejecuta el archivo fix-permissions.sh, que es un script que otorga permisos de escritura y ejecución a los archivos que se le pasan como parámetro.
  4. Copia en la imagen y establece como punto de entrada el script docker-entrypoint.sh, que lo que hace es ejecutar las instrucciones necesarias para inicializar la base de datos en función de los valores de unas variables de entorno, de las cuales nos centraremos sólo en una: MYSQL_ROOT_PASSWORD, que contendrá la contraseña del usuario root de la base de datos.
  5. Monta un volumen de datos en el directorio /var/lib/mysql
  6. Expone el puerto 3306
  7. Ejecuta el comando mysqld_safe, que recogerá el punto de entrada fijado en el script docker-entrypoint.sh e inicializará la base de datos.

De nuevo, ejecutamos un docker build y creamos una imagen, por ejemplo, moisesvilar/mariadb.

A continuación, lanzamos nuestro contenedor:

docker run -d -p 3306:3306 -e MYSQL_ROOT_PASSWORD=Ultrasecretpassword --name mariadb_container moisesvilar/mariadb

Como vemos, establecemos la contraseña de root en la variable de entorno MYSQL_ROOT_PASSWORD en el mismo comando docker run. De esta manera, cada miembro del equipo puede tener la contraseña de root que ellos consideren dejando el archivo Dockerfile totalmente reutilizable.

Siguientes pasos

Como veis, nuestros entornos de desarrollo están enteramente basados en Docker. Esto nos da como ventaja el poder replicar entornos en distintos equipos de una manera ultrarápida y eficaz, además de tener la seguridad de que todos y cada uno de nosotros estamos desarrollando sobre el mismo entorno exactamente.

Y cuando digo exactamente es exactamente: todos los bits, uno a uno (o cero a cero), son exactamente iguales entre mis imágenes Docker y las de cualquier otro miembro del equipo. Si estoy programando con @Nach_CaLa en el mismo proyecto, cada uno con su entorno Docker, ambos tenemos la seguridad de que si funciona en mi entorno, también va a funcionar en el del otro. Y eso, amigos míos, ayuda mucho a dormir bien por las noches.

Nuestros siguientes pasos en la adopción de Docker pasan por montar un entorno de integración contínua, de tal manera que al actualizar el código de nuestro repositorio central (Gitlab, Github, Bitbucket o el que más rabia o dé), se construya automáticamente una imagen de Docker con dicho código en su sistema de directorios, que se despliegue automáticamente tanto en los entornos de staging como production (o testing o los que tengáis).

Y con este paso se cerraría el círculo: ya no sólo tendríamos la seguridad de que todos los miembros del equipo trabajan sobre el mismo entorno, sino que el código en producción se está ejecutando exactamente (uno a uno y cero a cero) en el mismo entorno que fue desarrollado. Y con esto, niños, no es que ayude a dormir bien. Es que roncas a pierna suelta :)

Conclusiones

¡Terminamos Docker!

Hemos visto un montón de cosas pero hay muchas más que nos quedan en el tintero.

Mi próximo hito en este tema es ver Kubernetes como plataforma de orquestación de contenedores Docker. Tan pronto lo tenga aprendido, controlado, experimentado e interiorizado, pasaré por aquí a dejaros mis impresiones, por supuesto.

Mientras tanto, para la semana que viene cambiaremos de tercio.

Voy a continuar con un par de posts un poco más concretos: cómo implementar sistemas de autenticación segura. Primero, de una manera algo más “artesanal” pero perfectamente válida, y después empleando tokens JWT.

Nos vemos en 7 días (¡prometido!).