CI/CD para microsserviços

Ciclos de lançamento mais rápidos são uma grande vantagem das arquiteturas de microsserviços. Sem um processo de CI/CD (integração contínua e entrega contínua) confiável, você perde a agilidade que os microsserviços fornecem. Este artigo descreve os desafios comuns de CI/CD em arquiteturas de microsserviços e recomenda abordagens para criar, validar, proteger e implantar serviços de forma independente.

O que é CI/CD?

CI/CD refere-se a vários processos relacionados: integração contínua, entrega contínua e implantação contínua.

  • Integração contínua (CI): As alterações de código são mescladas com frequência no branch principal. Os processos automatizados de compilação e teste garantem que o código no ramo principal esteja sempre com qualidade de produção.

  • Entrega contínua (CD): As alterações de código que passam o processo de CI são publicadas automaticamente em um ambiente semelhante à produção. A implantação no ambiente de produção ao vivo pode exigir aprovação manual, mas é automatizada. A meta é que seu código esteja sempre pronto para ser implantado em produção.

  • Implantação contínua: As alterações de código que passam as duas etapas anteriores são implantadas automaticamente na produção.

Considere as seguintes metas de um processo de CI/CD robusto para uma arquitetura de microsserviços:

  • Cada equipe pode criar e implantar os serviços que ela possui independentemente, sem afetar ou interromper outras equipes.

  • Antes de uma nova versão de um serviço ser implantada em produção, ela é implantada em ambientes de desenvolvimento/testes e de QA para validação. Barreiras de qualidade são aplicadas em cada estágio.

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

  • Há políticas de controle de acesso suficientes em vigor. Pipelines se autenticam no Azure com credenciais federadas de curta duração, em vez de segredos de longa duração.

  • Para cargas de trabalho conteinerizadas, você pode confiar nas imagens de contêiner que estão implantadas na produção. Essa confiança é estabelecida por meio de imagens assinadas, de atestados de SBOM (fatura de software de materiais) e da verificação de vulnerabilidade imposta no pipeline.

Por que um pipeline de CI/CD sólido é fundamental

Em um aplicativo monolítico tradicional, um único pipeline de build produz o executável do aplicativo. Todo o trabalho de desenvolvimento se alimenta desse pipeline. Se a equipe encontrar um bug de alta prioridade, a correção deverá ser integrada, testada e publicada, o que pode atrasar a versão dos novos recursos. Você pode reduzir esses problemas ao usar módulos bem estruturados e ramificações de funcionalidade para limitar o impacto das alterações no código. Mas à medida que o aplicativo se torna mais complexo e mais recursos são adicionados, o processo de lançamento de um monólito tende a se tornar mais complicado e provavelmente falhará.

De acordo com a filosofia dos microsserviços, nunca deveria haver um longo ciclo de lançamento coordenado em que todas as equipes tenham de esperar sua vez. A equipe que cria o serviço A pode liberar uma atualização quando escolher e não precisa aguardar as alterações no serviço B para mesclar, testar e implantar.

Diagrama que compara CI/CD para arquiteturas de monolito versus microsserviços.

Para alcançar uma alta velocidade de lançamento, seu pipeline de lançamento deve ser automatizado e altamente confiável para minimizar o risco. Se você liberar para a produção uma ou mais vezes por dia, regressões ou interrupções de serviço devem ser raras. Ao mesmo tempo, se você implantar uma atualização com problema, deverá ter uma maneira confiável de voltar rapidamente para uma versão anterior do serviço ou avançar para ela.

Challenges

  • Muitas pequenas bases de código independentes: Cada equipe é responsável por criar seu próprio serviço, com seu próprio pipeline de build. Em algumas organizações, as equipes podem usar repositórios de código separados. Repositórios separados podem dispersar o conhecimento de como criar o sistema entre equipes. Como resultado, ninguém na organização sabe como implantar todo o aplicativo.

    Mitigação: Tenha um pipeline unificado e automatizado ou pelo menos uma infraestrutura de pipeline comum para criar e implantar serviços para que esse conhecimento não fique oculto em cada equipe. Modelos de pipeline reutilizáveis, como GitHub Actions fluxos de trabalho reutilizáveis ou modelos Azure Pipelines, ajudam a padronizar as etapas de compilação, teste, verificação e implantação em todos os serviços.

  • Vários idiomas e estruturas: Cada equipe usa sua própria combinação de tecnologias, portanto, pode ser difícil criar um único processo de build que funcione em toda a carga de trabalho. O processo de build deve ser flexível o suficiente para que cada equipe possa adaptá-lo para seu idioma ou estrutura escolhido.

    Mitigação: Conteinerize o processo de build para cada serviço para que o sistema de build só precise executar os contêineres. Plataformas como GitHub Actions, Azure Pipelines e tarefas Registro de Contêiner do Azure podem criar e publicar imagens de contêiner de forma consistente, independentemente da linguagem de origem.

  • Integração e teste de carga: O Teams libera atualizações em seu próprio ritmo, portanto, pode ser desafiador criar testes robustos de ponta a ponta, especialmente quando os serviços têm dependências de outros serviços. A execução de um cluster de produção completo pode ser dispendiosa, portanto, é improvável que cada equipe execute seu próprio cluster completo em escala de produção apenas para teste.

    Mitigação: Use ambientes efêmeros de visualização, como namespaces por pull request no Kubernetes ou ambientes do Aplicativos de Contêiner do Azure criados sob demanda. Utilize testes de contrato para identificar problemas de integração logo no início, sem exigir uma réplica completa do ambiente de produção.

  • Gerenciamento de versão: Todas as equipes devem ser capazes de implantar uma atualização na produção. Esse requisito não significa que cada membro da equipe tenha permissões para implantar. Uma função centralizada do gerenciador de lançamentos pode reduzir a velocidade de implantação.

    Mitigação: Quanto mais seu processo de CI/CD for automatizado e confiável, menos você precisará de uma autoridade central. Você ainda pode ter políticas diferentes para lançar grandes atualizações de funcionalidades em comparação com pequenas correções de bugs. Uma abordagem descentralizada não significa governança zero. Imponha aprovações usando Azure Pipelines environments and approvals ou GitHub Actions deployment environments and required reviewers e defina a política no lado do cluster como código usando Azure Policy for AKS (Serviço de Kubernetes do Azure) ou OPA Gatekeeper.

  • Atualizações de serviço: Quando você atualiza um serviço para uma nova versão, a atualização não deve fazer com que outros serviços que dependem dele falhem.

    Mitigação: Use técnicas de implantação como versões azul-verde ou canário para alterações sem quebra. Para alterações críticas na API, implante a nova versão em paralelo com a versão anterior. Com essa abordagem, os serviços que consomem a API anterior podem ser atualizados e testados para a nova API. Para obter mais informações, consulte Serviços de atualização.

  • Gerenciamento de identidades e segredos de pipelines: Segredos de longa duração de entidades de serviço armazenados em pipelines são uma fonte frequente de comprometimento e sobrecarga operacional. Os segredos da entidade de serviço expiram, podem vazar e precisam ser rotacionados em muitos pipelines independentes de microserviços.

    Mitigação: Autentique os pipelines no Azure com federação de identidade de carga de trabalho, que usa o OpenID Connect (OIDC), para que nenhum segredo do cliente seja armazenado no pipeline. Para obter mais informações, consulte Identidades de carga de trabalho para Azure Pipelines e Configurar o OpenID Connect no Azure para o GitHub Actions. Armazene todos os segredos restantes em Azure Key Vault e referencie-os no runtime.

  • Segurança da cadeia de suprimentos: Tudo o que você envia para produção deve ser rastreável para o código e as dependências dos quais ele foi criado. Os microsserviços aumentam o número de imagens, registros e pipelines, o que aumenta sua superfície de ataque da cadeia de suprimentos.

    Mitigação: Assine imagens de contêineres usando Notation e Key Vault e verifique as assinaturas no momento da admissão usando Integridade de Imagem do AKS ou Ratify. Gere um SBOM como um artefato de build. Verifique o código, as dependências e os pipelines usando o Microsoft Defender para Nuvem segurança de DevOps e o GitHub Advanced Security. Examine imagens de runtime usando Microsoft Defender para Contêineres. Exija que todas as verificações sejam aprovadas antes que uma versão possa continuar.

Monorepo vs. multirepo

Antes de criar um fluxo de trabalho de CI/CD, você deve saber como a base de código é estruturada e gerenciada, incluindo:

  • Se as equipes trabalham em repositórios separados ou em um monorepo.
  • Sua estratégia de ramificação.
  • Quem pode enviar o código por push para a produção e se existe um gerenciador de lançamentos.

As equipes usam amplamente as duas abordagens em produção. Sua escolha depende da topologia da equipe, da maturidade das ferramentas e da quantidade de código compartilhada entre os serviços.

  Monorepo Vários repositórios
Vantagens - Compartilhamento de código

– Mais fácil padronizar o código e as ferramentas

– Mais fácil de refatorar código

- Capacidade de descoberta (uma única exibição do código)
- Responsabilidade clara por equipe

- Potencialmente menos conflitos de mesclagem

- Ajuda a impor a desassociação de microsserviços
desafios – Alterações no código compartilhado podem afetar vários microsserviços

– Maior potencial para conflitos de mesclagem

- As ferramentas devem escalar para uma grande base de código

- Controle de acesso

– Processo de implantação mais complexo
– Mais difícil de compartilhar código

– Mais difícil impor padrões de codificação

– Gerenciamento de dependências

- Base de código difusa, baixa capacidade de descoberta

- Falta de infraestrutura compartilhada

Independentemente do modelo que você escolher, use gatilhos com escopo de caminho em seus pipelines, como filtros de caminho no GitHub Actions ou caminhos de gatilho no Azure Pipelines. Gatilhos com escopo por caminho ajudam a garantir que apenas os microsserviços afetados sejam recompilados e reimplantados a cada commit.

Atualizar serviços

Há várias estratégias para atualizar um serviço que já está em produção, incluindo atualização gradual, implantação azul-verde e lançamento canário. Esses padrões geralmente são coordenados por meio de um fluxo de trabalho do GitOps. Para obter mais informações, consulte GitOps e entrega progressiva.

Atualizações sem interrupção

Em uma atualização sem interrupção, você implanta novas instâncias de um serviço e as novas instâncias começam a receber solicitações imediatamente. À medida que as novas instâncias ficam prontas, as instâncias anteriores são removidas.

Exemplo no Kubernetes: No Kubernetes, as atualizações sem interrupção são o comportamento padrão quando você atualiza a especificação de pod para uma implantação. O controlador de implantação cria um novo ReplicaSet para os pods atualizados. Em seguida, ele aumenta o novo ReplicaSet enquanto reduz o ReplicaSet anterior para manter o número desejado de réplicas. Não exclui os pods anteriores até que os novos pods estejam prontos. O Kubernetes mantém um histórico da atualização, para que você possa reverter uma atualização, se necessário.

Exemplo em Aplicativos de Contêiner: Os Aplicativos de Contêiner usam revisões para gerenciar atualizações sem interrupção. Quando você implanta uma nova revisão, os Aplicativos de Contêiner podem mudar gradualmente o tráfego da revisão anterior para a nova revisão usando regras de divisão de tráfego. Se a nova revisão encontrar problemas, você poderá reverter redirecionando o tráfego para a revisão anterior. Você pode configurar várias revisões ativas simultaneamente e controlar a porcentagem de tráfego que cada revisão recebe.

Um desafio das atualizações sem interrupção é que, durante o processo de atualização, uma combinação de versões anteriores e novas são executadas e recebem tráfego. Durante esse período, o sistema pode rotear qualquer solicitação para qualquer versão.

Para mudanças que quebram a compatibilidade da API, uma boa prática é dar suporte a ambas as versões em paralelo, até que todos os clientes da versão anterior sejam atualizados. Para obter mais informações, consulte o controle de versão da API.

Implantação azul-verde

Em uma implantação azul-verde, você implanta a nova versão junto com a versão anterior. Depois de validar a nova versão, você alterna todo o tráfego de uma vez da versão anterior para a nova versão. Após a mudança, você monitora o aplicativo em busca de problemas. Se houver um problema, você pode redirecionar o tráfego de volta para a versão anterior. Se não houver problemas, você poderá excluir a versão anterior.

Com um aplicativo monolítico ou em N camadas mais tradicional, a implantação azul-verde geralmente significa que você cria dois ambientes idênticos. Você implanta a nova versão em um ambiente de preparo e redireciona o tráfego do cliente para esse ambiente, como trocando um endereço IP virtual. Em uma arquitetura de microsserviços, as atualizações ocorrem no nível do microsserviço, portanto, você normalmente implanta a atualização no mesmo ambiente e usa um mecanismo de descoberta de serviço para alternar o tráfego.

Exemplo no Kubernetes: No Kubernetes, você não precisa criar um cluster separado para fazer implantações azul-verde. Em vez disso, você pode aproveitar os seletores. Crie um novo recurso de deployment com uma nova especificação de pod e um conjunto distinto de rótulos. Crie essa implantação, mas não exclua a implantação anterior ou modifique o serviço que aponta para ela. Depois que os novos pods forem executados, você poderá atualizar o seletor do serviço para corresponder à nova implantação.

Uma desvantagem do modelo de implantação azul-verde é que, durante a atualização, você executa dois conjuntos de pods do serviço (atual e próximo) simultaneamente. Se os pods consumirem recursos substanciais de CPU ou memória, talvez seja necessário escalar horizontalmente o cluster temporariamente para atender à maior demanda de recursos.

Versão de lançamento canário

Em uma versão canário, você implanta uma versão atualizada em um pequeno subconjunto de clientes e, em seguida, monitora o comportamento do novo serviço antes de distribuí-lo para todos os clientes. Com essa abordagem, você pode implantar gradualmente de forma controlada, monitorar dados reais e identificar problemas antes que eles afetem todos os clientes.

Uma versão canário é mais complexa de gerenciar do que uma atualização azul-verde ou sem interrupção, pois você deve rotear solicitações dinamicamente para diferentes versões do serviço.

Exemplo no Kubernetes: No Kubernetes, você pode configurar um serviço para abranger dois conjuntos de réplicas (um para cada versão) e ajustar as contagens de réplica manualmente. No entanto, essa abordagem é grosseira devido à forma como o Kubernetes distribui a carga entre os pods. Por exemplo, se você tiver um total de 10 réplicas, só poderá redirecionar o tráfego em incrementos de 10%. Se você usar uma malha de serviço, poderá usar as regras de roteamento de malha de serviço para implementar uma estratégia de lançamento canário mais sofisticada.

Exemplo no Container Apps: No Container Apps, você pode usar traffic split para enviar uma porcentagem definida do tráfego para uma nova revisão (como 10% para v2 enquanto 90% permanece em v1) e ajustar os pesos à medida que a confiança aumenta, sem exigir uma malha de serviço externa.

Entrega progressiva e GitOps

Para equipes que operam muitos microsserviços no Kubernetes, um modelo baseado em pull do GitOps complementa os exemplos anteriores baseados em push. O estado de cluster desejado reside no Git e um operador no cluster reconcilia o cluster com esse estado. A CI compila, testa, verifica, assina e envia por push a imagem. O CD reconcilia o cluster com o manifesto. Essa separação oferece trilhas de auditoria e torna a recuperação de desastres (DR) mais fácil. Isso também elimina a necessidade de o runner de CI manter credenciais diretas do cluster.

Próximas Etapas