Ir al contenido

Laboratorio 09 - Persistencia, configuración y diagnóstico en aplicaciones compuestas

En el Laboratorio 07 construyó una aplicación multicontenedor con Docker Compose. En este laboratorio profundizará en tres responsabilidades que aparecen cuando una aplicación compuesta debe mantenerse y diagnosticarse durante su ejecución.

La primera corresponde a la persistencia. El código utilizado durante desarrollo y los datos de negocio tienen ciclos de vida diferentes y no deberían almacenarse del mismo modo.

La segunda corresponde a la configuración. Algunos valores deben poder cambiar entre entornos sin exigir una nueva construcción de la imagen.

La tercera corresponde al diagnóstico. Cuando un servicio falla, es necesario relacionar el síntoma observado con la configuración, la red, los montajes y los registros antes de aplicar una corrección.

Trabajará nuevamente con GroceryListApp y Nginx.

Sistema anfitrión
│
│ puerto 80
▼
┌─────────────┐
│ web │
│ Nginx │
└──────┬──────┘
│
│ red appnet
│ proxy_pass → app:8080
▼
┌─────────────┐
│ app │
│ Flask │
└──────┬──────┘
│
├── ./app → /app/app
│ código durante desarrollo
│
└── grocerylist-data → /data
datos SQLite

La configuración de Nginx también se monta desde el sistema anfitrión en modo de solo lectura.

Al finalizar podrá

  • diferenciar el uso de un bind mount y de un volumen nombrado;
  • utilizar un archivo .env para proporcionar configuración al servicio app;
  • comprobar qué archivos del sistema anfitrión están visibles dentro de un contenedor;
  • declarar una red explícita en Docker Compose;
  • inspeccionar servicios, redes, volúmenes, montajes y variables de entorno;
  • diagnosticar fallos de conectividad, configuración y persistencia;
  • relacionar un síntoma con la causa antes de aplicar una corrección.

Necesitará

  • Docker y Docker Compose instalados y en ejecución;
  • el Laboratorio 07 completado o el repositorio de GroceryListApp disponible;
  • el puerto 80 disponible;
  • una terminal con acceso a Docker.

Si todavía no dispone del repositorio, ejecute

Ventana de terminal
git clone https://github.com/jpadillaa/grocerylistapp.git
cd grocerylistapp
mkdir -p nginx

Después de agregar los archivos de este laboratorio, la estructura relevante será

grocerylistapp/
├── app/
├── static/
├── templates/
├── nginx/
│ └── default.conf
├── requirements.txt
├── Dockerfile
├── docker-compose.yml
├── .env
└── .env.example

Cree 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;
}
}

Nginx utilizará el nombre de servicio app para encontrar GroceryListApp dentro de la red de Compose.

Cree el archivo .env.

PORT=8080
FLASK_CONFIG=app.config.DevelopmentConfig
DATA_DIR=/data
DB_PATH=/data/shop.db

Estos valores configuran el puerto, la clase de configuración de Flask y la ubicación de los datos.

Cree también .env.example.

# Copie este archivo como .env y complete los valores
PORT=8080
FLASK_CONFIG=app.config.Config
DATA_DIR=/data
DB_PATH=/data/shop.db

.env.example documenta qué variables necesita la aplicación sin representar necesariamente la configuración de un entorno concreto.

Recomendación

En un proyecto real, puede versionar .env.example y excluir .env mediante .gitignore cuando este último contenga valores propios del entorno o información que no deba publicarse.

Cree docker-compose.yml.

services:
app:
build: .
env_file: .env
volumes:
- ./app:/app/app
- grocerylist-data:/data
healthcheck:
test: ["CMD-SHELL", "curl --fail http://localhost:${PORT}/health || exit 1"]
interval: 10s
timeout: 5s
start_period: 5s
retries: 3
networks:
- appnet
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
networks:
- appnet
restart: unless-stopped
volumes:
grocerylist-data:
networks:
appnet:

Antes de crear los servicios, valide la configuración.

Ventana de terminal
docker compose config

Revise la salida y confirme que Compose puede procesar el archivo sin errores.

La configuración utiliza tres montajes con propósitos diferentes.

Servicio Tipo de montaje Ruta Propósito
web Bind mount de solo lectura ./nginx/default.conf:/etc/nginx/conf.d/default.conf:ro Proporcionar la configuración local de Nginx
app Bind mount ./app:/app/app Hacer visible dentro del contenedor el código del directorio local
app Volumen nombrado grocerylist-data:/data Conservar la base de datos SQLite

La diferencia principal no es solo sintáctica.

El código del directorio app/ pertenece al entorno de desarrollo y cambia desde el sistema anfitrión. El bind mount permite que esos archivos estén disponibles directamente en /app/app.

Los datos almacenados en /data, en cambio, pertenecen al estado de la aplicación. El volumen nombrado permite conservarlos aunque el contenedor sea recreado.

Código de desarrollo
./app
│
│ bind mount
▼
/app/app
Datos de negocio
/data/shop.db
│
│ volumen nombrado
▼
grocerylist-data

La configuración de Nginx se monta en modo de solo lectura porque el contenedor solo necesita consumirla.

El servicio app incluye

env_file: .env

Compose utiliza ese archivo para proporcionar las variables definidas allí al contenedor.

Esto permite modificar valores de configuración sin editar el Dockerfile ni incorporarlos directamente en docker-compose.yml.

La imagen conserva una definición de la aplicación. El archivo de entorno aporta valores que pueden variar según el contexto donde se ejecute.

Ambos servicios declaran

networks:
- appnet

y el archivo incluye

networks:
appnet:

La red compartida permite que web resuelva el nombre app y se comunique con él.

Declararla de manera explícita también hace visible esta decisión dentro de la definición de la aplicación y facilita su inspección durante los ejercicios de diagnóstico.

Ejecute

Ventana de terminal
docker compose up -d

Compruebe el estado.

Ventana de terminal
docker compose ps

Los dos servicios deberían aparecer en ejecución y app debería alcanzar el estado healthy.

Compruebe la ruta de salud.

Ventana de terminal
curl http://localhost/health

La respuesta esperada es

{"status":"ok"}

Abra después

http://localhost

Cree algunos elementos en GroceryListApp. Estos datos se utilizarán para comprobar la persistencia en los pasos siguientes.

Recrear los servicios sin eliminar el volumen

Sección titulada «Recrear los servicios sin eliminar el volumen»

Ejecute

Ventana de terminal
docker compose down
docker compose up -d

Abra nuevamente

http://localhost

Los elementos creados anteriormente deberían continuar disponibles.

Esta prueba muestra que docker compose down puede eliminar los contenedores sin eliminar el volumen nombrado.

Ventana de terminal
docker volume ls | grep grocerylist-data
docker volume inspect grocerylistapp_grocerylist-data

La salida de docker volume inspect contiene información sobre el volumen administrado por Docker.

Ejecute

Ventana de terminal
docker compose down -v
docker compose up -d

La aplicación debería iniciar con una base de datos vacía porque el volumen anterior ya no existe.

Advertencia

docker compose down -v elimina los volúmenes nombrados del proyecto. Utilícelo únicamente cuando quiera descartar deliberadamente los datos almacenados allí.

Cree nuevamente una categoría y algunos elementos para los ejercicios de diagnóstico posteriores.

Liste el contenido visible dentro del contenedor.

Ventana de terminal
docker compose exec app ls -la /app/app/

Compárelo con el directorio local app/.

El montaje

./app:/app/app

hace que el contenido local sea visible en esa ruta del contenedor.

Puede comprobarlo sin modificar la lógica de la aplicación.

Ventana de terminal
echo "# Archivo de prueba para verificar bind mount" > app/test_bind.py
docker compose exec app cat /app/app/test_bind.py

El contenido debería coincidir.

Elimine el archivo de prueba.

Ventana de terminal
rm app/test_bind.py

Este ejercicio comprueba la relación entre ambos sistemas de archivos. La actualización automática del proceso de la aplicación depende además de cómo esté configurado el servidor que la ejecuta.

En un despliegue donde el código deba quedar definido únicamente por la imagen, este bind mount no sería necesario.

Ejecute

Ventana de terminal
docker compose exec app env | grep -E "PORT|FLASK_CONFIG|DB_PATH"

Los valores deberían corresponder con los definidos en .env.

También puede inspeccionar la configuración que Compose procesa mediante

Ventana de terminal
docker compose config

Las siguientes pruebas introducen errores deliberadamente.

En cada caso seguirá el mismo ciclo.

Introducir el fallo
↓
Observar el síntoma
↓
Recopilar evidencia
↓
Relacionar evidencia y configuración
↓
Corregir
↓
Comprobar el resultado

El objetivo no es memorizar una solución concreta, sino practicar una secuencia reproducible de diagnóstico.

Fallo 1 - Nginx utiliza localhost para encontrar app

Sección titulada «Fallo 1 - Nginx utiliza localhost para encontrar app»

Reemplace app por localhost en la configuración de Nginx.

Ventana de terminal
sed -i 's|proxy_pass http://app:8080|proxy_pass http://localhost:8080|' nginx/default.conf
docker compose restart web
Ventana de terminal
curl -I http://localhost

Nginx debería devolver

502 Bad Gateway
Ventana de terminal
docker compose logs --tail 10 web

Busque un mensaje similar a

connect() failed ... Connection refused

con una referencia a 127.0.0.1:8080.

Dentro del contenedor web, localhost representa al propio contenedor de Nginx.

GroceryListApp se ejecuta en otro contenedor. Para alcanzarlo, Nginx debe utilizar el nombre app dentro de la red compartida.

web
│
├── localhost → web
│
└── app → contenedor app
Ventana de terminal
sed -i 's|proxy_pass http://localhost:8080|proxy_pass http://app:8080|' nginx/default.conf
docker compose restart web
curl http://localhost/health

Fallo 2 - El archivo .env no está disponible

Sección titulada «Fallo 2 - El archivo .env no está disponible»
Ventana de terminal
mv .env .env.backup
docker compose down
docker compose up -d
Ventana de terminal
docker compose ps
docker compose logs app

El comportamiento dependerá de los valores que la imagen proporcione por defecto y de cuáles sean obligatorios para la aplicación.

Ventana de terminal
docker compose exec app env | grep -E "PORT|FLASK_CONFIG|DB_PATH"

Compare la salida con los valores esperados del archivo .env.

Ventana de terminal
mv .env.backup .env
docker compose down
docker compose up -d

Vuelva a inspeccionar las variables.

Ventana de terminal
docker compose exec app env | grep -E "PORT|FLASK_CONFIG|DB_PATH"

Fallo 3 - El volumen está montado en una ruta incorrecta

Sección titulada «Fallo 3 - El volumen está montado en una ruta incorrecta»

Cambie /data por /datos.

Ventana de terminal
sed -i 's|grocerylist-data:/data|grocerylist-data:/datos|' docker-compose.yml
docker compose down -v
docker compose up -d

La aplicación puede continuar funcionando.

Cree un elemento y después recree los contenedores.

Ventana de terminal
docker compose down
docker compose up -d

Los datos deberían desaparecer.

Este fallo es diferente de los anteriores porque el servicio puede iniciar normalmente aunque la persistencia esté mal configurada.

Inspeccione los montajes.

Ventana de terminal
docker inspect grocerylistapp-app-1 --format '{{json .Mounts}}' | python3 -m json.tool

El volumen aparecerá montado en

/datos

pero la aplicación utiliza

/data/shop.db

según DB_PATH.

La existencia del volumen no es suficiente. Debe estar asociado con la ruta donde la aplicación realmente escribe los datos que necesitan persistir.

Ventana de terminal
sed -i 's|grocerylist-data:/datos|grocerylist-data:/data|' docker-compose.yml
docker compose down -v
docker compose up -d

Cree nuevos datos y repita una recreación para comprobar la persistencia.

Reemplace temporalmente docker-compose.yml por esta configuración.

services:
app:
build: .
env_file: .env
volumes:
- ./app:/app/app
- grocerylist-data:/data
healthcheck:
test: ["CMD-SHELL", "curl --fail http://localhost:${PORT}/health || exit 1"]
interval: 10s
timeout: 5s
start_period: 5s
retries: 3
networks:
- backend
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
networks:
- frontend
restart: unless-stopped
volumes:
grocerylist-data:
networks:
frontend:
backend:

Recree los servicios.

Ventana de terminal
docker compose down
docker compose up -d
Ventana de terminal
curl -I http://localhost

Nginx debería devolver

502 Bad Gateway

Consulte los registros.

Ventana de terminal
docker compose logs --tail 5 web

Inspeccione después ambas redes.

Ventana de terminal
docker network inspect grocerylistapp_frontend --format '{{range .Containers}}{{.Name}} {{end}}'
docker network inspect grocerylistapp_backend --format '{{range .Containers}}{{.Name}} {{end}}'

Cada servicio aparece en una red diferente.

Nginx utiliza el nombre app, pero web y app no comparten una red.

frontend backend
│ │
web app

Dentro de esta topología no existe una red común a través de la cual web pueda resolver y alcanzar app.

Restaure la configuración donde ambos servicios pertenecen a appnet.

Después ejecute

Ventana de terminal
docker compose down
docker compose up -d
curl http://localhost/health

Cuando finalice, elimine los servicios y el volumen.

Ventana de terminal
docker compose down -v

Compruebe el resultado.

Ventana de terminal
docker volume ls | grep grocerylist-data
docker network ls | grep grocerylistapp

No deberían aparecer recursos asociados con el proyecto que hayan sido eliminados por el comando anterior.

Compruebe primero el estado.

Ventana de terminal
docker compose ps

Después revise los registros de app.

Ventana de terminal
docker compose logs app

Compruebe además que web y app compartan una red y que Nginx utilice app, no localhost, como destino.

La aplicación funciona pero los datos desaparecen

Sección titulada «La aplicación funciona pero los datos desaparecen»

Inspeccione los montajes del contenedor y confirme que grocerylist-data está asociado con /data.

Ventana de terminal
docker inspect grocerylistapp-app-1 --format '{{json .Mounts}}'

La existencia del volumen no demuestra por sí sola que la aplicación esté escribiendo dentro de él.

Los archivos modificados en el host no aparecen en el contenedor

Sección titulada «Los archivos modificados en el host no aparecen en el contenedor»

Compruebe que docker-compose.yml incluya

- ./app:/app/app

y que el archivo modificado pertenezca realmente al directorio local app/.

Compruebe que .env existe.

Ventana de terminal
ls -la .env
cat .env

Después inspeccione el entorno efectivo.

Ventana de terminal
docker compose exec app env | grep -E "PORT|FLASK_CONFIG|DB_PATH"

Este laboratorio combina tres mecanismos que resuelven problemas diferentes.

Los montajes determinan qué información procede del sistema anfitrión y qué información debe persistir independientemente de los contenedores.

La configuración externalizada permite proporcionar valores al contenedor sin modificar la imagen.

Las herramientas de diagnóstico permiten comprobar qué configuración, montajes, redes y variables están realmente presentes durante la ejecución.

La relación puede resumirse así.

Definición esperada
docker-compose.yml
│
▼
Ejecución real
contenedores, redes, volúmenes, entorno
│
▼
Evidencia observable
ps, logs, inspect, exec, config
│
▼
Diagnóstico
comparar lo esperado con lo observado

Cuando aparece un fallo, conviene evitar empezar por una corrección supuesta. Primero identifique el síntoma, consulte la evidencia relevante y compruebe qué parte de la configuración real difiere de la esperada.

Qué comprobar Comando
Estado de los servicios docker compose ps
Registros de un servicio docker compose logs <servicio>
Registros en tiempo real docker compose logs -f <servicio>
Variables de entorno docker compose exec <servicio> env
Resolución de nombres docker compose exec web sh -c "nslookup app"
Archivos montados docker compose exec <servicio> ls -la <ruta>
Redes del proyecto `docker network ls
Contenedores de una red docker network inspect <red> --format '{{range .Containers}}{{.Name}} {{end}}'
Montajes de un contenedor docker inspect <contenedor> --format '{{json .Mounts}}'
Configuración procesada docker compose config
Volúmenes disponibles docker volume ls
Recurso Enlace
Docker Compose Documentación de Docker Compose
Redes en Compose Networking in Compose
Volúmenes en Compose Volumes in Compose
Variables de entorno Environment variables in Compose
GroceryListApp Repositorio en GitHub
Compose CLI Referencia de Docker Compose CLI

Fuente original

Laboratorio 09 - Persistencia, configuración y diagnóstico en aplicaciones compuestas