CI/CD para arquiteturas de microsserviços

Azure

Os ciclos de lançamento mais rápidos são uma das principais vantagens das arquiteturas de microsserviços. Mas sem um bom processo de CI/CD, não conseguirá a agilidade que os microsserviços prometem. Este artigo descreve os desafios e recomenda algumas abordagens para o problema.

O que é a CI/CD?

Quando falamos de CI/CD, estamos a falar de vários processos relacionados: integração contínua, entrega contínua e implementação contínua.

  • Integração contínua. As alterações de código são frequentemente intercaladas no ramo principal. Os processos de compilação e teste automatizados garantem que o código no ramo principal é sempre de qualidade de produção.

  • Entrega contínua. Quaisquer alterações de código que passem no processo de CI são publicadas automaticamente num ambiente semelhante a produção. A implementação no ambiente de produção em direto pode exigir aprovação manual, mas é automatizada. O objetivo é que o seu código esteja sempre pronto para ser implementado na produção.

  • Implementação contínua. As alterações de código que passam os dois passos anteriores são implementadas automaticamente em produção.

Eis alguns objetivos de um processo de CI/CD robusto para uma arquitetura de microsserviços:

  • Cada equipa pode criar e implementar os serviços que possui de forma independente, sem afetar ou perturbar outras equipas.

  • Antes de uma nova versão de um serviço ser implementada na produção, é implementada em ambientes de desenvolvimento/teste/QA para validação. As portas de qualidade são impostas em cada fase.

  • Uma nova versão de um serviço pode ser implementada lado a lado com a versão anterior.

  • Existem políticas de controlo de acesso suficientes.

  • Para cargas de trabalho em contentores, pode confiar nas imagens de contentor que são implementadas na produção.

Por que motivo é importante um pipeline de CI/CD robusto

Numa aplicação monolítica tradicional, existe um único pipeline de compilação cujo resultado é o executável da aplicação. Todos os feeds de trabalho de desenvolvimento neste pipeline. Se for encontrado um erro de alta prioridade, uma correção tem de ser integrada, testada e publicada, o que pode atrasar o lançamento de novas funcionalidades. Pode mitigar estes problemas ao ter módulos bem fatorizados e ao utilizar ramos de funcionalidades para minimizar o impacto das alterações de código. Mas à medida que a aplicação se torna mais complexa e são adicionadas mais funcionalidades, o processo de lançamento de um monólito tende a tornar-se mais frágil e susceptível de quebrar.

Seguindo a filosofia dos microsserviços, nunca deve haver um comboio de lançamento longo onde todas as equipas têm de entrar na fila. A equipa que cria o serviço "A" pode lançar uma atualização em qualquer altura, sem aguardar que as alterações no serviço "B" sejam intercaladas, testadas e implementadas.

Diagrama de um monólito CI/CD

Para atingir uma velocidade de lançamento elevada, o pipeline de versão tem de ser automatizado e altamente fiável para minimizar o risco. Se lançar para produção uma ou mais vezes por dia, as regressões ou interrupções do serviço têm de ser raras. Ao mesmo tempo, se uma atualização incorreta for implementada, tem de ter uma forma fiável de reverter ou reverter rapidamente para uma versão anterior de um serviço.

Desafios

  • Muitas bases de código independentes pequenas. Cada equipa é responsável por criar o seu próprio serviço, com o seu próprio pipeline de compilação. Em algumas organizações, as equipas podem utilizar repositórios de código separados. Repositórios separados podem levar a uma situação em que o conhecimento de como criar o sistema é distribuído por equipas e ninguém na organização sabe como implementar toda a aplicação. Por exemplo, o que acontece num cenário de recuperação após desastre, se precisar de implementar rapidamente num novo cluster?

    Mitigação: tenha um pipeline unificado e automatizado para criar e implementar serviços, para que este conhecimento não esteja "oculto" em cada equipa.

  • Várias linguagens e arquiteturas. Com cada equipa a utilizar a sua própria combinação de tecnologias, pode ser difícil criar um único processo de compilação que funcione em toda a organização. O processo de compilação tem de ser suficientemente flexível para que todas as equipas possam adaptá-lo à sua escolha de linguagem ou arquitetura.

    Mitigação: contentorize o processo de compilação para cada serviço. Desta forma, o sistema de compilação só precisa de ser capaz de executar os contentores.

  • Testes de integração e carga. Com as equipas a lançarem atualizações ao seu próprio ritmo, pode ser um desafio conceber testes ponto a ponto robustos, especialmente quando os serviços têm dependências noutros serviços. Além disso, a execução de um cluster de produção completo pode ser dispendiosa, pelo que é improvável que todas as equipas executem o seu próprio cluster completo em escalas de produção, apenas para testes.

  • Gestão de versões. Todas as equipas devem conseguir implementar uma atualização para produção. Isso não significa que todos os membros da equipa têm permissões para o fazer. Contudo, ter uma função centralizada do Gestor de Versões pode reduzir a velocidade das implementações.

    Mitigação: quanto mais o seu processo de CI/CD for automatizado e fiável, menos deve haver necessidade de uma autoridade central. Dito isto, poderá ter políticas diferentes para lançar atualizações de funcionalidades principais em comparação com pequenas correções de erros. Ser descentralizado não significa governação zero.

  • Atualizações de serviço. Quando atualiza um serviço para uma nova versão, este não deve interromper outros serviços que dependem do mesmo.

    Mitigação: utilize técnicas de implementação como a versão azul-verde ou canário para alterações não interruptivas. Para alterar a API, implemente a nova versão lado a lado com a versão anterior. Desta forma, os serviços que consomem a API anterior podem ser atualizados e testados para a nova API. Veja Atualizar serviços, abaixo.

Monorepo vs. multi-repositório

Antes de criar um fluxo de trabalho CI/CD, tem de saber como a base de código será estruturada e gerida.

  • As equipas trabalham em repositórios separados ou num monorepo (repositório único)?
  • Qual é a sua estratégia de ramificação?
  • Quem pode emitir código para a produção? Existe uma função de gestor de versões?

A abordagem monorepo tem vindo a ganhar favores, mas há vantagens e desvantagens para ambos.

  Monorepo Vários repositórios
Vantagens Partilha de código
Mais fácil uniformizar o código e as ferramentas
Mais fácil refatorizar código
Deteção – vista única do código
Limpar a propriedade por equipa
Potencialmente menos conflitos de intercalação
Ajuda a impor a desacoplação de microsserviços
Desafios As alterações ao código partilhado podem afetar vários microsserviços
Maior potencial para conflitos de intercalação
As ferramentas têm de ser dimensionadas para uma base de código grande
Controlo de acesso
Processo de implementação mais complexo
Mais difícil partilhar código
Mais difícil impor normas de codificação
Gestão de dependências
Difundir base de código, deteção fraca
Falta de infraestrutura partilhada

Atualizar serviços

Existem várias estratégias para atualizar um serviço que já está em produção. Aqui, vamos abordar três opções comuns: Atualização sem interrupção, implementação azul-verde e versão canary.

Atualizações sem interrupção

Numa atualização sem interrupção, vai implementar novas instâncias de um serviço e as novas instâncias começam imediatamente a receber pedidos. À medida que as novas instâncias surgem, as instâncias anteriores são removidas.

Exemplo. No Kubernetes, as atualizações sem interrupção são o comportamento predefinido quando atualiza a especificação do pod para uma Implementação. O Controlador de implementação cria um novo ReplicaSet para os pods atualizados. Em seguida, aumenta verticalmente o novo ReplicaSet ao reduzir verticalmente o antigo, para manter a contagem de réplicas pretendida. Não elimina pods antigos até que os novos estejam prontos. O Kubernetes mantém um histórico da atualização, para que possa reverter uma atualização, se necessário.

Exemplo. O Azure Service Fabric utiliza a estratégia de atualização sem interrupção por predefinição. Esta estratégia é mais adequada para implementar uma versão de um serviço com novas funcionalidades sem alterar as APIs existentes. O Service Fabric inicia uma implementação de atualização ao atualizar o tipo de aplicação para um subconjunto dos nós ou um domínio de atualização. Em seguida, avança para o domínio de atualização seguinte até que todos os domínios sejam atualizados. Se não for possível atualizar um domínio de atualização, o tipo de aplicação reverte para a versão anterior em todos os domínios. Tenha em atenção que um tipo de aplicação com vários serviços (e se todos os serviços forem atualizados como parte de uma implementação de atualização) é propenso a falhas. Se um serviço não conseguir atualizar, toda a aplicação será revertida para a versão anterior e os outros serviços não serão atualizados.

Um dos desafios das atualizações sem interrupção é que, durante o processo de atualização, uma mistura de versões antigas e novas está em execução e a receber tráfego. Durante este período, qualquer pedido pode ser encaminhado para uma das duas versões.

Para alterações interruptivas da API, uma boa prática é suportar ambas as versões lado a lado, até que todos os clientes da versão anterior sejam atualizados. Veja Controlo de versões da API.

Implementação azul-verde

Numa implementação azul-verde, implemente a nova versão juntamente com a versão anterior. Depois de validar a nova versão, muda todo o tráfego de uma só vez da versão anterior para a nova versão. Após o comutador, irá monitorizar a aplicação quanto a problemas. Se algo correr mal, pode voltar à versão antiga. Partindo do princípio de que não existem problemas, pode eliminar a versão antiga.

Com uma aplicação monolítica ou de N camadas mais tradicional, a implementação azul-verde geralmente significava o aprovisionamento de dois ambientes idênticos. Implementaria a nova versão num ambiente de teste e, em seguida, redirecionaria o tráfego do cliente para o ambiente de teste, por exemplo, ao trocar endereços VIP. Numa arquitetura de microsserviços, as atualizações ocorrem ao nível do microsserviço, pelo que normalmente implementaria a atualização no mesmo ambiente e utilizaria um mecanismo de deteção de serviços para trocar.

Exemplo. No Kubernetes, não precisa de aprovisionar um cluster separado para efetuar implementações a azul-verde. Em vez disso, pode tirar partido dos seletores. Crie um novo Recurso de implementação com uma nova especificação de pod e um conjunto diferente de etiquetas. Crie esta implementação sem eliminar a implementação anterior ou modificar o serviço que aponta para a mesma. Assim que os novos pods estiverem em execução, pode atualizar o seletor do serviço para corresponder à nova implementação.

Uma desvantagem da implementação azul-verde é que, durante a atualização, está a executar o dobro dos pods para o serviço (atual e seguinte). Se os pods precisarem de muitos recursos de CPU ou memória, poderá ter de aumentar horizontalmente o cluster temporariamente para lidar com o consumo de recursos.

Versão de canário

Numa versão canary, implementa uma versão atualizada para um pequeno número de clientes. Em seguida, monitorize o comportamento do novo serviço antes de o implementar para todos os clientes. Isto permite-lhe efetuar uma implementação lenta de forma controlada, observar dados reais e detetar problemas antes de todos os clientes serem afetados.

Uma versão canary é mais complexa de gerir do que a atualização azul-verde ou sem interrupção, porque tem de encaminhar dinamicamente os pedidos para diferentes versões do serviço.

Exemplo. No Kubernetes, pode configurar um Serviço para abranger dois conjuntos de réplicas (um para cada versão) e ajustar manualmente as contagens de réplicas. No entanto, esta abordagem é bastante detalhada, devido à forma como o Kubernetes faz o balanceamento de carga entre pods. Por exemplo, se tiver um total de 10 réplicas, só pode mudar o tráfego em incrementos de 10%. Se estiver a utilizar uma malha de serviços, pode utilizar as regras de encaminhamento de malha de serviço para implementar uma estratégia de lançamento canário mais sofisticada.

Passos seguintes