Desafios e bastidores de uma migração de infraestrutura
Se podemos ter uma certeza quando o assunto é tecnologia e seu universo, é que cedo ou tarde a sua melhor escolha e estratégia de hoje estarão obsoletas amanhã. E tão estranha e ousada quanto possa parecer essa afirmação, é o fato de que: CALMA, TÁ TUDO BEM.
Parte da magia do desenvolvimento de software e toda sua competência é a capacidade de evolução e adaptação, fazendo com que parte do desafio dos times de desenvolvimento seja acompanhar e adequar seus produtos (no caso sistemas), tanto de um ponto de vista técnico, como por exemplo: otimizando soluções visando reduções de custo e melhor performance para atender uma massa maior de usuários, quanto de um ponto de vista de produto, pensando em técnicas e metodologias que possam ajudar na fácil evolução e manutenibilidade daquilo que está sendo construído.
Contudo, para além do código, design, especificações, jornadas de usuário e afins, literalmente um behind the scenes… Temos a nossa querida infraestrutura. Nossos containers, firewalls, load balancers, banco de dados… e tantas outras peculiaridades que nos permitem criar, compartilhar e usufruir de toda essa maravilha que é a transformação digital.
E é por isso que estamos aqui hoje, para conversar sobre a fragilidade e importância deste componente (a infra), e quais os desafios e aprendizados que tivemos ao entender que ficamos para trás e que seria necessário uma evolução.
O caso de uso
O ano era 2017 e na até então “recém-nascida” Creditas começava a se notar uma crescente necessidade de otimização de relacionamento com as pessoas clientes. Junto ao crescimento das nossas operações, a diversificação dos canais de atendimento para os diversos perfis de clientes diferentes que iam entrando em nossa esteira tornou-se uma realidade, e com ela, obviamente, novos problemas foram criados.
Eram utilizados múltiplos endereços de e-mail para se comunicar com a mesma pessoa durante uma única jornada, tínhamos diversos celulares corporativos para uso extensivo de WhatsApp, o que simplesmente impossibilitava a centralização de um histórico. Além disso, tínhamos ainda atendimento via telefone que exigia a instalação de softwares de VOIP nas estações de trabalho das pessoas consultoras, tornando muito alto o custo de manutenção deste canal.
Diante destas dores e até mesmo (porque não) algumas oportunidades, surgiu a versão inicial (ainda em produção como nosso legado) do que hoje chamamos de Integradora de Comunicação, que a nível de software resumida e basicamente é um serviço de backend (Python) que se integra com todos os nossos fornecedores de canais para envio e recebimento de mensagens e outra aplicação de frontend (Javascript + React) para agregar e disponibilizar toda a informação relacionada à comunicação de determinada pessoa para a nossa área de atendimento.
Quando falamos de “fornecedores de canais”, estamos nos referindo aos diversos serviços que contratamos para prover funcionalidades que não são o nosso foco, como por exemplo um serviço de envio de emails. Para nós, é muito mais prático e barato contratar um serviço desses ao invés de mantermos nossa própria infraestrutura de email com diversos servidores de SMTP e ter de lidar com detalhes como MTAs, MDAs, MUAs e etc.
E justamente sobre este legado e sua infraestrutura que iremos falar hoje.
Ok… Onde estávamos em relação à infraestrutura?
Fato era que podíamos estar em um cenário bem pior, contudo, já estávamos trabalhando com conteinerização, já tínhamos uma pipeline de CI/CD automatizada, ainda que fora do padrão adotado pela Creditas, já utilizávamos os serviços da AWS tanto de host, que no caso eram EC2 + ECS quanto os demais serviços de apoio como RDS, Elasticache (Redis), S3, Systems Manager (Parameter Store).
Mas, ainda que funcional, tínhamos problemas.
Como um time de desenvolvimento, a tendência era que o nosso foco fosse maior em desenvolver nossa plataforma e menos em detalhes de infraestrutura, o que acabou por se tornar um risco, pois adicionar qualquer novo componente como um simples SQS, por exemplo, exigia que soubéssemos desde detalhes mais básicos do SQS até detalhes de rede para tornar possível as conexões entre todos os componentes, e como IaC (Infra As Code) não era uma realidade facilmente, esses detalhes se perdiam com o tempo na cabeça das pessoas, tornando delicada qualquer alteração, pois não existia muita margem para erros, uma vez que o rollback não era simples e no fim o processo era executado manualmente no painel da AWS.
Com o passar do tempo, foi se moldando uma nova forma de lidar com infraestrutura na Creditas, métodos de IaC foram adotados, padrões estabelecidos, times dedicados para suportar os times de desenvolvimento e serem responsáveis pela base foram surgindo e, como mencionado anteriormente, dado nossa realidade e objetivo em momentos distintos, acabamos ficando para trás e perdendo coisas como:
- IaC
- API Gateway
- Automatic Auto Scaling
- Ferramentas desenvolvidas internamente para facilitar a gestão da infraestrutura e ainda manter o foco no desenvolvimento.
Entender estes e os fatores de risco já mencionados foram a base para darmos o próximo passo…
Onde queríamos chegar?
No Nucleon!
What is Nucleon?
O Nucleon é uma plataforma interna da Creditas, e como o próprio projeto se descreve:
“Nucleon is Creditas’ PaaS, and its main objective is to enable developers to focus on solving business needs instead of managing infrastructure.”
Em outras palavras, o Nucleon consiste em alguns componentes construídos sobre outras ferramentas e serviços, como Terraform e Kubernetes. Com ele, alguns arquivos de manifesto para descrever, versionar e lançar sua infraestrutura, e com o suporte da sua CLI (Command Line Interface) carinhosamente nomeada CLIditas, os times conseguem se preocupar menos com detalhes de infraestrutura (como criá-los, o que configurar, como configurar e etc.) e começam a criar esses componentes de uma forma simples (um arquivo .yaml), deixando o trabalho pesado para a plataforma, que por baixo dos panos configura todos os recursos necessários para tornar a aplicação disponível.
Além da facilidade em gerir a infraestrutura, como todas as aplicações passaram a seguir minimamente um mesmo padrão, foi possível incluir coisas em comum para todos, como:
- Ferramentas de monitoramento:
- Com todas as apps no mesmo ecossistema, o distributed trace ficou muito mais confiável e assertivo.
- Cruzar logs entre as aplicações passou a ser menos doloroso, não existia mais a surpresa de que parte do fluxo poderia estar logando em outra stack.
- Configurar os agents nas aplicações passou a ser tão simples quanto colocar uma linha na lista de dependências do projeto, pois todo o restante já estava configurado no cluster.
- API Gateways
- Estar na infraestrutura do Nucleon facilitou a utilização do API Gateway que temos, trazendo os ganhos de segurança e redução de workload da aplicação por conta de alguns plugins que passaram a fazer validações que antes eram feitas internamente na app.
Por fim, mas não menos importante, o processo de lançamento de versões das aplicações passou a ser basicamente:
“cliditas build”
“cliditas push”
“cliditas deploy”
E não mais uma porção de shell scripts interagindo diretamente com cada recurso da AWS no detalhe para poder realizar um Deploy.
Como nos organizamos
Dividimos para conquistar.
Inicialmente não tínhamos um prazo para iniciar e tão pouco concluir a migração. Contudo, com o passar do tempo, problemas e até mesmo algumas exigências foram surgindo e, dentre elas, a necessidade de trocarmos a ferramenta de APM que estávamos utilizando no legado.
Com isso, o prazo: 1 mês
Dado o tamanho da nossa squad e o tempo que tínhamos, precisamos nos organizar de uma forma que pudéssemos concluir a migração sem impactar nas outras demandas, então a primeira coisa que fizemos após elencar as pessoas que iriam atuar, foi olhar para tudo que tínhamos, mapear e documentar cada recurso e cada processo que deveria ser migrado e colocar isso em uma timeline, para entendermos se conseguiríamos ou se precisariam ser feitos algum tipo de corte ou solução alternativa.
Essa timeline consistia em:
- Mapear as configurações dos containers (réplicas, memória, cpu)
- Mapear as configurações dos serviços de apoio (RDS, S3, Elasticache)
- Criar todos os manifestos que definem essa infraestrutura (IaC) para os ambientes de Produção e Staging
- Definir uma estratégia para a migração dos dados
- Definir uma estratégia para a virada da aplicação para o novo ambiente
- Em caso de necessidade de downtime, estimar o tempo e alinhar com todos os clientes internos qual seria o melhor momento para executar.
- Testar todos os passos anteriores repetidas vezes no ambiente de staging
- Criar o ambiente de produção, ainda que sem tráfego, para aguardar o dia da virada
Ufa! Sim, eram muitos detalhes.
Começamos então mapeando as configurações dos container e notamos (como o esperado) que não existiam muitas configurações distintas, como o processo de criação era manual, acabava que todos eles tinham o mesmo tamanho, dentre outros detalhes, comparado ao que serviu de base. E isso era um problema, apesar de ter facilitado o nosso mapeamento inicial.
O problema era que todos os contêineres estavam over-provisioned, ou seja, todos eles exigiam recursos de infraestrutura de forma exagerada (memória e tempo de CPU), mas alguns não chegavam a consumir 30% da reserva.
Diante disso, e como sabíamos que parte do manifesto do Nucleon/Cliditas é sobre reserva e limitação de recursos, para cada tipo de container (20+), começamos a analisar as métricas de consumo de infra para entender qual a dimensão de cada, para que, no novo ambiente, eles já fossem criados de maneira correta e trazendo para nós uma bela redução de custo.
Passada essa reestruturação dos containers da aplicação, começamos a olhar para os nossos outros componentes de apoio, e com a ajuda do time de Data Engineering conseguimos entender o tamanho do nosso banco e estruturar um plano de migração. Foi aí que entendemos que não teria jeito, iríamos ter que parar a aplicação para migrar o banco de dados.
Cogitamos utilizar o DMS em live mode e descobrimos que tentativas passadas neste mesmo banco geraram indisponibilidade, começamos então a realizar diversos testes no ambiente de staging para conseguir estimar qual seria esse tempo de downtime e quais os passos deveriam ser feitos pré o pós migração.
Entendemos que precisaríamos de cerca de 5 horas para:
- Desligar a aplicação no ambiente legado
- Iniciar o sync dos dados
- Configurar o acesso ao banco de dados na aplicação no novo ambiente
- Ligar a aplicação (que teve a infra previamente criada) no novo ambiente
- Testar
- Realizar o shift da carga para o novo ambiente
Quanto aos dados que tínhamos em nosso cache, tivemos a triste surpresa de que eles não eram tão dispensáveis assim, e entramos para a estatística de….
Nem tudo são flores…
Inicialmente havíamos mapeado que o Elasticache (serviço que nos abstraia o Redis) era utilizado em nossos sistemas para:
- Implementar parte da stack de mensageria do Celery
- Armazenar chaves temporárias para controles de algumas regras de negócio, ex: bloqueio de envio de mensagens em determinado período de tempo
- Controle de eventos consumidos e processados de outros sistemas, como uma forma de evitar o processamento duplicado dado uma determinada retenção
- Histórico de mensagens lidas pelas pessoas consultoras para cada cliente com a qual elas estavam conversando.
Quanto aos três primeiros use cases estávamos confortáveis, pois:
- Para o Celery, uma vez a aplicação web que era o entrypoint dos dados pausada, poderíamos esperar as filas serem exauridas e então desligar todos os outros containers.
- Para os casos de bloqueio e outros que tinham um TTL configurado, entendemos que ao longo do dia e durante a migração, todas as chaves iriam expirar de acordo com seu caso de uso e no dia pós migração tudo estaria funcionando normalmente.
- Para o controle de eventos externos, entendemos que poderíamos reduzir o tempo de retenção das chaves para que pudessem se enquadrar nos novos moldes, como era uma implementação antiga e desatualizada aproveitamos para otimizar.
E por fim, o cache que não era cache.
Em meio às descobertas e testes, identificamos que uma das features do legado estava fazendo um uso equivocado do Cache, em outras palavras, dados que deveriam estar sendo persistidos no Postgres estavam sendo persistidos no Redis, e perder eles significava uma quebra de experiência gigantesca entre nossa operação e as nossas pessoas clientes.
Após algumas análises, entendemos que era possível consultar estes dados e mais simples do que levá-los para o banco, exigindo um grande refactor na app, seria mais simples fazer um “copy paste” do Redis de origem (legado) para o Redis de destino (nova infra).
Após algumas pesquisas e tentativas de desenvolver algo in-house para isso, tivemos a indicação de outros times da Creditas que haviam feito um processo similar para utilizar o Rump. Este script basicamente utiliza os comandos SCAN, DUMP e RESTORE do Redis e faz a cópia entre as instâncias.
Além do Rump, tivemos de desenvolver um script para limpar keys indesejáveis que identificamos ao analisar os casos de uso que tínhamos no legado e separamos em múltiplos databases do Redis os casos que queríamos ignorar na cópia. Dessa forma, em questão de segundos após a aplicação ser totalmente desligada, paralelamente ao processo de sync do Postgres, pudemos realizar o sync do Redis, reduzindo assim o nosso tempo de downtime.
Mas no fim... tudo deu certo 🙂
Como mencionado algumas vezes anteriormente, mesmo tendo pouco tempo e um time reduzido, nós conseguimos prever muitas coisas e com isso testar, re-testar, validar, mudar de ideia, e testar de novo… Tudo no ambiente de Staging, sem comprometer nenhuma operação de produção e sempre visando o menor impacto possível.
Essa iniciativa sem dúvida foi um dos nossos maiores acertos.
O trabalho em equipe, sem dúvida, foi primordial. Apesar de ter resumido aqui, é importante ressaltar que esse foi um trabalho a 500 mãos, e que o tempo todo tivemos apoio de todos os times internos que cuidam das partes de infra (Foundation), banco de dados (Data Engineering), Produto (para alinhamento com clientes).
Espero que tenham se divertido até aqui.
Por hoje é só 😁