Técnicas de redução de imagem do Docker

Você já se perguntou por que o tamanho de um contêiner do Docker contendo apenas um aplicativo pode ter cerca de 400 MB? Ou talvez você estivesse preocupado com o tamanho bastante grande da imagem do Docker contendo um único binário de várias dezenas de MB de tamanho?



O autor do artigo, cuja tradução estamos publicando hoje, deseja analisar os principais fatores que afetam o tamanho dos contêineres do Docker. Além disso, ele compartilhará recomendações sobre a redução do tamanho dos contêineres.

Camadas de imagem do Docker


Uma imagem de um contêiner do Docker é essencialmente uma coleção de arquivos empilhados uns sobre os outros em várias camadas. Um contêiner de trabalho é montado a partir desses arquivos. O Docker usa o sistema de arquivos UnionFS , no qual os arquivos são agrupados em camadas. Uma camada pode conter um arquivo ou vários arquivos, camadas que se sobrepõem. Durante a execução do contêiner, o conteúdo das camadas é combinado, como resultado, o usuário final do contêiner percebe os materiais "dispostos" em camadas como um único sistema de arquivos.


Visualização simplificada do UnionFS

O sistema de arquivos resultante é apresentado ao usuário final usando algumas implementações do UnionFS (o Docker suporta muitas implementações semelhantes por meio de drivers de armazenamento de plug-in). O tamanho total dos arquivos recebidos pelo usuário final é igual à soma dos tamanhos dos arquivos nas camadas. Quando o Docker cria um contêiner com base na imagem, ele usa todas as camadas somente leitura da imagem, adicionando uma camada fina sobre essas camadas que suporta leitura e gravação. É essa camada que permite modificar arquivos em um contêiner em execução.


O contêiner em execução contém uma camada de leitura e gravação localizada na parte superior das camadas de somente leitura

O que acontece se um arquivo for excluído na Layer 4 contêiner apresentado esquematicamente acima? Embora esse arquivo não esteja disponível no sistema de arquivos que o usuário vê, o tamanho desse arquivo ainda será um dos componentes do tamanho do contêiner, pois esse arquivo permanecerá em uma das camadas somente leitura.

É muito simples começar a criar a imagem com um pequeno arquivo executável de aplicativo e obter uma imagem muito grande. Abaixo, veremos vários métodos para tornar os contêineres o menor possível.

Preste atenção ao caminho para a pasta, com base nos materiais em que as imagens são coletadas


Qual é a maneira mais comum de montar imagens do Docker? Aparentemente - assim:

 docker build . 

O ponto neste comando diz ao Docker que consideramos o diretório de trabalho atual como a raiz do sistema de arquivos usado no processo de montagem da imagem.

Para entender melhor o que acontece depois que o comando acima é executado, vale lembrar que a criação de uma imagem do Docker é um processo cliente-servidor. A interface da linha de comandos do Docker (cliente), à ​​qual fornecemos o docker build , usa o mecanismo do Docker (servidor) para criar a imagem do contêiner. Para limitar o acesso ao sistema de arquivos base do cliente, o sistema de montagem de imagens precisa saber onde está localizada a raiz do sistema de arquivos virtual. É aí que as instruções do arquivo Dockerfile pesquisam recursos de arquivo que eventualmente podem acabar na imagem que está sendo montada.

Imagine um lugar onde um Dockerfile geralmente Dockerfile colocado. Este é provavelmente o diretório raiz do projeto? Se houver um Dockerfile na raiz do projeto, usado pelo docker build para criar a imagem, acontece que todos os arquivos do projeto podem entrar na imagem. Isso pode levar ao fato de que milhares de arquivos indesejados, com muitos megabytes de tamanho, podem entrar no contexto do conjunto de imagens. Se você usar levemente os comandos ADD e COPY no Dockerfile , todos os arquivos do projeto poderão se tornar parte da imagem final. Na maioria das vezes, aqueles que coletam imagens não precisam disso, pois a imagem final geralmente deve incluir apenas alguns arquivos selecionados.

Sempre verifique se o comando docker build caminho correto e se não há comandos no Dockerfile que adicionem arquivos desnecessários à imagem. Se, por algum motivo, você precisar tornar o projeto raiz no contexto de construção, poderá incluir seletivamente arquivos nele e excluí-los usando .dockerignore .

Otimizar camadas de imagem


O número máximo de camadas que uma imagem pode ter é 127 (dado o suporte para esse número de camadas usado pelo driver do armazém de dados). Essa limitação, se absolutamente necessária, pode ser relaxada, mas com essa abordagem, a gama de sistemas nos quais essas imagens podem ser coletadas é reduzida. O ponto é que o mecanismo do Docker deve ser executado em um sistema cujo kernel é modificado de acordo.

Como mencionado na seção anterior, devido ao fato de o UnionFS ser usado na montagem de imagens, os arquivos que caem em uma determinada camada permanecem lá, mesmo que tenham sido excluídos das camadas subjacentes. Vamos descobrir isso usando o Dockerfile experimental:

 FROM alpine RUN wget http://xcal1.vodafone.co.uk/10MB.zip -P /tmp RUN rm /tmp/10MB.zip 

Vamos montar a imagem:


Montagem de uma imagem experimental na qual existe espaço irracionalmente usado

Explore a imagem usando o mergulho :


O indicador de desempenho da imagem é de 34%

O indicador de eficiência de imagem de 34% indica que uma quantidade considerável de espaço na imagem é usada irracionalmente. Isso leva a um aumento no tempo de inicialização da imagem, ao desperdício desnecessário de recursos de rede e a um tempo de início mais lento do contêiner.

Como se livrar desse problema? Vamos considerar várias opções.

▍ Mesclando resultados do trabalho em equipe


Você já viu Dockerfile contendo diretivas RUN muito longas, nas quais muitos comandos do shell são combinados usando && ? Esta é a fusão dos resultados das equipes.

Usando esse método, criamos, com base nos resultados de uma única equipe longa, apenas uma camada. Como não haverá camadas na imagem que contenham arquivos excluídos nas camadas a seguir, a imagem final não incluirá esses “arquivos fantasmas”. Considere isso como um exemplo, trazendo o Dockerfile acima para este estado:

 FROM alpine RUN wget http://xcal1.vodafone.co.uk/10MB.zip -P /tmp && rm /tmp/10MB.zip 

Depois disso, analisamos a imagem:


Mesclar equipes permitiu criar uma imagem 100% otimizada

A aplicação desta técnica para otimizar o tamanho das imagens na prática é que, depois que você terminar de trabalhar no arquivo Dockerfile , precisará analisá-la e descobrir se a mesclagem de comandos pode ser usada para reduzir a quantidade de espaço desperdiçado.

▍Aplicando a opção --squash


Nos casos em que você usa Dockerfile outras pessoas que você não deseja ou não pode alterar, uma alternativa aos comandos de mesclagem pode ser a montagem de uma imagem usando a opção --squash .

As versões modernas do Docker (começando com 1.13) permitem reunir todas as camadas em uma camada, eliminando, assim, os "recursos fantasmas". Nesse caso, você pode usar o Dockerfile original não modificado, contendo muitos comandos separados. Mas você precisa criar a imagem usando a opção --squash :

 docker build --squash . 

A imagem resultante também é 100% otimizada:


O uso da opção --squash durante a montagem permitiu criar uma imagem 100% otimizada

Aqui você pode prestar atenção a um detalhe interessante. Ou seja, no Dockerfile uma camada foi criada para adicionar um arquivo e outra para excluir esse arquivo. A opção --squash é inteligente o suficiente para entender que, nesse cenário, você não precisa criar camadas adicionais (na imagem final, há apenas 9ccd9… camada 9ccd9… partir da imagem base que usamos). Em geral, para isso, podemos colocar --squash uma vantagem adicional. É verdade que, usando --squash , é necessário considerar que isso pode interferir no uso de camadas em cache.

Como resultado, é recomendável levar em consideração o fato de que, ao trabalhar com o Dockerfile outra pessoa que você não gostaria de alterar, você pode minimizar a quantidade de espaço de imagem usado irracionalmente coletando imagens usando a opção --squash . Para analisar a imagem final, você pode usar a ferramenta de mergulho .

Excluir caches e arquivos temporários


Ao contêiner de aplicativos, muitas vezes surge uma situação em que você precisa colocar ferramentas, bibliotecas e utilitários adicionais na imagem com eles. Isso é feito usando gerenciadores de pacotes como apk , yum , apt .

Os gerentes de pacotes se esforçam para economizar tempo do usuário e não carregar sua conexão de rede mais uma vez ao instalar pacotes. Portanto, eles armazenam em cache os dados baixados. Para que o tamanho da imagem final do Docker seja o menor possível, não precisamos armazenar caches do gerenciador de pacotes nessa imagem. Afinal, se precisarmos de outra imagem, sempre podemos reconstruí-la usando o Dockerfile atualizado.

Para remover os caches criados pelos três gerenciadores de pacotes populares mencionados acima, no final de um comando agregado (ou seja, aquele executado para criar uma camada), você pode adicionar o seguinte:

 APK: ... && rm -rf /etc/apk/cache YUM: ... && rm -rf /var/cache/yum APT: ... && rm -rf /var/cache/apt 

Como resultado, é recomendável que, antes de concluir o trabalho no Dockerfile adicione Dockerfile a ele que removem os caches dos gerenciadores de pacotes usados ​​para construir a imagem. O mesmo se aplica a arquivos temporários que não afetam a operação correta do contêiner.

Escolha sua imagem de base com cuidado


Cada Dockerfile começa com uma diretiva FROM . É aqui que definimos a imagem básica com base na qual nossa imagem será criada.

Aqui está o que a documentação do Docker diz sobre o assunto: “A instrução FROM inicializa uma nova fase de construção e configura a imagem base para as instruções a seguir. Como resultado, um Dockerfile adequadamente composto deve começar com uma instrução FROM . Uma imagem pode ser qualquer imagem viável. É mais fácil começar a montar sua própria imagem, tendo como base uma imagem de um repositório público ".

Obviamente, existem muitas imagens básicas, cada uma com suas próprias características. A seleção correta de uma imagem básica que contém exatamente o que o aplicativo precisa, nem mais nem menos, tem um enorme impacto no tamanho da imagem final.

Como você pode esperar, os tamanhos das imagens base populares variam tremendamente:


Tamanhos de imagens populares básicas do Docker

Portanto, a conteinerização do aplicativo usando a imagem base do Ubuntu 19.10 levará ao fato de que o tamanho da imagem, além do tamanho do aplicativo, serão adicionados outros 73 MB. Se coletarmos a mesma imagem com base na imagem do Alpine 3.10.3 , obteremos um "aditivo" apenas na quantidade de 6 MB. Como o Docker armazena em cache as camadas da imagem, os recursos de rede são gastos no carregamento de uma imagem somente quando o contêiner é iniciado pela primeira vez da maneira apropriada (em outras palavras, quando a imagem é carregada pela primeira vez). Mas o tamanho da imagem em si não fica menor com isso.

Aqui você pode chegar à seguinte conclusão (completamente lógica): "Então - eu sempre usarei o Alpine!". Mas, infelizmente, no mundo do desenvolvimento de software, nem tudo é tão simples.

Talvez os desenvolvedores do Alpine Linux tenham descoberto algum ingrediente secreto que o Ubuntu ou o Debian ainda não conseguem encontrar? Não. O fato é que, para criar uma imagem do Docker, cujo tamanho é uma ordem de magnitude menor que o tamanho da imagem do mesmo Debian, os desenvolvedores da Alpine tiveram que tomar algumas decisões sobre o que precisa ser incluído na imagem e o que não é necessário. Antes de chamar Alpine a imagem base que você sempre usará, pergunte se ela tem tudo o que você precisa. Além disso, mesmo que a Alpine tenha um gerenciador de pacotes, pode ser que o pacote específico usado em seu ambiente de trabalho com base, por exemplo, no Ubuntu, não esteja disponível no Alpine. Ou - não um pacote, mas a versão desejada do pacote. Essas são as compensações que você deve conhecer antes de escolher e testar a imagem básica mais adequada ao seu projeto.

E, finalmente, se você realmente precisa de uma das maiores imagens básicas, pode usar a ferramenta para minimizar o tamanho da imagem. Por exemplo - uma ferramenta gratuita de código aberto DockerSlim . Isso reduzirá o tamanho da imagem final.

No final, podemos dizer que o uso de uma imagem básica cuidadosamente selecionada é extremamente importante na criação de suas próprias imagens compactas. Avalie as necessidades do seu projeto e selecione uma imagem que contenha o que você precisa e, ao mesmo tempo, dimensões que são aceitáveis ​​para você.

Considere criar uma imagem que não tenha uma imagem básica.


Se seu aplicativo puder ser executado sem um ambiente adicional fornecido de uma maneira básica, você pode optar por não usar uma imagem básica. Obviamente, como a instrução FROM deve estar presente no Dockerfile , você não pode prescindir dela. Além disso, ela deve apontar para algum tipo de imagem. Que imagem usar em tal situação?

Uma aparência do zero pode ser útil aqui. A partir da descrição dele, você pode descobrir que ele foi especialmente vazio e projetado para criar imagens, se você falar o idioma do Dockerfile , do FROM scratch , ou seja, do zero. Essa imagem é especialmente útil ao criar imagens básicas (como imagens debian e busybox) ou imagens extremamente mínimas (aquelas que contêm um único arquivo binário e o que é necessário para sua operação, digamos olá mundo). O uso desta imagem como base da imagem descrita pelo Dockerfile é semelhante ao uso de uma "operação vazia" em algum programa. A aplicação de uma imagem de scratch não criará uma camada adicional na imagem final.

Como resultado, se o seu aplicativo for um executável independente que possa funcionar por si só, a escolha da imagem inicial de base permitirá minimizar o tamanho do contêiner.

Use compilações de vários estágios


Construções em vários estágios têm sido o foco de atenção desde o Docker 05/17. Era uma oportunidade aguardada há muito tempo. Ele permite que os construtores de imagens abandonem seus próprios scripts para criar imagens e implementar tudo o que precisam usando o conhecido formato Dockerfile .

Em termos gerais, um assembly de vários estágios pode ser considerado como combinação de vários Dockerfile ou como um Dockerfile , que possui várias instruções FROM .

Antes do surgimento de montagens de vários estágios, se você precisasse criar uma montagem do seu projeto e distribuí-la em um contêiner usando o Dockerfile , provavelmente seria necessário executar o processo de montagem, o que levaria à aparência de um contêiner, como o mostrado abaixo:


Construa e distribua um aplicativo sem usar a tecnologia de construção de vários estágios

Embora, do ponto de vista técnico, tudo tenha sido feito corretamente, a imagem final e o contêiner resultante são preenchidos com camadas criadas no processo de preparação dos materiais do projeto. E essas camadas não são necessárias para formar o ambiente de tempo de execução do projeto.

As montagens de vários estágios permitem separar as fases de criação e preparação dos materiais do projeto do ambiente em que o código do projeto é executado.


Montagem em várias etapas, separação do processo de criação e preparação dos materiais do projeto do ambiente de execução

Ao mesmo tempo, um único Dockerfile suficiente para descrever o processo completo de criação do projeto. Mas agora você pode copiar material de um estágio para outro e se livrar de dados desnecessários.

Assemblies de vários estágios permitem criar assemblies de plataforma cruzada que podem ser usados ​​repetidamente sem usar seus próprios scripts de assembly escritos para um sistema operacional específico. O tamanho final da imagem pode ser minimizado devido à possibilidade de inclusão seletiva de materiais gerados nas etapas anteriores do processo de montagem da imagem.

Sumário


Criar imagens de contêiner do Docker é um processo com o qual os programadores modernos geralmente precisam lidar. Existem muitos recursos para criar Dockerfile e você pode encontrar muitos exemplos desses arquivos na Internet. Mas não importa o que você use, ao criar seu próprio Dockerfile sempre vale a pena considerar o tamanho das imagens resultantes.

Aqui, vimos várias técnicas para minimizar o tamanho das imagens do Docker. Atenção ao conteúdo do Dockerfile , incluindo apenas o que é realmente necessário, escolhendo a imagem base apropriada, usando a tecnologia de construção em vários estágios - tudo isso pode ajudar a reduzir seriamente o tamanho das imagens do Docker que você cria.

PS Lançamos o mercado no site da RUVDS. No mercado, a imagem do Docker é instalada em um clique. Você pode verificar como os contêineres funcionam no VPS . Três dias para testes são fornecidos gratuitamente a todos os novos clientes.

Caros leitores! Como você otimiza o tamanho das imagens do Docker?

Source: https://habr.com/ru/post/pt485650/


All Articles