Neste guia, apresentamos as práticas recomendadas para projetar, implementar, testar e implantar um serviço do Cloud Run. Para mais dicas, consulte Como migrar um serviço atual.
Criar serviços eficazes
Nesta seção, descrevemos as práticas recomendadas gerais para projetar e implementar um serviço do Cloud Run.
Atividade em segundo plano
Uma atividade em segundo plano é qualquer evento ocorrido depois que sua resposta HTTP foi entregue. Para determinar se há atividade em segundo plano no serviço que não seja imediatamente aparente, verifique nos registros tudo que está registrado após a entrada para a solicitação HTTP.
Configurar a CPU para ser sempre alocada para usar atividades em segundo plano
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 têm acesso à CPU.
Evitar atividades em segundo plano se a CPU for alocada somente durante o processamento da solicitação
Se você precisar definir seu serviço para alocar CPU apenas durante o processamento da solicitação, quando o serviço do Cloud Run terminar de processar uma solicitação, o acesso da instância do contêiner à CPU será desativado ou gravemente limitado. Não inicie linhas de execução ou rotinas em segundo plano que sejam executadas fora do escopo dos gerenciadores de solicitações se você usar esse tipo de alocação de CPU.
Revise seu código para certificar-se de que todas as operações assíncronas terminem antes de entregar sua resposta.
A execução de threads em segundo plano com esse tipo de alocação de CPU pode resultar em um comportamento inesperado, pois qualquer solicitação subsequente à mesma instância de contêiner retoma qualquer atividade em segundo plano suspensa.
Excluir arquivos temporários
No ambiente do Cloud Run, o armazenamento em disco é um sistema de arquivos na memória. Os arquivos gravados no disco consomem memória de outra forma disponível para seu serviço e podem persistir entre as chamadas. Deixar de excluir esses arquivos pode resultar em um erro de memória insuficiente e tempos de inicialização lentos do contêiner.
Relatar erros
Gerencie todas as exceções e não deixe seu serviço falhar em erros. Uma falha leva a uma inicialização lenta do contêiner enquanto o tráfego fica na fila para uma instância de substituição.
Consulte o guia do Error Reporting para informações sobre como relatar erros corretamente.
Otimizar o desempenho
Nesta seção, você verá as práticas recomendadas para otimizar o desempenho.
Iniciar contêineres rapidamente
Como as instâncias de contêiner são escalonadas conforme necessário, o tempo de inicialização tem impacto na latência do serviço. Embora o Cloud Run desassocie a inicialização da instância e o processamento da solicitação, pode acontecer de uma solicitação aguardar até que uma nova instância de contêiner seja iniciada para ser processada, o que acontece principalmente durante o escalonamento a partir de zero.
A rotina de inicialização engloba estas etapas:
- Fazer o download da imagem do contêiner (usando a tecnologia de streaming de imagem do contêiner do Cloud Run)
- Iniciar o contêiner executando o comando entrypoint.
- Aguardar o contêiner começar a detectar na porta configurada.
A otimização da velocidade de inicialização do contêiner minimiza a latência do processamento de solicitações.
Usar a otimização da CPU de inicialização para reduzir a latência da inicialização
É possível ativar a otimização da CPU de inicialização para aumentar temporariamente a alocação de CPU durante a inicialização de instâncias para reduzir a latência da inicialização.
Usar instâncias mínimas para reduzir os tempos de inicialização do contêiner
É possível configurar instâncias mínimas e simultaneidade para minimizar os tempos de inicialização do contêiner. Por exemplo, usar no mínimo uma instância significa que o serviço está pronto para receber até o número de solicitações simultâneas configuradas para o serviço sem a necessidade de iniciar uma nova instância.
Observe que uma solicitação que aguarda o início de uma instância será mantida pendente em uma fila da seguinte maneira:
- Se novas instâncias estiverem inicializando, como durante um escalonamento horizontal, as solicitações ficarão pendentes pelo menos durante o tempo médio de inicialização das instâncias de contêiner deste serviço. Isso inclui quando a solicitação inicia um escalonamento horizontal, como ao escalonar do zero.
- Se o tempo de inicialização for menor que 10 segundos, as solicitações ficarão pendentes por até 10 segundos.
- Se não houver instâncias no processo de inicialização e a solicitação não iniciar um escalonamento horizontal, as solicitações ficarão pendentes por até 10 segundos.
Use dependências com sabedoria
Se você usar uma linguagem dinâmica com bibliotecas dependentes, como a importação de módulos no Node.js, o tempo de carregamento desses módulos será adicionado à latência de inicialização.
Reduza a latência de inicialização das maneiras a seguir:
- Minimize o número e o tamanho das dependências para criar um serviço enxuto.
- Desacelere o código de carregamento que é usado com pouca frequência, se a linguagem for compatível com ele.
- Use otimizações de carregamento de código, como a otimização do carregador automático do composer do PHP.
Usar variáveis globais
No Cloud Run, não é possível presumir que o estado do serviço seja preservado entre as solicitações. No entanto, o Cloud Run reutiliza instâncias de contêiner individuais para veicular o tráfego em andamento, para você declarar uma variável no escopo global e permitir que o valor seja reutilizado em chamadas subsequentes. Não é possível saber com antecedência se alguma solicitação individual recebe o benefício dessa reutilização.
Também é possível armazenar em cache objetos na memória, se eles são caros para recriar em cada solicitação de serviço. Mover isso da lógica de solicitação para o escopo global melhora o desempenho.
Node.js
Python
Go
Java
Executar a inicialização lenta de variáveis globais
A inicialização de variáveis globais sempre ocorre durante a inicialização, o que aumenta o tempo de inicialização do contêiner. Use a inicialização lenta para objetos usados com pouca frequência para adiar a cobrança relacionada ao tempo e diminuir os tempos de inicialização do contêiner.
Uma desvantagem da inicialização lenta é o aumento da latência das primeiras solicitações para novas instâncias. Isso pode causar superdimensionamento e solicitações descartadas quando você implanta uma nova revisão de um serviço que está processando ativamente muitas solicitações.
Node.js
Python
Go
Java
Usar um ambiente de execução diferente
Os tempos de inicialização podem ficar mais rápidos usando um ambiente de execução diferente.
Otimizar a simultaneidade
As instâncias do Cloud Run podem atender a várias solicitações ao mesmo tempo até atingir uma simultaneidade configurável máxima.
Isso é diferente das funções do Cloud Run, que usa concurrency = 1
.
O Cloud Run ajusta automaticamente a simultaneidade até o máximo configurado.
A simultaneidade máxima padrão de 80 é uma boa opção para muitas imagens de contêiner. No entanto, faça o seguinte:
- Reduza-a se o contêiner não conseguir processar várias solicitações simultâneas.
- Aumente-o se o contêiner puder lidar com um grande volume de solicitações.
Ajustar a simultaneidade do serviço
É possível que o número de solicitações simultâneas que cada instância de contêiner pode exibir seja limitado pela pilha de tecnologia e pelo uso de recursos compartilhados, como variáveis e conexões de banco de dados.
Para otimizar seu serviço e ter máxima simultaneidade estável:
- otimize seu desempenho de serviço;
- defina o nível esperado de suporte de simultaneidade em qualquer configuração de simultaneidade no nível de código. Nem todas as pilhas de tecnologia exigem essa configuração;
- implante seu serviço;
- defina a simultaneidade do Cloud Run para seu serviço como igual ou menor que qualquer configuração no nível de código. Se não houver configuração no nível de código, use sua simultaneidade esperada;
- Use ferramentas de teste de carga que sejam compatíveis com uma simultaneidade configurável. Confirme que seu serviço permanece estável sob a carga e simultaneidade esperadas;
- se o serviço não funcionar bem, vá para a etapa 1 para melhorá-lo ou para a etapa 2 para reduzir a simultaneidade. Se o serviço funcionar bem, volte para a etapa 2 e aumente a simultaneidade.
Continue iterando até encontrar a simultaneidade estável máxima.
Associar memória à simultaneidade
Cada solicitação que o serviço gerencia requer uma quantidade adicional de memória. Então, quando você ajusta a simultaneidade para cima ou para baixo, ajuste seu limite de memória também.
Evitar o estado global mutável
Se você quiser aproveitar o estado global mutável em um contexto simultâneo, execute etapas adicionais no código para garantir que isso seja feito com segurança. Minimize a contenção limitando as variáveis globais à inicialização única e reutilize conforme descrito acima em Desempenho.
Se você usar variáveis globais mutáveis em um serviço que atenda a várias solicitações ao mesmo tempo, certifique-se de usar bloqueios ou mutexes para evitar disputas.
Compensações entre capacidade de processamento, latência e custo
Ajustar a configuração de solicitações simultâneas máximas pode ajudar a equilibrar a troca entre capacidade de processamento, latência e custo do serviço.
Em geral, uma configuração de solicitações simultâneas máxima mais baixa resulta em latência e capacidade de processamento menores por instância. Com um número máximo menor de solicitações simultâneas, menos solicitações competem por recursos em cada instância, e cada solicitação consegue um desempenho melhor. No entanto, como cada instância pode atender menos solicitações de uma vez, a capacidade de processamento por instância é menor e o serviço precisa de mais instâncias para atender ao mesmo tráfego.
Na direção oposta, uma configuração de solicitações simultâneas máxima maior geralmente resulta em latência e capacidade de processamento maiores por instância. As solicitações podem precisar esperar pelo acesso a recursos como CPU, GPU e largura de banda de memória dentro da instância, o que leva a um aumento da latência. No entanto, cada instância pode processar mais solicitações de uma só vez, de modo que o serviço precisa de menos instâncias no geral para processar o mesmo tráfego.
Considerações sobre o custo
O faturamento do Cloud Run é por instância. Se a CPU for sempre alocada, o tempo de instância será o ciclo de vida total de cada instância. Se a CPU não for sempre alocada, o tempo de instância será o tempo que cada instância leva para processar pelo menos uma solicitação.
O impacto das solicitações simultâneas máximas no faturamento depende do seu padrão de tráfego. A redução do número máximo de solicitações simultâneas pode resultar em uma conta menor se a configuração mais baixa levar a
- Latência menor
- Instâncias que concluem o trabalho mais rápido
- Instâncias sendo encerradas mais rapidamente, mesmo que mais instâncias sejam necessárias
Mas o oposto também é possível: reduzir o número máximo de solicitações simultâneas pode aumentar a cobrança se o aumento no número de instâncias não for compensado pela redução no tempo de execução de cada instância, devido à latência melhorada.
A melhor maneira de otimizar o faturamento é por meio de testes de carga, usando diferentes configurações de solicitações simultâneas máximas para identificar a configuração que resulta no menor tempo de instância faturável, conforme mostrado na métrica de monitoramento container/billable_instance_time.
Segurança de contêineres
Muitas práticas de segurança de software de uso geral se aplicam a serviços em contêiner. Há algumas práticas que são específicas para contêineres ou que se alinham à filosofia e arquitetura deles.
Para melhorar a segurança do contêiner, siga estas recomendações:
Use imagens de base seguras e mantidas ativamente, como as imagens de base do Google ou as imagens oficiais do Docker Hub.
Aplique atualizações de segurança aos serviços, reconstruindo regularmente as imagens de contêiner e reimplantando os serviços.
Inclua no contêiner apenas o que for necessário para executar seu serviço. Códigos, pacotes e ferramentas extras podem deixar a segurança mais vulnerável. Confira acima o impacto relacionado ao desempenho.
Implemente um processo de compilação determinista que inclua versões específicas de software e biblioteca. Isso impede que códigos não verificados sejam incluídos no contêiner.
Defina o contêiner para ser executado como um usuário diferente de
root
com a instruçãoUSER
do Dockerfile (em inglês). Algumas imagens de contêiner talvez já tenham um usuário específico configurado.Impeça o uso de recursos em fase de pré-lançamento usando políticas personalizadas da organização.
Automatizar a verificação de segurança
Ative a verificação de vulnerabilidades para a verificação de segurança de imagens de contêiner armazenadas no Artifact Registry.
Criar o mínimo de imagens de contêiner
É possível que imagens grandes de contêiner aumentem as vulnerabilidades de segurança porque elas contêm mais do que o código precisa.
Devido à tecnologia de streaming de imagens de contêiner do Cloud Run, o tamanho da imagem do contêiner não afeta a inicialização do contêiner ou o tempo de processamento da solicitação. O tamanho da imagem do contêiner também não é contabilizado na memória disponível do seu contêiner.
Para criar um contêiner mínimo, utilize uma imagem básica enxuta, como estas:
O Ubuntu é maior em tamanho, mas é uma imagem de base comumente usada com um ambiente de servidor pronto para uso mais completo.
Se seu serviço tem um processo de criação de ferramentas pesadas, considere o uso de compilações em vários estágios para manter o contêiner leve no ambiente de execução.
Estes recursos fornecem informações adicionais sobre a criação de imagens de contêineres enxutas:
- Práticas recomendadas do Kubernetes: como e por que criar pequenas imagens de contêiner
- Sete práticas recomendadas para a compilação de contêineres