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 melhores práticas e otimizações em aplicações web Python comuns 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

Otimize a imagem do contêiner para reduzir o tempo de carregamento e inicialização, utilizando estes métodos:

  • Minimize os arquivos carregados na inicialização.
  • Otimizar o servidor WSGI

Minimize os arquivos carregados na inicialização.

Para otimizar o tempo de inicialização, carregue apenas os arquivos necessários na inicialização e reduza o tamanho deles. Para arquivos grandes, considere as seguintes opções:

  • Armazene arquivos grandes, como modelos de IA, em seu contêiner para acesso mais rápido. Considere carregar esses arquivos após a inicialização ou em tempo de execução.

  • Considere configurar montagens de volume do Cloud Storage para arquivos grandes que não são críticos na inicialização, como ativos de mídia.

  • Importe apenas os submódulos necessários de dependências pesadas ou importe os módulos quando necessário em seu código, em vez de carregá-los na inicialização do aplicativo.

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 alterar esse comportamento, pode invocar gunicorn com a configuração --preload para avaliar o código do seu aplicativo antes de ouvir. 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 o serviço do Cloud Run como faturamento com base em instâncias para que você possa executar atividades em segundo plano fora das solicitações e ainda ter acesso à CPU.

Reduzir tarefas de inicialização

Aplicações web em Python podem ter muitas tarefas a serem concluídas durante a inicialização, como pré-carregar dados, aquecer o cache e estabelecer pools de conexões. Quando executadas sequencialmente, essas tarefas podem ser lentas. No entanto, se você quiser que elas sejam executadas em paralelo, aumente o número de núcleos da CPU.

O Cloud Run envia uma solicitação de usuário real para acionar uma instância de inicialização a frio. Usuários que tiverem uma solicitação atribuída a uma instância recém-iniciada poderão enfrentar longos atrasos.

Melhore a segurança com imagens de base compactas.

Para melhorar a segurança da sua aplicação, utilize uma imagem base enxuta com menos pacotes e bibliotecas.

Se você optar por não instalar o Python a partir do código-fonte em seus contêineres, use uma imagem base oficial do Python do Docker Hub. Essas imagens são baseadas no sistema operacional Debian.

Se você estiver usando a imagem python do Docker Hub, considere usar a versão slim. Essas imagens são menores porque não incluem vários pacotes que seriam usados ​​para construir rodas, o que você pode não precisar para sua aplicação. A imagem python vem com o compilador GNU C, o pré-processador e os utilitários principais.

Para identificar os dez maiores pacotes em uma imagem base, execute o seguinte 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. Algumas dessas imagens podem não incluir os elementos necessários para construir rodas a partir do código-fonte.

É possível adicionar pacotes específicos novamente adicionando uma linha RUN apt install ao Dockerfile. Para obter mais informações, consulte Usando 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 Python podem não ter wheels pré-compilados que suportem sistemas baseados em Alpine. O suporte está melhorando (veja PEP-656), mas continua variando. Considere também usar odistroless base image, que não contém nenhum gerenciador de pacotes, shell ou qualquer outro programa.

Use a variável de ambiente PYTHONUNBUFFERED para registro de logs.

Para ver os logs não armazenados em buffer do seu aplicativo Python, defina a variável de ambiente PYTHONUNBUFFERED. Ao definir esta variável, os dados stdout e stderr ficam imediatamente visíveis nos logs do contêiner, em vez de serem mantidos em um buffer até que uma certa quantidade de dados seja acumulada ou o fluxo seja fechado.

A seguir

Veja mais dicas em