Otimizar Dockerfiles do Windows

Há muitas maneiras de otimizar o processo de build e as imagens resultantes do Docker. Este artigo explica como funciona o processo de build do Docker e como criar imagens de maneira ideal para contêineres do Windows.

Camadas de imagem no build do Docker

Para otimizar o build do Docker, você precisará saber como funciona o build do Docker. Durante o processo de build do Docker, um Dockerfile é consumido e cada instrução acionável é executada, uma de cada vez, em seu próprio contêiner temporário. O resultado é uma nova camada de imagem para cada instrução acionável.

Por exemplo, o Dockerfile de exemplo a seguir usa a imagem base do sistema operacional mcr.microsoft.com/windows/servercore:ltsc2019, instala o IIS e, em seguida, cria um site simples.

# 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" ]

Você pode esperar que esse Dockerfile produza uma imagem com duas camadas: uma para a imagem do sistema operacional do contêiner e outra que inclua o IIS e o site. No entanto, a imagem real tem muitas camadas, e cada camada depende da anterior.

Para tornar isso mais claro, vamos executar o comando docker history na imagem criadas pelo Dockerfile de exemplo.

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

O resultado nos mostra que essa imagem tem quatro camadas: a camada base e três camadas adicionais que são mapeadas para cada instrução no Dockerfile. A camada inferior (6801d964fda5 neste exemplo) representa a imagem do sistema operacional base. Uma camada acima é a instalação do IIS. A próxima camada inclui o novo site e assim por diante.

Os Dockerfiles podem ser escritos para minimizar as camadas de imagem, otimizar o desempenho de build e otimizar a acessibilidade por meio da legibilidade. Por fim, há várias maneiras de concluir a mesma tarefa de build de imagem. O entendimento de como o formato do Dockerfile afeta o tempo de build e a imagem criada por ele aprimora a experiência de automação.

Otimizar o tamanho da imagem

Dependendo dos seus requisitos de espaço, o tamanho da imagem pode ser um fator importante ao criar imagens de contêiner do Docker. As imagens de contêiner são movidas entre host e Registros, exportadas e importadas e, por fim, consomem espaço. Esta seção explicará como minimizar o tamanho da imagem durante o processo de build do Docker para contêineres do Windows.

Para obter mais informações sobre as melhores práticas do Dockerfile, confira Melhores práticas de composição de Dockerfiles em Docker.com.

Como cada instrução RUN criar uma camada na imagem de contêiner, o agrupamento de ações em uma só instrução RUN pode reduzir o número de camadas em um Dockerfile. Embora minimizar as camadas possa não afetar muito o tamanho da imagem, agrupar as ações relacionadas pode, o que será visto nos exemplos a seguir.

Nesta seção, vamos comparar dois Dockerfiles de exemplo que executam as mesmas ações. No entanto, um Dockerfile tem uma instrução por ação, enquanto o outro tinha as ações relacionadas agrupadas.

O Dockerfile de exemplo desagrupado a seguir baixa o Python para Windows, instala-o e remove o arquivo de instalação baixado após a conclusão da instalação. Nesse Dockerfile, cada ação recebe a própria instrução 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

A imagem resultante consiste em três camadas adicionais, uma para cada instrução 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

O segundo exemplo é um Dockerfile que executa exatamente a mesma operação. No entanto, todas as ações relacionadas foram agrupadas em uma só instrução RUN. Cada etapa na instrução RUN está em uma nova linha do Dockerfile, enquanto o caractere '\' é usado para a quebra automática de linha.

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

A imagem resultante tem apenas uma camada adicional para a instrução 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

Remover arquivos em excesso

Se houver um arquivo no Dockerfile, como um instalador, de que você não precisará depois que ele for usado, remova-o para reduzir o tamanho da imagem. Isso deve ocorrer na mesma etapa em que o arquivo foi copiado para a camada de imagem. Isso impede que o arquivo persista em uma camada da imagem de nível inferior.

No Dockerfile de exemplo a seguir, o pacote do Python é baixado, executado e, em seguida, removido. Isso tudo concluído em uma operação RUN e resulta em uma única camada de imagem.

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

Otimizar a velocidade de build

Várias linhas

Você pode dividir as operações em várias instruções individuais para otimizar a velocidade de build do Docker. Várias operações RUN aumentam a eficácia do cache, porque camadas individuais são criadas para cada instrução RUN. Se uma instrução idêntica já tiver sido executada em outra operação de build do Docker, essa operação armazenada em cache (camada de imagem) será reutilizada, resultando na redução do runtime de build do Docker.

No exemplo a seguir, os Pacotes Redistribuíveis do Apache e do Visual Studio são baixados, instalados e, em seguida, limpos com a remoção dos arquivos que não são mais necessários. Tudo isso é feito com uma só instrução RUN. Se uma dessas ações for atualizada, todas as ações serão executadas novamente.

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

A imagem resultante tem duas camadas: uma para a imagem base do sistema operacional e outra que contém todas as operações da única instrução 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

Por comparação, aqui estão as mesmas ações divididas em três instruções RUN. Nesse caso, cada instrução RUN é armazenada em cache em uma camada da imagem de contêiner, e somente aquelas que foram alteradas precisam ser executadas novamente em builds seguintes do 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

A imagem resultante consiste em quatro camadas: uma camada para a imagem base do sistema operacional e cada uma das três instruções RUN. Como cada instrução RUN foi executada na própria camada, as execuções seguintes desse Dockerfile ou um conjunto idêntico de instruções em outro Dockerfile usarão camadas de imagem armazenadas em cache, reduzindo o tempo de build.

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

A maneira de ordenar as instruções é importante ao trabalhar com caches de imagem, como você verá na próxima seção.

Como ordenar instruções

Um Dockerfile é processado de cima para baixo, cada instrução comparada com as camadas em cache. Quando for encontrada uma instrução sem uma camada de cache, essa instrução e todas as instruções subsequentes serão processadas em novas camadas de imagem de contêiner. Por isso, a ordem na qual as instruções são colocadas é importante. Coloque as instruções que permanecerão constantes em direção à parte superior do Dockerfile. Coloque as instruções que poderão mudar na direção da parte inferior do Dockerfile. Isso reduz a probabilidade de negar o cache existente.

Os exemplos a seguir mostram como a ordenação de instrução do Dockerfile pode afetar a eficácia do cache. Este Dockerfile de exemplo simples tem quatro pastas numeradas.

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

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

A imagem resultante tem cinco camadas: uma para a imagem base do sistema operacional e cada uma das instruções 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

Este próximo Dockerfile foi ligeiramente modificado, com a terceira instrução RUN alterada para um novo arquivo. Quando o build do Docker é executado em relação a esse Dockerfile, as três primeiras instruções, que são idênticas às do último exemplo, usam as camadas de imagem em cache. No entanto, como as instruções RUN alteradas não estão armazenadas em cache, uma camada é criada para a instrução alterada e todas as instruções seguintes.

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

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

Ao comparar as IDs de imagem da nova imagem com a que está no primeiro exemplo desta seção, você observará que as três primeiras camadas de baixo para cima são compartilhadas, mas a quarta e a quinta são exclusivas.

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

Otimização cosmética

Maiúsculas e minúsculas nas instruções

As instruções do Dockerfile não diferenciam maiúsculas de minúsculas, mas a convenção é usar letras maiúsculas. Isso aprimora a legibilidade diferenciando a chamada e a operação da instrução. Os dois exemplos a seguir comparam um Dockerfile em minúsculas e outro em maiúsculas.

Este é um Dockerfile em minú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" ]

Este é o mesmo Dockerfile que usa letras maiú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" ]

Quebra automática de linha

Operações longas e complexas podem ser separadas em várias linhas usando o caractere de barra invertida \. O Dockerfile a seguir instala o pacote redistribuível do Visual Studio, remove os arquivos do instalador e cria um arquivo de configuração. Essas três operações são todas especificadas em uma linha.

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

O comando pode ser dividido com barras invertidas, de modo que cada operação de uma instrução RUN seja especificada na própria linha.

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

Referências e leituras adicionais

Dockerfile no Windows

Best practices for writing Dockerfiles (Práticas recomendadas para escrever Dockerfiles) em Docker.com