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