Otimize aplicações Python para o Cloud Run

Este guia descreve as otimizações para serviços do Cloud Run escritos na linguagem de programação Python, juntamente com informações gerais para ajudar a compreender as concessões envolvidas em algumas das otimizações. As informações nesta página complementam as sugestões de otimização gerais, que também se aplicam ao Python.

Muitas das práticas recomendadas e otimizações na aplicação baseada na Web Python comum giram em torno do seguinte:

  • Processar pedidos simultâneos (com base em threads e E/S sem bloqueio)
  • Reduzir a latência de resposta através da pool de ligações e do processamento em lote de funções não críticas, por exemplo, o envio de rastreios e métricas para tarefas em segundo plano.

Otimize a imagem do contentor

Otimize a imagem do contentor para reduzir os tempos de carregamento e arranque através dos seguintes métodos:

  • Minimize os ficheiros que carrega no arranque
  • Otimize o servidor WSGI

Minimize os ficheiros que carrega no arranque

Para otimizar o tempo de arranque, carregue apenas os ficheiros necessários no arranque e reduza o respetivo tamanho. Para ficheiros grandes, considere as seguintes opções:

  • Armazene ficheiros grandes, como modelos de IA, no contentor para um acesso mais rápido. Considere carregar estes ficheiros após o arranque ou no tempo de execução.

  • Considere configurar montagens de volumes do Cloud Storage para ficheiros grandes que não sejam críticos no arranque, como recursos multimédia.

  • Importe apenas os submódulos necessários de quaisquer dependências pesadas ou importe módulos quando necessário no seu código, em vez de os carregar no arranque da aplicação.

Otimize o servidor WSGI

O Python padronizou a forma como as aplicações podem interagir com servidores Web através da implementação da norma WSGI, PEP-3333. Um dos servidores WSGI mais comuns é o gunicorn, que é usado em grande parte da documentação de exemplo.

Otimize o gunicorn

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

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

Se estiver a ponderar alterar estas definições, ajuste o número de trabalhadores e threads por aplicação. Por exemplo, experimente usar um número de trabalhadores igual aos núcleos disponíveis e certifique-se de que existe uma melhoria no desempenho. Em seguida, ajuste o número de threads. A definição de demasiados trabalhadores ou threads pode ter um impacto negativo, como uma latência de início a frio mais longa, mais memória consumida, pedidos por segundo mais pequenos, etc.

Por predefinição, o gunicorn gera trabalhadores e ouve na porta especificada quando é iniciado, mesmo antes de avaliar o código da sua aplicação. Neste caso, deve configurar sondagens de arranque personalizadas para o seu serviço, uma vez que a sondagem de arranque predefinida do Cloud Run marca imediatamente uma instância de contentor como saudável assim que começa a ouvir em $PORT.

Se quiser alterar este comportamento, pode invocar gunicorn com a definição --preload para avaliar o código da sua aplicação antes de ouvir. Isto pode ajudar a:

  • Identifique erros de tempo de execução graves no momento da implementação
  • Poupe recursos de memória

Deve considerar o que a sua aplicação está a pré-carregar antes de adicionar esta funcionalidade.

Outros servidores WSGI

Não está restrito à utilização do gunicorn para executar Python em contentores. Pode usar qualquer servidor Web WSGI ou ASGI, desde que o contentor escute na porta HTTP $PORT, de acordo com o contrato de tempo de execução do contentor.

As alternativas comuns incluem uwsgi, uvicorn, e waitress.

Por exemplo, dado um ficheiro denominado main.py que contém o objeto app, as invocações seguintes 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

Podem ser adicionadas como uma linha CMD exec num ficheiro Dockerfile ou como uma entrada web: em Procfile quando usar os buildpacks da Google Cloud.

Otimize as aplicações

No código do serviço do Cloud Run, também pode fazer a otimização para tempos de arranque mais rápidos e utilização de memória.

Reduza as discussões

Pode otimizar a memória reduzindo o número de threads, usando estratégias reativas não bloqueadoras e evitando atividades em segundo plano. Evite também escrever no sistema de ficheiros, conforme mencionado na página de sugestões gerais.

Se quiser suportar atividades em segundo plano no seu serviço do Cloud Run, defina o serviço do Cloud Run para a faturação baseada em instâncias para poder executar atividades em segundo plano fora dos pedidos e continuar a ter acesso à CPU.

Reduza as tarefas de arranque

As aplicações Web baseadas em Python podem ter muitas tarefas a concluir durante o arranque, como pré-carregar dados, aquecer a cache e estabelecer pools de ligações. Quando executadas sequencialmente, estas tarefas podem ser lentas. No entanto, se quiser que sejam executados em paralelo, aumente o número de núcleos do CPU.

O Cloud Run envia um pedido de utilizador real para acionar uma instância de início a frio. Os utilizadores que tenham um pedido atribuído a uma instância iniciada recentemente podem sofrer longos atrasos.

Melhore a segurança com imagens base simplificadas

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

Se optar por não instalar o Python a partir da origem nos seus contentores, use uma imagem base oficial do Python do Docker Hub. Estas imagens baseiam-se no sistema operativo Debian.

Se estiver a usar a imagem python do Docker Hub, considere usar a versão slim. Estas imagens são mais pequenas porque não incluem vários pacotes que seriam usados para criar rodas, o que pode não ser necessário para a sua aplicação. A imagem python inclui o compilador GNU C, o pré-processador e as utilidades principais.

Para identificar os dez pacotes maiores numa imagem de 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'

Uma vez que existem menos pacotes de baixo nível, as imagens baseadas no slim também oferecem uma superfície de ataque menor para potenciais vulnerabilidades. Algumas destas imagens podem não incluir os elementos necessários para criar rodas a partir da origem.

Pode adicionar novamente pacotes específicos adicionando uma linha RUN apt install ao seu ficheiro Dockerfile. Para mais informações, consulte o artigo Usar pacotes do sistema no Cloud Run.

Também existem opções para contentores não baseados no Debian. A opção python:alpine pode resultar num contentor muito mais pequeno, mas muitos pacotes Python podem não ter rodas pré-compiladas que suportem sistemas baseados em Alpine. O suporte está a melhorar (consulte o PEP-656), mas continua a variar. Considere também usar o distroless base image, que não contém gestores de pacotes, shells nem outros programas.

Use a variável de ambiente PYTHONUNBUFFERED para o registo

Para ver registos não armazenados em buffer da sua aplicação Python, defina a variável de ambiente PYTHONUNBUFFERED. Quando define esta variável, os dados stdout e stderr ficam imediatamente visíveis nos registos do contentor, em vez de serem mantidos num buffer até se acumular uma determinada quantidade de dados ou o fluxo ser fechado.

O que se segue?

Para mais sugestões, consulte