De entorno local a Docker: una historia de (des)encanto

Luego de varios meses alejado del blog (ya sea por los nuevos proyectos, por estudio o por familia) toca volver a los viejos amores.

Mi capítulo Docker se inaugura con un histórico y sostenido rechazo a las opciones de virtualización en entornos de desarrollo.

Por un lado, porque la performance o el consumo de recursos de las viejas soluciones fue siempre desmedido para un entorno local. Por el otro, justamente, mi computadora ha sido siempre para programar… ¿por qué no tener un stack armado si es lo que hago el 90% del tiempo?

El año pasado, luego de estar leyendo durante mucho tiempo sobre Docker, comencé a utilizarlo, primero, para probar algo muy puntual. Siempre fue algo muy satelital. Este año, debido a las dimensiones de algunos proyectos, comencé a usar servicios específicos y hasta algunas bases de datos, vía Docker, junto a mi stack local.

Hoy logré, casi en su totalidad, desinstalar el stack local y llevar todo a Docker (aún queda alguna cosita ya que no se trata de 1 proyecto solamente).

La idea de este primer post es bajar un ejemplo de configuración específico, multiproyecto, en Linux. Cualquier otra cosa, no estaría siendo cubierto, no hoy.

Para poder explicar la transición, lo mejor será recorrer la historia de mi entorno local. No desde el comienzo, ya que no tiene sentido hablar hoy de PWS (mi primera configuración local).

Supongamos que hoy instaláramos nuestro primer webserver (en el comienzo fue Apache) porque estábamos adentrándonos en el desarrollo PHP (mirar con ojos de newbie).

Una vez terminada la instalación, podíamos ir a nuestro navegador e iniciar la magia de tener un servidor web.

http://www.localhost/

Así fue que muchos pudimos comenzar a desarrollar local, sin tener que subir por FTP (si, estoy haciendo un viaje en el tiempo intencional). Incluso, hubo una era en que había hosting gratuito que sólo dejaba subir por web los archivos, pero esa es otra historia.

A medida que comenzamos a programar y probar cosas, es muy probable que terminemos con algo así en nuestro día a día.

Múltiples proyectos bajo un único directorio

¿Les suena familiar?

Para quienes supimos usar IIS (con o sin PHP), salvo que usaras NT o 2000, no podías tener múltiples dominios. Cuando aprendimos a usar Apache en Windows o directamente en Linux, nos enamoramos de los VirtualHost.

Webserver con múltiples VirtualHost

Ahora si, cada cosa en su lugar, separada y sin generar ruido.

Otro salto cualitativo fue/es el uso de bases de datos. Una vez que aprendimos a mostrar algo en pantalla, siempre vamos a querer que hayan datos que podamos manipular. Llega entonces el momento de las bases de datos.

Entorno local con Webserver y Base de datos

Aquí nuestro stack local comienza a crecer, y dependiendo de lo que estemos haciendo, ese nuevo invitado podrá multiplicarse de diferentes formas.

En mi caso, siempre fue algo así.

Entorno local con diferentes motores de base de datos.

Mi pasado mi condena, y PostgreSQL suele ser mi opción por defecto.

Volviendo a la historia del stack, con los años, luego de vaya a saber uno cuántos proyectos, principalmente con Magento; mi entorno local se parecía a esto.

Entorno local con múltiples VirtualHost y múltiples bases de datos.

Si bien un entorno local de esta manera comienza a sentirse pesado, no fue hasta el salto de PHP 7 (y subsiguientes) que la historia se puso más entretenida.

Llegado este punto mi entorno local se veía más o menos así.

Entorno local múltiples VirtualHost, múltiples bases de datos y diferentes versiones de PHP.

Como mi opción local siempre siguió siendo Apache, cambiar la versión de PHP, dependiendo el proyecto, lo hacía con un sencillo script bash.

!/bin/bash

if [ "$(whoami)" != 'root' ]; then
  echo "Este script debe ejecutarse con el usuario root"
  exit 1;
fi

read -p "Current PHP version : " php_current_version
read -p "New PHP version : " php_new_version

current_version=$php_current_version
new_version=$php_new_version

a2dismod php$current_version ; a2enmod php$new_version ; service apache2 restart
ln -sfn /usr/bin/php$new_version /etc/alternatives/php

El script se ejecutaba indicando la versión configurada primero y la que necesitaba (previamente instalada) luego.

Tenía un paso adicional, pero se podía soportar. En el último tiempo han abundado más los proyectos Magento 2 que Magento 1, así que los downgrades eran realmente esporádicos.

Pero al mismo tiempo, al ser más proyectos Magento 2, hubo mayor necesidad de servicios adicionales.

Entorno local compuesto por Webserver, múltiples motores de base de datos y capa de servicios adicionales.

Ahora si el stack local se hace más pesado. En el pasado supe usar VirtualBox para levantar servidores específicos (por ejemplo, para Solr, Varnish u otros servicios). Hoy día, con Magento 2 en especial, hay varios que se usan casi por defecto.

Mi entorno local se fue convirtiendo hasta llegar a:

Entorno local con Webserver y múltiples VirtualHost, múltiples bases de datos y múltiples servicios.

Y acá fue en donde dejó de ser divertido. No sólo por la complejidad del stack local, sino por las dimensiones.

En caso de migraciones de tiendas, hablar de importar y operar bases de datos que arrancan en los 30 GB, es más que normal. Prueben de importar algunas de esas bases y trabajen de a un proyecto a la vez, pero controlen que pasa con Mysql cuando se pone a hacer mantenimiento o revisar integridad, y se ensaña con tu disco.

Así fue que, posiblemente más tarde que temprano (gracias fin de semana largo por estar ahí), me tocó hacer la deconstrucción de todo esto y repensar cómo simplificarlo.

Momento entonces de dejar de huirle a Docker, hacerse amigo de la virtualización (en alguna de sus variantes) y a rearmar el entorno de desarrollo.

Lo primero, definir el stack básico para un proyecto Magento 2 (que es lo que ocupa la mayor parte de mi día laboral).

Stack de software mínimo para Magento 2.

Llegué así a quedarme con la combinación conocida, sumando MailHog para el envío y captura de emails, Redis en local para cache y Elasticsearch como motor de búsqueda predeterminado.

Volviendo a la separación original del stack, me quedaría entonces Web, DB y Servicios.

Stack de software mínimo para Magento 2.

Gracias a Docker Compose es muy sencillo definir un entorno que funciona en conjunto. Y puedo también iniciarlo y detenerlo (prenderlo y apagarlo) cuando quiera o necesite.

De esta forma, replicando mi archivo docker-compose.yml en cada proyecto, puedo contenerizar cada uno de mis proyectos.

Proyectos contenerizados con Docker.

Todo muy cómodo realmente, aunque tuve mi primer detalle (de esos que no son blocker pero que hacen que la neurosis despierte de su letargo).

Puertos de cada container en mi stack local.

Asi es como funcionaba cuando tenía todo en local (casi… phpMyAdmin lo podemos soportar en un puerto no convencional porque no es parte del proyecto), o incluso podríamos conectarnos a Mysql o MariaDB por consola con:

mysql -h localhost -P 3306 --protocol=tcp -u root -p

De esa forma llegamos directo al motor de base de datos dentro del container.

Esto está bien, pero yo antes tenía múltiples proyectos vivos al mismo tiempo. Es más, hay proyectos o VirtualHosts o containers que necesito funcionando mientras estoy en un proyecto determinado.

Si intentamos levantar más de uno de esos proyectos, vamos a tener problemas porque los puertos, si fueron publicados, estarán ocupados.

La primera solución y más fácil, es usar diferentes puertos. quizás con 2 proyectos se tolera. Más allá de eso, mi otro yo no es capaz de convivir.

¿Qué solución podríamos aplicar entonces sin que nos obligue a recordar puertos o tener la necesidad de calcular qué container prender o replicar containers dentro de stacks para poder contar con todo lo necesario?.

Luego de leer bastante, y aún sin estar realmente seguro sobre si es la mejor o más óptima solución según el manual del buen programador (o el rol que sea, aunque todos sabemos que de la boca para afuera clamamos por «lo que debe ser» y puertas adentro tenemos al menos un cuchillo de palo), la solución a la que llegué, por el momento, es la siguiente.

Proxy reverso por sobre todos los stacks en Docker.

Lo que hice fue crear un Proxy Reverso, con Apache. Mantengo mis dominios en /etc/hosts, apuntando a 127.0.0.1, por lo que todo request cae en el Proxy.

Internamente, y ya iré publicando las construcciones de cada stack en posts siguientes, el proxy envía el request a cada container.

Si antes tenía que tener funcionado mi Webserver con N VirtualHosts, mi Mysql con N bases de datos, mi PostgreSql con N bases de datos, y levantar o parar servicios adicionales; ahora sólo tengo que iniciar (o dejarlo iniciado always) mi Proxy y de acá en más, puedo hacer esto.

Si necesito un proyecto, o una aplicación (por ejemplo, tengo mi propia wiki en Grav en http://wiki.localhost/), sólo tengo que ir iniciando el o los containers que se necesiten, y como todo resuelve a localhost, el proxy sabrá cursar el request.

Claramente hay algunos detalles adicionales que se necesitan a nivel de configuración, pero que se hace una sola vez, y luego nos podremos olvidar.