Article image

IA

I Almeida14/12/2024 11:27
Compartilhe

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:

    1. Construções em vários estágios
    2. Otimizações de camadas
    3. Imagens de base mínimas (incluindo Scratch)
    4. Técnicas avançadas como imagens distroless
    5. 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:

    1. Usando uma imagem Python completa
    2. Incluindo ferramentas de construção e dependências desnecessárias
    3. Cache de camada ineficiente
    4. Possível inclusão de arquivos desnecessários
    5. 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:

    1. Arrume todo o seu guarda-roupa (Imagem de distribuição completa)
    2. Não leve nada e compre tudo no seu destino (Scratch Image)
    3. 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 Divee 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

    Compartilhe
    Comentários (1)
    Ronaldo Schmidt
    Ronaldo Schmidt - 14/12/2024 12:07

    Excelente artigo.

    Obrigado por compartilhar.