Um Mergulho em Imagens de Containers - Parte 1

Recentemente li uma série de artigos do Scott Coulton[1][2][3] no Medium sobre como escolher as imagens base para seus containers. Decidi então escrever estes artigos para que outras pessoas também possam entender como escolher melhor como começar a criar seus containers usando Docker da melhor forma possível!


  1. I Chose You Container Image - Part 1 ↩︎

  2. I Chose You Container Image - Part 2 ↩︎

  3. I Chose You Container Image - Part 3 ↩︎

Imagens Base

Quando começamos a criar nossas imagens, a primeira coisa que temos que nos preocupar é: Qual será a imagem base que vamos utilizar para a nossa aplicação?

Por exemplo, eu crio muitas imagens que utilizam o Node.js, portanto minha escolha natural seria utilizar uma imagem base do próprio Node, como a node:14 que é uma imagem oficial presente no DockerHub.

Porém eu poderia tranquilamente criar minha própria imagem tendo o Node como uma ferramenta instalada ao invés de tê-la nativamente a partir da minha imagem base. Para isso, basta que eu escolha uma outra imagem base que contenha apenas o sistema operacional, poderíamos utilizar a imagem do Debian e então criar um Dockerfile parecido com o seguinte:

FROM debian:buster 
RUN sudo apt update \ 
    && sudo apt upgrade -y \ 
    && sudo apt install -y curl \ 
    && curl -sL https://deb.nodesource.com/setup_14.x | bash - \ 
    && sudo apt install -y nodejs

O que não é muito diferente do que é feito na imagem oficial. Mas será que estamos criando uma imagem que possui os requisitos básicos para que ela seja utilizada por outras pessoas?

O que quero dizer com esta introdução é que temos que pensar muito além da imagem base. Qualquer imagem do Docker precisa ser pensada para obter a melhor escalabilidade e usabilidade possível. Isto é feito com os seguintes requisitos:

  • O tamanho da imagem é pequeno o suficiente
  • A aplicação que executará dentro da imagem possui uma boa performance
  • A segurança está garantida dentro da imagem base

Para podermos atacar todas as partes, vamos trabalhar com linguagens compiladas – como Golang – na próxima parte deste artigo e, na última parte, vamos trabalhar com linguagens dinâmicas – como JavaScript ou Python.

Tipos de imagens base

Para deixar a escolha diretamente a quem lê, não vou dizer se um sistema operacional é melhor que outro, afinal isto é uma escolha completamente pessoal. Ao invés disso, vamos trabalhar com três tipos de nomenclaturas:

  • Imagens full
  • Imagens slim
  • Imagens alpine

Imagens Full

Foto de um navio carregado de containers Andrey Sharpilo / Unsplash

As imagens full (ou completas), são imagens que contém toda a extensão de um sistema operacional, por exemplo, uma imagem debian-full é uma imagem que possui a distribuição Debian do Linux por completo, com todos os pacotes e módulos carregados.

Este tipo de imagem é o melhor tipo de imagem para se começar e, provavelmente, o jeito mais simples de colocar uma aplicação no ar. Isto porque elas possuem todas as ferramentas e pacotes já instalados por padrão.  Neste artigo vamos apenas comparar as imagens e não vamos nos preocupar muito com as aplicações que vamos rodar dentro delas.

Um dos grandes problemas de imagens full é que, por terem muitos pacotes instalados por padrão, isso significa que existem muitos pacotes com vulnerabilidades comprovadas que também vem instalados por padrão. Isto abre portas para que um ataque possa ocorrer.

O próprio Docker Hub possui uma ferramenta de análise de vulnerabilidades que pode ser utilizada diretamente do site, através da aba Tags. Como mostra a imagem a seguir.

Detecção de vulnerabilidades do Docker Hub

Como você pode ver, a própria imagem tem vulnerabilidades que você está "herdando" antes mesmo de fazer qualquer deploy do seu código.

Imagens Slim

Foto de uma etiqueta escrito "the slim" Mat Reding / Unsplash

Uma imagem slim é uma imagem um pouco menor. Toda a imagem slim só possui os pacotes necessários para que o sistema operacional funcione, é esperado que você instale todos os pacotes extras para que sua aplicação possa funcionar. Atualmente, só o Debian possui uma imagem slim.

Um dos benefícios diretos do uso de imagens slim é que, por ter menos pacotes, você tem menos dependências que podem estar sujeitas a vulnerabilidades. Veja um exemplo.‌

Vulnerabilidades de uma imagem slim

Outro ganho interessante é que, justamente por serem mais "limpas", imagens slim são muito menores em tamanho do que uma imagem full. Em casos mais extremos, uma imagem slim pode ser até 50% menor do que uma imagem full. Veja o exemplo da comparação do debian:buster com o debian:buster-slim.

Tamanho da imagem slim do Debian Buster

Agora veja o tamanho da imagem completa

Tamanho da imagem full do Debian Buster

Veja que, se pegarmos a arquitetura amd64, que é a mais comum para processadores de 64 bits, e compararmos as versões full com 48.06MB contra os 25.84MB da mesma imagem, só que na versão slim. Isso é uma redução de 47% no tamanho da imagem. Tudo isso só removendo pacotes não utilizados.

Imagens Alpine Linux

Foto dos Alpes por Benni Asal / Unsplash

As imagens Alpine Linux são o tipo de imagem mais otimizada possível. Este tipo de OS foi construído do zero para ser um sistema operacional já nativo de containers. A principal diferença entre esse tipo de imagem para uma imagem tradicional é que esse sistema não utiliza a glibc. O Alpine depende de uma outra biblioteca chamada musl libc. Além disso, por padrão, o Alpine só vem com os pacotes base instalados, ou seja, qualquer outra coisa que sua aplicação precisar, você vai ter que instalar manualmente.

Isso pode parecer um pouco complicado, mas veja como reduzimos a quantidade de vulnerabilidades só por reduzir os pacotes. Perceba que não há nenhuma vulnerabilidade crítica.

Vulnerabilidades de uma imagem Alpine

Além disso, uma imagem alpine tem menos de 6MB de tamanho total. No caso das imagens mais novas, esse tamanho pode chegar a pouco mais de 2MB. Isso é uma redução de mais 95% no tamanho geral da imagem.

Tamanho de uma imagem Alpine Linux

Os contras de se trabalhar com uma imagem Alpine é que é um sistema completamente diferente, você terá de aprender a lidar com seu package manager nativo, como ele não usa as bibliotecas padrões do Linux, provavelmente a grande maioria dos sistemas precisarão ser recompilados utilizando as ferramentas do Alpine.

Imagens Scratch

Foto de um papel preto sem nada escrito com um lápis branco em cima por Kelly Sikkema / Unsplash

Por último, vamos dar uma olhada nas imagens do tipo scratch. Quando usamos scratch como imagem base, estamos dizendo ao Docker que queremos que o próximo comando no nosso Dockerfile seja a primeira camada do nosso sistema de arquivos dentro dessa imagem.

Isso significa que não temos nenhum gerenciador de pacotes, nem mesmo pacotes. Somente um sistema de arquivos vazio. Você pode começar utilizando FROM scratch, já que este é um namespace reservado no DockerHub e não possui nenhum tipo de vulnerabilidade ou pacote instalado.

FROM scratch
ADD rootfs.tar.xz /
CMD ["bash"]

A maioria das imagens de sistemas operacionais começam deste modo, pois a maioria delas são consideradas imagens vazias. Que começam do absoluto zero.

Conclusão

Vamos explorar um pouco mais sobre imagens e colocar esses conceitos em prática nos próximos artigos escrevendo uma aplicação em Go.

Fique ligado para mais novidades! Se inscreva na newsletter para receber todo o conteúdo diretamente no seu e-mail!