El bueno y viejo PHP. Un lenguaje tan usado como vilipendiado.
Pero a mi me gusta.
Vale que su API ha crecido sin orden ni concierto y a veces te encuentras tantas incongruencias que parece que la han diseñado durante un atracón de cerveza y marihuana.
Vale que ha sido usado durante años por gente sin formación, o por lo menos sin un mínimo cariño por las cosas bien hechas, y eso lo ha convertido en uno de los referentes del código espagueti.
Pero recordad que no hace mucho tiempo lo mismo le pasaba a Javascript. ¡Cuántos atentados al software he visto en Javascript! Y hoy lo tenemos en todos lados e incluso en el 2014 fue nombrado lenguaje del año. ¡Hasta Liam Neeson está un poco harto del auge de Javascript!
PHP es un lenguaje ultra-flexible, cosa que lo convierte en un lenguaje divertido… pero que también puede hacer que escribas muy mal código. ¿Culpa del lenguaje o culpa del desarrollador? Yo la respuesta la tengo clara.
Y por ello, he decidido tomarlo como framework para ilustar el ejemplo de este artículo: vamos a montar un entorno de desarrollo en PHP sobre Docker.
Let’s go!
Una pequeña sorpresa de la docker-machine
Si estáis en un entorno con soporte nativo para Docker, podéis saltar a la siguiente sección.
Arrancad vuestro entorno Docker…
docker-machine start default
… conectaros a él…
docker-machine ssh default
… y ejecutad lo siguiente:
cd /
ls -l
Fijaos en esta carpeta que os marco aquí:
Mmm. Entrad en ella y veréis que contiene otra carpeta llamada Users. Entrad también en ella y haced un ls. ¡Son vuestras carpetas personales de vuestro equipo Windows!
En efecto, la máquina virtual tiene una carpeta compartida con vuestro host Windows que nos facilitará mucho la vida, como veremos al final de este artículo.
Es más, si abrís directamente la GUI de Virtualbox, podéis ver dicha carpeta seleccionando la máquina virtual, pulsando en configuración y yendo a “Carpetas compartidas”:
La distro Alpine
Como hemos dicho en posts anteriores, Alpine es una distribución Linux superligera, ocupando menos de 4MB.
Viene con un gestor de paquetes, llamado apk, cuyo uso podéis investigar en la documentación oficial.
Y cuenta con paquetes para PHP y para Apache.
Así que supondremos que tenemos un proyecto web programado en PHP con Apache como servidor y todo sobre el sistema operativo Alpine. ¡Montemos su entorno usando Docker!
Instalando Apache y PHP en Alpine
Muy bien, lancemos un contenedor a partir de la imagen de Alpine en su versión 3.3:
docker run -t -i -p 8080:80 --name tom_collins alpine:3.3 /bin/sh
Todo nos debería de sonar excepto ese nuevo parámetro -p 8080:80. Con este parámetro lo que estamos haciendo es publicar el puerto 80 del contenedor en el puerto 8080 del host. Es decir, Docker publicará todas las peticiones entrantes en el puerto 8080 del host al puerto 80 del contenedor, y publicará todas las respuestas salientes del puerto 80 del contenedor al puerto 8080 del host. ¿Recordáis como decíamos en este post que Docker le asignaba a cada contenedor su propia interfaz de red? Por defecto, dicha interfaz viene completamente aislada del mundo. Con este comando, estamos abriendo en ella una pequeña ventana.
Esto nos será útil para probar la instalación desde el host, como haremos en seguida.
Pero antes, una vez hayamos lanzado el contenedor, ejecutemos en él los siguientes comandos:
apk update
apk add apache2 php-apache2
mkdir /run/apache2
Con esto tendremos instalado Apache y PHP en nuestro contenedor. Finalmente, si ejecutamos:
httpd -D FOREGROUND
habremos lanzado nuestro servidor Apache.
No voy a explicar los comandos aquí expuestos, ya que está fuera del alcance de estos artículos, pero si hay alguna duda: ¡preguntad en los comentarios!
Probando el entorno desde la VM Docker
En un nuevo terminal hagamos una conexión a nuestra VM Docker:
docker-machine ssh default
Y ejecutemos el siguiente comando:
curl localhost:8080
Debería de mostrarnos la respuesta HTML que escupe nuestro Apache:
Aunque curl mola, entiendo que no lo veáis muy útil si lo que tenemos que depurar es una aplicación web. ¡Pongámosle solución!
Configurando el reenvío de puertos de Virtualbox
Si vuestro sistema posee soporte nativo de Docker, podéis pasar directamente a la siguiente sección.
Abrid el GUI de Virtualbox, haced click derecho sobre la VM Docker e iros a Configuración > Red > Avanzadas > Reenvío de puertos:
Y estableced la regla que yo he nombrado 8080, tal y como se ve en esta imagen:
Es decir, reenviad todo lo que salga o entre del puerto 8080 de la VM Docker hacia el puerto 80 de la máquina Windows.
Probando el entorno desde la máquina anfitrión
Simplemente abrid un navegador e iros a la URL localhost (o a localhost:8080 si vuestra máquina ya tiene soporte nativo para Docker).
¡Tenemos acceso directo al apache de nuestro contenedor desde nuestra máquina anfitrión!
Más o menos lo que tenemos es algo parecido a esto:
Vale, esto empieza a tener mejor pinta si somos desarrolladores web y no nos llamamos Neo.
Como ya tenemos nuestro entorno montado, lo que podemos hacer ahora es congelar el sistema de ficheros de este contenedor en una nueva imagen y así, cuando tengamos que lanzar nuevos contenedores, no tendremos que volver a instalar nuestro entorno Alpine + Apache + PHP.
Recordad que el comando para ello era:
docker commit -m "Apache & PHP installed" -a "Moises Vilar" tom_collins moisesvilar/apache_php:alpine3.3
A nuestra nueva imagen la he bautizado como moisesvilar/apache_php:alpine3.3. Si ejecutamos un docker images veréis la nueva imagen creada.
Si en vez de moisesvilar utilizáis vuestro nombre de usuario en el Docker Hub, podéis utilizar el siguiente comando para subir esta imagen a vuestro repositorio público:
docker push moisesvilar/apache_php:alpine3.3
Es posible que os muestre el error unauthorized: authentication required. En ese caso, basta con ejecutar el comando docker login e introducir nuestras credenciales de Docker Hub
Fijaros en el resultado del comando docker push:
¿Véis que ha subido dos capas? ¡Claro! La capa de alpine y la capa de lectura/escritura donde hemos instalado Apache + PHP.
Vamos a limpiar un poco el entorno eliminando todos nuestros contenedores:
docker rm $(docker ps -aq)
y también vamos a eliminar todas nuestras imagenes:
docker rmi $(docker images -q)
A continuación vamos a seguir modificando nuestro entorno para poder picar nuestros ficheros de código desde nuestra máquina anfitrión (con nuestro IDE preferido) y que estos se compartan directamente en el directorio correcto en el contenedor Alpine + Apache + PHP.
Volúmenes entre el host y los contenedores
La situación a la que queremos llegar es la siguiente:
Queremos escribir el código en nuestro equipo, donde tenemos las herramientas que más nos gusten, generalmente un IDE como Netbeans, WebStorm, Eclipse (si te van las drogas duras) o Notepad++ (si le llamamos IDE a cualquier cosa).
Queremos que dicho código esté automáticamente disponible en el interior de nuestro contenedor, en la carpeta correspondiente.
Queremos ver el resultado a través de un navegador instalado en nuestro equipo, porque los desarrolladores tenemos esa manía: nos gusta probar las cosas, ver si están rotas y en cuyo caso, arreglarlas.
El punto 3 lo hemos solventado a partir de la publicación de puertos, tal y como hemos visto en las secciones anteriores.
Para el segundo punto, tenemos que utilizar un nuevo parámetro del comando docker run:
docker run -d -v /c/Users/Moises/workspace:/var/www/localhost/htdocs -p 80:80 moisesvilar/apache_php:alpine3.3 httpd -D FOREGROUND
Unos pequeños comentarios:
- Veis que no he utilizado los parámetros -t -i, sino el parámetro -d (de dettached o daemon): quiero que el contenedor se ejecute en background.
- Veis que he publicado el puerto 80 del contenedor en el puerto 80 del host (el ejemplo anterior, donde utilizaba el puerto 8080 del host, sólo era ilustrativo). Recordad cambiar el reenvío de puertos en vuestro Virtualbox, si estáis utilizando la Docker machine
- Veis que no he usado el parámetro - -name: Docker bautizará automáticamente a nuestro contenedor.
- Veis que la instrucción que se va a ejecutar no es /bin/sh sino httpd -D FOREGROUND: queremos que lance nuestro servidor una vez arranque el contenedor.
- Cuando ejecutéis el comando, al haber eliminado todas nuestras imágenes, Docker no encontrará la imagen moisesvilar/apache_php:alpine3.3, pero no hay problema: ya que la hemos subido al Docker Hub, la descargará automáticamente.
Ahora fijaos en el parámetro -v /c/Users/Moises/workspace:/var/www/localhost/htdocs. Lo que estamos haciendo es montar directamente el contenido de mi carpeta local C:/Users/Moises/workspace en el directorio /var/www/localhost/htdocs de mi contenedor. Este último directorio es a donde va a ir nuestro Apache para servir los ficheros que allí almacenemos.
A esto se le llama crear un volumen del contenedor. En este caso, el contenido del volumen es el contenido de un directorio de nuestro host.
Para demostraros cómo funciona, lo que he hecho es irme a esa carpeta local (si no existiese, la crearía) y he creado allí un archivo index.php:
El contenido de este archivo es una sencilla aplicación web que podéis descargar desde su repositorio en Github.
Si ahora abrimos un navegador y vamos a la URL localhost/index.php ¡alehop! ¡Ahí tenemos nuestra pequeña aplicación web up and running!
Probad a hacer una pequeña modificación en el código (añadid un elemento más HTML, por ejemplo, para mostrar algún texto), guardad el fichero y recargad la página: ¡los cambios debería reflejarse automáticamente en el navegador!
Sacándole partido
Haced el ejercicio mental de complicar la situación. Imaginad que estáis trabajando en varios proyectos, cada uno en distintas tecnologías. Por ejemplo:
- Nuestro proyecto web en PHP.
- Una API Rest en NodeJS.
- Una página web HTML + CSS + Javascript sobre un servidor Ngix.
¡No tenemos porqué tener instalados todos esos componentes en nuestra máquina local! Simplemente necesitamos tres contenedores con los entornos instalados. Incluso podríamos tener los tres ejecutándose al mismo tiempo, siempre y cuando publiquemos los puertos correspondientes a cada contenedor en un puerto distinto de nuestro host.
Por ejemplo, podemos tener nuestro proyecto web en PHP accesible desde la URL localhost:9000, nuestro proyecto NodeJS en localhost:9001 y nuestro proyecto web en HTML en localhost:9002.
Ahora se incorpora otro programador al equipo. ¿Tienes que instalarle los tres entornos en su máquina? ¡Por supuesto que no! Le instalas Docker, ejecutas los tres comandos docker run correspondientes… ¡y a trabajar!
Por el momento, este planteamiento sólo es válido en desarrollo. Pero veremos dentro de pocas semanas que es supersencillo crear una imagen con nuestro código dentro de una capa de dicha imagen. Por lo que la puesta en producción se reduce a… ¡Exacto! ¡Otra ejecución del comando docker run correspondiente! ¡Olvídate del RedBull para las traumáticas puestas en producción!
Espero que le vayáis viendo las ventajas a este nuevo paradigma, porque estoy convencido de que ha llegado para quedarse. Se llame Docker o con otro nombre comercial, pero el planteamiento es muy, muy interesante.
Avisados quedáis :)
Un pequeño ejercicio
Recordáis como en este post hablábamos del concepto de “capas reutilizables” y dibujábamos nuestras arquitecturas “apilando” capas (la del sistema operativo, encima de ésta la de NodeJS y encima de ésta la de ExpressJS).
Pero también hemos dicho hoy que la imagen que hemos creado sólo contiene dos capas (la del sistema operativo, Alpine en nuestro caso, y otra más que contiene los binarios de Apache + PHP). Lo vimos al subir dicha imagen al Docker Hub.
Si ahora creásemos otra imagen donde no necesitásemos la capa de PHP, sino únicamente la de Apache en la misma versión que tenemos instalada en nuestra imagen, ¿el UFS podría reutilizarla?
Si vuestra respuesta es NO, ¿qué procedimiento deberíamos haber seguido para que dicha capa fuese realmente reutilizable por el UFS?
¡Id poniendo vuestras respuestas/análisis/conjeturas en los comentarios! Si por lo menos hay un comentario sobre el tema, daré la respuesta la semana que viene ;)
Conclusiones
¡Hey, chic@s! ¿Veis lo que hemos conseguido? ¡Tenemos un entorno de desarrollo dockerizado para nuestros proyectos Apache + PHP!
¿Qué pasaría si ahora un rayo cayese sobre mi equipo y éste ardiese hasta convertise en cenizas?
Pues lo primero, me tendría que comprar otro equipo.
Lo segundo, ejecutaría un git clone para descargarme mi último push del código dentro de mi carpeta /c/Users/Moises/workspace.
Y por último, ejecutaría:
docker run -d -v /c/Users/Moises/workspace:/var/www/localhost/htdocs -p 80:80 moisesvilar/apache_php:alpine3.3 httpd -D FOREGROUND
¡Y listo! Mi entorno estaría perfectamente arrancado y configurado para seguir con mi proyecto.
Cosas que hemos visto hoy:
- La Docker machine comparte automáticamente tus carpetas personales con su VM Docker.
- Cómo instalar Apache y PHP en una distro Alpine.
- Cómo reenviar puertos entre Virtualbox y nuestro host (la VM Docker).
- Como publicar puertos entre nuestro host y nuestro contenedor con el comando -p.
- Cómo subir nuestras imágenes al Docker hub con el comando docker push.
- Cómo eliminar imágenes descargadas en nuestro equipo con el comando docker rmi.
- Cómo lanzar nuestros contenedores en modo dettached con el parámetro -d.
- Cómo montar un directorio de nuestro host automáticamente en nuestro contenedor con el parámetro -v
¡Y de bonus, un meme y chistes de Chuck Norris! ¡No os quejéis!
Nos vemos dentro de nada. En el siguiente post vamos a introducir los Dockerfiles, para poder automatizar la prepación de entornos y convertirlos directamente en imágenes.
P.S. Recordad que pulsando en los botones que tenéis arriba de todo que pone “Facebook”, “Twitter” y “Google+” me ayudáis a difundir el blog, que seamos más por aquí y que esto sea más divertido. ¡Muchas gracias!