Optimizar Dockerfiles de Windows

Existen varias maneras de optimizar el proceso de compilación de Docker y las imágenes de Docker resultantes. En este artículo se explica cómo funciona el proceso de compilación de Docker y cómo crear imágenes óptimamente para contenedores de Windows.

Capas de imagen en la compilación de Docker

Antes de poder optimizar la compilación de Docker, debe saber cómo funciona la compilación de Docker. Durante el proceso de compilación de Docker se usa un Dockerfile y cada instrucción accionable se ejecuta, de una en una, en su propio contenedor temporal. El resultado es una nueva capa de imagen para cada instrucción accionable.

Por ejemplo, en el siguiente ejemplo de Dockerfile se usa la imagen de sistema operativo base mcr.microsoft.com/windows/servercore:ltsc2019, se instala IIS y, a continuación, se crea un sitio web sencillo.

# Sample Dockerfile

FROM mcr.microsoft.com/windows/servercore:ltsc2019
RUN dism /online /enable-feature /all /featurename:iis-webserver /NoRestart
RUN echo "Hello World - Dockerfile" > c:\inetpub\wwwroot\index.html
CMD [ "cmd" ]

Podría esperar que este Dockerfile genere una imagen con dos capas, una para la imagen del sistema operativo del contenedor y otra que incluye IIS y el sitio web. Sin embargo, la imagen real tiene muchas capas y cada capa depende de la anterior.

Para que esto quede más claro, vamos a ejecutar el comando docker history en la imagen que se ha creado en el ejemplo de Dockerfile.

docker history iis

IMAGE               CREATED              CREATED BY                                      SIZE                COMMENT
f4caf476e909        16 seconds ago       cmd /S /C REM (nop) CMD ["cmd"]                 41.84 kB
f0e017e5b088        21 seconds ago       cmd /S /C echo "Hello World - Dockerfile" > c   6.816 MB
88438e174b7c        About a minute ago   cmd /S /C dism /online /enable-feature /all /   162.7 MB
6801d964fda5        4 months ago                                                         0 B

La salida muestra que esta imagen tiene cuatro capas: la capa base y tres capas adicionales que se asignan a cada instrucción de Dockerfile. La capa inferior (6801d964fda5 en este ejemplo) representa la imagen base del sistema operativo. Una capa más arriba está la instalación de IIS. La siguiente capa incluye el nuevo sitio web y así sucesivamente.

Se pueden escribir Dockerfiles para minimizar las capas de imagen, optimizar el rendimiento de la compilación y optimizar la accesibilidad a través de la legibilidad. En última instancia, hay muchas formas de realizar la misma tarea de compilación de la imagen. La comprensión de cómo afecta el formato de Dockerfile al tiempo de compilación y a la imagen que crea mejora la experiencia de automatización.

Optimización del tamaño de la imagen

En función de los requisitos de espacio, el tamaño de la imagen puede ser un factor importante a la hora de crear imágenes de contenedor de Docker. Las imágenes de contenedor se mueven entre registros y host, se exportan e importan y, en última instancia, usan espacio. En esta sección se explica cómo minimizar el tamaño de la imagen durante el proceso de compilación de Docker para contenedores de Windows.

Para obtener información adicional sobre los procedimientos recomendados de Dockerfile, consulta Procedimientos recomendados para escribir Dockerfiles.

Dado que cada instrucción RUN crea una nueva capa en la imagen del contenedor, la agrupación de acciones en una instrucción RUN puede reducir el número de capas en una instancia de Dockerfile. Aunque la minimización de capas puede no afectar mucho al tamaño de la imagen, la agrupación de acciones relacionadas sí, lo que se verá en los ejemplos siguientes.

En esta sección, se compararán dos ejemplos de Dockerfile que hacen lo mismo. Sin embargo, una instancia de Dockerfile contiene una instrucción por acción, mientras que en el otro se han agrupado sus acciones relacionadas.

En el siguiente ejemplo de Dockerfile desagrupado se descarga Python para Windows, se instala y se quita el archivo de instalación descargado una vez finalizada. En este Dockerfile, cada acción recibe su propia instrucción RUN.

FROM mcr.microsoft.com/windows/servercore:ltsc2019

RUN powershell.exe -Command Invoke-WebRequest "https://www.python.org/ftp/python/3.5.1/python-3.5.1.exe" -OutFile c:\python-3.5.1.exe
RUN powershell.exe -Command Start-Process c:\python-3.5.1.exe -ArgumentList '/quiet InstallAllUsers=1 PrependPath=1' -Wait
RUN powershell.exe -Command Remove-Item c:\python-3.5.1.exe -Force

La imagen resultante se compone de tres capas adicionales, una para cada instrucción RUN.

docker history doc-example-1

IMAGE               CREATED             CREATED BY                                      SIZE                COMMENT
a395ca26777f        15 seconds ago      cmd /S /C powershell.exe -Command Remove-Item   24.56 MB
6c137f466d28        28 seconds ago      cmd /S /C powershell.exe -Command Start-Proce   178.6 MB
957147160e8d        3 minutes ago       cmd /S /C powershell.exe -Command Invoke-WebR   125.7 MB

El segundo ejemplo es una instancia de Dockerfile que realiza la misma operación exacta. Sin embargo, todas las acciones relacionadas se han agrupado en una sola instrucción RUN. Cada paso de la instrucción RUN está en una nueva línea de Dockerfile y se usa el carácter "\" para el ajuste de línea.

FROM mcr.microsoft.com/windows/servercore:ltsc2019

RUN powershell.exe -Command \
  $ErrorActionPreference = 'Stop'; \
  Invoke-WebRequest https://www.python.org/ftp/python/3.5.1/python-3.5.1.exe -OutFile c:\python-3.5.1.exe ; \
  Start-Process c:\python-3.5.1.exe -ArgumentList '/quiet InstallAllUsers=1 PrependPath=1' -Wait ; \
  Remove-Item c:\python-3.5.1.exe -Force

La imagen resultante solo tiene una capa adicional para la instrucción RUN.

docker history doc-example-2

IMAGE               CREATED             CREATED BY                                      SIZE                COMMENT
69e44f37c748        54 seconds ago      cmd /S /C powershell.exe -Command   $ErrorAct   216.3 MB

Quitar archivos sobrantes

Si hay un archivo en Dockerfile, como un instalador, que no necesitas después de usarlo, puedes quitarlo para reducir el tamaño de la imagen. Esto debe hacerse en el mismo paso en el que se copia el archivo en la capa de imagen. Esto evita que el archivo se conserve en una capa de la imagen de nivel inferior.

En el siguiente ejemplo de Dockerfile, se descarga, ejecuta y se quita el paquete de Python. Todo se realiza en una operación RUN y da lugar a una única capa de imagen.

FROM mcr.microsoft.com/windows/servercore:ltsc2019

RUN powershell.exe -Command \
  $ErrorActionPreference = 'Stop'; \
  Invoke-WebRequest https://www.python.org/ftp/python/3.5.1/python-3.5.1.exe -OutFile c:\python-3.5.1.exe ; \
  Start-Process c:\python-3.5.1.exe -ArgumentList '/quiet InstallAllUsers=1 PrependPath=1' -Wait ; \
  Remove-Item c:\python-3.5.1.exe -Force

Optimización de la velocidad de compilación

Varias líneas

Puedes dividir las operaciones en varias instrucciones individuales para optimizar la velocidad de compilación de Docker. Varias operaciones RUN aumentan la eficacia del almacenamiento en caché porque se crean capas individuales para cada instrucción RUN. Si ya se ha ejecutado una instrucción idéntica en una operación de compilación de Docker diferente, se reutiliza esta operación almacenada en caché (capa de imagen), lo que disminuye el tiempo de ejecución de compilación de Docker.

En el ejemplo siguiente, se descargan los paquetes de redistribución de Apache y Visual Studio, se instalan y luego se limpian quitando los archivos que ya no se necesitan. Todo se hace con una única instrucción RUN. Si alguna de estas acciones se actualiza, se vuelven a ejecutar todas.

FROM mcr.microsoft.com/windows/servercore:ltsc2019

RUN powershell -Command \

  # Download software ; \

  wget https://www.apachelounge.com/download/VC11/binaries/httpd-2.4.18-win32-VC11.zip -OutFile c:\apache.zip ; \
  wget "https://download.microsoft.com/download/1/6/B/16B06F60-3B20-4FF2-B699-5E9B7962F9AE/VSU_4/vcredist_x86.exe" -OutFile c:\vcredist.exe ; \
  wget -Uri http://windows.php.net/downloads/releases/php-5.5.33-Win32-VC11-x86.zip -OutFile c:\php.zip ; \

  # Install Software ; \

  Expand-Archive -Path c:\php.zip -DestinationPath c:\php ; \
  Expand-Archive -Path c:\apache.zip -DestinationPath c:\ ; \
  Start-Process c:\vcredist.exe -ArgumentList '/quiet' -Wait ; \

  # Remove unneeded files ; \

  Remove-Item c:\apache.zip -Force; \
  Remove-Item c:\vcredist.exe -Force; \
  Remove-Item c:\php.zip

La imagen resultante consta de dos capas, una para la imagen base del sistema operativo y otra que contiene todas las operaciones de la instrucción única RUN.

docker history doc-sample-1

IMAGE               CREATED             CREATED BY                                      SIZE                COMMENT
9bdf3a21fd41        8 minutes ago       cmd /S /C powershell -Command     Invoke-WebR   205.8 MB
6801d964fda5        5 months ago                                                        0 B

Para comparar, aquí están las mismas acciones divididas en tres instrucciones RUN. En este caso, cada instrucción RUN se almacena en caché en una capa de imagen de contenedor y solo es necesario volver a ejecutar aquellas que han cambiado en posteriores compilaciones del Dockerfile.

FROM mcr.microsoft.com/windows/servercore:ltsc2019

RUN powershell -Command \
    $ErrorActionPreference = 'Stop'; \
    wget https://www.apachelounge.com/download/VC11/binaries/httpd-2.4.18-win32-VC11.zip -OutFile c:\apache.zip ; \
    Expand-Archive -Path c:\apache.zip -DestinationPath c:\ ; \
    Remove-Item c:\apache.zip -Force

RUN powershell -Command \
    $ErrorActionPreference = 'Stop'; \
    wget "https://download.microsoft.com/download/1/6/B/16B06F60-3B20-4FF2-B699-5E9B7962F9AE/VSU_4/vcredist_x86.exe" -OutFile c:\vcredist.exe ; \
    Start-Process c:\vcredist.exe -ArgumentList '/quiet' -Wait ; \
    Remove-Item c:\vcredist.exe -Force

RUN powershell -Command \
    $ErrorActionPreference = 'Stop'; \
    wget http://windows.php.net/downloads/releases/php-5.5.33-Win32-VC11-x86.zip -OutFile c:\php.zip ; \
    Expand-Archive -Path c:\php.zip -DestinationPath c:\php ; \
    Remove-Item c:\php.zip -Force

La imagen resultante se compone de cuatro capas: una capa para la imagen base del sistema operativo y tres para cada una de las instrucciones RUN. Dado que cada instrucción RUN se ejecutó en su propia capa, todas las ejecuciones posteriores de este Dockerfile o de un conjunto idéntico de instrucciones de un Dockerfile diferente usarán la capa de imagen almacenada en caché, lo que disminuirá el tiempo de compilación.

docker history doc-sample-2

IMAGE               CREATED             CREATED BY                                      SIZE                COMMENT
ddf43b1f3751        6 days ago          cmd /S /C powershell -Command  Sleep 2 ;  Inv   127.2 MB
d43abb81204a        7 days ago          cmd /S /C powershell -Command  Sleep 2 ;  Inv   66.46 MB
7a21073861a1        7 days ago          cmd /S /C powershell -Command  Sleep 2 ;  Inv   115.8 MB
6801d964fda5        5 months ago

La forma de ordenar las instrucciones es importante cuando se trabaja con cachés de imágenes, como se verá en la sección siguiente.

Clasificación de instrucciones

Un Dockerfile se procesa de arriba a abajo y cada instrucción se compara con las capas almacenadas en caché. Cuando se encuentra una instrucción sin una capa en caché, esta instrucción y todas las siguientes se procesan en nuevas capas de la imagen del contenedor. Por este motivo, es importante el orden en que se colocan las instrucciones. Coloque las instrucciones que se van a mantener constantes hacia la parte superior del Dockerfile. Coloque las que pueden cambiar hacia la parte inferior del Dockerfile. Al hacerlo, se reduce la probabilidad de que se niegue la caché existente.

En los siguientes ejemplos se muestra cómo puede afectar la clasificación de las instrucciones de Dockerfile a la eficacia del almacenamiento en caché. Este sencillo ejemplo de Dockerfile tiene cuatro carpetas numeradas.

FROM mcr.microsoft.com/windows/servercore:ltsc2019

RUN mkdir test-1
RUN mkdir test-2
RUN mkdir test-3
RUN mkdir test-4

La imagen resultante tiene cinco capas, una para la imagen base del sistema operativo y una para cada instrucción RUN.

docker history doc-sample-1

IMAGE               CREATED              CREATED BY               SIZE                COMMENT
afba1a3def0a        38 seconds ago       cmd /S /C mkdir test-4   42.46 MB
86f1fe772d5c        49 seconds ago       cmd /S /C mkdir test-3   42.35 MB
68fda53ce682        About a minute ago   cmd /S /C mkdir test-2   6.745 MB
5e5aa8ba1bc2        About a minute ago   cmd /S /C mkdir test-1   7.12 MB
6801d964fda5        5 months ago                                  0 B

El Dockerfile siguiente se ha modificado ligeramente, ya que se ha cambiado la tercera instrucción RUN a un archivo nuevo. Cuando se ejecuta la compilación de Docker en este Dockerfile, las tres primeras instrucciones, que son idénticas a las del último ejemplo, usan las capas de la imagen almacenadas en caché. Sin embargo, dado que la instrucción RUN modificada no se ha almacenado en caché, se crea una nueva capa para ella y todas las instrucciones posteriores.

FROM mcr.microsoft.com/windows/servercore:ltsc2019

RUN mkdir test-1
RUN mkdir test-2
RUN mkdir test-5
RUN mkdir test-4

Cuando se comparan los id. de imagen de la nueva imagen con los del primer ejemplo de esta sección, observarás que las tres primeras capas de abajo a arriba están compartidas, pero la cuarta y la quinta son únicas.

docker history doc-sample-2

IMAGE               CREATED             CREATED BY               SIZE                COMMENT
c92cc95632fb        28 seconds ago      cmd /S /C mkdir test-4   5.644 MB
2f05e6f5c523        37 seconds ago      cmd /S /C mkdir test-5   5.01 MB
68fda53ce682        3 minutes ago       cmd /S /C mkdir test-2   6.745 MB
5e5aa8ba1bc2        4 minutes ago       cmd /S /C mkdir test-1   7.12 MB
6801d964fda5        5 months ago                                 0 B

Optimización cosmética

Mayúsculas y minúsculas de las instrucciones

La instrucciones de Dockerfile no distinguen mayúsculas de minúsculas, pero la convención es usar mayúsculas. Esto mejora la legibilidad al diferenciar entre la llamada y la operación de la instrucción. En los dos ejemplos siguientes se compara un Dockerfile con mayúsculas y sin mayúsculas.

A continuación, se muestra un Dockerfile sin mayúsculas:

# Sample Dockerfile

from mcr.microsoft.com/windows/servercore:ltsc2019
run dism /online /enable-feature /all /featurename:iis-webserver /NoRestart
run echo "Hello World - Dockerfile" > c:\inetpub\wwwroot\index.html
cmd [ "cmd" ]

El siguiente es el mismo Dockerfile usando mayúsculas:

# Sample Dockerfile

FROM mcr.microsoft.com/windows/servercore:ltsc2019
RUN dism /online /enable-feature /all /featurename:iis-webserver /NoRestart
RUN echo "Hello World - Dockerfile" > c:\inetpub\wwwroot\index.html
CMD [ "cmd" ]

Ajuste de línea

Las operaciones largas y complejas pueden dividirse en varias líneas por el carácter de barra diagonal inversa \. El Dockerfile siguiente instala el paquete redistribuible de Visual Studio, quita los archivos del instalador y luego crea un archivo de configuración. Estas tres operaciones se especifican en una sola línea.

FROM mcr.microsoft.com/windows/servercore:ltsc2019

RUN powershell -Command c:\vcredist_x86.exe /quiet ; Remove-Item c:\vcredist_x86.exe -Force ; New-Item c:\config.ini

El comando se puede dividir con barras inclinadas inversas para que cada operación de la instrucción RUN se especifique en su propia línea.

FROM mcr.microsoft.com/windows/servercore:ltsc2019

RUN powershell -Command \
    $ErrorActionPreference = 'Stop'; \
    Start-Process c:\vcredist_x86.exe -ArgumentList '/quiet' -Wait ; \
    Remove-Item c:\vcredist_x86.exe -Force ; \
    New-Item c:\config.ini

Lecturas y referencias adicionales

Dockerfile en Windows

Best practices for writing Dockerfiles (Procedimientos recomendados para escribir Dockerfiles) en Docker.com