Introducción a Docker Compose

Agustin Bassi

May 09, 2021 ‧ 30 min estimados  ‧ #docker #docker-compose #multicontainer

Contenido

Objetivos

El ecosistema Docker

Partes principales

Ejecución de contenedores

Docker Compose

Ejemplo de Docker Compose

Características de Docker Compose

Casos de uso

Comandos frecuentes

Conclusiones

Bibliografía

Licencia

Objetivos

Docker Compose es una herramienta que sirve para llevar a un nivel superior la ejecución tradicional de contenedores de Docker. Lo que vas a ver en este documento son los siguientes temas:

El ecosistema Docker

Docker es una herramienta que utiliza la tecnología de contenedores para configurar e implementar aplicaciones. Permite controlar de manera eficiente los entornos y sus ciclos de vida, y establece un marco de trabajo para poder desplegar aplicaciones de igual manera en diferentes plataformas. Entre algunas de sus ventajas podemos encontrar las siguientes:

Partes principales

A la hora de trabajar con docker se crean imágenes, se inician contenedores, se configuran redes y volúmenes para compartir información, y se gestionan Dockerfiles y registries para obtener y publicar imágenes. Veamos brevemente algunas de estas partes para luego entender de mejor manera la herramienta Docker Compose.

Arquitectura de Docker

La arquitectura de Docker está formada por capas. La capa inferior es el hardware físico donde corre el sistema. La segunda capa es el sistema operativo host (Linux, Windows, MacOS). Sobre el sistema operativo se instala el engine de Docker. Sobre el engine de Docker se manejan los objetos de Docker que están formados por imágenes y contenedores.

Images

Son plantillas que describen cómo está dispuesto un contenedor. Las imágenes se componen de una base y sobre ésta las modificaciones especiales que necesites para una aplicación.

Containers

Son las instancias en ejecución de una imagen. Podés crear, iniciar, detener, mover o eliminar un contenedor mediante la API o CLI de Docker. También podés conectar un contenedor a una o más redes, asignarle almacenamiento o incluso crear una nueva imagen basada en su estado actual.

Dockerfiles

Los Dockerfiles son la forma de convertir tu aplicación en un contenedor. Representan los pasos para construir un contenedor a medida con los requerimientos y lógica que necesita cada aplicación. Si querés crear un contenedor personalizado necesitás escribir un Dockerfile.

Networks

Docker utiliza una interfaz virtual para comunicarse con la red del equipo host. A su vez, podés crear configuraciones especiales de redes para comunicar a los contenedores. Incluso, los contenedores pueden pertenecer a más de una red.

Volumes

Los volúmenes permiten tener un espacio de almacenamiento dentro de cada contenedor. Por lo general los volúmenes se usan para compartir datos/archivos entre el host y vendría a ser un mecanismo similar al de carpetas compartidas de las máquinas virtuales.

Registry

Un registry es un banco de imágenes de Docker. Es un registro público que se puede utilizar libremente, y el engine de Docker está configurado para buscar imágenes en DockerHub de forma predeterminada. También es posible que tengas tu propio registro privado.

Ejecución de contenedores

Ahora que ya hicimos un repaso por las principales partes del ecosistema Docker, pasemos a otro tema: la ejecución de contenedores.

A la hora de correr aplicaciones complejas a través de contenedores de Docker, es necesario desplegar varios servicios para que estas puedan funcionar. Un diagrama de arquitectura típico podría parecerse al que ves en la siguiente imagen, con clientes accediendo a un proxy, y detrás de éste una aplicación web junto con otros servicios de administración, más una base de datos, por nombrar solo un ejemplo.

Como podrás imaginar, cada uno de estos servicios necesita configuraciones especiales para poder funcionar, tales como archivos de inicialización, variables de entornos, acceso a puertos, comunicaciones con otros servicios, entre otros. Administrar la ejecución de estos servicios con el tradicional comando docker run puede resultar realmente complejo, difícil de mantener y poco escalable.

Para que veas un ejemplo, el comando a continuación muestra cómo ejecutar una base de datos MySQL con algunos argumentos de ejecución.

docker run \

--rm --detach \

--name mysql-server \

--network mysql-net \
--env MYSQL_ROOT_PASSWORD=userpass \

--volume /home/app/db:/var/lib/mysql \
mysql:5.7

Imaginate la ejecución de este tipo de comandos para poder correr cada uno de los servicios que necesita tu aplicación. ¿Cómo acordarte de qué argumentos necesita cada uno? Bueno, en este punto las cosas comienzan a complejizarse un poco.

Como primera solución podrías pensar en crear un script de bash para automatizar el despliegue. Continuando con el ejemplo anterior de la base de datos, el siguiente script de bash muestra cómo correr el comando anterior con solo dos argumentos.

#!/bin/bash
NETWORK_NAME=
$1
DATABASE_DIR=$2

docker run \

--rm --detach \

--name mysql-server \

--network $NETWORK_NAME \
--env MYSQL_ROOT_PASSWORD=userpass \

--volume $DATABASE_DIR:/var/lib/mysql \
mysql:5.7

El problema en este punto resultaría más sencillo, y para correr el script (suponiendo que se llama start_mysql.sh), el comando que deberías ejecutar sería como el siguiente:

./start_mysql.sh mysql-net "$PWD"/database

Si bien utilizar un script como este facilita los comandos de ejecución, si tuvieras que coordinar los múltiples contenedores de una app, de qué manera iniciar los servicios, qué pasa si un servicio falla, qué datos comparten los contenedores entre sí, y demás; te resultaría una tarea muy compleja , y es en este punto donde entra en juego Docker Compose.

Docker Compose

Docker Compose es una herramienta del ecosistema Docker que sirve para definir aplicaciones multicontenedor en un archivo de texto llamado docker-compose.yml. Dentro de este archivo podés configurar prácticamente las mismas propiedades para los contenedores como si lo hicieras desde el comando docker run o mediante un script, configurando además el comportamiento de los contenedores entre sí, redes de comunicación, volúmenes, entre otros.

Es una herramienta ideal para el desarrollo y testing de aplicaciones, así como también para configurar distintos flujos de integración continua y escenarios de prueba. Algunas de las características más populares de Docker Compose son:

Una vez que definas el comportamiento de la aplicación en el archivo docker-compose.yml, la herramienta te permite iniciar con un único comando todos los contenedores en el orden especificado. En cierto modo, el archivo docker-compose.yml funciona también como documentación, ya que podés ver qué servicios, imágenes, volúmenes, enlaces y demás propiedades tiene la aplicación.

En esta imagen podés ver un ejemplo en el que dentro del archivo docker-compose.yml se definen 3 contenedores; todos ellos están conectados entre sí a través de la APP network. Además, el contenedor 1 y 2 comparten el APP volume. Por último, es posible acceder a la aplicación a través del contenedor 3 que expone un puerto fuera de sí mismo.

Un caso real donde resulta útil usar Docker Compose es un sitio web desarrollado con WordPress, donde se necesita un servidor web con soporte para PHP y una base de datos para almacenar los datos de las páginas (usuarios, artículos, datos, categorías, etc).

Un caso más complejo podría ser una aplicación web está compuesta por un servidor para desplegar nuestro código de backend, el servicio que ejecuta nuestra aplicación, un caché para almacenar datos temporales, una base de datos para persistir información, un proxy que actúa como puerta de entrada a la aplicación, y un servidor FTP para la descarga de archivos.

Desde el caso más sencillo al más complejo, la administración, organización, sincronización y comunicación de los contenedores puede estar completamente centralizada a través del archivo docker-compose.yml, por lo que resulta clave que entiendas el funcionamiento de Docker Compose a la hora de trabajar con el ecosistema Docker.

Como nombramos anteriormente, es una herramienta ideal para el desarrollo y testing de aplicaciones de manera local, así como también para configurar distintos flujos de integración continua y escenarios de prueba. Cuando quieras llevar tu aplicación a  producción se utilizan otras herramientas conocidas como orquestadores, que sirven para manejar el escalado y mantenimiento de múltiples contenedores. Algunas de estas herramientas pueden ser Kubernetes o Docker Swarm.

Ejemplo de Docker Compose

Con todo el contexto anterior estas en condiciones de entender cómo funciona Docker Compose a través de la creación paso a paso de una aplicación sencilla que ejecuta un contenedor de Wordpress junto con una base de datos MySQL.

Es necesario que tengas instalado Docker y Docker Compose en tu máquina.

1. Crear estructura de directorios y archivos

Comencemos creando un nuevo directorio llamado wordpress_app y dentro un archivo llamado docker-compose.yml (la extensión puede ser .yml o .yaml). La aplicación también va a necesitar un directorio para guardar datos, así que también crea un directorio llamado database dentro de wordpress_app.

2. Definir el contenido de docker-compose.yml

Cuando tengas la estructura creada, agrega este contenido al archivo docker-compose.yml que define cómo está armada la aplicación y los argumentos de ejecución necesarios para cada servicio.

version: '3'

services:

 mysql-db:

   image:                    mysql:5.7

   hostname:                 mysql-db

   container_name:           mysql-db

   restart:                  always

   environment:

     MYSQL_ROOT_PASSWORD:    userpass

     MYSQL_DATABASE:         wordpress

     MYSQL_USER:             wordpress

     MYSQL_PASSWORD:         wordpress

   volumes:

     -                       ./database:/var/lib/mysql

   networks:

     -                       net-wordpress-app

 wordpress:

   image:                    wordpress:latest

   hostname:                 wordpress

   container_name:           wordpress

   restart:                  always

   environment:

     WORDPRESS_DB_HOST:      mysql-db:3306

     WORDPRESS_DB_USER:      wordpress

     WORDPRESS_DB_PASSWORD:  wordpress

     WORDPRESS_DB_NAME:      wordpress

   networks:

     -                       net-wordpress-app

   depends_on:

     -                       mysql-db

   ports:

     -                       "8080:80"

networks:

 net-wordpress-app:

   driver:                   bridge

Acá podés ver la explicación de los argumentos de ejecución dentro del archivo:

3. Construir e iniciar la aplicación

Una vez que el contenido del archivo docker-compose.yml está listo, el siguiente paso es construir la aplicación. Desde la raíz del proyecto ejecuta el siguiente comando para iniciar:

docker-compose up

La ejecución del comando va a crear una red llamada net-wordpress-app, va a descargar las imágenes utilizadas (si no están descargadas) y va a iniciar los servicios en el orden que fueron descritos en el archivo docker-compose.yml. En la terminal, deberías ver una salida similar a la siguiente.

$ docker-compose up

Creating network "net-wordpress-app" with the default driver

Pulling db (mysql:5.7)...

5.7: Pulling from library/mysql

...

Status: Downloaded newer image for mysql:5.7

Pulling wordpress (wordpress:latest)...

...

Status: Downloaded newer image for wordpress:latest

Creating mysql-db

Creating wordpress

...

Una vez que la aplicación haya iniciado podrías acceder al servicio de Wordpress ingresando la URL http://localhost:8080 en el navegador, donde deberías ver una imagen como la siguiente.

Si querés detener la aplicación presioná CTRL+C, o bien desde otra terminal corré el comando docker-compose down que va a detenerla y eliminar todo su contexto asociado.

Como podés notar, pasar de la ejecución de contenedores a través del comando docker run o utilizando scripts a tenerlo centralizado en un único archivo y ejecutarlos bajo un único comando, realmente simplifica la tarea. Veamos ahora algunos detalles más.

Características de Docker Compose

La función principal es crear aplicaciones basadas en múltiples contenedores, sus enlaces, redes y orden de ejecución; aunque en combinación con el engine de Docker la herramienta es capaz de mucho más. Algunas funciones destacadas son:

Veamos de manera general otras funcionalidades destacadas de la herramienta.

Múltiples entornos aislados en un solo host

Compose utiliza un nombre de proyecto para aislar los entornos entre sí. Podés hacer uso de este nombre de proyecto en varios contextos diferentes, como en un host de desarrollo, en un servidor de integración continua o bien en un host compartido o dev host. En cada uno de estos entornos se pueden tener diferentes configuraciones sin que interfieran entre sí.

El nombre del proyecto por defecto es el nombre del directorio del proyecto, aunque podés establecer un nombre personalizado utilizando la opción -p PROJECT_NAME al momento de ejecución o bien la variable de entorno COMPOSE_PROJECT_NAME.

Conservar los datos cuando se crean contenedores

Compose conserva todos los volúmenes utilizados por los servicios de tu aplicación. Cuando ejecutas docker-compose up, si el engine encuentra contenedores de ejecuciones anteriores, copia los volúmenes del contenedor antiguo al contenedor nuevo. Este proceso garantiza que los datos que haya creado en volúmenes no se pierdan.

Regenerar contenedores que hayan cambiado

Compose almacena en caché la configuración utilizada para crear un contenedor. Cuando reinicias un servicio que no ha cambiado, Compose reutiliza los contenedores existentes, lo que significa que podés realizar cambios en tu entorno muy rápidamente.

Variables y movimiento de composiciones entre entornos

Compose admite variables en el archivo docker-compose.yml. Podés usar estas variables para personalizar la composición de diferentes entornos o usuarios. También es posible extender configuraciones desde otro archivo docker-compose, mejorando así la reutilización.

Casos de uso

Compose se puede utilizar de muchas formas diferentes. A continuación podés ver algunos casos de uso comunes.

Entornos de desarrollo

Cuando se desarrolla software, la capacidad de ejecutar una aplicación en un entorno aislado e interactuar con ella es fundamental. Podés crear rápidamente el entorno de trabajo e iniciar uno o más contenedores con un solo comando. Utilizando Compose podés pasar de las tradicionales y tediosas guías de configuración a un solo archivo legible y autoexplicativo.

Entornos de prueba automatizados

Una parte importante en el desarrollo es el proceso de integración continua para realizar pruebas automáticas cada vez que realizas una modificación en tu código. Las pruebas automatizadas requieren un entorno específico en el que ejecutar las pruebas y Compose proporciona una forma conveniente de crear y destruir entornos de prueba aislados para tu suite de pruebas.

Implementaciones en producción

Compose comenzó centrándose tradicionalmente en los flujos de trabajo correspondientes al desarrollo e implementación de pruebas, pero con cada actualización avanza también en funciones más orientadas a producción. A través de la modificación del docker-compose.yml, junto con el despliegue de las aplicaciones en hosts remotos es posible utilizar Docker Compose para el despliegue de aplicaciones productivas.

Comandos frecuentes

Ahora que ya conoces bastante sobre cómo funciona Docker Compose, veamos algunos de los comandos más frecuentes que necesitarás usar en el día a día.

Estos son sólo alguno de los comandos disponibles y realmente podés hacer muchas acciones a través de los comandos. Si querés conocer más funcionalidades, en la documentación oficial vas a encontrar todos los detalles.

Conclusiones

En este documento vimos varios de los conceptos más importantes que necesitas saber para utilizar Docker Compose. Para hacer un resumen vimos los siguientes temas.

Bibliografía

Licencia

Este material es distribuido bajo licencia Creative Commons BY-SA 4.0. Podés encontrar detalles sobre el uso del material en este link.