Tabla de contenidos
¿Qué es docker?
Docker es la tendencia actual para construir los entornos de desarrollo locales y hacer despliegues (bueno… eso todavía no está tan extendido) a producción. Docker mola porqué permite describir sistemas que podrán ser replicados por los distintos desarrolladores de la empresa en su ordenador. Esto facilita que todos utilicen el mismo sistema, mismas versiones del software, mismos plugins, etc. y que a su vez, estas sean las mismas versiones que existen en el servidor de producción.
¡Genial! pero todo esto ya lo podía hacer con Vagrant + (Ansible o Puppet o Chef). Sí, eso es cierto, pero, lo que realmente marca la diferencia en Docker es que no necesitamos de un hypervisor (o lo que es lo mismo, no corre sobre una máquina virtual) lo que permite que permite aprovechar mejor los recursos y por lo tanto un mejor rendimiento o eso parece, al menos en Linux.
Corriendo Docker en mac
Como hemos dicho antes, la magia de Docker es que no es necesario virtualizar el hardware, para ello, es necesario que se integre en el kernel del sistema operativo host, cosa que solamente es posible en Linux (bueno, y en Windows para contenedores Windows que no es el caso de nadie). Para poder desarrollar en mac, es necesario hacer un poco de trampas e instalar Docker for Mac, una herramienta oficial que permite hacer uso de contenedores en macOS >El Capitan (o Yosemite, pero con soporte limitado). Esta herramienta se encargará de crear una capa de virtualización gracias a Hyperkit, que nos permitirá levantar nuestros contenedores durante el desarrollo, eliminando la necesidad de instalar Docker Toolbox y junto a la misma Virtualbox.
Tiene buena pinta, ¡probemos!
Orquestamos un contenedor con Nginx + Php para correr el proyecto de ejemplo oficial de Symfony (no es necesario incluir base de datos ya que utiliza Sqlite).
¿¿¿2296ms??? Y eso es solo la página de inicio. Si seguimos navegando por las diferentes páginas, vemos que la cosa no mejora. ¿Mejorará el rendimiento si trabajamos en el entorno de producción? Parece que tampoco:
Con estos tiempos de carga, nos podemos hacer viejos desarrollando una aplicación con Docker para Mac.
Por qué Docker para Mac es tan lento
Los problemas de rendimiento parece que están relacionadas con el acceso a los ficheros. Con los vendors ya instalados, nuestra aplicación cuenta con más de 14.000 archivos que deben estar sincronizados entre ambos sistemas. Esta sincronización, que resulta innecesaria en entornos Linux, se realiza osxfs en macOS. Esto provoca problemas de rendimiento en aplicaciones de este tamaño.
Para verificar las causas del problema, probamos a copiar los archivos durante la construcción del contenedor:
FROM php:7.1-fpm-alpine
ADD ./html /var/www/html/
...
Será necesario hacer algunas trampas para que funcione, como copiar la carpeta html en el mismo path que el Dockerfile y cambiar el propietario de los archivos en el owner:
docker-compose exec php /bin/sh
chown -R www-data .
Reconstruímos los contenedores:
docker-compose up --build
Aunque la solución no es válida para entornos de desarrollo ya que necesitamos editar los archivos dinámicamente desde el host, nos permite comprobar que el problema está en la sincronización de los archivos.
Solución oficial al problema de sincronización de Docker para Mac
Si leemos la documentación oficial, vemos que a partir de la versión 17.04 CE (del 05/04/2017) se han añadido unos modificadores para modificar la consistencia de los archivos sincronizados. Nos ofrecen 3 modificadores:
- Consistent: Se trata la solución por defecto y nos asegura una perfecta consistencia
- Cached: Permite que las modificaciones en el host se demoren en el contenedor
- Delegated: Permite que las modificaciones en el contenedor se demoren en el host
Estos modificadores, ordenados de mayor a menor fiabilidad influyen en el rendimiento de nuestra aplicación, siendo el último de ellos el que mejor rendimiento debería ofrecer.
Probemos con la opción delegated ya que es la que mejor rendimiento debería ofrecer:
...
volumes:
- ./html:/var/www/html/:delegated
...
¡Voilà! 699ms Parece que algo hemos mejorado. Aunque el resto de páginas permanece alrededor de los 1200ms, se aleja de la media de carga de 3300ms de la opción por defecto. Parece que con esta opción ya se puede empezar a trabajar.
Si elegimos esta opción, debemos tener en cuenta que, en caso de fallo del contenedor, algunas de las escrituras se podrían perder, lo que podría dejar un estado de los datos inconsistente. En nuestro caso, esto no debería preocuparnos ya que, las escrituras que se realizan desde el contenedor son de caché y logs.
Con la opción cached, obtenemos unos resultados similares (incluso reducimos algunos milisegundos en algunos casos), por lo que resultaría preferible esta opción ya que ofrece más garantías.
Solución de terceros: sincronizando volúmenes con rsync
El proyecto Docker-sync es una solución no oficial al problema de sincronización de Docker. Este se apoya sobre rsync para mantener sincronizados los volúmenes y requiere una configuración más avanzada además de la instalación de software de terceros. Vayamos a ello.
En primer lugar, tendremos que instalar docker-sync en nuestro mac:
gem install docker-sync
Una vez instalado docker-sync, crearemos un archivo con el nombre docker-sync.yml con la siguiente configuración:
version: '2'
options:
compose-file-path: 'docker-compose-mac.yml'
syncs:
html-sync: src: './html/'
sync_userid: 1000
En el, indicamos cual será el nombre del archivo docker-compose que ejecutaremos y crearemos los distintos volúmenes para sincronizar, en nuestro caso html-sync que sincronizará la carpeta html. Además, será necesario indicarle que copie los archivos bajo el usuario de php (en nuestro caso www-data con id 1000), para no tener problemas de permisos con los archivos.
Como podemos observar, en el código anterior, hemos indicado que el archivo docker-compose a ejecutar será docker-compose-mac.yml. Esto lo hacemos para que nuestra configuración no interfiera con la de otros desarrolladores que trabajan en plataformas Linux y no requieren de esta solución. Así pues, creamos una copia de docker-compose.yml con el nombre docker-compose-mac.yml y lo alojamos en el mismo path.
Ahora, deberemos realizar algunos cambios para indicarle a docker que deberá utilizar docker-sync para sincronizar el volumen:
...
services:
php:
...
volumes:
- html-sync:/var/www/html/:nocopy
...
#Añadir al final del archivo
volumes:
html-sync:
external: true
En el, añadimos el volumen html-sync que hemos definido en el archivo anterior y le indicamos el punto de carga. También añadimos el atributo nocopy, éste es importante ya que, de no hacerlo, si existen datos en el contenedor al construirlo, se sincronizarán en nuestro host, lo que no es deseable.
Una vez configurados dichos archivos, lanzaremos nuestros contenedores con el comando
docker-sync-stack start
Como se puede observar, el tiempo de carga de los contenedores es considerablemente mayor, pero el impacto en el rendimiento es increíble:
Podríamos decir que el rendimiento es muy similar al acceso a los archivos sin sincronización por lo que esta solución nos permite desarrollar con un rendimiento muy similar al obtenido en los entornos Linux.
Conclusiones: eligiendo la mejor solución
Tras haber realizado el análisis de las distintas soluciones al problema de sincronización de archivos en osx, podemos concluir que existen soluciones viables que facilitan el desarrollo de aplicaciones con Docker en mac.
Aunque Docker-sync es el claro ganador, también cuenta con las desventajas de que es necesaria la instalación de software de terceros, además de requerir una mayor configuración del sistema.
Esta desventaja, hace que sea recomendable probar antes la solución nativa de Docker (solo supone el cambio de una linea de código) y evaluar si la mejora es suficiente para el proyecto en desarrollo.
Si queréis probar la configuración de Symfony con Docker-sync, podéis acceder al repositorio del proyecto y realizar vuestras propias pruebas.
Comparativa de performance
Estos son los resultados obtenidos para diferentes páginas con las diferentes soluciones:
Sin sincronización | Consistent | Cached | Delegated | Docker-sync | Linux | |
http://localhost/app_dev.php/ | 153 | 2296 | 848 | 699 | 203 | 163 |
http://localhost/app_dev.php/es/login | 150 | 2642 | 1039 | 1164 | 220 | 197 |
http://localhost/app_dev.php/es/admin/post/ | 191 | 3351 | 1105 | 1378 | 200 | 170 |
http://localhost/app_dev.php/es/admin/post/2 | 208 | 3185 | 1293 | 1145 | 215 | 176 |
http://localhost/app_dev.php/es/blog/ | 234 | 3326 | 1209 | 1275 | 218 | 193 |
http://localhost/app_dev.php/es/admin/post/1/edit | 247 | 4409 | 1888 | 1418 | 255 | 298 |
Velocidad Media | 199.5 | 3255.5 | 1157 | 1219.5 | 216.5 | 184.5 |
Características del sistema
Características hardware:
Mac:
- Macbook Pro (Retira, 13-inch, principios 2015)
- Procesador Intel Core i5 2,7Ghz
- RAM 16GB 1867
Versión software:
- macOS Sierra (10.12.6)
- Docker Community Edition version 17.06.2-ce-mac27 (19124)
- Docker-sync 0.4.6
- PHP 7.1.8
- nginx version: nginx/1.13.3