Cómo Gestionar el Apagado y Reinicio Seguro de Contenedores Docker con Systemd

Cómo Gestionar el Apagado y Reinicio Seguro de Contenedores Docker con Systemd

Apagado y reinicio seguro de contenedores Docker con scripts y systemd para evitar errores y garantizar consistencia.

1. Introducción

¿Alguna vez has apagado tu servidor y descubierto que los contenedores Docker no se detuvieron correctamente? ¿O que algunos servicios críticos no se reiniciaron en el orden adecuado? En entornos productivos, un apagado desordenado puede causar corrupción de datos, tiempos de inactividad y dolores de cabeza innecesarios.

En este artículo, te mostraré una solución robusta y automatizada para garantizar que tus contenedores se detengan e inicien de manera controlada durante reinicios o apagados del sistema. Usaremos scripts personalizados y servicios systemd para:

  • Detener contenedores en orden inverso (evitando dependencias rotas).
  • Verificar el estado antes y después del reinicio.
  • Integrarse con Cockpit, SSH o reinicios automáticos.
  • sar alias para ejecutar más rápido los scripts.

Si buscas una forma confiable de manejar la vida útil de tus contenedores sin intervención manual, ¡sigue leyendo!

Estructura de Archivos

Crear estos archivos:

/usr/local/bin/
├── docker-shutdown-sequence.sh
├── docker-startup-sequence.sh
└── check-containers.sh
/etc/systemd/system/
├── docker-shutdown.service
└── docker-startup.service

2. Pre-requisitos: Qué Necesitas Antes de Empezar

Antes de implementar esta solución, asegúrate de cumplir con estos requisitos técnicos:

  • Docker instalado y en ejecución: Verifica con sudo docker ps. Si no lo tienes, lo puedes instalar en Debian/Ubuntu:

    sudo apt-get update && sudo apt-get install docker.io
    

    o Usar la versión Oficial de Docker, que es la que yo uso.

  • Systemd disponible: La mayoría de las distribuciones Linux modernas (Ubuntu, Debian, CentOS) lo incluyen por defecto. Verifica con:

    systemctl --version
    
  • Permisos de administrador: Necesitarás ejecutar comandos con sudo y tener acceso para crear/modificar archivos en:

    • /usr/local/bin/ (scripts).
    • /etc/systemd/system/ (servicios).
  • Contenedores con políticas de reinicio claras:

    Si usas --restart unless-stopped, asegúrate de que no entre en conflicto con tus scripts.

3. Scripts de Apagado/Reinicio: Lógica y Orden Crítico

Los scripts son el núcleo de esta solución. Aquí te explicamos su estructura clave:

3.1. Apagado (docker-shutdown-sequence.sh):

Detiene contenedores en orden inverso a sus dependencias (ej: primero portainer, al final central-db).

#!/bin/bash

# Lista de contenedores en orden inverso (último al primero)
CONTAINERS=(
    "portainer"
    "..."
    "central-db"
)

MAX_RETRIES=3
RETRY_DELAY=5

echo "🔴 Iniciando secuencia de apagado de contenedores..."

# Detener contenedores en orden inverso
for container in "${CONTAINERS[@]}"; do
    retry_count=0
    while [ $retry_count -lt $MAX_RETRIES ]; do
        echo "⏳ Deteniendo $container..."
        docker stop "$container" >/dev/null 2>&1

        if [ "$(docker inspect -f '{{.State.Running}}' "$container" 2>/dev/null)" != "true" ]; then
            echo "✅ $container detenido correctamente"
            break
        fi

        retry_count=$((retry_count+1))
        echo "⚠️  Reintentando ($retry_count/$MAX_RETRIES)..."
        sleep $RETRY_DELAY
    done

    if [ $retry_count -eq $MAX_RETRIES ]; then
        echo "❌ Error: No se pudo detener $container después de $MAX_RETRIES intentos"
        exit 1
    fi
done

# Verificación final usando check-containers.sh
echo "🔍 Verificación profesional del estado..."
if /usr/local/bin/check-containers.sh shutdown; then
    echo "🟢 Verificación confirmada: Todos los contenedores detenidos"
    exit 0
else
    echo "🔴 Error: Verificación fallida - Contenedores aún activos"
    exit 1
fi

Uso para detener todos los contenedores:

sudo /usr/local/bin/docker-shutdown-sequence.sh

3.2. Inicio (docker-startup-sequence.sh):

Inicia contenedores en orden de dependencias (ej: primero central-db, al final portainer).

#!/bin/bash

# Lista de contenedores en orden de inicio
CONTAINERS=(
    "central-db"
    "..."
    "portainer"
)

MAX_RETRIES=3
RETRY_DELAY=10

echo "🟢 Iniciando secuencia de arranque de contenedores..."

for container in "${CONTAINERS[@]}"; do
    # Verificar si el contenedor ya está corriendo antes de iniciarlo
    if [ "$(docker inspect -f '{{.State.Running}}' "$container" 2>/dev/null)" == "true" ]; then
        echo "⚠️  $container ya está en ejecución. Omitiendo..."
        continue
    fi

    retry_count=0
    while [ $retry_count -lt $MAX_RETRIES ]; do
        echo "⏳ Iniciando $container..."
        docker start "$container" >/dev/null 2>&1

        if [ "$(docker inspect -f '{{.State.Running}}' "$container" 2>/dev/null)" == "true" ]; then
            echo "✅ $container iniciado correctamente"

            # Esperar a que el contenedor esté saludable (si tiene healthcheck)
            if docker inspect --format '{{.State.Health.Status}}' "$container" 2>/dev/null | grep -q "healthy"; then
                echo "⏳ Esperando a que $container esté saludable..."
                while [ "$(docker inspect --format '{{.State.Health.Status}}' "$container" 2>/dev/null)" != "healthy" ]; do
                    sleep 5
                done
                echo "👍 $container está saludable"
            fi

            break
        fi

        retry_count=$((retry_count+1))
        echo "⚠️  Reintentando ($retry_count/$MAX_RETRIES)..."
        sleep $RETRY_DELAY
    done

    if [ $retry_count -eq $MAX_RETRIES ]; then
        echo "❌ Error: No se pudo iniciar $container después de $MAX_RETRIES intentos"
        exit 1
    fi
done

# Verificación final usando check-containers.sh
echo "🔍 Verificación profesional del estado..."
if /usr/local/bin/check-containers.sh startup; then
    echo "🟢 Verificación confirmada: Todos los contenedores activos"
    exit 0
else
    echo "🔴 Error: Verificación fallida - Contenedores no iniciados"
    exit 1
fi

Uso para iniciar los contenedores:

sudo /usr/local/bin/docker-startup-sequence.sh

3.3. Verificación del estado de los contenedores (check-containers.sh)

#!/bin/bash

# Verificación del estado de los contenedores Docker
# Uso:
#   check-containers.sh [startup|shutdown]

MODE="$1"
CONTAINERS=(
    "central-db"
    "..."
    "portainer"
)

# Colores para la salida
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color

check_running() {
    local all_running=true
    for container in "${CONTAINERS[@]}"; do
        if [ "$(docker inspect -f '{{.State.Running}}' "$container" 2>/dev/null)" == "true" ]; then
            echo -e "${GREEN}${NC} $container está en ejecución"
        else
            echo -e "${RED}${NC} $container NO está en ejecución"
            all_running=false
        fi
    done
    $all_running && return 0 || return 1
}

check_stopped() {
    local all_stopped=true
    for container in "${CONTAINERS[@]}"; do
        if [ "$(docker inspect -f '{{.State.Running}}' "$container" 2>/dev/null)" == "true" ]; then
            echo -e "${RED}${NC} $container sigue en ejecución"
            all_stopped=false
        else
            echo -e "${GREEN}${NC} $container está detenido"
        fi
    done
    $all_stopped && return 0 || return 1
}

case "$MODE" in
    startup)
        echo "Verificando estado de arranque de contenedores..."
        check_running
        exit $?
        ;;
    shutdown)
        echo "Verificando estado de apagado de contenedores..."
        check_stopped
        exit $?
        ;;
    *)
        echo "Uso: check-containers.sh [startup|shutdown]"
        exit 1
        ;;
esac

Uso para verificar estado:

sudo /usr/local/bin/check-containers.sh [startup|shutdown]

3.4. Comandos clave usados internamente:

docker stop [nombre] # Detiene un contenedor
docker start [nombre] # Inicia un contenedor
docker inspect -f '{{.State.Running}}' [nombre] # Verifica estado

4. Integración con Systemd: El “Motor” Automatizado

Para que los scripts se ejecuten automáticamente durante el apagado/reinicio (sin depender de comandos manuales), los vinculamos a systemd mediante:

4.2. Archivo de servicio para Inicio (docker-startup.service)

Ubicación: /etc/systemd/system/
Propósito:
Inicia contenedores Docker después de que el sistema y la red estén disponibles.

Relaciones clave:

  • Activación: Al alcanzar multi-user.target.
  • Dependencias:
    • After=docker.service network-online.target (espera Docker y red).
    • Wants=network-online.target (dependencia opcional de red).

Configuración:

  • Type=oneshot (ejecución única).
  • TimeoutStartSec=300 (5 minutos máximo de espera).

Instalación:

  • WantedBy=multi-user.target.

Código del servicio:

[Unit]
Description=Docker Container Startup Sequence
After=docker.service network-online.target
Wants=network-online.target

[Service]
Type=oneshot
ExecStart=/usr/local/bin/docker-startup-sequence.sh
TimeoutStartSec=300

[Install]
WantedBy=multi-user.target

4.1. Archivo de servicio para Apagado (docker-shutdown.service)

Ubicación: /etc/systemd/system/
Propósito:
Detiene contenedores Docker de manera segura antes de un apagado/reinicio del sistema.

Relaciones clave:

  • Activación: Ante shutdown.target, reboot.target, poweroff.target, halt.target.
  • Prioridad: Before=shutdown.target (se ejecuta antes del apagado).
  • Dependencias:
    • Requires=docker.service (exige Docker activo).
    • DefaultDependencies=no (evita conflictos en el apagado).

Configuración crítica:

  • Type=oneshot.
  • TimeoutStartSec=300 (5 minutos máximo).
  • RemainAfterExit=yes (permite verificar estado).

Instalación:

  • WantedBy=shutdown.target.

Código del servicio:

[Unit]
Description=Apagado seguro de contenedores Docker
DefaultDependencies=no
Before=shutdown.target reboot.target poweroff.target halt.target
Requires=docker.service
After=docker.service

[Service]
Type=oneshot
ExecStart=/usr/local/bin/docker-shutdown-sequence.sh
TimeoutStartSec=300
RemainAfterExit=yes

[Install]
WantedBy=shutdown.target

5. Verificación y Logs: ¿Cómo Saber Si Todo Funcionó?

Una vez configurados los scripts y servicios, es crucial verificar que el apagado/reinicio se ejecutó correctamente. Aquí te mostramos cómo hacerlo y resolver problemas comunes.

📌 Método 1: Verificación con journalctl

Los logs se almacenan en el journal de systemd. Tras reiniciar el servidor, ejecuta:

# Ver logs específicos del servicio de apagado
sudo journalctl -u docker-shutdown.service -b -1

# Ver logs del servicio de inicio (si aplica)
sudo journalctl -u docker-startup.service -b 0

Qué buscar:

  • ✅ [nombre-contenedor] detenido: Contenedores detenidos en orden.
  • 🟢 Todos los contenedores detenidos: Confirmación de éxito.
  • ❌ Error: Fallos críticos (ej: contenedor no se detuvo).

Ejemplo de salida exitosa:

oct 25 12:34:56 servidor docker-shutdown[1234]: ✅ portainer detenido
oct 25 12:34:57 servidor docker-shutdown[1234]: ✅ central-db detenido
oct 25 12:35:00 servidor docker-shutdown[1234]: 🟢 Verificación exitosa

🔧 Troubleshooting Básico

Si no ves logs o hay errores:

  1. ¿El servicio se ejecutó?

    sudo systemctl status docker-shutdown.service
    
    • Si está inactive, revisa permisos:

      sudo chmod +x /usr/local/bin/docker-shutdown-sequence.sh
      
  2. ¿Faltan dependencias? Asegúrate de que el servicio esté vinculado a los targets correctos:

    sudo systemctl list-dependencies shutdown.target | grep docker-shutdown
    
  3. Logs incompletos? Habilita logs persistentes:

    sudo mkdir -p /var/log/journal
    sudo systemctl restart systemd-journald
    

🛠️ Casos Comunes y Soluciones

Error Solución
-- No entries -- en logs Revisa que el servicio esté en WantedBy=shutdown.target.
Contenedor no se detiene Usa docker stop [nombre] manualmente y revisa dependencias.
Logs borrados tras reinicio Habilita Storage=persistent en /etc/systemd/journald.conf.

📂 Alternativa: Logs en Archivo

Para guardar logs en un archivo (útil para auditorías), añade al final de tus scripts:

exec > >(tee -a /var/log/docker-shutdown.log) 2>&1

Luego verifica con:

sudo cat /var/log/docker-shutdown.log

Con estos comandos y técnicas, tendrás visibilidad total sobre el proceso de apagado/reinicio. Si algo falla, los logs te darán pistas claras para resolverlo rápidamente.

6. ✨ Mejoras de Productividad: Aliases y Atajos:

Para ahorrar tiempo en el día a día, define estos aliases en tu sesión de Bash ~/.bash_aliases o ~/.bashrc:

# Iniciar contenedores en orden
alias dcstart='sudo /usr/local/bin/docker-startup-sequence.sh'

# Detener contenedores en orden inverso
alias dcstop='sudo /usr/local/bin/docker-shutdown-sequence.sh'

# Verificación rápida del estado
alias dccheck='sudo /usr/local/bin/check-containers.sh'

# Apagado seguro + detener contenedores
alias powoff='dcstop && sudo shutdown -h now'

# Reinicio seguro + detener contenedores
alias reboot='dcstop && sudo shutdown -r now'

# Listar contenedores con formato claro
dps='docker ps --format "table {{.ID}}\t{{.Names}}\t{{.Status}}\t{{.Ports}}"'

7. Preguntas Frecuentes

1. ¿Cómo asegurar que el script se ejecute SIEMPRE?

  • Opción A: Usar los aliases (powoff/reboot).
  • Opción B: Configurar el servicio con Before= y WantedBy= como arriba.

2. ¿Qué hacer si el servicio falla?

  • Verifica permisos:

    sudo chmod +x /usr/local/bin/docker-*.sh
    
  • Depuración manual:

    sudo /usr/local/bin/docker-shutdown-sequence.sh
    

Conclusión:

Esta solución garantiza que tus contenedores Docker se apaguen y reinicien en orden, evitando errores críticos sin esfuerzo manual. Con scripts + systemd, obtienes:

  1. Seguridad: Detiene contenedores en el orden correcto (evitando corrupción).
  2. Automatización: Funciona con reinicios desde SSH, Cockpit o programas.
  3. Visibilidad: Logs claros (journalctl) para diagnosticar problemas.

Invierte 5 minutos en implementarlo y olvídate de apagados desordenados. ¡Configúralo hoy y trabaja tranquilo mañana!

Fuentes:


Gracias

¡Gracias a todos! Sus aportes lo hacen posible. Si desea ayudar y apoyarnos a crear mejor contenido, puede hacer su donativo a través de paypal:

Algunos derechos reservados

Compartir en Redes sociales

A continuación

Cómo desactivar power saving en servidor Debian con WiFi

Solución al power saving en WiFi para Debian: configuración estable con systemd.

Relacionado


¿Músico?

Si desea conocer sobre mi actividad musical lo invito a que visite la página principal. Allí encontrará todo sobre mi música.