Introducción
En este laboratorio práctico, aprenderá a identificar y resolver un problema común de Docker: contenedores que se cierran inmediatamente después de iniciarse. Este problema a menudo confunde a los principiantes y puede ocurrir por varias razones, que van desde errores de configuración hasta problemas de la aplicación.
Al completar este laboratorio, comprenderá el ciclo de vida de los contenedores Docker, aprenderá a diagnosticar las salidas inmediatas de los contenedores, dominará las técnicas de depuración e implementará las mejores prácticas para garantizar que sus contenedores se ejecuten de manera confiable. Estas habilidades son esenciales para cualquier persona que trabaje con Docker en entornos de desarrollo o producción.
Comprensión de los Conceptos Básicos de los Contenedores Docker
Comencemos explorando los fundamentos de los contenedores Docker y familiarizándonos con los comandos básicos utilizados para administrar contenedores.
¿Qué es un Contenedor Docker?
Un contenedor Docker es un paquete de software ligero, autónomo y ejecutable que incluye todo lo necesario para ejecutar una aplicación:
- Código
- Tiempo de ejecución (Runtime)
- Herramientas del sistema
- Bibliotecas
- Configuración
Los contenedores se ejecutan de forma aislada del sistema host y de otros contenedores, proporcionando consistencia en diferentes entornos.
Explorando Docker en su Sistema
Primero, verifiquemos que Docker esté correctamente instalado y en ejecución en su sistema:
docker --version
Debería ver una salida similar a:
Docker version 20.10.21, build baeda1f
A continuación, comprobemos si hay contenedores en ejecución actualmente:
docker ps
Este comando enumera los contenedores en ejecución. Como aún no hemos iniciado ninguno, solo debería ver los encabezados de las columnas:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
Para ver todos los contenedores, incluidos los detenidos:
docker ps -a
Comprensión del Ciclo de Vida del Contenedor
El ciclo de vida del contenedor Docker consta de varios estados:
- Created (Creado): Se crea un contenedor pero aún no se ha iniciado
- Running (En ejecución): El contenedor está ejecutando sus procesos definidos
- Paused (Pausado): Los procesos del contenedor se suspenden temporalmente
- Stopped (Detenido): El contenedor ha salido o se ha detenido
- Deleted (Eliminado): El contenedor ha sido eliminado del sistema
Ejecutemos un contenedor simple y observemos su ciclo de vida:
docker run hello-world
Debería ver una salida similar a:
Hello from Docker!
This message shows that your installation appears to be working correctly.
To generate this message, Docker took the following steps:
1. The Docker client contacted the Docker daemon.
2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
3. The Docker daemon created a new container from that image which runs the
executable that produces the output you are currently reading.
4. The Docker daemon streamed that output to the Docker client, which sent it
to your terminal.
...
Observe que este contenedor se ejecutó y salió inmediatamente después de mostrar el mensaje. Este es en realidad un comportamiento normal para el contenedor hello-world, ya que está diseñado para simplemente mostrar un mensaje y salir.
Compruebe el contenedor en la lista de todos los contenedores:
docker ps -a
Debería ver su contenedor hello-world en la lista con un estado "Exited" (Salido):
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
a1b2c3d4e5f6 hello-world "/hello" 30 seconds ago Exited (0) 30 seconds ago peaceful_hopper
El código de salida (0) indica que el contenedor salió con éxito sin errores.
Comandos Clave de Docker para la Gestión de Contenedores
Aquí hay algunos comandos esenciales de Docker que utilizará a lo largo de este laboratorio:
docker run [OPTIONS] IMAGE [COMMAND]: Crear e iniciar un contenedordocker ps: Listar los contenedores en ejecucióndocker ps -a: Listar todos los contenedores (incluidos los detenidos)docker logs [CONTAINER_ID]: Ver los registros del contenedordocker inspect [CONTAINER_ID]: Obtener información detallada del contenedordocker exec -it [CONTAINER_ID] [COMMAND]: Ejecutar un comando en un contenedor en ejecucióndocker stop [CONTAINER_ID]: Detener un contenedor en ejecucióndocker rm [CONTAINER_ID]: Eliminar un contenedor
Ahora que comprende los conceptos básicos de los contenedores Docker y su ciclo de vida, pasaremos a la solución de problemas de contenedores que salen inesperadamente.
Identificación de Problemas de Salida de Contenedores
En este paso, crearemos un contenedor Docker que sale inmediatamente y aprenderemos a diagnosticar el problema.
Ejecución de un Contenedor que Sale Inmediatamente
Primero, intentemos ejecutar un contenedor Ubuntu:
docker run ubuntu
Notará algo interesante: el comando se completa instantáneamente y regresa a su indicador. ¿Dónde está nuestro contenedor Ubuntu? Comprobémoslo:
docker ps
No hay contenedores en ejecución. Ahora compruebe todos los contenedores, incluidos los detenidos:
docker ps -a
Verá una salida similar a:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
f7d9e7f6543d ubuntu "/bin/bash" 10 seconds ago Exited (0) 10 seconds ago focused_galileo
a1b2c3d4e5f6 hello-world "/hello" 10 minutes ago Exited (0) 10 minutes ago peaceful_hopper
El contenedor Ubuntu se inició y luego salió inmediatamente con un código de estado de 0, lo que indica que salió sin errores. Este es el comportamiento esperado, pero puede ser confuso para los principiantes.
Comprensión de Por Qué Salen los Contenedores
Los contenedores están diseñados para ejecutar un comando o proceso específico. Cuando ese proceso se completa o sale, el contenedor se detiene. Este es un principio fundamental del diseño de Docker.
El contenedor Ubuntu salió inmediatamente porque:
- El comando predeterminado para la imagen Ubuntu es
/bin/bash - Cuando se ejecuta sin las banderas
-it(interactivo, terminal), no hay entrada para el shell bash - Sin entrada y sin un comando específico para ejecutar, bash sale inmediatamente
- Cuando el proceso principal (bash) sale, el contenedor se detiene
Visualización de Registros e Información del Contenedor
Examinemos los registros de nuestro contenedor Ubuntu que salió para comprender qué sucedió. Primero, encuentre el ID del contenedor en su salida docker ps -a, luego:
docker logs CONTAINER_ID
Reemplace CONTAINER_ID con el ID de su contenedor real. Es probable que no vea ninguna salida porque el contenedor no produjo ningún registro antes de salir.
Para obtener información más detallada sobre el contenedor:
docker inspect CONTAINER_ID
Esto mostrará un gran objeto JSON con la configuración del contenedor y la información de estado.
Centrémonos en el código de salida:
docker inspect CONTAINER_ID --format='{{.State.ExitCode}}'
Debería ver:
0
Esto confirma que el contenedor salió normalmente, no debido a un error.
Mantener un Contenedor en Ejecución
Para mantener el contenedor Ubuntu en ejecución, necesitamos:
- Proporcionar una sesión interactiva, o
- Anular el comando predeterminado con un proceso de larga duración
Intentemos el enfoque interactivo:
docker run -it ubuntu
Ahora está dentro del contenedor con un indicador bash:
root@3a4b5c6d7e8f:/#
El contenedor permanece en ejecución mientras esta sesión bash esté activa. Escriba exit o presione Ctrl+D para salir del contenedor.
exit
Alternativamente, podemos mantener un contenedor en ejecución proporcionando un comando que no se complete inmediatamente:
docker run -d ubuntu sleep 300
Esto ejecuta el contenedor Ubuntu y ejecuta el comando sleep 300, que mantendrá el contenedor en ejecución durante 300 segundos (5 minutos).
Compruebe que el contenedor se está ejecutando:
docker ps
Debería ver su contenedor en el estado de ejecución:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
9a8b7c6d5e4f ubuntu "sleep 300" 10 seconds ago Up 10 seconds hopeful_hopper
Al diagnosticar contenedores que salen inmediatamente, recuerde estos puntos clave:
- Los contenedores salen cuando su proceso principal se completa
- Compruebe los registros y los códigos de salida para comprender por qué se detuvieron
- Si un contenedor debe seguir ejecutándose, asegúrese de que su proceso principal no salga
Solución de Problemas Comunes de Salida de Contenedores
Ahora que entendemos por qué los contenedores salen inmediatamente, exploremos las causas comunes de las salidas inesperadas de contenedores y cómo solucionar los problemas.
Creación de un Contenedor con un Problema
Creemos una situación simple donde un contenedor sale inesperadamente. Primero, cree un directorio para nuestros archivos de prueba:
mkdir -p ~/project/docker-exit-test
cd ~/project/docker-exit-test
Ahora, cree un script Python simple con un error:
nano app.py
Agregue el siguiente código:
import os
## Attempt to read a required environment variable
database_url = os.environ['DATABASE_URL']
print(f"Connecting to database: {database_url}")
print("Application running...")
## Rest of the application code would go here
Guarde y salga del archivo (presione Ctrl+O, Enter, luego Ctrl+X).
Ahora, cree un Dockerfile para construir una imagen con esta aplicación:
nano Dockerfile
Agregue el siguiente contenido:
FROM python:3.9-slim
WORKDIR /app
COPY app.py .
CMD ["python", "app.py"]
Guarde y salga del archivo.
Construya la imagen Docker:
docker build -t exit-test-app .
Debería ver una salida que indica que la imagen se construyó correctamente:
Successfully built a1b2c3d4e5f6
Successfully tagged exit-test-app:latest
Ahora, ejecute el contenedor:
docker run exit-test-app
Debería ver que el contenedor sale inmediatamente con un error:
Traceback (most recent call last):
File "/app/app.py", line 4, in <module>
database_url = os.environ['DATABASE_URL']
File "/usr/local/lib/python3.9/os.py", line 679, in __getitem__
raise KeyError(key) from None
KeyError: 'DATABASE_URL'
El contenedor salió porque nuestro script Python esperaba una variable de entorno que no se proporcionó.
Diagnóstico del Problema
Cuando un contenedor sale inesperadamente, siga estos pasos para la solución de problemas:
- Compruebe el código de salida para determinar si es un error:
docker ps -a
Busque su contenedor y anote el código de salida. Debería ser distinto de cero, lo que indica un error:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
b1c2d3e4f5g6 exit-test-app "python app.py" 20 seconds ago Exited (1) 19 seconds ago vigilant_galileo
- Examine los registros del contenedor:
docker logs $(docker ps -a -q --filter ancestor=exit-test-app --latest)
Esto recupera los registros del contenedor más reciente creado a partir de nuestra imagen.
El mensaje de error muestra claramente el problema: el script Python está intentando acceder a una variable de entorno llamada DATABASE_URL que no existe.
Solución del Problema
Ahora, solucionemos el problema proporcionando la variable de entorno que falta:
docker run -e DATABASE_URL=postgresql://user:password@db:5432/mydatabase exit-test-app
Ahora debería ver que el contenedor se ejecuta correctamente:
Connecting to database: postgresql://user:password@db:5432/mydatabase
Application running...
El contenedor aún sale, pero esta vez es porque nuestro script llega al final y termina normalmente. Si quisiéramos que el contenedor siguiera ejecutándose, necesitaríamos modificar nuestro script para incluir un bucle infinito o un proceso de larga duración.
Causas Comunes de Salidas de Contenedores
Aquí hay varias razones comunes por las que los contenedores podrían salir inesperadamente:
- Variables de entorno faltantes: Como acabamos de demostrar
- Dependencias ausentes: Bibliotecas o paquetes del sistema faltantes
- Fallos de conexión: No se puede acceder a una base de datos u otro servicio
- Problemas de permisos: Permisos insuficientes para acceder a archivos o recursos
- Limitaciones de recursos: El contenedor se queda sin memoria o CPU
- Fallos de la aplicación: Errores en el código de la aplicación
Técnicas de Solución de Problemas
Para cada uno de estos problemas, aquí hay enfoques efectivos para la solución de problemas:
- Revisar los registros: Siempre compruebe primero los registros del contenedor con
docker logs - Anular el punto de entrada (entrypoint): Use
docker run --entrypoint /bin/sh -it my-imagepara ingresar al contenedor e investigar - Agregar declaraciones de depuración: Modifique su aplicación para agregar más registros
- Comprobar el uso de recursos: Use
docker statspara monitorear el uso de recursos del contenedor - Inspeccionar el entorno: Ejecute
docker exec -it CONTAINER_ID envpara verificar las variables de entorno
Intentemos la técnica de anulación del punto de entrada con nuestra imagen:
docker run --entrypoint /bin/sh -it exit-test-app
Ahora está dentro del contenedor con un shell. Puede explorar el entorno:
ls -la
cat app.py
echo $DATABASE_URL
Verá que DATABASE_URL no está configurado. Salga del contenedor cuando haya terminado:
exit
Al comprender por qué los contenedores salen y aplicar estas técnicas de solución de problemas, puede diagnosticar y resolver rápidamente la mayoría de los problemas de salida de contenedores.
Implementación de las Mejores Prácticas para la Fiabilidad de los Contenedores
Ahora que entendemos cómo solucionar los problemas de salida de los contenedores, implementemos las mejores prácticas para que nuestros contenedores sean más fiables y robustos. Mejoraremos nuestra aplicación de ejemplo con un manejo adecuado de errores, comprobaciones de estado y registro.
1. Mejora del Manejo de Errores
Modifiquemos nuestra aplicación Python para manejar las variables de entorno faltantes con elegancia:
cd ~/project/docker-exit-test
nano app.py
Actualice el contenido a:
import os
import time
import sys
## Get environment variables with defaults
database_url = os.environ.get('DATABASE_URL', 'sqlite:///default.db')
print(f"Connecting to database: {database_url}")
## Simulate a long-running process
try:
print("Application running... Press Ctrl+C to exit")
counter = 0
while True:
counter += 1
print(f"Application heartbeat: {counter}")
time.sleep(10)
except KeyboardInterrupt:
print("Application shutting down gracefully...")
sys.exit(0)
Guarde y salga del archivo.
Esta versión mejorada:
- Usa
os.environ.get()con un valor predeterminado en lugar de generar una excepción - Implementa un bucle de larga duración para mantener el contenedor activo
- Maneja el apagado correcto cuando se termina
Reconstruyamos la imagen:
docker build -t exit-test-app:v2 .
Y ejecute el contenedor mejorado:
docker run -d --name improved-app exit-test-app:v2
Compruebe que el contenedor se está ejecutando:
docker ps
Debería ver su contenedor en ejecución:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
c1d2e3f4g5h6 exit-test-app:v2 "python app.py" 10 seconds ago Up 10 seconds improved-app
Vea los registros para confirmar que está funcionando:
docker logs improved-app
Debería ver una salida como:
Connecting to database: sqlite:///default.db
Application running... Press Ctrl+C to exit
Application heartbeat: 1
Application heartbeat: 2
2. Implementación de una Comprobación de Estado en Dockerfile
Las comprobaciones de estado permiten a Docker monitorear el estado de su contenedor. Actualicemos nuestro Dockerfile para incluir una comprobación de estado:
nano Dockerfile
Actualícelo a:
FROM python:3.9-slim
WORKDIR /app
## Install curl for health checks
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*
COPY app.py .
## Add a health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
CMD curl -f http://localhost:8080/health || exit 1
## Expose port for the health check endpoint
EXPOSE 8080
CMD ["python", "app.py"]
Ahora necesitamos agregar un punto final de comprobación de estado a nuestra aplicación:
nano app.py
Reemplace el contenido con:
import os
import time
import sys
import threading
from http.server import HTTPServer, BaseHTTPRequestHandler
## Get environment variables with defaults
database_url = os.environ.get('DATABASE_URL', 'sqlite:///default.db')
## Simple HTTP server for health checks
class HealthRequestHandler(BaseHTTPRequestHandler):
def do_GET(self):
if self.path == '/health':
self.send_response(200)
self.send_header('Content-type', 'text/plain')
self.end_headers()
self.wfile.write(b'OK')
else:
self.send_response(404)
self.end_headers()
def run_health_server():
server = HTTPServer(('0.0.0.0', 8080), HealthRequestHandler)
print("Starting health check server on port 8080")
server.serve_forever()
## Start health check server in a separate thread
health_thread = threading.Thread(target=run_health_server, daemon=True)
health_thread.start()
print(f"Connecting to database: {database_url}")
## Main application loop
try:
print("Application running... Press Ctrl+C to exit")
counter = 0
while True:
counter += 1
print(f"Application heartbeat: {counter}")
time.sleep(10)
except KeyboardInterrupt:
print("Application shutting down gracefully...")
sys.exit(0)
Guarde y salga del archivo.
Reconstruya la imagen con nuestra comprobación de estado:
docker build -t exit-test-app:v3 .
Ejecute el contenedor con la nueva versión:
docker run -d --name healthcheck-app -p 8080:8080 exit-test-app:v3
Después de unos 30 segundos, compruebe el estado de salud:
docker inspect --format='{{.State.Health.Status}}' healthcheck-app
Debería ver:
healthy
También puede probar el punto final de estado directamente:
curl http://localhost:8080/health
Esto debería devolver OK.
3. Uso de Políticas de Reinicio de Docker
Docker proporciona políticas de reinicio para reiniciar automáticamente los contenedores cuando salen o encuentran errores:
docker run -d --restart=on-failure:5 --name restart-app exit-test-app:v3
Esta política reiniciará el contenedor hasta 5 veces si sale con un código distinto de cero.
Políticas de reinicio disponibles:
no: Nunca reiniciar (predeterminado)always: Siempre reiniciar independientemente del estado de salidaunless-stopped: Siempre reiniciar a menos que se detenga manualmenteon-failure[:max-retries]: Reiniciar solo en la salida distinta de cero
4. Establecimiento de Límites de Recursos
Para evitar que los contenedores se bloqueen debido al agotamiento de los recursos, establezca los límites de recursos apropiados:
docker run -d --name resource-limited-app \
--memory=256m \
--cpus=0.5 \
exit-test-app:v3
Esto limita el contenedor a 256 MB de memoria y la mitad de un núcleo de CPU.
5. Configuración de Registro Adecuada
Para una mejor depuración, configure su aplicación para que genere registros estructurados:
docker run -d --name logging-app \
--log-driver=json-file \
--log-opt max-size=10m \
--log-opt max-file=3 \
exit-test-app:v3
Esto configura el contenedor para usar el controlador de registro JSON con rotación de registros (máximo 3 archivos de 10 MB cada uno).
Resumen de las Mejores Prácticas
Al implementar estas mejores prácticas, ha mejorado significativamente la fiabilidad de sus contenedores Docker:
- Manejo de errores correcto con valores predeterminados
- Comprobaciones de estado del contenedor para el monitoreo
- Políticas de reinicio apropiadas para la recuperación automática
- Límites de recursos para evitar el agotamiento de recursos
- Configuración de registro adecuada para facilitar la solución de problemas
Estas técnicas le ayudarán a crear contenedores resilientes que puedan recuperarse de fallos y proporcionar una mejor observabilidad cuando ocurren problemas.
Resumen
En este laboratorio, ha aprendido habilidades esenciales para solucionar problemas y resolver problemas de salida de contenedores Docker:
- Comprender el ciclo de vida del contenedor Docker y por qué los contenedores salen cuando su proceso principal se completa
- Identificar las causas comunes de las salidas inmediatas de contenedores a través de registros y códigos de salida
- Implementar un manejo de errores robusto en aplicaciones en contenedores
- Agregar comprobaciones de estado del contenedor para monitorear el estado de la aplicación
- Configurar políticas de reinicio para la recuperación automática de fallos
- Establecer límites de recursos apropiados para evitar bloqueos de contenedores
- Implementar estrategias de registro adecuadas para facilitar la depuración
Estas habilidades forman la base de las implementaciones fiables de contenedores Docker. A medida que continúe trabajando con Docker en escenarios del mundo real, encontrará que estas técnicas de solución de problemas son invaluables para mantener aplicaciones en contenedores estables y resilientes.
Recuerde que las causas más comunes de las salidas inmediatas de contenedores son:
- El proceso principal que completa su tarea (por diseño)
- Variables de entorno o configuración faltantes
- Errores o excepciones de la aplicación
- Restricciones de recursos o problemas de conectividad
Al aplicar las técnicas de diagnóstico y las mejores prácticas cubiertas en este laboratorio, puede identificar y resolver rápidamente estos problemas, asegurando que sus contenedores Docker se ejecuten de manera fiable en cualquier entorno.



