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!

¿Otro framework Javascript? Señor, llévanos pronto...

¿Otro framework Javascript? Señor, llévanos pronto...

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í:

Una misteriosa carpeta C...

Una misteriosa carpeta C...

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”:

Carpeta compartida entre nuestro windows y la VM Docker

Carpeta compartida entre nuestro windows y la VM Docker

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:

It's alive!

It's alive!

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:

Reenvío de puertos en Virtualbox

Reenvío de puertos en Virtualbox

Y estableced la regla que yo he nombrado 8080, tal y como se ve en esta imagen:

Reenviando el puerto 8080 de la VM Docker al puerto 80 de nuestra máquina Windows

Reenviando el puerto 8080 de la VM Docker al puerto 80 de nuestra máquina Windows

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).

It's still alive!

It's still alive!

¡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:

Si estamos utilizando Virtualbox y su reenvío de puertos porque nuestro sistema operativo no tiene soporte nativo para Docker

Si estamos utilizando Virtualbox y su reenvío de puertos porque nuestro sistema operativo no tiene soporte nativo para Docker

Si nuestro sistema operativo tiene soporte nativo para Docker (un Linux, por ejemplo)

Si nuestro sistema operativo tiene soporte nativo para Docker (un Linux, por ejemplo)

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:

Resultado de ejecutar docker push sobre nuestra imagen

Resultado de ejecutar docker push sobre nuestra imagen

¿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:

  1. 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).

  2. Queremos que dicho código esté automáticamente disponible en el interior de nuestro contenedor, en la carpeta correspondiente.

  3. 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:

Un archivo index.php en mi directorio workspace

Un archivo index.php en mi directorio workspace

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:

  1. Nuestro proyecto web en PHP.
  2. Una API Rest en NodeJS.
  3. 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:

  1. La Docker machine comparte automáticamente tus carpetas personales con su VM Docker.
  2. Cómo instalar Apache y PHP en una distro Alpine.
  3. Cómo reenviar puertos entre Virtualbox y nuestro host (la VM Docker).
  4. Como publicar puertos entre nuestro host y nuestro contenedor con el comando -p.
  5. Cómo subir nuestras imágenes al Docker hub con el comando docker push.
  6. Cómo eliminar imágenes descargadas en nuestro equipo con el comando docker rmi.
  7. Cómo lanzar nuestros contenedores en modo dettached con el parámetro -d.
  8. 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!