Creación de Imágenes Docker a partir de Contenedores en Ejecución

Este artículo detalla un método avanzado para capturar el estado completo de un contenedor Docker en ejecución y transformarlo en una nueva imagen Docker. Esta técnica es invaluable para la migración de entornos, la creación de copias de seguridad consistentes y la replicación rápida de configuraciones complejas.

¿Por Qué Clonar un Contenedor?

El proceso de "clonación" se refiere a empaquetar un contenedor Docker activo en una nueva imagen. Esto permite:

  • Migración Sencilla: Trasladar un entorno de desarrollo o producción completamente configurado a otra máquina con facilidad.
  • Consistencia de Entornos: Asegurar que los entornos de desarrollo, pruebas y producción sean idénticos.
  • Despliegue Rápido: Restaurar un entorno completo en minutos en una nueva infraestructura.
  • Compartir Configuraciones: Distribuir configuraciones complejas a miembros del equipo.

Sin esta técnica, la replicación de un entorno implicaría documentar y reinstalar manualmente cada paquete y configuración, un proceso propenso a errores y que consume mucho tiempo.

Mecanismo de Funcionamiento

Una imagen Docker es una colección de capas que contienen el sistema operativo base, las aplicaciones instaladas, las dependencias, las variables de entorno y las instrucciones de ejecución.

Comparativa de Métodos de Captura de Estado

Docker ofrece varias formas de guardar el estado de un contenedor:

  • docker commit: Crea una nueva imagen a partir de las diferencias del sistema de archivos del contenedor. Genera imágenes con múltiples capas, lo que puede resultar en un tamaño considerable.
  • docker export: Exporta el sistema de archivos de un contenedor como un archivo tar de una sola capa. Es rápido y genera archivos pequeños, pero pierde el historial de capas y metadatos de la imagen.
  • Método Propuesto (tar + docker import): Este enfoque combina la creación de un archivo tar del sistema de archivos del contenedor con la opción de compresión avanzada (xz) y la exclusión de directorios innecesarios, seguido de la importación a una nueva imagen. Ofrece un equilibrio entre tamaño, control y limpieza.

La elección del método tar + import se debe a:

  • Control de Compresión: El uso de la compresión xz (-J) reduce drásticamente el tamaño del archivo resultante.
  • Exclusión Flexible: Permite omitir directorios que no forman parte de la configuración esencial del entorno (ej. logs, archivos temporales).
  • Estructura de Imagen Limpia: Genera una imagen de una sola capa, simplificando su gestión.

Pasos Detallados para la Clonación

Paso 0: Preparación - Script de Verificación

Antes de proceder, es crucial tener un script para validar que el entorno clonado funciona correctamente. A continuación, se presenta un ejemplo para verificar la configuración de PyTorch con múltiples GPUs:


import torch
import torch.nn as nn
import torch.multiprocessing as mp
import torch.distributed as dist
from torch.nn.parallel import DistributedDataParallel as DDP
import os

def run_gpu_test(rank, world_size):
    """Función ejecutada por cada proceso para pruebas de DDP."""
    print(f"Proceso {rank}/{world_size} iniciado.")

    # 1. Inicializar el grupo de procesos
    os.environ['MASTER_ADDR'] = 'localhost'
    os.environ['MASTER_PORT'] = '12355'
    dist.init_process_group("nccl", rank=rank, world_size=world_size)

    # 2. Asignar la GPU actual
    torch.cuda.set_device(rank)

    # 3. Crear un modelo simple
    model = nn.Sequential(
        nn.Linear(10, 20),
        nn.ReLU(),
        nn.Linear(20, 10)
    ).cuda()

    # 4. Envolver el modelo con DDP
    ddp_model = DDP(model, device_ids=[rank])

    # 5. Prueba de inferencia
    test_input = torch.randn(5, 10).cuda()
    output = ddp_model(test_input)

    # 6. Prueba de backpropagation
    loss = output.sum()
    loss.backward()

    print(f"GPU {rank}: Prueba de inferencia/backprop superada! Forma de salida: {output.shape}")

    # 7. Prueba de comunicación entre GPUs
    tensor = torch.tensor([float(rank)], device=f'cuda:{rank}')
    dist.all_reduce(tensor, op=dist.ReduceOp.SUM)
    print(f"GPU {rank}: Prueba de comunicación superada! Suma de ranks: {tensor.item()}")

    # 8. Limpieza
    dist.destroy_process_group()

if __name__ == "__main__":
    print("Versión de PyTorch:", torch.__version__)
    print("CUDA disponible:", torch.cuda.is_available())

    if torch.cuda.is_available():
        num_gpus = torch.cuda.device_count()
        print(f"Se detectaron {num_gpus} GPUs:")

        for i in range(num_gpus):
            print(f"  GPU {i}: {torch.cuda.get_device_name(i)}")

        if num_gpus > 1:
            print("\nIniciando pruebas DDP multicanal...")
            mp.spawn(
                run_gpu_test,    # Función a ejecutar por proceso
                args=(num_gpus,), # Argumentos para la función
                nprocs=num_gpus, # Número de procesos
                join=True        # Esperar a que todos los procesos terminen
            )
            print("\n✅ ¡Todas las pruebas de GPU superadas!")
        else:
            print("\n⚠️  Solo hay una GPU, omitiendo pruebas DDP.")
            print("Prueba de canal único:")
            model_single = nn.Linear(10, 10).cuda()
            test_input_single = torch.randn(5, 10).cuda()
            output_single = model_single(test_input_single)
            print(f"Forma de salida: {output_single.shape}")
            print("✅ ¡Prueba de canal único superada!")
    else:
        print("❌ CUDA no está disponible, no se pueden realizar pruebas de GPU.")

Paso 1: Creación del Contenedor Original

Inicie un contenedor que sirva como su "espacio de trabajo". Se recomienda el uso de imágenes base con soporte para GPU, como las de NVIDIA PyTorch:


docker run --gpus all --shm-size=32g -ti -e NVIDIA_VISIBLE_DEVICES=all \
        --privileged --net=host -v $PWD:/home \
        -w /home --rm \
        nvcr.io/nvidia/pytorch:23.07-py3 /bin/bash

  • --gpus all: Habilita el acceso a las GPUs (requiere nvidia-docker).
  • --shm-size=32g: Asigna memoria compartida para entrenamiento distribuido.
  • --privileged: Otorga privilegios elevados al contenedor (usar con precaución en producción).
  • -v $PWD:/home: Monta el directorio actual del host dentro del contenedor.
  • -w /home: Establece el directorio de trabajo dentro del contenedor.

Paso 2: Verificación del Contenedor Original

Dentro del contenedor, ejecute el script de verificación para asegurar que el entorno está correctamente configurado:


CUDA_VISIBLE_DEVICES=0,1,2,3 python mlp_ddp.py

Paso 3: Captura del Sistema de Archivos del Contenedor

Este es el paso central. Cree un archivo tar comprimido de todo el sistema de archivos del contenedor, excluyendo directorios innecesarios:


cd /
tar --exclude=home --exclude=proc --exclude=sys --exclude=base_image.tar.xz -Jcvf /home/base_image.tar.xz .

  • --exclude=...: Omite directorios volátiles o no esenciales.
  • -J: Utiliza compresión xz para una mayor eficiencia.
  • .: Empaqueta el directorio actual (la raíz del sistema de archivos del contenedor).

Paso 4: Salida del Contenedor e Importación como Imagen

Una vez creado el archivo tar, salga del contenedor. Luego, importe el archivo tar como una nueva imagen Docker:


# Salir del contenedor (se eliminará automáticamente)
exit

# Descomprimir y importar la imagen
unxz -c /ruta/a/base_image.tar.xz | docker import - mi_imagen_clonada

  • unxz -c ...: Descomprime el archivo tar y lo envía a la salida estándar.
  • |: Redirige la salida a docker import.
  • -: Indica a docker import que lea desde la entrada estándar.
  • mi_imagen_clonada: Nombre de la nueva imagen.

Paso 5: Verificación de la Imagen Clonada

Cree un nuevo contenedor a partir de la imagen importada y ejecute nuevamente el script de verificación:


# Iniciar un nuevo contenedor desde la imagen clonada
docker run --gpus all --shm-size=32g -ti -e NVIDIA_VISIBLE_DEVICES=all \
        --privileged --net=host -v $PWD:/home \
        -w /home --rm mi_imagen_clonada /bin/bash

# Dentro del nuevo contenedor, ejecutar la prueba
CUDA_VISIBLE_DEVICES=0,1,2,3 python mlp_ddp.py

Casos de Uso

Este método es ideal para:

  • Migración de Entornos: Mover configuraciones complejas entre nubes o de la nube a local.
  • Backups de Estado: Guardar instantáneas de configuraciones de trabajo críticas.
  • Duplicación Rápida: Preparar entornos idénticos para nuevos miembros del equipo o proyectos.
  • Recuperación ante Fallos: Restaurar un entorno operativo si la imagen base original se vuelve inaccesible.

Con esta técnica, puede empaquetar y desplegar entornos Docker complejos de manera eficiente y confiable.

Etiquetas: Docker imagen contenedor tar import

Publicado el 6-29 08:35