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.
Arquitectura del ejercicio
Sección titulada «Arquitectura del ejercicio»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 SQLiteLa configuración de Nginx también se monta desde el sistema anfitrión en modo de solo lectura.
Lo que aprenderá
Sección titulada «Lo que aprenderá»Al finalizar podrá
- diferenciar el uso de un bind mount y de un volumen nombrado;
- utilizar un archivo
.envpara proporcionar configuración al servicioapp; - 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.
Antes de comenzar
Sección titulada «Antes de comenzar»Necesitará
- Docker y Docker Compose instalados y en ejecución;
- el Laboratorio 07 completado o el repositorio de GroceryListApp disponible;
- el puerto
80disponible; - una terminal con acceso a Docker.
Preparar el proyecto
Sección titulada «Preparar el proyecto»Si todavía no dispone del repositorio, ejecute
git clone https://github.com/jpadillaa/grocerylistapp.gitcd grocerylistappmkdir -p nginxDespué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.exampleConfigurar Nginx
Sección titulada «Configurar Nginx»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.
Externalizar la configuración
Sección titulada «Externalizar la configuración»Cree el archivo .env.
PORT=8080FLASK_CONFIG=app.config.DevelopmentConfigDATA_DIR=/dataDB_PATH=/data/shop.dbEstos 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 valoresPORT=8080FLASK_CONFIG=app.config.ConfigDATA_DIR=/dataDB_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.exampley excluir.envmediante.gitignorecuando este último contenga valores propios del entorno o información que no deba publicarse.
Definir la aplicación con Docker Compose
Sección titulada «Definir la aplicación con Docker Compose»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.
docker compose configRevise la salida y confirme que Compose puede procesar el archivo sin errores.
Distinguir qué se monta y por qué
Sección titulada «Distinguir qué se monta y por qué»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-dataLa configuración de Nginx se monta en modo de solo lectura porque el contenedor solo necesita consumirla.
Comprender la configuración externalizada
Sección titulada «Comprender la configuración externalizada»El servicio app incluye
env_file: .envCompose 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.
Comprender la red explícita
Sección titulada «Comprender la red explícita»Ambos servicios declaran
networks: - appnety 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.
Levantar la aplicación
Sección titulada «Levantar la aplicación»Ejecute
docker compose up -dCompruebe el estado.
docker compose psLos dos servicios deberían aparecer en ejecución y app debería alcanzar el estado healthy.
Compruebe la ruta de salud.
curl http://localhost/healthLa respuesta esperada es
{"status":"ok"}Abra después
http://localhostCree algunos elementos en GroceryListApp. Estos datos se utilizarán para comprobar la persistencia en los pasos siguientes.
Comprobar la persistencia
Sección titulada «Comprobar la persistencia»Recrear los servicios sin eliminar el volumen
Sección titulada «Recrear los servicios sin eliminar el volumen»Ejecute
docker compose downdocker compose up -dAbra nuevamente
http://localhostLos elementos creados anteriormente deberían continuar disponibles.
Esta prueba muestra que docker compose down puede eliminar los contenedores sin eliminar el volumen nombrado.
Inspeccionar el volumen
Sección titulada «Inspeccionar el volumen»docker volume ls | grep grocerylist-datadocker volume inspect grocerylistapp_grocerylist-dataLa salida de docker volume inspect contiene información sobre el volumen administrado por Docker.
Eliminar también el volumen
Sección titulada «Eliminar también el volumen»Ejecute
docker compose down -vdocker compose up -dLa aplicación debería iniciar con una base de datos vacía porque el volumen anterior ya no existe.
Advertencia
docker compose down -velimina 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.
Comprobar el bind mount
Sección titulada «Comprobar el bind mount»Liste el contenido visible dentro del contenedor.
docker compose exec app ls -la /app/app/Compárelo con el directorio local app/.
El montaje
./app:/app/apphace que el contenido local sea visible en esa ruta del contenedor.
Puede comprobarlo sin modificar la lógica de la aplicación.
echo "# Archivo de prueba para verificar bind mount" > app/test_bind.pydocker compose exec app cat /app/app/test_bind.pyEl contenido debería coincidir.
Elimine el archivo de prueba.
rm app/test_bind.pyEste 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.
Comprobar las variables de entorno
Sección titulada «Comprobar las variables de entorno»Ejecute
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
docker compose configDiagnóstico de fallos inducidos
Sección titulada «Diagnóstico de fallos inducidos»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 resultadoEl 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»Introducir el fallo
Sección titulada «Introducir el fallo»Reemplace app por localhost en la configuración de Nginx.
sed -i 's|proxy_pass http://app:8080|proxy_pass http://localhost:8080|' nginx/default.confdocker compose restart webObservar el síntoma
Sección titulada «Observar el síntoma»curl -I http://localhostNginx debería devolver
502 Bad GatewayRecopilar evidencia
Sección titulada «Recopilar evidencia»docker compose logs --tail 10 webBusque un mensaje similar a
connect() failed ... Connection refusedcon una referencia a 127.0.0.1:8080.
Relacionar la evidencia con la causa
Sección titulada «Relacionar la evidencia con la causa»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 appCorregir y comprobar
Sección titulada «Corregir y comprobar»sed -i 's|proxy_pass http://localhost:8080|proxy_pass http://app:8080|' nginx/default.confdocker compose restart webcurl http://localhost/healthFallo 2 - El archivo .env no está disponible
Sección titulada «Fallo 2 - El archivo .env no está disponible»Introducir el fallo
Sección titulada «Introducir el fallo»mv .env .env.backupdocker compose downdocker compose up -dObservar el síntoma
Sección titulada «Observar el síntoma»docker compose psdocker compose logs appEl comportamiento dependerá de los valores que la imagen proporcione por defecto y de cuáles sean obligatorios para la aplicación.
Recopilar evidencia
Sección titulada «Recopilar evidencia»docker compose exec app env | grep -E "PORT|FLASK_CONFIG|DB_PATH"Compare la salida con los valores esperados del archivo .env.
Corregir y comprobar
Sección titulada «Corregir y comprobar»mv .env.backup .envdocker compose downdocker compose up -dVuelva a inspeccionar las variables.
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»Introducir el fallo
Sección titulada «Introducir el fallo»Cambie /data por /datos.
sed -i 's|grocerylist-data:/data|grocerylist-data:/datos|' docker-compose.ymldocker compose down -vdocker compose up -dObservar el síntoma
Sección titulada «Observar el síntoma»La aplicación puede continuar funcionando.
Cree un elemento y después recree los contenedores.
docker compose downdocker compose up -dLos datos deberían desaparecer.
Este fallo es diferente de los anteriores porque el servicio puede iniciar normalmente aunque la persistencia esté mal configurada.
Recopilar evidencia
Sección titulada «Recopilar evidencia»Inspeccione los montajes.
docker inspect grocerylistapp-app-1 --format '{{json .Mounts}}' | python3 -m json.toolEl volumen aparecerá montado en
/datospero la aplicación utiliza
/data/shop.dbsegú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.
Corregir y comprobar
Sección titulada «Corregir y comprobar»sed -i 's|grocerylist-data:/datos|grocerylist-data:/data|' docker-compose.ymldocker compose down -vdocker compose up -dCree nuevos datos y repita una recreación para comprobar la persistencia.
Fallo 4 - web y app no comparten una red
Sección titulada «Fallo 4 - web y app no comparten una red»Introducir el fallo
Sección titulada «Introducir el fallo»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.
docker compose downdocker compose up -dObservar el síntoma
Sección titulada «Observar el síntoma»curl -I http://localhostNginx debería devolver
502 Bad GatewayRecopilar evidencia
Sección titulada «Recopilar evidencia»Consulte los registros.
docker compose logs --tail 5 webInspeccione después ambas redes.
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.
Relacionar la evidencia con la causa
Sección titulada «Relacionar la evidencia con la causa»Nginx utiliza el nombre app, pero web y app no comparten una red.
frontend backend │ │ web appDentro de esta topología no existe una red común a través de la cual web pueda resolver y alcanzar app.
Corregir y comprobar
Sección titulada «Corregir y comprobar»Restaure la configuración donde ambos servicios pertenecen a appnet.
Después ejecute
docker compose downdocker compose up -dcurl http://localhost/healthLimpiar el entorno
Sección titulada «Limpiar el entorno»Cuando finalice, elimine los servicios y el volumen.
docker compose down -vCompruebe el resultado.
docker volume ls | grep grocerylist-datadocker network ls | grep grocerylistappNo deberían aparecer recursos asociados con el proyecto que hayan sido eliminados por el comando anterior.
Solución de problemas frecuentes
Sección titulada «Solución de problemas frecuentes»Nginx devuelve 502 Bad Gateway
Sección titulada «Nginx devuelve 502 Bad Gateway»Compruebe primero el estado.
docker compose psDespués revise los registros de app.
docker compose logs appCompruebe 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.
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/appy que el archivo modificado pertenezca realmente al directorio local app/.
Las variables esperadas no aparecen
Sección titulada «Las variables esperadas no aparecen»Compruebe que .env existe.
ls -la .envcat .envDespués inspeccione el entorno efectivo.
docker compose exec app env | grep -E "PORT|FLASK_CONFIG|DB_PATH"Lo que debe poder reconocer ahora
Sección titulada «Lo que debe poder reconocer ahora»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 esperadadocker-compose.yml │ ▼Ejecución realcontenedores, redes, volúmenes, entorno │ ▼Evidencia observableps, logs, inspect, exec, config │ ▼Diagnósticocomparar lo esperado con lo observadoCuando 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.
Comandos de diagnóstico
Sección titulada «Comandos de diagnóstico»| 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 |
Referencias
Sección titulada «Referencias»| 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