Funcionamiento de las imágenes de Docker

Recuerde que la imagen de contenedor es la unidad que usamos para distribuir las aplicaciones. También hemos mencionado que un contenedor es un formato normalizado que usan nuestros equipos de desarrollo y operaciones.

Aquí veremos las diferencias entre el software, los paquetes y las imágenes que se usan en Docker. Conocer las diferencias entre estos conceptos nos ayudará a comprender mejor el funcionamiento de las imágenes de Docker.

También analizaremos brevemente los roles del sistema operativo que se ejecuta en el host y del sistema operativo que se ejecuta en el contenedor.

Software empaquetado en un contenedor

El software que se empaqueta en un contenedor no está limitado a las aplicaciones que crean nuestros desarrolladores. Cuando hablamos del software, nos referimos al código de la aplicación, los paquetes de sistema, los archivos binarios, las bibliotecas, los archivos de configuración y el sistema operativo que se ejecutan en el contenedor.

Por ejemplo, supongamos que estamos desarrollando un portal de seguimiento de pedidos que usarán las tiendas de su empresa. Tenemos que examinar la pila completa de software que ejecutará la aplicación web. La aplicación que estamos creando es una aplicación MVC de .NET Core y planeamos implementarla con Nginx como servidor proxy inverso en Ubuntu Linux. Todos estos componentes de software forman parte de la imagen de contenedor.

¿Qué es una imagen de contenedor?

Una imagen de contenedor es un paquete portátil que contiene software. Cuando se ejecuta esta imagen, se convierte en nuestro contenedor. El contenedor es la instancia en memoria de una imagen.

Las imágenes de contenedor son inmutables. Una vez que se crea una imagen, no se puede cambiar. La única forma de cambiar una imagen es crear una nueva. Esta característica es nuestra garantía de que la imagen que usamos en el equipo de producción es la misma que se usa en los equipos de desarrollo y de control de calidad.

¿Qué es el sistema operativo del host?

El sistema operativo del host es el sistema operativo en el que se ejecuta el motor de Docker. Los contenedores de Docker que se ejecutan en Linux comparten el kernel del sistema operativo del host y no requieren un sistema operativo de contenedor, siempre que el archivo binario pueda acceder directamente al kernel del sistema operativo.

Diagrama en la que se muestra una imagen de Docker sin sistema operativo base y la dependencia en el kernel del sistema operativo del host.

Pero los contenedores de Windows necesitan un sistema operativo de contenedor. El contenedor depende del kernel del sistema operativo para administrar servicios como el sistema de archivos, la administración de redes, la programación de procesos y la administración de memoria.

¿Qué es el sistema operativo del contenedor?

El sistema operativo del contenedor es el sistema operativo que forma parte de la imagen empaquetada. Tenemos flexibilidad para incluir diferentes versiones de los sistemas operativos de Linux o Windows en un contenedor. Esta flexibilidad nos permite tener acceso a determinadas características del sistema operativo o instalar software adicional que pueden usar nuestras aplicaciones.

Diagrama en la que se muestra una imagen de Docker con un sistema operativo base de Ubuntu y la dependencia en el kernel del sistema operativo del host.

El sistema operativo del contenedor está aislado del sistema operativo del host y es el entorno en el que se implementa y ejecuta la aplicación. Si se combina con la inmutabilidad de la imagen, este aislamiento implica que el entorno de la aplicación que se ejecuta en el departamento de desarrollo es el mismo que el del departamento de producción.

En nuestro ejemplo, usamos Ubuntu Linux como sistema operativo del contenedor y este sistema operativo no cambia en el proceso de desarrollo ni en el de producción. La imagen que usamos es siempre la misma.

¿Qué es el sistema de archivos de unificación apilable (Unionfs)?

Unionfs se usa para crear imágenes de Docker. Unionfs es un sistema de archivos que permite apilar varios directorios, denominados ramas, de tal forma que parece que el contenido está combinado, pero el contenido está separado físicamente. Unionfs permite agregar y quitar ramas a medida que se crea el sistema de archivos.

Diagrama que muestra el apilamiento de capas en una imagen de Docker creada con uniones.

Por ejemplo, supongamos que vamos a compilar una imagen para nuestra aplicación web de antes. Montamos la distribución de Ubuntu como una imagen base encima del sistema de archivos de arranque. Después, instalamos Nginx y nuestra aplicación web. Montamos de forma eficaz Nginx y la aplicación web encima de la imagen original de Ubuntu.

Cuando se ejecuta el contenedor desde la imagen, se crea una capa final grabable, pero esta capa no se conserva cuando se destruye el contenedor.

¿Qué es una imagen base?

Una imagen base es una imagen que usa la imagen de Docker scratch. La imagen scratch es una imagen de contenedor vacía que no crea una capa de sistema de archivos. Esta imagen asume que la aplicación que se va a ejecutar puede usar directamente el kernel del sistema operativo del host.

¿Qué es una imagen primaria?

Una imagen primaria es una imagen de contenedor a partir de la cual se crean las imágenes.

Por ejemplo, en lugar de crear una imagen a partir de scratch y, después, instalar Ubuntu, vamos a usar una imagen ya basada en Ubuntu. Incluso podemos usar una imagen que ya tenga Nginx instalado. Una imagen primaria normalmente incluye un sistema operativo de contenedor.

¿Cuál es la diferencia principal entre las imágenes base y las imágenes primarias?

Ambos tipos de imagen nos permiten crear una imagen reutilizable, pero las imágenes base nos proporcionan más control sobre el contenido de la imagen final. Recuerde que las imágenes son inmutables, solo puede agregar contenido a una imagen, no sustraerlo.

¿Qué es un Dockerfile?

Un Dockerfile es un archivo de texto que contiene las instrucciones que se usan para compilar y ejecutar una imagen de Docker. Define los siguientes aspectos de la imagen:

  • La imagen base o primaria que usamos para crear la nueva imagen.
  • Los comandos para actualizar el sistema operativo base e instalar software adicional.
  • Los artefactos de compilación que se incluirán, como una aplicación desarrollada.
  • Los servicios que se expondrán, como la configuración de red y del almacenamiento.
  • El comando que se ejecutará cuando se inicie el contenedor.

Vamos a ver estos aspectos en un ejemplo de Dockerfile. Supongamos que vamos a crear una imagen de Docker para nuestro sitio web de ASP.NET Core. El Dockerfile podría tener un aspecto similar al ejemplo siguiente.

# Step 1: Specify the parent image for the new image
FROM ubuntu:18.04

# Step 2: Update OS packages and install additional software
RUN apt -y update &&  apt install -y wget nginx software-properties-common apt-transport-https \
    && wget -q https://packages.microsoft.com/config/ubuntu/18.04/packages-microsoft-prod.deb -O packages-microsoft-prod.deb \
    && dpkg -i packages-microsoft-prod.deb \
    && add-apt-repository universe \
    && apt -y update \
    && apt install -y dotnet-sdk-3.0

# Step 3: Configure Nginx environment
CMD service nginx start

# Step 4: Configure Nginx environment
COPY ./default /etc/nginx/sites-available/default

# STEP 5: Configure work directory
WORKDIR /app

# STEP 6: Copy website code to container
COPY ./website/. .

# STEP 7: Configure network requirements
EXPOSE 80:8080

# STEP 8: Define the entry point of the process that runs in the container
ENTRYPOINT ["dotnet", "website.dll"]

No vamos a cubrir aquí la especificación del archivo Dockerfile o la información detallada de cada comando del ejemplo anterior, pero tenga en cuenta que hay varios comandos en este archivo que nos permiten manipular la estructura de la imagen.

Antes hemos mencionado que las imágenes de Docker usan unionfs. En cada uno de estos pasos se crea una imagen de contenedor en caché a medida que compilamos la imagen de contenedor final. Estas imágenes temporales se superponen encima de la anterior y se presentan como una sola imagen una vez completados todos los pasos.

Finalmente, observe el último paso, el paso 8. El ENTRYPOINT del archivo indica qué proceso se ejecutará una vez que se ejecute un contenedor a partir de una imagen.

Cómo administrar imágenes de Docker

Las imágenes de Docker son archivos de gran tamaño que se almacenan inicialmente en nuestro equipo, y necesitamos herramientas para administrar estos archivos.

La CLI de Docker nos permite administrar imágenes mediante su compilación, enumeración, eliminación y ejecución. Administramos las imágenes de Docker con el cliente docker. El cliente no ejecuta los comandos directamente, sino que envía todas las consultas al demonio dockerd.

Aquí no vamos a analizar todos los comandos del cliente ni todas las marcas de comando, pero veremos algunos de los comandos más usados. La sección más información de este módulo incluye vínculos a la documentación de Docker, la cual abarca todos los comandos y las marcas de comando en detalle.

Cómo compilar una imagen

Usamos el comando docker build para compilar imágenes de Docker. Supongamos que usamos la definición de Dockerfile anterior para compilar una imagen. El siguiente ejemplo muestra el comando de compilación.

docker build -t temp-ubuntu .

A continuación, se muestra la salida generada por el comando de compilación:

Sending build context to Docker daemon  4.69MB
Step 1/8 : FROM ubuntu:18.04
 ---> a2a15febcdf3
Step 2/8 : RUN apt -y update && apt install -y wget nginx software-properties-common apt-transport-https && wget -q https://packages.microsoft.com/config/ubuntu/18.04/packages-microsoft-prod.deb -O packages-microsoft-prod.deb && dpkg -i packages-microsoft-prod.deb && add-apt-repository universe && apt -y update && apt install -y dotnet-sdk-3.0
 ---> Using cache
 ---> feb452bac55a
Step 3/8 : CMD service nginx start
 ---> Using cache
 ---> ce3fd40bd13c
Step 4/8 : COPY ./default /etc/nginx/sites-available/default
 ---> 97ff0c042b03
Step 5/8 : WORKDIR /app
 ---> Running in 883f8dc5dcce
Removing intermediate container 883f8dc5dcce
 ---> 6e36758d40b1
Step 6/8 : COPY ./website/. .
 ---> bfe84cc406a4
Step 7/8 : EXPOSE 80:8080
 ---> Running in b611a87425f2
Removing intermediate container b611a87425f2
 ---> 209b54a9567f
Step 8/8 : ENTRYPOINT ["dotnet", "website.dll"]
 ---> Running in ea2efbc6c375
Removing intermediate container ea2efbc6c375
 ---> f982892ea056
Successfully built f982892ea056
Successfully tagged temp-ubuntu:latest

No se preocupe si no entiende la salida anterior. Pero fíjese en los pasos enumerados en la salida. Cuando se ejecuta cada paso, se agrega una nueva capa a la imagen que estamos compilando.

Además, tenga en cuenta que ejecutamos una serie de comandos para instalar software y administrar la configuración. Por ejemplo, en el paso 2, se ejecutan los comandos apt -y update y apt install -y para actualizar el sistema operativo. Estos comandos se ejecutan en un contenedor en ejecución que se ha creado para ese paso. Una vez ejecutado el comando, se elimina el contenedor intermedio. La imagen subyacente almacenada en caché se conserva en el host de compilación y no se elimina de forma automática. Esta optimización garantiza que las compilaciones posteriores vuelvan a usar estas imágenes para acelerar los tiempos de compilación.

¿Qué es una etiqueta de imagen?

Una etiqueta de imagen es una cadena de texto que se usa para generar versiones de una imagen.

En la compilación de ejemplo anterior, observe el último mensaje de compilación que dice "Successfully tagged temp-ubuntu: latest". A la hora de compilar una imagen, asignamos un nombre a la imagen y, opcionalmente, la etiquetamos mediante la marca de comando -t. En nuestro ejemplo, hemos asignado un nombre a la imagen con -t temp-ubuntu, mientras que el nombre de la imagen resultante contiene la etiqueta temp-ubuntu: latest. Si no especifica una etiqueta para una imagen, se le asigna la etiqueta latest.

Una sola imagen puede tener varias etiquetas asignadas. Por convención, a la versión más reciente de una imagen se le asigna la etiqueta latest y otra etiqueta que describe el número de versión de la imagen. Cuando publica una nueva versión de una imagen, puede volver a asignar la etiqueta "latest" para hacer referencia a la nueva imagen.

Aquí tiene otro ejemplo. Imagine que quiere usar las imágenes de Docker de ejemplo de .NET Core. Aquí tenemos cuatro versiones de plataformas entre las que podemos elegir:

  • mcr.microsoft.com/dotnet/core/samples:dotnetapp

  • mcr.microsoft.com/dotnet/core/samples:aspnetapp

  • mcr.microsoft.com/dotnet/core/samples:wcfservice

  • mcr.microsoft.com/dotnet/core/samples:wcfclient

Cómo enumerar imágenes

El software de Docker configura automáticamente un registro local de imágenes en su equipo. Puede ver las imágenes de este registro con el comando docker images.

docker images

La salida es similar a la del ejemplo siguiente.

REPOSITORY          TAG                     IMAGE ID            CREATED                     SIZE
tmp-ubuntu          latest             f89469694960        14 minutes ago         1.69GB
tmp-ubuntu          version-1.0        f89469694960        14 minutes ago         1.69GB
ubuntu              18.04                   a2a15febcdf3        5 weeks ago            64.2MB

Observe cómo se muestra la imagen con su nombre, su etiqueta y su identificador de imagen. Recuerde que se pueden aplicar varias etiquetas a una imagen. Aquí tiene un ejemplo. Aunque los nombres de las imágenes son diferentes, podemos ver que los identificadores son los mismos.

El identificador de la imagen proporciona una manera útil de identificar y administrar las imágenes cuando el nombre o la etiqueta son ambiguos.

Cómo eliminar una imagen

Puede eliminar una imagen del registro de Docker local con el comando docker rmi. Especifique el nombre o el identificador de la imagen que se va a eliminar. En este ejemplo, se elimina la imagen de la aplicación web de ejemplo mediante el uso del nombre de la imagen:

docker rmi temp-ubuntu:version-1.0

No se puede eliminar una imagen si todavía la está usando un contenedor. El comando docker rmi devuelve un mensaje de error que muestra el contenedor basado en la imagen.

Hemos examinado los aspectos básicos de las imágenes de Docker, cómo administrar estas imágenes y cómo ejecutar un contenedor desde una imagen. A continuación, veremos cómo administrar los contenedores.

Comprobación de conocimientos

1.

Docker Desktop es una aplicación para compilar y compartir microservicios y aplicaciones en contenedores. ¿En cuáles de los siguientes sistemas operativos está disponible?

2.

¿Cuál es el comando de Docker correcto para recompilar una imagen de contenedor?

3.

¿Cuál de las siguientes frases describe mejor una imagen de contenedor?