Bilhões de usuários interagem com os produtos e serviços do Google todos os anos. Principais produtos, como a Pesquisa, o Gmail, o Maps, o YouTube, o Chrome e agora também o Google Cloud, estão tão integrados à vida moderna que ajudam a definir a experiência do século 21. Esse impacto global é o resultado da qualidade comprovada das nossas ofertas e da expectativa de que o Google esteja sempre disponível.
No Google Cloud, introduzimos continuamente mudanças de código nos nossos produtos e serviços para garantir a melhor experiência do usuário possível, melhorando a segurança e a confiabilidade e obedecendo aos requisitos regulamentares e de compliance. Qualquer mudança, por menor que seja, pode causar problemas. Para lidar com esse risco, priorizamos a segurança de mudanças em todo o ciclo de vida delas.
Este documento explica como as equipes do Google Cloud se baseiam nas décadas de investimento do Google em excelência de desenvolvimento para implementar as práticas recomendadas de confiabilidade e padrões de engenharia que atendem às expectativas dos clientes do Google Cloud em relação à velocidade e confiabilidade do desenvolvimento.
A vida de uma mudança no Google Cloud
As equipes de produtos do Google Cloud compartilham grande parte do processo de gerenciamento e ferramentas com outras equipes de engenharia do Google. Implementamos uma abordagem padrão de desenvolvimento de software para gerenciamento de mudanças que prioriza a integração contínua (CI) e a entrega contínua (CD). A CI inclui a proposta, o teste e o envio de mudanças com frequência, muitas vezes várias vezes por dia para qualquer produto ou serviço. O CD é uma extensão da CI em que os engenheiros continuamente preparam versões candidatas com base no snapshot estável mais recente de uma base de código.
Essa abordagem prioriza a criação e a implementação de mudanças em etapas para clientes do Google Cloud o mais rápido possível, mas também com a maior segurança possível. Consideramos a segurança da mudança antes de escrever qualquer código e continuamos nos concentrando na segurança mesmo depois de lançar as mudanças na produção. Há quatro fases gerais no nosso modelo de gestão de mudanças: design, desenvolvimento, qualificação e implantação. Essas quatro fases são mostradas no diagrama a seguir e são discutidas com mais detalhes ao longo deste documento:
Segurança integrada
Sabemos que até mesmo pequenos erros no início do processo de desenvolvimento podem causar grandes problemas mais tarde e afetar significativamente a experiência do cliente. Como resultado, todas as mudanças importantes precisam começar com um documento de design aprovado. Temos um modelo de documento de design comum para que as equipes de engenharia proponham mudanças importantes. Este documento de design comum nos ajuda a avaliar as principais mudanças nos produtos do Google Cloud de forma consistente. O diagrama a seguir mostra como é nosso processo de design padrão para uma mudança importante:
A fase de design começa quando um desenvolvedor de software propõe uma mudança que aborda requisitos comerciais, técnicos, de custo e de manutenção. Depois de enviar a mudança, um processo abrangente de análise e aprovação é iniciado com especialistas sênior, incluindo especialistas em confiabilidade e segurança, e líderes técnicos. O trabalho para implementar a mudança só pode prosseguir depois que o engenheiro que propôs o design aborda todo o feedback dos especialistas e cada especialista aprova o design. Esse processo de design e revisão reduz a probabilidade de as equipes de produtos do Google Cloud começarem a trabalhar em mudanças que possam afetar negativamente os clientes na produção.
Seguro conforme desenvolvido
Nosso processo de desenvolvimento de código aumenta a qualidade e a confiabilidade do nosso código. Após a aprovação de uma mudança proposta, o processo de desenvolvimento começa com uma integração abrangente para novos engenheiros, incluindo treinamento, orientação e feedback detalhado sobre as mudanças propostas no código. Uma abordagem de desenvolvimento e teste em várias camadas com testes manuais e automatizados valida continuamente o código em todas as fases do desenvolvimento. Cada mudança de código é analisada cuidadosamente para garantir que atenda aos altos padrões do Google.
O diagrama a seguir mostra um fluxo de trabalho que ilustra aproximadamente como é nosso processo de desenvolvimento:
A fase de desenvolvimento começa quando um engenheiro começa a escrever código e testes de unidade e de integração correspondentes. Durante essa fase, o engenheiro pode executar os testes que ele escreveu e um conjunto de testes antes do envio para garantir que as adições e alterações de código sejam válidas. Depois de concluir as mudanças no código e executar testes, o engenheiro solicita uma revisão manual de outra pessoa que esteja familiarizada com o código. Esse processo de revisão humana geralmente é iterativo e pode resultar em outras revisões de código. Quando o autor e o revisor chegam a um consenso, o autor envia o código.
Padrões de programação garantem mudanças de alta qualidade
A cultura, as práticas e as ferramentas de engenharia do Google foram projetadas para garantir que nosso código seja correto, claro, conciso e eficiente. O desenvolvimento de código no Google acontece no nosso monorepo, o maior repositório de código integrado do mundo. O monorepo abriga milhões de arquivos de origem, bilhões de linhas de código e tem um histórico de centenas de milhões de confirmações chamadas de listas de mudanças. O crescimento é rápido, com dezenas de milhares de novas listas de mudanças adicionadas a cada dia útil. Os principais benefícios do monorepo são que ele facilita a reutilização de código, facilita o gerenciamento de dependências e reforça fluxos de trabalho consistentes de desenvolvedores em produtos e serviços.
A reutilização de código é útil porque já temos uma boa ideia sobre como o código reutilizado funciona na produção. Ao aproveitar o código de alta qualidade que já existe, as mudanças são inerentemente mais robustas e mais fáceis de manter no padrão necessário. Essa prática não apenas economiza tempo e esforço, mas também garante que a integridade geral da base de código permaneça alta, levando a produtos mais confiáveis.
Os serviços do Google Cloud que são criados com software de código aberto de alta qualidade podem complementar o monorepo com outro repositório, geralmente o Git, para usar a ramificação para gerenciar o software de código aberto.
Observação sobre o treinamento
O investimento na qualidade do código começa quando um engenheiro entra em uma equipe. Se o engenheiro for novo no Google ou não estiver familiarizado com a infraestrutura e a arquitetura da equipe, ele vai passar por uma integração extensa. Como parte desse processo de integração, eles estudam guias de estilo, práticas recomendadas e guias de desenvolvimento e realizam exercícios práticos manualmente. Além disso, novos engenheiros precisam de um nível extra de aprovação para cada envio individual de lista de mudanças. A aprovação para mudanças em uma determinada linguagem de programação é concedida por engenheiros que passaram por um conjunto rigoroso de expectativas com base na experiência deles e que alcançaram a legibilidade nessa linguagem de programação. Qualquer engenheiro pode ganhar legibilidade para uma linguagem de programação. A maioria das equipes tem vários aprovadores para as linguagens de programação em que codificam.
O deslocamento para a esquerda melhora a velocidade com segurança
O deslocamento para a esquerda é um princípio que move os testes e a validação para o início do processo de desenvolvimento. Esse princípio se baseia na nossa observação de que os custos aumentam drasticamente quanto mais tarde no processo de lançamento encontramos e corrigimos um bug. Em um caso extremo, considere um bug que um cliente encontra na produção. Esse bug pode afetar negativamente as cargas de trabalho e os aplicativos do cliente. Além disso, o cliente talvez precise passar pelo processo de atendimento ao cliente do Cloud antes que a equipe de engenharia relevante possa mitigar o bug. Se um engenheiro designado para resolver o problema for uma pessoa diferente da que introduziu originalmente a mudança que continha o bug, o novo engenheiro precisará se familiarizar com as mudanças no código, o que provavelmente aumentará o tempo necessário para reproduzir e eventualmente corrigir o bug. Todo esse processo exige muito tempo dos clientes e do suporte do Google Cloud e exige que os engenheiros abandonem o que estão fazendo para corrigir algo.
Por outro lado, considere um bug que um teste automatizado detecta enquanto um engenheiro trabalha em uma mudança em desenvolvimento. Quando o engenheiro encontra a falha no teste, ele pode corrigi-la imediatamente. Devido aos nossos padrões de programação, o engenheiro não conseguiria enviar a mudança com a falha no teste. Essa detecção precoce significa que o engenheiro pode corrigir o bug sem impacto no cliente e que não há sobrecarga de alternância de contexto.
O último cenário é infinitamente melhor para todos os envolvidos. Como resultado, ao longo dos anos, o Google Cloud investiu bastante nesse princípio de deslocamento para a esquerda, movendo os testes tradicionalmente realizados durante as fases de qualificação e implantação de mudanças diretamente para o ciclo de desenvolvimento. Hoje, todos os testes de unidade, exceto os maiores testes de integração, e análises estáticas e dinâmicas extensas são concluídas em paralelo enquanto um engenheiro propõe mudanças de código.
Os testes automatizados antes do envio aplicam padrões de programação
Os testes antes do envio são verificações que são executadas antes que as mudanças sejam enviadas em um determinado diretório. Os testes antes do envio podem ser testes de unidade e de integração específicos para uma mudança ou testes gerais (por exemplo, análise estática e dinâmica) que são executados para qualquer mudança. Historicamente, os testes antes do envio eram executados como a última etapa antes de alguém enviar uma mudança para uma base de código. Hoje, em parte devido ao princípio de deslocamento para a esquerda e à implementação da CI, executamos testes pré-envio de forma contínua enquanto um engenheiro faz mudanças de código em um ambiente de desenvolvimento e antes de mesclar as mudanças no monorepo. Um engenheiro também pode executar manualmente um conjunto de testes antes do envio com um único clique na interface de desenvolvimento. Todos os testes antes do envio são executados automaticamente para cada lista de mudanças antes de uma análise manual do código.
O conjunto de testes antes do envio geralmente abrange testes de unidade, testes de fuzz (fuzzing), testes de integração herméticos e análises de código estáticas e dinâmicas. Para mudanças nas bibliotecas principais ou no código usado amplamente no Google, os desenvolvedores executam um pré-envio global. Um pré-envio global testa a mudança em relação a todo o repositório de códigos do Google, minimizando o risco de que uma mudança proposta afete negativamente outros projetos ou sistemas.
Testes de unidade e integração
Testes completos são essenciais para o processo de desenvolvimento de código. Todos precisam escrever testes de unidade para mudanças de código, e rastreamos continuamente a cobertura de código no nível do projeto para garantir que estamos validando o comportamento esperado. Além disso, exigimos que qualquer jornada de usuário crítica tenha testes de integração em que validamos a funcionalidade de todos os componentes e dependências necessários.
Os testes de unidade e todos os testes de integração, exceto os maiores, são projetados para serem concluídos rapidamente e são executados de forma incremental com alto paralelismo em um ambiente distribuído, resultando em feedback de desenvolvimento rápido e contínuo automatizado.
Fuzzing
Enquanto os testes de unidade e de integração nos ajudam a validar o comportamento esperado com entradas e saídas predeterminadas, o fuzzing é uma técnica que bombardeia um aplicativo com entradas aleatórias, com o objetivo de expor falhas ou fraquezas ocultas que poderiam levar a vulnerabilidades de segurança ou falhas. O fuzzing nos permite identificar e resolver proativamente possíveis pontos fracos no software, melhorando a segurança e a confiabilidade geral dos nossos produtos antes que os clientes interajam com as mudanças. A aleatoriedade desses testes é particularmente útil porque os usuários às vezes interagem com nossos produtos de maneiras interessantes que não esperamos, e o fuzzing nos ajuda a considerar cenários que não consideramos manualmente.
Análise estática
As ferramentas de análise estática desempenham um papel fundamental na manutenção da qualidade do código nos fluxos de trabalho de desenvolvimento. A análise estática evoluiu significativamente desde os primeiros dias de inspeção com expressões regulares para identificar padrões de código C++ problemáticos. Atualmente, a análise estática abrange todas as linguagens de produção do Google Cloud e encontra padrões de código incorretos, ineficientes ou descontinuados.
Com frontends de compilador avançados e LLMs, podemos propor melhorias automaticamente enquanto os engenheiros estão escrevendo código. Cada mudança de código proposta é analisada com análises estáticas. À medida que novas verificações estáticas são adicionadas ao longo do tempo, todo o conjunto de códigos é verificado constantemente para verificar a conformidade, e as correções são geradas e enviadas automaticamente para revisão.
Análise dinâmica
Enquanto a análise estática se concentra na identificação de padrões de código conhecidos que podem levar a problemas, a análise dinâmica tem uma abordagem diferente. Ela envolve a compilação e a execução de código para descobrir problemas que só aparecem durante a execução, como violações de memória e condições de corrida. O Google tem um longo histórico de uso de análise dinâmica e até mesmo compartilhou várias ferramentas com a comunidade de desenvolvedores, incluindo as seguintes:
- AddressSanitizer: detecta erros de memória, como estouro de buffer e uso após a liberação.
- ThreadSanitizer (C++, Go): detecta disputas de dados e outros bugs de linha de execução.
- MemorySanitizer: descobre o uso de memória não inicializado.
Essas ferramentas e outras semelhantes são essenciais para detectar bugs complexos que não podem ser detectados apenas com a análise estática. Ao usar análises estáticas e dinâmicas, o Google se esforça para garantir que o código esteja bem estruturado, livre de problemas conhecidos e se comporte como esperado em cenários reais.
As revisões de código humanas validam as mudanças e os resultados dos testes
Quando um engenheiro atinge um marco crítico no código e quer integrá-lo ao repositório principal, ele inicia uma análise de código propondo uma lista de mudanças. Uma solicitação de análise de código consiste no seguinte:
- Uma descrição que capture o objetivo das mudanças e qualquer contexto adicional
- O código modificado
- Testes de unidade e de integração para o código modificado
- Resultados do teste automatizado antes do envio
É nesse ponto do processo de desenvolvimento que outro humano entra em ação. Um ou mais revisores designados examinam cuidadosamente a lista de mudanças para verificar a correção e a clareza, usando os testes e os resultados de pré-envio como guia. Cada diretório de código tem um conjunto de revisores designados responsáveis por garantir a qualidade desse subconjunto da base de código e cuja aprovação é necessária para fazer mudanças nesse diretório. Os revisores e engenheiros colaboram para detectar e resolver problemas que possam surgir com uma mudança de código proposta. Quando a lista de mudanças atende aos nossos padrões, um revisor dá a aprovação ("LGTM", sigla em inglês para "Tudo certo para mim"). No entanto, se o engenheiro ainda estiver em treinamento para a linguagem de programação usada, ele precisará de mais aprovação de um especialista que tenha conquistado a capacidade de leitura na linguagem de programação.
Depois que uma lista de mudanças é aprovada nos testes e nas verificações automatizadas e recebe um LGTM, o engenheiro que propôs a mudança só pode fazer alterações mínimas no código. Qualquer alteração substancial invalida a aprovação e exige outra rodada de análise. Até mesmo pequenas mudanças são sinalizadas automaticamente para os revisores originais. Depois que o engenheiro envia o código finalizado, ele passa por outra rodada completa de testes antes do envio, antes que a lista de mudanças seja mesclada ao monorepo. Se algum teste falhar, o envio será rejeitado, e o desenvolvedor e os revisores receberão um alerta para tomar medidas corretivas antes de tentar enviar as mudanças novamente.
Qualificação de lançamento segura
Embora o teste antes do envio seja abrangente, ele não é o fim do processo de teste no Google. As equipes geralmente têm outros testes, como testes de integração em grande escala, que não são viáveis para execução durante a revisão inicial do código (podem levar mais tempo para serem executados ou exigir ambientes de teste de alta fidelidade). Além disso, as equipes precisam estar cientes de todas as falhas causadas por fatores fora do controle, como mudanças em dependências externas.
É por isso que o Google exige uma fase de qualificação após a fase de desenvolvimento. Essa fase de qualificação usa um processo contínuo de criação e teste, conforme mostrado no diagrama abaixo:
Esse processo executa testes periodicamente para todo o código afetado por mudanças diretas ou indiretas desde a última execução. Todas as falhas são encaminhadas automaticamente para a equipe de engenharia responsável. Em muitos casos, o sistema consegue identificar automaticamente a lista de mudanças que causou a falha e revertê-la. Esses testes de integração em grande escala são executados em um espectro de ambientes de teste que vão de ambientes parcialmente simulados a locais físicos inteiros.
Os testes têm várias metas de qualificação, que variam de confiabilidade básica e segurança até lógica de negócios. Esses testes de qualificação incluem o teste do código para o seguinte:
- A capacidade de oferecer a funcionalidade necessária, que é testada usando testes de integração em grande escala.
- A capacidade de atender aos requisitos de negócios, que é testada com representações sintéticas das cargas de trabalho do cliente
- A capacidade de suportar falhas da infraestrutura subjacente, que é testada usando a injeção de falhas na pilha
- A capacidade de manter a capacidade de serviço, que é testada com frameworks de teste de carga
- A capacidade de reverter com segurança
Lançamentos seguros
Mesmo com os processos de desenvolvimento, teste e qualificação mais fortes, às vezes os defeitos se infiltram em ambientes de produção e afetam negativamente nossos usuários. Nesta seção, explicamos como o processo de lançamento do Google Cloud limita o impacto de mudanças com defeito e garante a detecção rápida de qualquer regressão. Aplicamos essa abordagem a todos os tipos de mudanças implantadas na produção, incluindo binários, configurações, atualizações de esquema, mudanças de capacidade e listas de controle de acesso.
Mudança de propagação e supervisão
Aplicamos uma abordagem consistente para implantar mudanças no Google Cloud para minimizar os impactos negativos nos clientes e isolar problemas em domínios de falha lógica e física. O processo é baseado em décadas de práticas de confiabilidade de SRE e no nosso sistema de monitoramento em escala planetária para detectar e mitigar mudanças ruins o mais rápido possível. A detecção rápida nos permite notificar os clientes mais rapidamente e tomar medidas corretivas para evitar sistematicamente que falhas semelhantes aconteçam novamente.
A maioria dos produtos do Google Cloud é regional ou zonal. Isso significa que um produto regional executado na Região A é independente do mesmo produto executado na Região B. Da mesma forma, um produto zonal executado na Zona C na Região A é independente do mesmo produto zonal executado na Zona D na Região A. Essa arquitetura minimiza o risco de uma falha que afete outras regiões ou outras zonas em uma única região. Alguns serviços, como o IAM ou o console do Google Cloud, fornecem uma camada consistente em todo o mundo que abrange todas as regiões. Por isso, eles são chamados de serviços globais. Os serviços globais são reproduzidos em todas as regiões para evitar pontos únicos de falha e minimizar a latência. A plataforma de lançamento compartilhada do Google Cloud sabe se um serviço é zonal, regional ou global e orquestra as mudanças de produção de forma adequada.
O processo de lançamento do Google Cloud divide todas as réplicas de um serviço implantado em vários locais de destino em ondas. As ondas iniciais incluem um pequeno número de réplicas, com atualizações em série. As ondas iniciais balanceiam a proteção da maioria das cargas de trabalho do cliente com a maximização da exposição à diversidade de carga de trabalho para detectar problemas o mais cedo possível e incluem cargas de trabalho sintéticas que imitam padrões comuns de carga de trabalho do cliente.
Se o lançamento continuar bem-sucedido à medida que as réplicas de serviço forem atualizadas nos locais de destino, as ondas de lançamento subsequentes vão aumentar progressivamente de tamanho e introduzir mais paralelismo. Embora seja necessário algum paralelismo para contabilizar o número de locais do Google Cloud, não permitimos atualizações simultâneas em locais que estão em ondas diferentes. Se uma onda se estender até a noite ou um fim de semana, ela poderá concluir a progressão, mas nenhuma nova onda poderá começar até o início do horário comercial da equipe que gerencia o lançamento.
O diagrama a seguir é um exemplo de fluxo de trabalho que ilustra a lógica de lançamento que usamos no Google Cloud para produtos e serviços regionais:
O processo de lançamento do Google Cloud usa o serviço de análise de teste canário (CAS) para automatizar o teste A/B durante todo o lançamento. Algumas réplicas se tornam canários, ou seja, uma implantação parcial e temporária de uma mudança em um serviço, e as réplicas restantes compõem o grupo de controle que não inclui a mudança. Cada etapa do processo de lançamento tem um tempo de cozimento para detectar problemas de queima lenta antes de avançar para a próxima etapa, garantindo que todas as funcionalidades de um serviço sejam bem exercidas e que possíveis anomalias sejam detectadas pelo CAS. O tempo de cozimento é definido com cuidado para equilibrar a detecção de problemas de queima lenta com a velocidade de desenvolvimento. Os lançamentos do Google Cloud geralmente levam uma semana.
Este diagrama mostra uma visão geral rápida do fluxo de trabalho do CAS:
O fluxo de trabalho começa com a ferramenta de lançamento implantando a mudança na réplica do canário. Em seguida, a ferramenta de lançamento solicita um veredito do CAS. O CAS avalia a réplica de canário em relação ao grupo de controle e retorna um veredito de PASS ou FAIL. Se algum indicador de integridade falhar, um alerta será gerado para os proprietários do serviço, e a etapa em execução do lançamento será pausada ou revertida. Se a mudança causar interrupções para clientes externos, um incidente externo será declarado e os clientes afetados serão notificados usando o serviço Personalized Service Health. Os incidentes também acionam uma análise interna. A filosofia post-mortem do Google garante que o conjunto correto de ações corretivas seja identificado e aplicado para minimizar a probabilidade de falhas semelhantes acontecerem novamente.
Monitoramento de indicadores e segurança pós-lançamento
Os defeitos de software nem sempre se manifestam instantaneamente, e alguns podem exigir circunstâncias específicas para serem acionados. Por isso, continuamos monitorando os sistemas de produção depois que o lançamento é concluído. Ao longo dos anos, notamos que, mesmo que um lançamento não acione problemas imediatamente, um lançamento incorreto ainda é o culpado mais provável de um incidente de produção. Por isso, nossos manuais de produção instruem os responsáveis pela resposta a incidentes a correlacionar os lançamentos recentes com os problemas observados e a reverter um lançamento recente por padrão se os responsáveis pela resposta a incidentes não puderem descartar as mudanças recentes como a causa raiz do incidente.
O monitoramento pós-lançamento é baseado no mesmo conjunto de indicadores de monitoramento que usamos para análises A/B automatizadas durante um período de lançamento. A filosofia de monitoramento e alerta do Google Cloud combina dois tipos de monitoramento: introspectivo (também conhecido como white-box) e sintético (também conhecido como black-box). O monitoramento introspectivo usa métricas como utilização da CPU, utilização da memória e outros dados de serviço internos. O monitoramento sintético analisa o comportamento do sistema do ponto de vista do cliente, rastreando as taxas de erro do serviço e as respostas ao tráfego sintético dos serviços de detecção. O monitoramento sintético é orientado a sintomas e identifica problemas ativos, enquanto o monitoramento introspectivo nos permite diagnosticar problemas confirmados e, em alguns casos, identificar problemas iminentes.
Para ajudar na detecção de incidentes que afetam apenas alguns clientes, agrupamos as cargas de trabalho dos clientes em coortes de cargas de trabalho relacionadas. Os alertas são acionados assim que a performance de um grupo se desvia da norma. Esses alertas permitem detectar regressões específicas do cliente, mesmo que a performance agregada pareça ser normal.
Proteção da cadeia de suprimentos de software
Sempre que as equipes do Google Cloud introduzem mudanças, usamos uma verificação de segurança chamada Autorização binária para Borg (BAB) para proteger nossa cadeia de suprimentos de software e os clientes do Cloud contra riscos internos. A BAB começa na fase de revisão de código e cria uma trilha de auditoria do código e da configuração implantada na produção. Para garantir a integridade da produção, a BAB só aceita mudanças que atendam aos seguintes critérios:
- São à prova de adulteração e assinadas
- Vêm de uma parte do processo de criação identificada e de um local de origem identificado
- Foram revisadas e aprovadas explicitamente por uma parte distinta do autor do código
Se você quiser aplicar alguns dos mesmos conceitos no seu ciclo de vida de desenvolvimento de software, incluímos os principais conceitos da BAB em uma especificação aberta chamada Níveis da cadeia de suprimentos para artefatos de software (SLSA). O SLSA atua como um framework de segurança para desenvolver e executar códigos em ambientes de produção.
Conclusão
O Google Cloud se baseia nas décadas de investimento do Google em excelência de desenvolvimento. A integridade do código e da produção são princípios culturais instilados em todas as equipes de engenharia do Google. Nosso processo de análise de design garante que as implicações no código e na integridade da produção sejam consideradas desde o início. Nosso processo de desenvolvimento, baseado no princípio de deslocamento para a esquerda e em testes extensivos, garante que as ideias de design sejam implementadas de forma segura e correta. Nosso processo de qualificação expande ainda mais os testes para abranger integrações em grande escala e dependências externas. Por fim, nossa plataforma de lançamento nos permite aumentar progressivamente a confiança de que uma determinada mudança realmente se comporta como esperado. Nossa abordagem, que abrange desde a concepção até a produção, permite atender às expectativas dos clientes do Google Cloud em relação à velocidade e confiabilidade do desenvolvimento.