Docker compose

Docker Compose: Aplicaciones con múltiples servicios

Con Docker Compose ejecutamos de manera sencilla aplicaciones que necesitan hacer uso de diferentes servicios para su funcionamiento. Podemos establecer la estructura de una aplicación y la relación entre sus diferentes componentes en un archivo con formato YAML[1] denominado docker-compose.yml, permitiéndonos interactuar con la aplicación desde una visión más holística y gestionarla como un todo, ofreciendo una forma básica de orquestación de contenedores. Al final, la idea es usar el binario docker-compose como si estuviéramos usando el cliente (binario docker) con un único contenedor, contando con muchos de los comandos que posee éste último como lo son: run, stop, start, ps, logs, entre otros para la gestión de todos los servicios (contenedores) de la aplicación. Aunque en teoría y por sus características se puede usar para aplicaciones en producción, su principal uso es el provisionamiento de entornos de desarrollo local, y, como naturalmente usa contenedores, se convierte en una alternativa más liviana a Vagrant que hace uso de máquinas virtuales.

Luego de la sencilla instalación[2] de Docker Compose, vamos a configurar una arquitectura para la implementación de una instancia de WordPress que contará con los siguientes servicios: MySQL como base de datos. Dos contenedores que ejecutarán WordPress. Un servicio de Nginx que servirá como balanceador de cargas entre los dos contenedores WordPress. La siguiente es la arquitectura propuesta y adicionalmente el archivo docker-compose.yml:

Docker Compose: Estructura de la aplicación

Docker Compose: Estructura de la aplicación

version: '2'

services:

mysql:
image: mysql
env_file:
- mysql.env

wordpress:
image: wordpress
env_file:
- wordpress.env
links:
- mysql

nginx:
image: nginx
volumes:
- $PWD/default.conf:/etc/nginx/conf.d/default.conf:ro
links:
- wordpress
ports:
- "127.0.0.1:80:80"

Al inicio del archivo docker-compose.yml se establece la versión de formato utilizada, en caso de no estar presente se usaría el formato legacy que oficialmente no es recomendado. Luego se indican cada uno de los servicios utilizados y gracias a que se implementa mediante el formato YAML, su definición es de fácil lectura. A continuación, se destacan algunas directivas usadas:

  • env_file → Archivo plano que contiene las variables de entorno para la ejecución de los contenedores, usando el formato VARIABLE=VALOR. Para los servicios MySQL y WordPress se han definido las variables MYSQL_ROOT_PASSWORD y WORDPRESS_DB_PASSWORD respectivamente, que en este caso deben tener el mismo valor.
  • volumes → Reemplazamos el archivo de configuración del sitio por defecto de Nginx para que funcione como un Load Balancer.
  • ports → Exponemos el puerto 80 del contenedor de Nginx al puerto 80 del host que tiene el docker daemon instalado.
  • links → Aquí establecemos la relación entre cada uno de los servicios. Tiene la misma lógica que la opción –link del cliente Docker.

La siguiente es la configuración básica de Nginx para que funcione como Load Balancer:

upstream wordpress {
server compose_wordpress_1;
server compose_wordpress_2;
}

server {
listen 80;

location / {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

proxy_pass http://wordpress;
}
}

Antes de iniciar la aplicación, debemos tener los siguientes archivos que componen el proyecto:

compose/
├── default.conf
├── docker-compose.yml
├── mysql.env
└── wordpress.env

Para subir la aplicación, realizamos un docker-compose up -d y revisamos el estado de los servicios con docker-compose ps:

$ docker-compose up -d
Creating compose_mysql_1
Creating compose_wordpress_1
Creating compose_nginx_1
$ docker-compose ps
Name Command State Ports
------------------------------------------------------------------------
compose_mysql_1 docker-entrypoint.sh mysqld Up 3306/tcp
compose_nginx_1 nginx -g daemon off; Exit 1
compose_wordpress_1 docker-entrypoint.sh apach ... Up 80/tcp

Por el output anterior, se puede apreciar que el estado del servicio Nginx no es el correcto ya que registra un “Exit 1”. Procedemos a revisar sus logs:

$ docker-compose logs nginx
Attaching to compose_nginx_1
nginx_1 | 2016/10/15 04:25:25 [emerg] 1#1: host not found in upstream "compose_wordpress_2" in /etc/nginx/conf.d/default.conf:3
nginx_1 | nginx: [emerg] host not found in upstream "compose_wordpress_2" in /etc/nginx/conf.d/default.conf:3

Se puede ver claramente el error, compose_wordpress_2 no existe ya que no ha sido creado, porque al realizar el docker-compose up -d se crea un contenedor por cada servicio. El escalamiento de servicios a través del archivo docker-compose.yml no se ha implementado al momento de escribir la presente entrada, aunque existe un “Feature request” al respecto en Github[3]. Si queremos escalar a dos contenedores el servicio WordPress, debemos usar docker-compose scale para luego iniciar de nuevo el servicio Nginx:

$ docker-compose scale wordpress=2
Creating and starting compose_wordpress_2 ... done
$ $ docker-compose start nginx
Starting nginx ... done
$ docker-compose ps
Name Command State Ports
------------------------------------------------------------------------------------------
compose_mysql_1 docker-entrypoint.sh mysqld Up 3306/tcp
compose_nginx_1 nginx -g daemon off; Up 443/tcp, 0.0.0.0:80->80/tcp
compose_wordpress_1 docker-entrypoint.sh apach ... Up 80/tcp
compose_wordpress_2 docker-entrypoint.sh apach ... Up 80/tcp

Docker Compose nombra los contendedores que crea uniendo con un guión al piso los siguientes datos: El directorio de trabajo, el nombre del servicio y un número consecutivo. De ésta forma, se crearán dos contenedores llamados compose_wordpress_1 y compose_wordpress_2 para el servicio WordPress, que son los que están referenciados en la configuración default.conf de Nginx. La configuración presentada de éste último como Load Balancer es estática, ya que si escaláramos a un número mayor a dos el servicio WordPress, esos nuevos contenedores no serían visibles para el servicio de Nginx. Ésta característica de detectar de manera automática los nuevos contenedores pertenecientes a un servicio es conocida como Service Discovery, la cual no posee Docker Compose porque como se mencionaba antes, solamente nos brinda una orquestación básica de contenedores ideal para ambientes locales de desarrollo, pero que dependiendo de los requerimientos de aplicaciones de mayor tamaño y complejidad, seguramente no se adecue para ambientes de producción, siendo más indicados para esos casos herramientas de orquestación más avanzadas como Kubernetes[4], AWS EC2 Container Service[5], Docker Swarm[6], entre otras.

En este punto, ya podemos acceder al instalador de WordPress a través de la URL http://localhost:

Docker Compose: WordPress

Docker Compose: WordPress

Si miramos los logs de servicio WordPress, tendremos acceso a los logs de todos los contenedores pertenecientes a éste:

$ docker-compose logs -f --tail=0 wordpress
Attaching to compose_wordpress_2, compose_wordpress_1
wordpress_2 | 172.20.0.5 - - [15/Oct/2016:04:44:13 +0000] "GET / HTTP/1.0" 302 337 "-" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Ubuntu Chromium/53.0.2785.143 Chrome/53.0.2785.143 Safari/537.36"
wordpress_1 | 172.20.0.5 - - [15/Oct/2016:04:44:13 +0000] "GET /wp-admin/install.php HTTP/1.0" 200 3448 "-" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Ubuntu Chromium/53.0.2785.143 Chrome/53.0.2785.143 Safari/537.36"
wordpress_2 | 172.20.0.5 - - [15/Oct/2016:04:44:14 +0000] "GET /wp-includes/css/buttons.min.css?ver=4.6.1 HTTP/1.0" 200 1662 "http://localhost/wp-admin/install.php" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Ubuntu Chromium/53.0.2785.143 Chrome/53.0.2785.143 Safari/537.36"
wordpress_1 | 172.20.0.5 - - [15/Oct/2016:04:44:14 +0000] "GET /wp-admin/css/install.min.css?ver=4.6.1 HTTP/1.0" 200 2299 "http://localhost/wp-admin/install.php" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Ubuntu Chromium/53.0.2785.143 Chrome/53.0.2785.143 Safari/537.36"
wordpress_2 | 172.20.0.5 - - [15/Oct/2016:04:44:14 +0000] "GET /wp-includes/css/dashicons.min.css?ver=4.6.1 HTTP/1.0" 200 28914 "http://localhost/wp-admin/install.php" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Ubuntu Chromium/53.0.2785.143 Chrome/53.0.2785.143 Safari/537.36"
wordpress_1 | 172.20.0.5 - - [15/Oct/2016:04:44:14 +0000] "GET /wp-admin/js/language-chooser.min.js?ver=4.6.1 HTTP/1.0" 200 553 "http://localhost/wp-admin/install.php" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Ubuntu Chromium/53.0.2785.143 Chrome/53.0.2785.143 Safari/537.36"
wordpress_2 | 172.20.0.5 - - [15/Oct/2016:04:44:14 +0000] "GET /wp-includes/js/jquery/jquery-migrate.min.js?ver=1.4.1 HTTP/1.0" 200 4329 "http://localhost/wp-admin/install.php" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Ubuntu Chromium/53.0.2785.143 Chrome/53.0.2785.143 Safari/537.36"
wordpress_1 | 172.20.0.5 - - [15/Oct/2016:04:44:14 +0000] "GET /wp-includes/js/jquery/jquery.js?ver=1.12.4 HTTP/1.0" 200 34083 "http://localhost/wp-admin/install.php" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Ubuntu Chromium/53.0.2785.143 Chrome/53.0.2785.143 Safari/537.36"
...

En los logs anteriores se observa con claridad el balanceo de tráfico web que realiza Nginx a los dos contenedores del servicio WordPress, alternando cada petición HTTP. Para detener la aplicación usamos docker-compose stop y para eliminar los contenedores docker-compose rm:

$ docker-compose stop
Stopping compose_nginx_1 ... done
Stopping compose_wordpress_2 ... done
Stopping compose_wordpress_1 ... done
Stopping compose_mysql_1 ... done
$ docker-compose rm
Going to remove compose_nginx_1, compose_wordpress_2, compose_wordpress_1, compose_mysql_1
Are you sure? [yN] y
Removing compose_nginx_1 ... done
Removing compose_wordpress_2 ... done
Removing compose_wordpress_1 ... done
Removing compose_mysql_1 ... done

Para una referencia completa de las directivas del archivo de Docker Compose, podemos visitar la documentación oficial[7].