Dockerfile.1
executa vários RUN
:
FROM busybox
RUN echo This is the A > a
RUN echo This is the B > b
RUN echo This is the C > c
Dockerfile.2
junta-se a eles:
FROM busybox
RUN echo This is the A > a &&\
echo This is the B > b &&\
echo This is the C > c
Cada RUN
um cria uma camada, então eu sempre assumi que menos camadas são melhores e, portanto, Dockerfile.2
são melhores.
Isso é obviamente verdade quando um RUN
remove algo adicionado por um anterior RUN
(ie yum install nano && yum clean all
), mas nos casos em que todos RUN
adiciona algo, há alguns pontos que precisamos considerar:
As camadas devem adicionar apenas um diff acima do anterior, portanto, se a camada posterior não remover algo adicionado na anterior, não haverá muito espaço em disco economizando vantagem entre os dois métodos ...
As camadas são puxadas em paralelo no Docker Hub, portanto
Dockerfile.1
, embora provavelmente sejam um pouco maiores, teoricamente seriam baixadas mais rapidamente.Se adicionar uma quarta frase (ie
echo This is the D > d
) e reconstruir localmente,Dockerfile.1
criaria mais rápido graças ao cache, masDockerfile.2
teria que executar todos os 4 comandos novamente.
Então, a pergunta: qual é a melhor maneira de criar um Dockerfile?
fonte
Respostas:
Sempre que possível, sempre mesclo comandos que criam arquivos com comandos que excluem esses mesmos arquivos em uma única
RUN
linha. Isso ocorre porque cadaRUN
linha adiciona uma camada à imagem, a saída é literalmente as alterações no sistema de arquivos que você pode visualizardocker diff
no contêiner temporário criado. Se você excluir um arquivo criado em uma camada diferente, tudo o que o sistema de arquivos da união fará é registrar a alteração do sistema de arquivos em uma nova camada, o arquivo ainda existe na camada anterior e é enviado pela rede e armazenado no disco. Portanto, se você baixar o código-fonte, extraí-lo, compilá-lo em um binário e excluir os arquivos tgz e de origem no final, você realmente deseja que tudo seja feito em uma única camada para reduzir o tamanho da imagem.Em seguida, eu pessoalmente divido as camadas com base em seu potencial de reutilização em outras imagens e no uso esperado de armazenamento em cache. Se eu tiver 4 imagens, todas com a mesma imagem base (por exemplo, debian), posso puxar uma coleção de utilitários comuns para a maioria dessas imagens no primeiro comando de execução, para que as outras imagens se beneficiem do cache.
A ordem no Dockerfile é importante ao analisar a reutilização do cache de imagem. Examino todos os componentes que serão atualizados muito raramente, possivelmente apenas quando a imagem base é atualizada e os coloca no alto do Dockerfile. No final do Dockerfile, incluo todos os comandos que serão executados rapidamente e podem mudar com frequência, por exemplo, adicionando um usuário com um UID específico do host ou criando pastas e alterando permissões. Se o contêiner incluir código interpretado (por exemplo, JavaScript) que está sendo desenvolvido ativamente, isso será adicionado o mais tarde possível, para que uma reconstrução execute apenas essa única alteração.
Em cada um desses grupos de alterações, consolido o melhor possível para minimizar as camadas. Portanto, se houver quatro pastas de código-fonte diferentes, elas serão colocadas em uma única pasta para que possam ser adicionadas com um único comando. Qualquer instalação de pacote de algo como o apt-get é mesclada em um único RUN, quando possível, para minimizar a quantidade de sobrecarga do gerenciador de pacotes (atualização e limpeza).
Atualização para compilações de vários estágios:
Preocupo-me muito menos em reduzir o tamanho da imagem nos estágios não finais de uma compilação de vários estágios. Quando esses estágios não são marcados e enviados para outros nós, você pode maximizar a probabilidade de uma reutilização de cache dividindo cada comando em uma
RUN
linha separada .No entanto, essa não é uma solução perfeita para esmagar camadas, pois tudo o que você copia entre os estágios são os arquivos e não o restante dos metadados da imagem, como configurações de variáveis de ambiente, ponto de entrada e comando. E quando você instala pacotes em uma distribuição linux, as bibliotecas e outras dependências podem estar espalhadas pelo sistema de arquivos, dificultando a cópia de todas as dependências.
Por esse motivo, utilizo compilações de vários estágios como substituto para a construção de binários em um servidor de CI / CD, para que meu servidor de CI / CD precise apenas ter as ferramentas para executar
docker build
e não ter um jdk, nodejs, go e quaisquer outras ferramentas de compilação instaladas.fonte
Resposta oficial listada em suas melhores práticas (as imagens oficiais DEVEM aderir a elas)
Desde a janela de encaixe 1.10
COPY
, as instruçõesADD
eRUN
adicionam uma nova camada à sua imagem. Seja cauteloso ao usar essas instruções. Tente combinar comandos em uma únicaRUN
instrução. Separe isso apenas se for necessário para facilitar a leitura.Mais informações: https://docs.docker.com/engine/userguide/eng-image/dockerfile_best-practices/#/minimize-the-number-of-layers
Atualização: Estágio múltiplo na janela de encaixe> 17,05
Com compilações de vários estágios, você pode usar várias
FROM
instruções no seu Dockerfile. CadaFROM
declaração é um estágio e pode ter sua própria imagem de base. No estágio final, você usa uma imagem base mínima como alpine, copia os artefatos de construção dos estágios anteriores e instala os requisitos de tempo de execução. O resultado final desta fase é a sua imagem. Portanto, é aqui que você se preocupa com as camadas, conforme descrito anteriormente.Como de costume, o docker possui ótimos documentos sobre compilações de vários estágios. Aqui está um trecho rápido:
Um ótimo post sobre isso pode ser encontrado aqui: https://blog.alexellis.io/mutli-stage-docker-builds/
Para responder seus pontos:
Sim, as camadas são como diferenças. Eu não acho que há camadas adicionadas se houver absolutamente zero alterações. O problema é que, depois de instalar / baixar algo na camada 2, você não pode removê-lo na camada 3. Assim, quando algo é escrito em uma camada, o tamanho da imagem não pode mais ser diminuído removendo-o.
Embora as camadas possam ser puxadas em paralelo, tornando-a potencialmente mais rápida, sem dúvida, cada camada aumenta o tamanho da imagem, mesmo que esteja removendo arquivos.
Sim, o cache é útil se você estiver atualizando seu arquivo docker. Mas funciona em uma direção. Se você tiver 10 camadas e alterar a camada 6, ainda precisará reconstruir tudo da camada 6 a 10. Portanto, não é muito frequente que acelere o processo de criação, mas é garantido que aumenta desnecessariamente o tamanho da sua imagem.
Obrigado a @Mohan por me lembrar de atualizar esta resposta.
fonte
Parece que as respostas acima estão desatualizadas. Os documentos observam:
https://docs.docker.com/engine/userguide/eng-image/dockerfile_best-practices/#minimize-the-number-of-layers
e
https://docs.docker.com/engine/userguide/eng-image/multistage-build/
A melhor prática parece ter mudado para o uso de compilações de vários estágios e para manter a
Dockerfile
legibilidade.fonte
docker image build --squash
opção for fora do experimental.squash
experimental. Ele tem muitos truques e só fazia sentido antes da construção em vários estágios. Com compilações de vários estágios, você só precisa otimizar o estágio final, o que é muito fácil.Depende do que você incluir nas camadas da imagem.
O ponto principal é compartilhar o maior número possível de camadas:
Mau exemplo:
Dockerfile.1
Dockerfile.2
Bom exemplo:
Dockerfile.1
Dockerfile.2
Outra sugestão é excluir não é tão útil apenas se ocorrer na mesma camada que a ação de adição / instalação.
fonte
RUN yum install big-package
cache from?