Laboratorio 07 - Aplicación multicontenedor con Docker Compose
Hasta este punto ha ejecutado contenedores de manera individual mediante docker run. En este laboratorio trabajará con una aplicación compuesta por varios servicios que deben comunicarse y coordinar su ciclo de vida.
Utilizará Docker Compose para definir y operar dos servicios de GroceryListApp. El servicio app ejecutará la aplicación Flask y el servicio web utilizará Nginx como punto de entrada. Ambos compartirán una red creada por Compose y la aplicación conservará sus datos en un volumen nombrado.
Arquitectura del ejercicio
Sección titulada «Arquitectura del ejercicio»| Servicio | Imagen o construcción | Responsabilidad |
|---|---|---|
app |
Imagen construida a partir del Dockerfile del repositorio |
Ejecutar GroceryListApp y almacenar sus datos SQLite en /data |
web |
nginx:alpine |
Recibir las solicitudes externas y reenviarlas al servicio app |
El flujo de una solicitud puede representarse así.
Navegador │ │ http://localhost ▼Puerto 80 del sistema anfitrión │ ▼Servicio webNginx │ │ http://app:8080 ▼Servicio appGroceryListApp │ ▼/data/shop.db │ ▼Volumen grocerylist-dataSolo web publica un puerto hacia el sistema anfitrión. El servicio app permanece accesible dentro de la red del proyecto y Nginx se comunica con él mediante su nombre de servicio.
Lo que aprenderá
Sección titulada «Lo que aprenderá»Al finalizar podrá
- definir una aplicación multicontenedor mediante Docker Compose;
- construir una imagen desde un
Dockerfilecomo parte de un proyecto Compose; - declarar servicios, volúmenes y dependencias en
docker-compose.yml; - configurar Nginx como proxy inverso hacia un servicio interno;
- utilizar nombres de servicio para la comunicación entre contenedores;
- comprobar el estado de salud de una aplicación antes de iniciar un servicio dependiente;
- observar la red creada automáticamente por Compose;
- consultar registros y ejecutar comandos en servicios administrados por Compose;
- comprobar la persistencia de datos entre ciclos de
downyup.
Antes de comenzar
Sección titulada «Antes de comenzar»Necesitará
- Docker y Docker Compose instalados y en ejecución;
- el puerto
80disponible en su equipo; - una terminal con acceso a Docker;
- un navegador web.
Este laboratorio parte del repositorio utilizado en los ejercicios anteriores.
Preparar el proyecto
Sección titulada «Preparar el proyecto»Clone el repositorio si todavía no lo tiene disponible.
git clone https://github.com/jpadillaa/grocerylistapp.gitcd grocerylistappmkdir -p nginxDespués de agregar los archivos de este laboratorio, la estructura relevante será similar a la siguiente.
grocerylistapp/├── app/├── static/├── templates/├── tests/├── nginx/│ └── default.conf├── requirements.txt├── Dockerfile└── docker-compose.ymlEl repositorio ya contiene el Dockerfile necesario para construir la imagen de la aplicación.
Configurar Nginx como proxy inverso
Sección titulada «Configurar Nginx como proxy inverso»Nginx recibirá las solicitudes externas y las reenviará al servicio app.
Cree el archivo nginx/default.conf.
server { listen 80;
location / { proxy_pass http://app:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; }}La instrucción central es
proxy_pass http://app:8080;app no es una dirección IP escrita de forma manual. Es el nombre del servicio que definirá posteriormente en Compose. Los contenedores conectados a la red del proyecto pueden utilizar esos nombres para encontrarse entre sí.
El puerto 8080 corresponde al puerto donde GroceryListApp escucha dentro del contenedor.
Definir los servicios con Docker Compose
Sección titulada «Definir los servicios con Docker Compose»Cree docker-compose.yml en la raíz del proyecto.
services: app: build: . volumes: - grocerylist-data:/data healthcheck: test: ["CMD-SHELL", "curl --fail http://localhost:8080/health || exit 1"] interval: 10s timeout: 5s start_period: 5s retries: 3 restart: unless-stopped
web: image: nginx:alpine ports: - "80:80" volumes: - ./nginx/default.conf:/etc/nginx/conf.d/default.conf:ro depends_on: app: condition: service_healthy restart: unless-stopped
volumes: grocerylist-data:Antes de crear los contenedores, pida a Compose que procese y valide la configuración.
docker compose configSi el archivo es válido, Compose mostrará la configuración resultante.
Comprender el servicio app
Sección titulada «Comprender el servicio app»La definición comienza con
app: build: .build: . indica que Compose debe construir la imagen utilizando el contexto del directorio actual y el Dockerfile disponible allí.
El servicio no declara ports. Por esta razón, el ejercicio no publica directamente el puerto 8080 hacia el sistema anfitrión.
La persistencia se configura mediante
volumes: - grocerylist-data:/dataGroceryListApp almacena su base de datos SQLite en /data. El volumen permite que esos datos tengan un ciclo de vida independiente de un contenedor específico.
El servicio incluye además una comprobación de salud.
healthcheck: test: ["CMD-SHELL", "curl --fail http://localhost:8080/health || exit 1"] interval: 10s timeout: 5s start_period: 5s retries: 3Compose puede utilizar ese estado para determinar cuándo la aplicación está lista para aceptar solicitudes.
Comprender el servicio web
Sección titulada «Comprender el servicio web»El segundo servicio utiliza una imagen existente.
web: image: nginx:alpineSolo este servicio publica un puerto.
ports: - "80:80"El archivo de configuración de Nginx se monta en modo de solo lectura.
volumes: - ./nginx/default.conf:/etc/nginx/conf.d/default.conf:roLa dependencia con app se expresa mediante
depends_on: app: condition: service_healthyEn esta configuración, Compose espera a que app alcance el estado de salud esperado antes de iniciar web.
Esto reduce la posibilidad de que Nginx comience a recibir tráfico mientras GroceryListApp todavía se encuentra en su proceso inicial de arranque.
Comprender la red y el volumen
Sección titulada «Comprender la red y el volumen»El archivo no declara una sección networks.
Compose crea automáticamente una red para el proyecto y conecta a ella los servicios definidos. Esa red permite que web pueda resolver el nombre app sin conocer de antemano la dirección IP concreta del contenedor.
La sección final
volumes: grocerylist-data:declara el volumen nombrado utilizado por app.
La arquitectura declarada puede resumirse así.
Proyecto Compose│├── Red del proyecto│ ├── app│ └── web│└── Volumen └── grocerylist-dataLevantar la aplicación
Sección titulada «Levantar la aplicación»Ejecute
docker compose up -dCompose realizará varias acciones a partir de la declaración del proyecto.
- Construirá la imagen de
app. - Descargará
nginx:alpinesi todavía no está disponible localmente. - Creará la red del proyecto.
- Creará el volumen
grocerylist-data. - Creará los contenedores correspondientes.
- Iniciará
app. - Evaluará su comprobación de salud.
- Iniciará
webde acuerdo con la dependencia declarada.
Compruebe el estado.
docker compose psUna salida aproximada puede verse así.
NAME SERVICE STATUS PORTSgrocerylistapp-app-1 app running (healthy)grocerylistapp-web-1 web running 0.0.0.0:80->80/tcpapp debería aparecer en ejecución y con estado saludable. web debería aparecer en ejecución y con el puerto 80 publicado.
Importante
Si un servicio aparece como
exitedorestarting, consulte sus registros antes de continuar.Ventana de terminal docker compose logs <servicio>
Comprobar el recorrido de una solicitud
Sección titulada «Comprobar el recorrido de una solicitud»La primera prueba consulta la ruta de salud desde el sistema anfitrión.
curl http://localhost/healthLa respuesta esperada es
{"status":"ok"}La solicitud entra por el servicio web y Nginx la reenvía a app.
Abra después
http://localhostDebería aparecer la interfaz de GroceryListApp.
Comprobar la funcionalidad
Sección titulada «Comprobar la funcionalidad»Realice algunas operaciones.
- Cree una categoría desde
/categories. - Agregue elementos desde
/add. - Marque un elemento como completado desde la pantalla principal.
- Recargue la página y compruebe que los cambios permanecen disponibles.
También puede consultar únicamente los encabezados HTTP.
curl -I http://localhostUna respuesta aproximada puede verse así.
HTTP/1.1 200 OKServer: nginx/...Content-Type: text/html; charset=utf-8El encabezado Server permite observar que la respuesta pasa por Nginx antes de llegar al cliente.
Inspeccionar la red del proyecto
Sección titulada «Inspeccionar la red del proyecto»Compose crea una red para que los servicios puedan comunicarse.
Localizar la red
Sección titulada «Localizar la red»docker network ls | grep grocerylistappEn este proyecto debería aparecer una red similar a
grocerylistapp_defaultInspeccionar sus miembros
Sección titulada «Inspeccionar sus miembros»docker network inspect grocerylistapp_defaultRevise la sección Containers. Allí deberían aparecer los contenedores correspondientes a app y web, cada uno con una dirección dentro de la red.
Comprobar la resolución por nombre
Sección titulada «Comprobar la resolución por nombre»Desde el servicio web, intente resolver app.
docker compose exec web sh -c "nslookup app 2>/dev/null || ping -c 1 -W 2 app"La prueba permite observar que la comunicación no depende de escribir manualmente la dirección IP del contenedor.
Nginx utiliza
appcomo nombre estable del servicio dentro del proyecto.
Comprobar que app no está publicado directamente
Sección titulada «Comprobar que app no está publicado directamente»Ejecute desde el sistema anfitrión
curl -s --max-time 3 http://localhost:8080 || echo "No accesible desde el host (esperado)"El fallo de conexión es el resultado esperado en esta configuración.
app participa en la red interna, pero no tiene una publicación de puertos hacia el host. El punto de entrada externo es web.
Este patrón permite concentrar en Nginx responsabilidades como el enrutamiento hacia la aplicación. En arquitecturas más amplias, un proxy inverso también puede participar en funciones adicionales, según los requisitos del sistema.
Consultar registros y ejecutar comandos
Sección titulada «Consultar registros y ejecutar comandos»Compose permite operar los servicios como parte de un mismo proyecto.
Consultar registros de todos los servicios
Sección titulada «Consultar registros de todos los servicios»docker compose logs --tail 10Consultar un servicio específico
Sección titulada «Consultar un servicio específico»docker compose logs appSeguir los registros de Nginx
Sección titulada «Seguir los registros de Nginx»En una terminal ejecute
docker compose logs -f webDesde otra terminal o desde el navegador acceda a
http://localhostLas nuevas solicitudes deberían aparecer en los registros de web.
Finalice el seguimiento con Ctrl + C.
Ejecutar comandos dentro de app
Sección titulada «Ejecutar comandos dentro de app»Compruebe que la base de datos existe.
docker compose exec app ls -la /data/Debería aparecer shop.db.
Puede consultar las tablas mediante Python.
docker compose exec app python -c "import sqlite3conn = sqlite3.connect('/data/shop.db')tables = conn.execute(\"SELECT name FROM sqlite_master WHERE type='table'\").fetchall()print('Tablas:', [t[0] for t in tables])conn.close()"docker compose exec utiliza el nombre lógico del servicio. No necesita conocer el nombre generado para el contenedor.
Comprobar la persistencia
Sección titulada «Comprobar la persistencia»Hasta este punto el volumen existe, pero conviene comprobar que realmente desacopla los datos del ciclo de vida de los contenedores.
Detener y eliminar los contenedores
Sección titulada «Detener y eliminar los contenedores»Ejecute
docker compose downEste comando elimina los contenedores y la red creada para la aplicación, pero no elimina por defecto el volumen nombrado.
Comprobar el volumen
Sección titulada «Comprobar el volumen»docker volume ls | grep grocerylist-dataDebería seguir apareciendo un volumen con un nombre similar a
grocerylistapp_grocerylist-dataRecrear la aplicación
Sección titulada «Recrear la aplicación»docker compose up -dAbra nuevamente
http://localhostLos elementos y categorías creados anteriormente deberían continuar disponibles.
La prueba demuestra esta separación.
docker compose down │ ├── elimina contenedores ├── elimina la red del proyecto └── conserva el volumen │ ▼docker compose up -d │ └── nuevos contenedores reutilizan los datosComprobar qué ocurre al eliminar el volumen
Sección titulada «Comprobar qué ocurre al eliminar el volumen»El siguiente comando cambia deliberadamente el resultado.
docker compose down -vLa opción -v solicita eliminar también los volúmenes nombrados definidos por el proyecto.
Si después ejecuta
docker compose up -dGroceryListApp comenzará con una base de datos nueva.
Advertencia
docker compose down -velimina los volúmenes del proyecto y, con ellos, los datos almacenados allí. Utilícelo únicamente cuando realmente quiera descartar ese estado.
Limpiar el proyecto
Sección titulada «Limpiar el proyecto»Cuando ya no necesite conservar la aplicación ni sus datos, ejecute
docker compose down -vCompruebe que el volumen desapareció.
docker volume ls | grep grocerylist-dataSi no hay coincidencias, el volumen ya no está disponible.
Solución de problemas
Sección titulada «Solución de problemas»Nginx devuelve 502 Bad Gateway
Sección titulada «Nginx devuelve 502 Bad Gateway»Compruebe primero el estado de los servicios.
docker compose psDespués consulte los registros de app.
docker compose logs appUn 502 en este ejercicio indica que Nginx está disponible, pero no pudo obtener una respuesta válida del servicio al que intenta reenviar la solicitud.
Los datos desaparecen al recrear los contenedores
Sección titulada «Los datos desaparecen al recrear los contenedores»Compruebe los montajes del contenedor de app.
docker inspect grocerylistapp-app-1 --format '{{json .Mounts}}' | python3 -m json.toolBusque un montaje asociado con /data.
Si el nombre real del contenedor difiere del ejemplo, puede identificarlo previamente con
docker compose psEl puerto 80 ya está ocupado
Sección titulada «El puerto 80 ya está ocupado»Compruebe qué proceso utiliza el puerto.
lsof -i :80También puede cambiar únicamente el puerto del sistema anfitrión.
ports: - "8888:80"En ese caso, acceda mediante
http://localhost:8888El servicio Nginx continúa escuchando en el puerto 80 dentro del contenedor.
Compose informa un error de sintaxis
Sección titulada «Compose informa un error de sintaxis»Ejecute
docker compose configCompose procesará el archivo y señalará problemas de sintaxis o estructura YAML.
Revise especialmente la indentación. YAML depende de ella para representar la jerarquía del documento.
Lo que debe poder reconocer ahora
Sección titulada «Lo que debe poder reconocer ahora»Después de completar el laboratorio debería poder explicar qué aporta Compose frente a una sucesión de comandos docker run.
El archivo docker-compose.yml declara los servicios que forman la aplicación y las relaciones necesarias para operarlos conjuntamente. En este ejercicio, app se construye localmente, web utiliza una imagen existente, ambos comparten una red del proyecto y app utiliza un volumen persistente.
También debería poder distinguir cuatro relaciones.
- Exposición externa — solo
webpublica un puerto hacia el sistema anfitrión. - Comunicación interna —
webencuentra aappmediante el nombre de servicio. - Dependencia de inicio —
webdepende del estado de salud deapp. - Persistencia — el volumen
grocerylist-dataconserva la base de datos fuera del ciclo de vida de los contenedores.
La arquitectura final puede resumirse así.
Host │ │ puerto 80 ▼webNginx │ │ red Compose ▼appFlask │ │ /data ▼grocerylist-dataComandos de referencia
Sección titulada «Comandos de referencia»| Acción | Comando |
|---|---|
| Validar la configuración | docker compose config |
| Crear e iniciar los servicios | docker compose up -d |
| Consultar su estado | docker compose ps |
| Consultar registros | docker compose logs |
| Seguir los registros de un servicio | docker compose logs -f web |
| Ejecutar un comando | docker compose exec app <comando> |
| Detener y eliminar contenedores | docker compose down |
| Eliminar también los volúmenes | docker compose down -v |
Referencias
Sección titulada «Referencias»| Recurso | Enlace |
|---|---|
| Docker Compose | Documentación de Docker Compose |
| Compose file | Referencia del archivo Compose |
| Redes en Compose | Networking in Compose |
| GroceryListApp | Repositorio en GitHub |
| Nginx | Imagen oficial en Docker Hub |
Fuente original
Laboratorio 07 - Aplicación multicontenedor con Docker Compose