Cloud Intelligence™Cloud Intelligence™

Cloud Intelligence™

Rumo aos ambientes de desenvolvimento cloud-native [Parte 1 — Por quê]

By Yarel MamanApr 13, 20218 min read

Esta página também está disponível em English, Deutsch, Español, Français, Italiano e 日本語.

Em muitos projetos de software baseados em nuvem, há um processo de CI/CD desenhado e mantido para fazer o deploy das aplicações em ambientes de nuvem de forma eficiente, segura e produtiva.

Quase sempre o foco do CI/CD recai sobre o lado remoto, na nuvem, mas raramente se fala da etapa que vem logo antes: o desenvolvimento e os testes locais feitos no laptop da pessoa desenvolvedora.

Neste artigo, mostro por que muitos métodos comuns de desenvolvimento local estão longe do ideal e apresento uma demo de uma abordagem alternativa, cloud-native, que vai melhorar a sua produtividade (ou a do seu time de devs). 🌤

Foto de Christina @ wocintechchat.com no Unsplash

👨🏼‍💻 Desenvolvimento local

Não existe uma boa prática única que sirva para todo mundo — isso varia conforme as necessidades de cada organização ou projeto —, mas, em geral, o fluxo típico de CI/CD é mais ou menos assim:

Vamos olhar o que acontece logo antes do passo nº 1: o desenvolvimento local.

Na maior parte dos casos, isso é feito na máquina da pessoa dev: planejar, desenhar, escrever código, escrever testes, rodar testes e talvez um POC — e, em alguns cenários, validar manualmente se tudo funciona como esperado.

Muitos projetos investem pesado em setups locais que imitam o ambiente de nuvem, para testar se a nova feature ou correção funciona direito — sem causar regressão em outras funcionalidades.

Esse tipo de setup é usado principalmente para rodar certos testes automatizados (testes de sistema, de integração ou ponta a ponta). E, se você quiser, dá até para fazer testes manuais 😏.

Além disso, ambientes locais costumam ser usados para depurar o código. Algumas pessoas dev são adeptas convictas do que eu chamo de "desenvolvimento orientado a debug", mas a coisa vai além disso. O debug é útil quando você precisa investigar um bug cabeludo ou entender melhor um fluxo complexo.

Métodos comuns de desenvolvimento local

Imagine que você está em um projeto com vários microsserviços implantados em Kubernetes. Veja alguns métodos que costumo encontrar por aí:

  • Método docker-compose: escrever e manter manifests docker-compose (que acabam virando uma espécie de duplicata dos manifests YAML do K8s já existentes) para subir seus serviços + dependências de terceiros e rodar testes locais.
  • Método K8s local: rodar um cluster Kubernetes local com minikube/k3s/kind/etc. e fazer o deploy dos seus serviços localmente para teste, junto com um monte de scripts.
  • Método de suítes de testes: Makefiles ou scripts bash que disparam suítes de testes de integração por serviço, em que as dependências de terceiros sobem localmente como containers via Docker (o dockertest, por exemplo, é uma escolha popular para esse tipo de setup).

Todas essas são opções válidas para rodar e testar sua aplicação localmente.

Desvantagens dos métodos comuns de desenvolvimento local

Só que esses métodos também têm suas desvantagens, entre elas:

Falta de suporte local de terceiros

Algumas dependências de nuvem de terceiros simplesmente não oferecem uma forma de rodar localmente. Em alguns casos, dá para emular fazendo "mock/stub" das APIs, mas isso dá muito trabalho de manter e não reflete a realidade. É meio que uma trapaça. E se, por exemplo, uma das suas queries de banco estiver com problema? Como você vai descobrir isso nos testes ou no debug se não está "conversando" com uma instância real do banco?

Aplicações pesadas em recursos

Se a sua aplicação consome muitos recursos, talvez o laptop não dê conta. Por exemplo: você roda um teste de stress para reproduzir um bug de memory leak. Ou um bug que só aparece quando há 20 réplicas da aplicação rodando ao mesmo tempo. No fim das contas, o laptop trava 😅. E lá se vai a produtividade.

Desligamentos no meio do debug

Se você quer testar ou depurar um bug raro, que aparece a cada poucos dias, sua máquina local pode desligar, dormir ou hibernar — e você é obrigado a recomeçar tudo do zero.

Sem fonte única da verdade

No método docker-compose, você acaba violando o princípio da "fonte única da verdade" ao manter várias representações dos seus ambientes.

Rodar testes de integração ≠ integração de verdade

No método de suítes de testes, embora rodar testes de integração seja uma boa prática com vários benefícios (mesmo localmente), isso ainda não é o mesmo que integrar a sua mudança em um ambiente de nuvem real. Em alguns casos, há bugs e falhas de integração/deploy que passam batido (problemas de conectividade de rede com outros serviços, configuração, falhas no boot etc.).

As desvantagens descritas acima prejudicam a sua experiência de desenvolvimento e fazem você render menos. E, em muitos casos, não refletem a realidade (o clássico "na minha máquina funciona!") — então entregam pouco valor.

Existe um jeito melhor e mais moderno de encarar a fase de desenvolvimento local, ganhando em produtividade e usabilidade? Existe, sim.

☁️ **_Ambientes de desenvolvimento cloud-native_**

Agora vamos falar sobre ambientes remotos, cloud-native.

Ao transformar o seu ambiente local em um ambiente remoto, os problemas acima desaparecem. E, de quebra, sua experiência de desenvolvimento ganha um upgrade: vira algo moderno, confiável e cloud-native.

Como funcionam os ambientes de desenvolvimento cloud-native?

Aloque um ambiente de nuvem separado para cada pessoa do seu time, sendo uma réplica (não exata, mas bem próxima) dos seus ambientes de desenvolvimento/produção.

Com a abordagem de ambiente pessoal cloud-native, cada dev do time tem autonomia para usar o ambiente como bem entender — testar novas features e correções, reproduzir bugs ou até experimentar para aprender. Tudo num ambiente bem parecido com produção. ✌️

Daí dá para adaptar suas ferramentas em torno disso para tornar esses ambientes ainda mais úteis: rodar testes manuais ou automatizados, "espelhar" tráfego de produção para os ambientes pessoais e por aí vai.

Outra coisa bacana é fazer debug remoto ao vivo nesses ambientes, com isolamento total, sem interferir em outros. Você pode usar ferramentas prontas feitas para isso (vamos ver um exemplo no próximo capítulo).

Ganhos em produtividade, manutenibilidade e confiabilidade

Há vários motivos para migrar para ambientes de dev cloud-native, mas vou destacar meus oito favoritos.

Você vai:

  • Reaproveitar seus manifests YAML do K8s já existentes, sem precisar manter configurações de deploy duplicadas. (Mantendo o DRY 🌵)
  • Fazer deploy em ambiente de nuvem do mesmo jeito que faz para dev/prod, mas com isolamento extra.
  • Parar de depender do hardware local. A infra de nuvem escala fácil para cima quando você precisa rodar testes pesados — e escala para baixo no fim de semana.
  • Disparar testes longos no seu ambiente sem medo de o laptop desligar ou hibernar.
  • Detectar problemas de integração rapidamente, já que você usa APIs reais dos serviços de nuvem, e não mocks (por exemplo, dá para se comunicar com PubSub, Cloud SQL, Datastore e muitos outros serviços).
  • Aproveitar a infraestrutura nativa que a nuvem oferece para monitoramento, alertas, profiling, tracing e agregação de logs — exatamente como você já faz em produção.
  • Economizar tempo, sem precisar rodar scripts de setup complicados toda hora. A nuvem é rápida. Seu ambiente está pronto na hora em que você precisa.
  • Usar esses ambientes para aprender Kubernetes na prática (se você está começando), sem afetar outros ambientes.

Por fim — além das pessoas dev — engenheiros de DevOps/SRE também ganham com um ambiente pessoal: dá para testar mudanças de configuração de infra no próprio ambiente antes de mexer em ambientes compartilhados.

🙋‍♂️ Oi! Tenho uma pergunta!

Minha conta de nuvem vai estourar?

O primeiro impulso é provisionar uma réplica totalmente separada e independente das suas instâncias de nuvem para cada pessoa dev, isolada por projetos, em que cada projeto contém uma réplica da sua infraestrutura.

No geral, é uma boa prática — sobretudo do ponto de vista de segurança, porque garante um bom isolamento. Mas o custo dessa abordagem pode virar um problema, principalmente conforme a empresa ou o time crescem.

Como estamos falando de ambientes de desenvolvimento, que em muitos casos já são isolados de produção, dá para fazer o tradeoff entre custo e segurança.

Observação: esse tradeoff entre segurança e custo varia conforme a organização e os requisitos — entenda bem as implicações antes de decidir. Este guia de segurança do GKE pode ajudar.

Então, como fazer esse tradeoff? Você pode usar recursos de multi-tenancy dos seus serviços de nuvem para reduzir custos compartilhando recursos entre ambientes.

Isso costuma ser chamado de "Soft Multi-Tenancy": o "Soft" indica que o risco é limitado, já que vários usuários da mesma organização "confiável" compartilham os recursos.

Por exemplo, todas as pessoas dev de um time que trabalham na mesma aplicação podem compartilhar um cluster K8s, em que cada uma tem a stack inteira da aplicação replicada em seu próprio namespace K8s.

Outro exemplo: todas podem compartilhar uma única instância de banco, em que cada dev tem usuário, schema e tabelas próprios.

É essa a abordagem que vamos demonstrar no próximo capítulo.

É um pesadelo configurar e manter? 🤕🥸

Nem tanto! Você pode (e deve) automatizar tudo e tornar facilmente reproduzível, com princípios de Infrastructure as Code e GitOps.

Quando toda essa configuração está descrita como código, criar ou destruir um ambiente pessoal vira questão de poucos cliques. E o risco de erro humano também cai, já que o processo é automatizado.

Por que isso não cabe no escopo do ambiente de desenvolvimento? 🤔

O ambiente de desenvolvimento tradicional não é isolado: ele é compartilhado por todo o time. Você dá merge no seu código e seu colega dá merge no dele ao mesmo tempo — vira uma sopa. Não dá para depurar e fazer experimentos em um ambiente compartilhado quando o time cresce.

Além disso, a etapa do ambiente de desenvolvimento é tarde demais. Você já criou o PR, passou pelo code review e fez o merge. Aí descobre que suas mudanças derrubam o serviço na hora. Vira um bloqueio e precisa de revert/rollback/hotfix. É tempo perdido para você e para o time inteiro.

Ambientes pessoais ou ambientes efêmeros? 👀

Resposta curta: depende de você.

Em resumo: nos ambientes pessoais, você dedica um ambiente a uma pessoa específica, com propósito de longo prazo. Já nos ambientes efêmeros, você cria um ambiente para uma mudança específica (que é destruído depois que a mudança vai para o merge).

Meu colega

escreveu um ótimo artigo sobre ambientes efêmeros — vale a leitura.

Dá para fazer os dois, claro — mas, dependendo da necessidade, pode ser exagero.

Na Parte 2, vou ilustrar um setup desse tipo com uma arquitetura de exemplo, usando Terraform, ArgoCD e Telepresence. Vá para a Parte 2!