Otimizar aplicativos Python para o Cloud Run

Neste guia, descrevemos otimizações para serviços do Cloud Run escritos na linguagem de programação Python, além de informações básicas para ajudar você a entender as contrapartidas envolvidas em algumas das otimizações. As informações desta página complementam as dicas gerais de otimização, que também se aplicam ao Python.

Muitas das práticas recomendadas e otimizações desses aplicativos tradicionais em Python baseados na Web giram em torno de:

  • como processar solicitações simultâneas (E/S com base em linhas de execução e sem bloqueio);
  • como reduzir a latência de resposta usando o pool de conexões e agrupando em lote funções não críticas, como o envio de traces e métricas para tarefas em segundo plano.

Otimizar a imagem do contêiner

Ao otimizar a imagem do contêiner, é possível reduzir os tempos de carregamento e de inicialização. É possível otimizar a imagem:

  • Colocar no contêiner apenas o que o aplicativo precisar no ambiente de execução
  • Como otimizar o servidor WSGI

Só coloque no contêiner o que o aplicativo precisar no ambiente de execução

Considere quais componentes estão incluídos no contêiner e se eles são necessários para a execução do serviço. Há várias maneiras de minimizar a imagem do contêiner:

  • Usar uma imagem de base menor
  • Mova arquivos grandes para fora do contêiner

Usar uma imagem de base menor

O Docker Hub fornece várias imagens de base oficiais do Python que podem ser usadas se optar por não instalar o Python pela origem nos contêineres. Elas são baseadas no sistema operacional Debian.

Se você estiver usando a imagem python do Docker Hub, use a versão slim. O tamanho dessas imagens é menor porque elas não vêm com vários pacotes que seriam usados para criar indicadores, por exemplo, que podem não ser necessários para o aplicativo. Por exemplo, a imagem Python vem com o compilador GNU C, pré-processador e utilitários principais.

Para identificar os dez maiores pacotes em uma imagem base, execute este comando:

DOCKER_IMAGE=python # or python:slim
docker run --rm ${DOCKER_IMAGE} dpkg-query -Wf '${Installed-Size}\t${Package}\t${Description}\n' | sort -n | tail -n10 | column -t -s $'\t'

Como há menos desses pacotes de baixo nível, as imagens baseadas em slim também oferecem menos superfície de ataque para possíveis vulnerabilidades. Essas imagens podem não incluir os elementos necessários para criar indicadores a partir da origem.

É possível adicionar pacotes específicos novamente adicionando uma linha RUN apt install ao Dockerfile. Veja mais sobre como usar os pacotes do sistema no Cloud Run.

Também há opções para contêineres não baseados no Debian. A opção python:alpine pode resultar em um contêiner muito menor, mas muitos pacotes do Python podem não ter indicadores pré-compilados compatíveis com sistemas baseados em alpino. O suporte está melhorando (consulte PEP-656), mas continua variado. Também é possível usar o distroless base image, que não contém nenhum gerenciador de pacotes, shell ou qualquer outro programa.

Mover arquivos grandes para fora do contêiner

Arquivos grandes, como recursos de mídia etc., não precisam ser incluídos no contêiner de base.

O Google Cloud oferece várias opções de hospedagem, como o Cloud Storage, para armazenar esses itens grandes. Mova grandes recursos para esses serviços e faça referência a eles a partir do aplicativo no ambiente de execução.

Otimizar o servidor WSGI

O Python padronizou a maneira como os aplicativos podem interagir com servidores da Web pela implementação do padrão WSGI, PEP-3333. Um dos servidores WSGI mais comuns é gunicorn, usado em grande parte da documentação de amostra.

Otimizar o Gunicorn

Adicione o seguinte CMD a Dockerfile para otimizar a invocação de gunicorn:

CMD exec gunicorn --bind :$PORT --workers 1 --threads 8 --timeout 0 main:app

Se você pretende alterar essas configurações, ajuste o número de workers e linhas de execução por aplicativo. Por exemplo, tente usar um número de workers igual aos núcleos disponíveis e verifique se há uma melhoria no desempenho e ajuste o número de linhas de execução. A configuração de muitos workers ou linhas de execução pode ter um impacto negativo, como latência de inicialização a frio mais longa, mais memória consumida, solicitações menores por segundo etc.

Por padrão, gunicorn gera workers e ouve a porta especificada ao iniciar, mesmo antes de avaliar o código do aplicativo. Nesse caso, você precisa configurar sondas de inicialização personalizadas para seu serviço, já que a sonda de inicialização padrão do Cloud Run marca uma instância de contêiner como saudável assim que começa a detectar em $PORT.

Se você quiser mudar esse comportamento, invoque gunicorn com o Configuração --preload para avaliar o código do aplicativo antes de escutá-lo. Estas são algumas das vantagens:

  • Identificar bugs graves no ambiente de execução no momento da implantação
  • Economizar recursos de memória

Considere o que o aplicativo está pré-carregando antes de realizar essa adição.

Outros servidores WSGI

Não há restrição de uso gunicorn para executar Python em contêineres. É possível usar qualquer servidor da Web WSGI ou ASGI, desde que o contêiner detecte na porta HTTP $PORT, de acordo com o contrato de ambiente de execução do contêiner.

Alternativas comuns incluem uwsgi, uvicorn e waitress.

Por exemplo, com o arquivo main.py contendo o objeto app, as seguintes invocações iniciariam um servidor WSGI:

# uwsgi: pip install pyuwsgi
uwsgi --http :$PORT -s /tmp/app.sock --manage-script-name --mount /app=main:app

# uvicorn: pip install uvicorn
uvicorn --port $PORT --host 0.0.0.0 main:app

# waitress: pip install waitress
waitress-serve --port $PORT main:app

Elas podem ser adicionadas como uma linha CMD exec em um Dockerfile ou como uma entrada web: em Procfile, quando os buildpacks do Google Cloud são usados.

Otimizar aplicativos

No código de serviço do Cloud Run, também é possível otimizar para tempos de inicialização e uso de memória mais rápidos.

Reduzir linhas de execução

Para otimizar a memória, reduza o número de linhas de execução. Para isso, use estratégias reativas sem bloqueios e evite atividades em segundo plano. Evite gravar no sistema de arquivos, conforme mencionado na página de dicas gerais.

Se você quiser oferecer suporte a atividades em segundo plano no serviço do Cloud Run, defina a CPU do serviço do Cloud Run como sempre alocada para que você possa executar atividades em segundo plano fora das solicitações e ainda assim tenha acesso à CPU.

Reduzir tarefas de inicialização

Os aplicativos em Python baseados na Web podem ter muitas tarefas a serem concluídas durante a inicialização, como pré-carregamento de dados, aquecimento do cache, estabelecimento de pools de conexão etc. Essas tarefas, quando executadas sequencialmente, podem ficar lentas. No entanto, se você quiser que elas sejam executadas em paralelo, aumente o número de núcleos de CPU.

No momento, o Cloud Run envia uma solicitação de usuário real para acionar uma instância de inicialização a frio. Os usuários que têm uma solicitação atribuída a uma instância recém-iniciada podem enfrentar atrasos demorados. O Cloud Run não tem uma verificação de "prontidão" para evitar o envio de solicitações a aplicativos que não estão prontos.

A seguir

Veja mais dicas em