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 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 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
docker-compose.ymldocker-compose.yml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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:

default.confdefault.conf
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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. 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      127.0.0.1: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, AWS EC2 Container Service, Docker Swarm, entre otras.

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

Docker Compose: WordPress install Docker Compose: WordPress install

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.