Como reduzir imagens Docker
A jornada de 1,2 GB a 8 MB: um estudo de caso
Para ilustrar o poder dessas técnicas, vamos dar uma olhada em um exemplo do mundo real. Pegamos um aplicativo de machine learning padrão baseado em Python com um tamanho de imagem Docker inicial de 1,2 GB e o otimizamos para meros 8 MB:
- Construções em vários estágios
- Otimizações de camadas
- Imagens de base mínimas (incluindo Scratch)
- Técnicas avançadas como imagens distroless
- Melhores práticas de segurança
Ponto de partida: a imagem inchada
Dockerfile típico
FROM python:3.9
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
CMD ["python", "main.py"]
Este Dockerfile, embora funcional, resulta em um tamanho de imagem grande devido a:
- Usando uma imagem Python completa
- Incluindo ferramentas de construção e dependências desnecessárias
- Cache de camada ineficiente
- Possível inclusão de arquivos desnecessários
- Agora vamos dar uma olhada nas técnicas que ajudarão você a otimizar a imagem
Construções em vários estágios: o divisor de águas
As compilações em vários estágios são uma técnica poderosa para reduzir drasticamente o tamanho final da imagem do Docker. Elas nos permitem separar as dependências de tempo de compilação das de tempo de execução.
Use uma imagem base mínima
substitua a versão completa do python por uma versão slim ou alpine de acordo com seu caso de uso:
FROM python:3.9-slim AS builder
Dockerfile de Etapa Única
# Use uma imagem oficial do Python runtime como uma imagem principal
FROM python:3.9-slim
# Instale as dependências necessárias
RUN apt-get update && apt-get install -y --no-install-recommends \
build-essential \
gcc \
&& rm -rf /var/lib/apt/lists/*
# Defina o diretório de trabalho
WORKDIR /app
# Copie o arquivo de requisitos e instale as dependências
COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt
# Copie o restante do código do aplicativo
COPY . .
# Compile o modelo (se necessário)
RUN python compile_model.py
# Execute o script
CMD ["python", "inference.py"]
A construção desta imagem resulta em aproximadamente 1,2 GB. Esse tamanho grande se deve à inclusão de todas as ferramentas de construção e bibliotecas de desenvolvimento.
Dockerfile de Múltiplas Etapas
Etapa 1: Etapa de construção
- configuraremos o diretório de trabalho
- Instalar ferramentas de construção necessárias
- Copie e instale nossas dependências python
- Copiar código do aplicativo
- Use o PyInstaller para criar um executável autônomo
# Stage 1: Build
FROM python:3.9-slim AS builder
# Install necessary build dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
build-essential \
gcc \
&& rm -rf /var/lib/apt/lists/*
# Set the working directory
WORKDIR /app
# Copy the requirements file and install dependencies
COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt
# Copy the application code
COPY . .
# Compile the model (if necessary)
RUN python compile_model.py
# Install PyInstaller
RUN pip install pyinstaller
# Create a standalone executable
RUN pyinstaller --onefile inference.py
Etapa 2: Etapa de produção
- Comece com uma imagem "scratch" - uma imagem totalmente vazia
- Copie apenas os arquivos necessários da etapa de Build
- Definimos o entry-point para a aplicação compilada
# Stage 2: Production
FROM scratch
# Set the working directory
WORKDIR /app
# Copy only the necessary files from the build stage
COPY --from=builder /app/dist/inference /app/inference
COPY --from=builder /app/model /app/model
# Run the inference executable
ENTRYPOINT ["/app/inference"]
A construção deste Dockerfile de vários estágios resulta em um tamanho de imagem de aproximadamente 85 MB — uma redução de mais de 90%.
Otimização de Camadas: Cada Byte Conta
Cada instrução que você escreve em seu Dockerfile cria uma nova camada na imagem do Docker. Por exemplo, os comandos RUN, COPY e ADD, cada um adiciona uma camada, e cada camada aumenta o tamanho da imagem, assim como o tempo de build.
- Minimizar camadas : combinando vários comandos de execução em uma única instrução RUN, essa abordagem minimiza camadas redundantes e mantém a imagem mais limpa. Por exemplo: em vez de comandos RUN separados para instalar pacotes e limpar arquivos temporários, você deve combiná-los.
- Use && para encadear comandos e limpar na mesma camada.
RUN apt-get update && apt-get install -y python3-pip python3-dev && \
pip3 install numpy pandas && \
apt-get clean && rm -rf /var/lib/apt/lists/*
Imagens de base mínimas do zero: menos é mais
Esta é a maneira mais poderosa, mas também a mais desafiadora de criar uma imagem do Docker. Criar uma imagem do zero significa que você usa a imagem base do zero. Nenhum sistema operacional subjacente. Nenhuma dependência. Nenhum dado ou aplicativo preexistente. Pense nisso como um disco de armazenamento vazio, você tem que preenchê-lo com dados porque não há nada nele.
O que você colocar nele contribuirá para seu tamanho. Mas isso significa que se você precisar de quaisquer dependências ou aplicativos ou ferramentas de suporte, você mesmo precisará instalá-los na imagem.
Pode ser útil principalmente em dois cenários:
- quando você está criando sua própria imagem base. Por exemplo. Você criou sua própria distribuição Linux, certo. Você não quer colocar isso em cima de outra imagem base, como Ubuntu ou algo assim. Você pode usar uma imagem de rascunho em vez disso, e então colocar sua própria distribuição Linux em cima dela.
- Quando você tem um aplicativo executável autônomo. Por exemplo, para um aplicativo ML/DL autônomo baseado em Python, como um servidor de modelo que manipula previsões, você pode compilar o código em um executável usando ferramentas como PyInstaller. Depois de compilado, coloque o executável em uma imagem do Docker do zero. Como essa imagem não tem um sistema operacional ou bibliotecas, você precisará adicionar manualmente apenas as dependências necessárias (por exemplo, TensorFlow, PyTorch, arquivos de modelo ou arquivos de configuração). Isso mantém a imagem mínima e otimizada para implantação.
# syntax=docker/dockerfile:1
FROM scratch
ADD myapp /
CMD ["/myapp"]
Técnicas avançadas:
Imagens sem Distro
Imagine que você está fazendo as malas para uma viagem. Você tem três opções:
- Arrume todo o seu guarda-roupa (Imagem de distribuição completa)
- Não leve nada e compre tudo no seu destino (Scratch Image)
- Leve apenas o que você realmente precisa (Imagem Distroless)
As imagens distroless do Google fornecem um meio termo entre distribuições completas e imagens scratch. Esta é a opção "perfeita". Ela inclui apenas o que é necessário para executar seu aplicativo.
As imagens sem distro são especiais porque:
- São menores que as imagens de distribuição completa
- São mais seguras porque têm menos componentes desnecessários
- Ainda inclui itens importantes como certificados SSL e dados de fuso horário
FROM gcr.io/distroless/python3-debian10
COPY --from=builder /app/dist/main /app/main
COPY --from=builder /app/model /app/model
COPY --from=builder /app/config.yml /app/config.yml
ENTRYPOINT ["/app/main"]
Usar Docker Buildkit
O Docker BuildKit oferece desempenho aprimorado, segurança e invalidação de cache mais flexível para a construção de imagens docker. Habilite-o com
DOCKER_BUILDKIT=1 docker build -t myapp .
Outras técnicas:
- Eliminando arquivos desnecessários: Não mantenha nenhum dos seus aplicativos, dados dentro da sua imagem, isso aumentará diretamente o tamanho da imagem. Em vez disso, conecte um contêiner a um volume de armazenamento externo e armazene seus dados lá para que sejam acessíveis pelo aplicativo, mas também não inchem a imagem. Como alternativa, seu aplicativo também pode se conectar a um armazenamento de dados externo, como MySQL ou AWS S3, e acessar os dados de lá.
- Use o arquivo .dockerignore: Docker ignore semelhante ao .gitignore. Ele permite que você exclua arquivos e diretórios específicos da sua imagem final. Podemos adicionar o arquivo .dockerignore à raiz do nosso projeto. Por exemplo, você pode adicionar grandes arquivos de dados, ambiente virtual, Logs, pontos de verificação de modelo e arquivos temporários ao seu arquivo .dockerignore.
# Exclude large datasets
data/
# Exclude virtual environment
venv/
# Exclude cache, logs, and temporary files
__pycache__/
*.log
*.tmp
*.pyc
*.pyo
*.pyd
.pytest_cache
.git
.gitignore
README.md
# Exclude model training checkpoints and tensorboard logs
checkpoints/
runs/
- Aproveitando as ferramentas de análise de imagem: ferramentas de compressão de imagem como
Dive
e Docker slim também são muito poderosas. Elas permitirão que você analise cada camada de imagem, incluindo o tamanho da camada e quais arquivos estão dentro, e podem ajudar você a descobrir onde está o peso morto e o que você pode remover. - Unikernels: são imagens muito menores que vêm com seu aplicativo e sistema operacional subjacente, projetadas para serem executadas diretamente, mas exigem um entendimento mais profundo para serem implementadas de forma eficaz. (elas podem ser 80% menores do que uma imagem típica do Docker)
Práticas essenciais de segurança para reduzir imagens do Docker com segurança:
- Use imagens de base confiáveis e oficiais. Evite imagens não verificadas de fontes desconhecidas.
- Sempre execute contêineres como usuários não root
RUN adduser --disabled-password --gecos "" appuser
USER appuser
- Limite a exposição da rede do seu contêiner restringindo as portas e endereços IP
docker run -p 127.0.0.1:8080:8080 myimage
Verifique regularmente suas imagens do Docker em busca de vulnerabilidades conhecidas. (Considere usar ferramentas como o Trivy para verificar regularmente suas imagens em busca de vulnerabilidades.)
docker scan your-image:tag
- Evite codificar informações confidenciais, como chaves de API ou senhas, diretamente em seu Dockerfile ou variáveis de ambiente. Em vez disso, use métodos como segredos do Docker ou variáveis de ambiente gerenciadas por ferramentas de orquestração
- Habilitar registro e monitoramento para contêineres para rastrear quaisquer atividades suspeitas
Conclusão
Depois de implementar essas técnicas, aqui está o que alcançamos:
- Tamanho da imagem: reduzido de 1,2 GB para 8 MB (redução de 99,33%)
- Tempo de implantação: reduzido em 85%
- Custos da Nuvem: Redução de 60%
Lembre-se, a chave é começar com uma imagem base mínima, usar builds multiestágio para separar seu ambiente de build do seu ambiente de tempo de execução e otimizar continuamente suas camadas e dependências. Com esses métodos, você também pode obter reduções significativas nos tamanhos de imagem do Docker!
Comece com builds de vários estágios, então aplique progressivamente as outras técnicas para ver o quão pequenas e eficientes você pode tornar suas imagens Docker. Boa otimização :)
Fonte: https://aws.plainenglish.io/docker-pros-are-shrinking-images-by-99-the-hidden-techniques-you-cant-afford-to-miss-a70ee26b4cbf