Otimizar Dockerfiles do Windows

É possível usar vários métodos para otimizar o processo de build do Docker e as imagens do Docker resultantes. Este documento detalha como o processo de build do Docker funciona e demonstra várias táticas que podem ser usadas para a criação da imagem ideal com contêineres do Windows.

Build do Docker

Camadas da imagem

Antes de examinar a otimização do build do Docker, é importante entender como o build do Docker funciona. 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.

Examine o Dockerfile a seguir. Neste exemplo, a imagem do sistema operacional base windowsservercore está sendo usada, o IIS instalado e um site simples criado.

# Sample Dockerfile

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

Desse Dockerfile, é possível que a imagem resultante consista em duas camadas, uma para a imagem do sistema operacional do contêiner e uma segundo que inclui o IIS e o site, no entanto, esse não é o caso. A nova imagem é composta por muitas camadas, cada uma dependente anterior. Para visualizar isso, o comando docker history pode ser executado em relação à nova imagem. Fazer isso mostra que a imagem consiste em quatro camadas, a base e três camadas adicionais, um para cada instrução no 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

Cada uma dessas camadas pode ser mapeada para uma instrução do Dockerfile. A camada inferior (6801d964fda5 neste exemplo) representa a imagem do sistema operacional base. Uma camada acima, é possível ver 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 também otimizar pontos cosméticos, como a legibilidade. Por fim, há várias maneiras de concluir a mesma tarefa de build de imagem. Entender como o formato de um Dockerfile afeta o tempo de build e a imagem resultante melhora a experiência de automação.

Otimizar o tamanho da imagem

Ao criar imagens de contêiner do Docker, o tamanho da imagem pode ser um fator importante. As imagens de contêiner são movidas entre host e Registros, exportadas e importadas e, por fim, consomem espaço. É possível usar várias táticas durante o processo de build do Docker para minimizar o tamanho da imagem. Esta seção detalha algumas dessas táticas específicas para contêineres do Windows.

Para obter informações adicionais sobre as práticas recomendadas do Dockerfile, consulte Best practices for writing Dockerfiles (Práticas recomendadas para escrever Dockerfiles) em Docker.com.

Agrupar ações relacionadas

Pelo fato de cada instrução RUN criar uma nova camada na imagem do contêiner, agrupar ações em uma instrução RUN pode reduzir o número de camadas. 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.

Os dois exemplos a seguir demonstram a mesma operação, que resulta em imagens de contêiner de capacidade idêntica, no entanto, os dois Dockerfiles são construídos de forma diferente. As imagens resultantes também são comparadas.

Este primeiro exemplo baixa o Python para Windows, o instala e faz uma limpeza removendo o arquivo de instalação baixado. Cada uma dessas ações é executada em sua própria instrução RUN.

FROM windowsservercore

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

Para comparar, aqui está a mesma operação, no entanto, todas as etapas são executadas com a mesma instrução RUN. Observe que cada etapa na instrução RUN está em uma nova linha do Dockerfile, o caractere '\' está sendo utilizado para a quebra automática de linha.

FROM windowsservercore

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 aqui consiste em 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 um arquivo, como um instalador, não for necessário após ele ter sido usado, remova o arquivo 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.

Neste exemplo, o pacote do Python é baixado, executado, e, em seguida, o executável é removido. Isso tudo concluído em uma operação RUN e resulta em uma única camada de imagem.

FROM windowsservercore

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

Ao otimizar para a velocidade de build do Docker, pode ser vantajoso separar as operações em várias instruções individuais. Ter várias operações RUN aumenta a eficiência do cache. Como camadas individuais são criadas para cada instrução RUN, se uma etapa idêntica já tiver sido executada em uma operação de build do Docker diferente, essa operação em cache (camada de imagem) será utilizada novamente. O resultado é que o tempo de execução de build do Docker é reduzido.

No exemplo a seguir, os pacotes de redistribuição do Apache e Visual Studio são baixados, instalados e os arquivos desnecessários são apagados. Isso é feito com uma instrução RUN. Se qualquer uma dessas ações for atualizada, todas as ações serão executadas novamente.

FROM windowsservercore

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:\vcredistexe -ArgumentList '/quiet' -Wait ; \

  # Remove unneeded files ; \

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

A imagem resultante consiste em duas camadas, um para a imagem do sistema operacional base e a segunda 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

Para contraste, 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 de imagem do contêiner e somente aquelas que foram alteradas precisam ser executadas novamente em builds do Dockerfile subsequentes.

FROM windowsservercore

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, um para a imagem do sistema operacional base e uma para cada instrução RUN. Pelo fato de cada instrução RUN ter sido executada em sua própria camada, todas as execuções subsequentes desse Dockerfile ou conjunto idêntico de instruções em um Dockerfile diferente usarão a camada de imagem em cache, reduzindo assim o tempo de build. A ordenação de instruções é importante ao trabalhar com o cache de imagem, para obter mais detalhes, consulte a próxima seção deste documento.

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

Ordenação de 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.

A intenção deste exemplo é demonstrar como a ordenação das instruções de Dockerfile pode afetar a eficiência do cache. Neste Dockerfile simples, são criadas quatro pastas numeradas.

FROM windowsservercore

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

A imagem resultante tem cinco camadas, um para a imagem do sistema operacional base e uma para 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    

O Dockerfile foi ligeiramente modificado. Observe que a terceira instrução RUN foi alterada. 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, pelo fato de as instruções RUN alteradas não terem sido armazenadas em cache, uma nova camada é criada para si mesma e todas as instruções subsequentes.

FROM windowsservercore

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

Comparando as IDs de imagem da nova imagem com a do último exemplo, você verá que as três primeiras camadas (de baixo para cima) são compartilhadas, no entanto, a quarta e a quinta são ú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

Otimização cosmética

Maiúsculas e minúsculas da instrução

As instruções do Dockerfile não diferenciam maiúsculas de minúsculas, mas a convenção é usar letras maiúsculas. Isso melhora a legibilidade diferenciando a chamada de instrução e a operação da instrução. Os dois exemplos a seguir demonstram esse conceito.

Letras minúsculas:

# Sample Dockerfile

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

Letras maiúsculas:

# Sample Dockerfile

FROM windowsservercore
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 windowsservercore

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

O comando pode ser reescrito para que cada operação de uma instrução RUN seja especificada em sua própria linha.

FROM windowsservercore

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