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 el contenedor está en un formato estandarizado que usan los equipos de desarrollo y operación.
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 para que usen los distintos puntos de venta de nuestra 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 una instancia en memoria de una imagen.
Las imágenes de contenedor son inmutables. Una vez que haya creado una imagen, no podrá cambiarla. 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.
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 de 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 adicionales que pueden usar nuestras aplicaciones.
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
)?
Usamos Unionfs
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.
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, Sin embargo, 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, Sin embargo, 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.
En Windows, solo puede crear imágenes de contenedor basadas en imágenes de contenedor base de Windows. Microsoft proporciona y ofrece estas imágenes de contenedor base de Windows.
¿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 van a exponer, 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. Sin embargo, tenga en cuenta que hay varios comandos en este archivo que nos permiten manipular la estructura de la imagen. Por ejemplo, el comando COPY
copia el contenido de una carpeta específica de la máquina local en la imagen de contenedor que estamos compilando.
Recordemos que 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 imagen 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. Si no hay ningún PUNTO DE ENTRADA u otro proceso para ejecutar, Docker interpretará que no hay nada que hacer en el contenedor y, por tanto, lo cerrará.
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 Y Docker Desktop nos permiten 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 esta unidad Resumende este módulo incluye vínculos a la documentación de Docker, que cubre todos los comandos y marcas de comandos 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 .
Esta es la salida que genera 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. Cuando se ejecuta 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.
Para Windows, Microsoft no proporciona imágenes de contenedor base con la etiqueta más reciente. En el caso de las imágenes de contenedor base de Windows, tiene que especificar una etiqueta que quiera usar. Por ejemplo, la imagen de contenedor base de Windows para Server Core es mcr.microsoft.com/windows/servercore
. Entre sus etiquetas se encuentran ltsc2016
, ltsc2019
y ltsc2022
.
Este es 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
En la lista de imágenes anteriores, podemos ver que Microsoft proporciona varios ejemplos de .NET Core. Las etiquetas especifican a qué muestras hace referencia la imagen: ASP.NET, servicio WCF, etc.
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 se parece al 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. La salida anterior muestra un ejemplo; aunque los nombres de imagen 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
. Esto resulta útil si necesita ahorrar espacio en el disco host del contenedor, ya que las capas de imagen de contenedor se agregan al espacio total disponible.
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 quitar una imagen si un contenedor sigue usando la imagen. 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.