Editar

Crie um pipeline de CI/CD para microsserviços no Kubernetes com o Azure DevOps e o Helm

Azure Kubernetes Service (AKS)
Azure Container Registry
Azure DevOps

Pode ser um desafio criar um processo confiável de integração contínua/entrega contínua (CI/CD) para uma arquitetura de microsserviços. As equipes individuais devem ser capazes de liberar serviços de forma rápida e confiável, sem interromper outras equipes ou desestabilizar o aplicativo como um todo.

Este artigo descreve um exemplo de pipeline de CI/CD para implantar microsserviços no Serviço Kubernetes do Azure (AKS). Cada equipa e projeto é diferente, por isso não tome este artigo como um conjunto de regras rígidas e rápidas. Em vez disso, destina-se a ser um ponto de partida para projetar seu próprio processo de CI/CD.

Os objetivos de um pipeline de CI/CD para microsserviços hospedados pelo Kubernetes podem ser resumidos da seguinte forma:

  • As equipes podem criar e implantar seus serviços de forma independente.
  • As alterações de código que passam pelo processo de CI são implantadas automaticamente em um ambiente semelhante ao de produção.
  • Os portões de qualidade são aplicados em cada estágio do gasoduto.
  • Uma nova versão de um serviço pode ser implantada lado a lado com a versão anterior.

Para obter mais informações, consulte CI/CD para arquiteturas de microsserviços.

Suposições

Para fins deste exemplo, aqui estão algumas suposições sobre a equipe de desenvolvimento e a base de código:

  • O repositório de código é um monorepo, com pastas organizadas por microsserviço.
  • A estratégia de ramificação da equipe é baseada no desenvolvimento baseado em troncos.
  • A equipe usa ramificações de liberação para gerenciar lançamentos. Versões separadas são criadas para cada microsserviço.
  • O processo CI/CD usa o Azure Pipelines para criar, testar e implantar os microsserviços no AKS.
  • As imagens de contêiner para cada microsserviço são armazenadas no Registro de Contêiner do Azure.
  • A equipe usa gráficos Helm para empacotar cada microsserviço.
  • Um modelo de implantação por push é usado, onde o Azure Pipelines e agentes associados executam implantações conectando-se diretamente ao cluster AKS.

Essas suposições orientam muitos dos detalhes específicos do pipeline de CI/CD. No entanto, a abordagem básica descrita aqui deve ser adaptada para outros processos, ferramentas e serviços, como Jenkins ou Docker Hub.

Alternativas

A seguir estão alternativas comuns que os clientes podem usar ao escolher uma estratégia de CI/CD com o Serviço Kubernetes do Azure:

  • Como alternativa ao uso do Helm como uma ferramenta de gerenciamento e implantação de pacotes, o Kustomize é uma ferramenta de gerenciamento de configuração nativa do Kubernetes que introduz uma maneira livre de modelos para personalizar e parametrizar a configuração do aplicativo.
  • Como alternativa ao uso do Azure DevOps para repositórios e pipelines Git, os Repositórios GitHub podem ser usados para repositórios Git privados e públicos, e as Ações do GitHub podem ser usadas para pipelines de CI/CD.
  • Como alternativa ao uso de um modelo de push deployment, o gerenciamento da configuração do Kubernetes em grande escala pode ser feito usando GitOps (pull deployment model), onde um operador Kubernetes no cluster sincroniza o estado do cluster, com base na configuração armazenada em um repositório Git.

Compilações de validação

Suponha que um desenvolvedor esteja trabalhando em um microsserviço chamado Serviço de Entrega. Ao desenvolver um novo recurso, o desenvolvedor verifica o código em uma ramificação de recurso. Por convenção, as ramificações de recursos são nomeadas feature/*.

CI/CD workflow

O arquivo de definição de compilação inclui um gatilho que filtra pelo nome da ramificação e pelo caminho de origem:

trigger:
  batch: true
  branches:
    include:
    # for new release to production: release flow strategy
    - release/delivery/v*
    - refs/release/delivery/v*
    - master
    - feature/delivery/*
    - topic/delivery/*
  paths:
    include:
    - /src/shipping/delivery/

Usando essa abordagem, cada equipe pode ter seu próprio pipeline de construção. Somente o /src/shipping/delivery código verificado na pasta dispara uma compilação do Serviço de Entrega. Enviar confirmações para uma ramificação que corresponde ao filtro aciona uma compilação de CI. Neste ponto do fluxo de trabalho, a compilação CI executa algumas verificações mínimas de código:

  1. Crie o código.
  2. Execute testes de unidade.

O objetivo é manter os tempos de compilação curtos para que o desenvolvedor possa obter feedback rápido. Quando o recurso estiver pronto para ser mesclado no master, o desenvolvedor abrirá um PR. Esta operação dispara outra compilação de CI que executa algumas verificações adicionais:

  1. Crie o código.
  2. Execute testes de unidade.
  3. Crie a imagem do contêiner de tempo de execução.
  4. Execute verificações de vulnerabilidade na imagem.

Diagram showing ci-delivery-full in the Build pipeline.

Nota

No Azure DevOps Repos, você pode definir políticas para proteger ramificações. Por exemplo, a política pode exigir uma compilação de CI bem-sucedida mais uma aprovação de um aprovador para mesclar no mestre.

Compilação completa de CI/CD

Em algum momento, a equipe está pronta para implantar uma nova versão do serviço de entrega. O gerenciador de versão cria uma ramificação da ramificação principal com este padrão de nomenclatura: release/<microservice name>/<semver>. Por exemplo, release/delivery/v1.0.2.

Diagram showing ci-delivery-full in the Build pipeline and cd-delivery in the Release pipeline.

A criação dessa ramificação aciona uma compilação de CI completa que executa todas as etapas anteriores, além de:

  1. Envie a imagem do contêiner para o Registro de Contêiner do Azure. A imagem é marcada com o número da versão retirado do nome da filial.
  2. Execute helm package para empacotar o gráfico Helm para o serviço. O gráfico também é marcado com um número de versão.
  3. Empurre o pacote Helm para o Container Registry.

Supondo que essa compilação seja bem-sucedida, ela dispara um processo de implantação (CD) usando um pipeline de liberação do Azure Pipelines. Esse pipeline tem as seguintes etapas:

  1. Implante o gráfico Helm em um ambiente de QA.
  2. Um aprovador assina antes que o pacote passe para a produção. Consulte Controle de implantação de liberação usando aprovações.
  3. Remarque a imagem do Docker para o namespace de produção no Registro de Contêiner do Azure. Por exemplo, se a tag atual for myrepo.azurecr.io/delivery:v1.0.2, a tag de produção será myrepo.azurecr.io/prod/delivery:v1.0.2.
  4. Implante o gráfico Helm no ambiente de produção.

Mesmo em um monorepo, essas tarefas podem ser direcionadas para microsserviços individuais para que as equipes possam implantar com alta velocidade. O processo tem algumas etapas manuais: aprovação de RPs, criação de ramificações de liberação e aprovação de implantações no cluster de produção. Estes passos são manuais; eles podem ser automatizados se a organização preferir.

Isolamento de ambientes

Você terá vários ambientes onde implantará serviços, incluindo ambientes para desenvolvimento, teste de fumaça, teste de integração, teste de carga e, finalmente, produção. Esses ambientes precisam de algum nível de isolamento. No Kubernetes, você pode escolher entre isolamento físico e isolamento lógico. Isolamento físico significa implantação em clusters separados. O isolamento lógico usa namespaces e políticas, conforme descrito anteriormente.

Nossa recomendação é criar um cluster de produção dedicado junto com um cluster separado para seus ambientes de desenvolvimento/teste. Use o isolamento lógico para separar ambientes dentro do cluster de desenvolvimento/teste. Os serviços implantados no cluster de desenvolvimento/teste nunca devem ter acesso a armazenamentos de dados que armazenam dados corporativos.

Processo de construção

Quando possível, empacote seu processo de compilação em um contêiner do Docker. Essa configuração permite que você crie artefatos de código usando o Docker e sem configurar um ambiente de compilação em cada máquina de compilação. Um processo de compilação em contêiner facilita a expansão do pipeline de CI adicionando novos agentes de compilação. Além disso, qualquer desenvolvedor da equipe pode criar o código simplesmente executando o contêiner de compilação.

Usando compilações de vários estágios no Docker, você pode definir o ambiente de compilação e a imagem de tempo de execução em um único Dockerfile. Por exemplo, aqui está um Dockerfile que cria um aplicativo .NET:

FROM mcr.microsoft.com/dotnet/core/runtime:3.1 AS base
WORKDIR /app

FROM mcr.microsoft.com/dotnet/core/sdk:3.1 AS build
WORKDIR /src/Fabrikam.Workflow.Service

COPY Fabrikam.Workflow.Service/Fabrikam.Workflow.Service.csproj .
RUN dotnet restore Fabrikam.Workflow.Service.csproj

COPY Fabrikam.Workflow.Service/. .
RUN dotnet build Fabrikam.Workflow.Service.csproj -c release -o /app --no-restore

FROM build AS testrunner
WORKDIR /src/tests

COPY Fabrikam.Workflow.Service.Tests/*.csproj .
RUN dotnet restore Fabrikam.Workflow.Service.Tests.csproj

COPY Fabrikam.Workflow.Service.Tests/. .
ENTRYPOINT ["dotnet", "test", "--logger:trx"]

FROM build AS publish
RUN dotnet publish Fabrikam.Workflow.Service.csproj -c Release -o /app

FROM base AS final
WORKDIR /app
COPY --from=publish /app .
ENTRYPOINT ["dotnet", "Fabrikam.Workflow.Service.dll"]

Este Dockerfile define vários estágios de compilação. Observe que o estágio nomeado usa o tempo de execução do .NET, enquanto o estágio nomeado basebuild usa o SDK completo do .NET. O build estágio é usado para criar o projeto .NET. Mas o contêiner de tempo de execução final é construído a partir do , que contém apenas o tempo de execução e é significativamente menor do que a baseimagem completa do SDK.

Construindo um corredor de teste

Outra boa prática é executar testes de unidade no contêiner. Por exemplo, aqui está parte de um arquivo do Docker que cria um executor de teste:

FROM build AS testrunner
WORKDIR /src/tests

COPY Fabrikam.Workflow.Service.Tests/*.csproj .
RUN dotnet restore Fabrikam.Workflow.Service.Tests.csproj

COPY Fabrikam.Workflow.Service.Tests/. .
ENTRYPOINT ["dotnet", "test", "--logger:trx"]

Um desenvolvedor pode usar esse arquivo Docker para executar os testes localmente:

docker build . -t delivery-test:1 --target=testrunner
docker run delivery-test:1

O pipeline de CI também deve executar os testes como parte da etapa de verificação de compilação.

Observe que esse arquivo usa o comando Docker para executar os testes, não o comando Docker ENTRYPOINTRUN .

  • Se você usar o RUN comando, os testes serão executados toda vez que você criar a imagem. Ao usar ENTRYPOINTo , os testes são opt-in. Eles são executados somente quando você segmenta explicitamente o testrunner palco.
  • Um teste com falha não faz com que o comando Docker build falhe. Dessa forma, você pode distinguir falhas de compilação de contêiner de falhas de teste.
  • Os resultados do teste podem ser salvos em um volume montado.

Práticas recomendadas de contêiner

Aqui estão algumas outras práticas recomendadas a serem consideradas para contêineres:

  • Defina convenções em toda a organização para tags de contêiner, controle de versão e convenções de nomenclatura para recursos implantados no cluster (pods, serviços e assim por diante). Isso pode facilitar o diagnóstico de problemas de implantação.

  • Durante o ciclo de desenvolvimento e teste, o processo CI/CD construirá muitas imagens de contêiner. Apenas algumas dessas imagens são candidatas a lançamento, e então apenas alguns desses candidatos a lançamento serão promovidos à produção. Tenha uma estratégia clara de controle de versão para saber quais imagens estão atualmente implantadas na produção e para ajudar a reverter para uma versão anterior, se necessário.

  • Sempre implante tags de versão de contêiner específicas, não latest.

  • Use namespaces no Registro de Contêiner do Azure para isolar imagens aprovadas para produção de imagens que ainda estão sendo testadas. Não mova uma imagem para o namespace de produção até que esteja pronto para implantá-la na produção. Se você combinar essa prática com o controle de versão semântico de imagens de contêiner, isso pode reduzir a chance de implantar acidentalmente uma versão que não foi aprovada para lançamento.

  • Siga o princípio do menor privilégio executando contêineres como um usuário sem privilégios. No Kubernetes, você pode criar uma política de segurança de pod que impede que os contêineres sejam executados como root.

Gráficos Helm

Considere usar o Helm para gerenciar a criação e a implantação de serviços. Aqui estão alguns dos recursos do Helm que ajudam com CI/CD:

  • Muitas vezes, um único microsserviço é definido por vários objetos do Kubernetes. Helm permite que esses objetos sejam empacotados em um único gráfico Helm.
  • Um gráfico pode ser implantado com um único comando Helm em vez de uma série de comandos kubectl.
  • Os gráficos são explicitamente versionados. Use o Helm para lançar uma versão, exibir versões e reverter para uma versão anterior. Acompanhamento de atualizações e revisões, usando versionamento semântico, juntamente com a capacidade de reverter para uma versão anterior.
  • Os gráficos Helm usam modelos para evitar a duplicação de informações, como rótulos e seletores, em muitos arquivos.
  • O Helm pode gerenciar dependências entre gráficos.
  • Os gráficos podem ser armazenados em um repositório Helm, como o Azure Container Registry, e integrados ao pipeline de compilação.

Para obter mais informações sobre como usar o Registro de Contêiner como um repositório Helm, consulte Usar o Registro de Contêiner do Azure como um repositório Helm para seus gráficos de aplicativo.

Um único microsserviço pode envolver vários arquivos de configuração do Kubernetes. Atualizar um serviço pode significar tocar em todos esses arquivos para atualizar seletores, rótulos e tags de imagem. Helm trata estes como um único pacote chamado gráfico e permite que você atualize facilmente os arquivos YAML usando variáveis. O Helm usa uma linguagem de modelo (baseada em modelos Go) para permitir que você escreva arquivos de configuração YAML parametrizados.

Por exemplo, aqui está parte de um arquivo YAML que define uma implantação:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ include "package.fullname" . | replace "." "" }}
  labels:
    app.kubernetes.io/name: {{ include "package.name" . }}
    app.kubernetes.io/instance: {{ .Release.Name }}
  annotations:
    kubernetes.io/change-cause: {{ .Values.reason }}

...

  spec:
      containers:
      - name: &package-container_name fabrikam-package
        image: {{ .Values.dockerregistry }}/{{ .Values.image.repository }}:{{ .Values.image.tag }}
        imagePullPolicy: {{ .Values.image.pullPolicy }}
        env:
        - name: LOG_LEVEL
          value: {{ .Values.log.level }}

Você pode ver que o nome da implantação, os rótulos e as especificações do contêiner usam parâmetros de modelo, que são fornecidos no momento da implantação. Por exemplo, na linha de comando:

helm install $HELM_CHARTS/package/ \
     --set image.tag=0.1.0 \
     --set image.repository=package \
     --set dockerregistry=$ACR_SERVER \
     --namespace backend \
     --name package-v0.1.0

Embora seu pipeline de CI/CD possa instalar um gráfico diretamente no Kubernetes, recomendamos criar um arquivo de gráficos (arquivo .tgz) e enviar o gráfico para um repositório Helm, como o Azure Container Registry. Para obter mais informações, consulte Empacotar aplicativos baseados no Docker em gráficos de leme no Azure Pipelines.

Revisões

Os gráficos de leme sempre têm um número de versão, que deve usar o controle de versão semântico. Um gráfico também pode ter um appVersionarquivo . Este campo é opcional e não tem de estar relacionado com a versão do gráfico. Algumas equipes podem querer aplicar versões separadas das atualizações dos gráficos. Mas uma abordagem mais simples é usar um número de versão, então há uma relação 1:1 entre a versão do gráfico e a versão do aplicativo. Dessa forma, você pode armazenar um gráfico por versão e implantar facilmente a versão desejada:

helm install <package-chart-name> --version <desiredVersion>

Outra boa prática é fornecer uma anotação de causa de alteração no modelo de implantação:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ include "delivery.fullname" . | replace "." "" }}
  labels:
     ...
  annotations:
    kubernetes.io/change-cause: {{ .Values.reason }}

Isso permite visualizar o campo de causa de alteração para cada revisão, usando o kubectl rollout history comando. No exemplo anterior, a causa de alteração é fornecida como um parâmetro de gráfico Helm.

kubectl rollout history deployments/delivery-v010 -n backend
deployment.extensions/delivery-v010
REVISION  CHANGE-CAUSE
1         Initial deployment

Você também pode usar o comando para exibir o helm list histórico de revisões:

helm list
NAME            REVISION    UPDATED                     STATUS        CHART            APP VERSION     NAMESPACE
delivery-v0.1.0 1           Sun Apr  7 00:25:30 2020    DEPLOYED      delivery-v0.1.0  v0.1.0          backend

Azure DevOps Pipeline

No Azure Pipelines, os pipelines são divididos em pipelines de compilação e pipelines de lançamento. O pipeline de construção executa o processo de CI e cria artefatos de construção. Para uma arquitetura de microsserviços no Kubernetes, esses artefatos são as imagens de contêiner e os gráficos Helm que definem cada microsserviço. O pipeline de liberação executa esse processo de CD que implanta um microsserviço em um cluster.

Com base no fluxo de CI descrito anteriormente neste artigo, um pipeline de compilação pode consistir nas seguintes tarefas:

  1. Crie o contêiner do executor de teste.

    - task: Docker@1
      inputs:
        azureSubscriptionEndpoint: $(AzureSubscription)
        azureContainerRegistry: $(AzureContainerRegistry)
        arguments: '--pull --target testrunner'
        dockerFile: $(System.DefaultWorkingDirectory)/$(dockerFileName)
        imageName: '$(imageName)-test'
    
  2. Execute os testes, invocando a execução do docker contra o contêiner do executor de teste.

    - task: Docker@1
      inputs:
        azureSubscriptionEndpoint: $(AzureSubscription)
        azureContainerRegistry: $(AzureContainerRegistry)
        command: 'run'
        containerName: testrunner
        volumes: '$(System.DefaultWorkingDirectory)/TestResults:/app/tests/TestResults'
        imageName: '$(imageName)-test'
        runInBackground: false
    
  3. Publique os resultados do teste. Consulte Criar uma imagem.

    - task: PublishTestResults@2
      inputs:
        testResultsFormat: 'VSTest'
        testResultsFiles: 'TestResults/*.trx'
        searchFolder: '$(System.DefaultWorkingDirectory)'
        publishRunAttachments: true
    
  4. Crie o contêiner de tempo de execução.

    - task: Docker@1
      inputs:
        azureSubscriptionEndpoint: $(AzureSubscription)
        azureContainerRegistry: $(AzureContainerRegistry)
        dockerFile: $(System.DefaultWorkingDirectory)/$(dockerFileName)
        includeLatestTag: false
        imageName: '$(imageName)'
    
  5. Envie a imagem do contêiner para o Registro de Contêiner do Azure (ou outro registro de contêiner).

    - task: Docker@1
      inputs:
        azureSubscriptionEndpoint: $(AzureSubscription)
        azureContainerRegistry: $(AzureContainerRegistry)
        command: 'Push an image'
        imageName: '$(imageName)'
        includeSourceTags: false
    
  6. Empacote o gráfico Helm.

    - task: HelmDeploy@0
      inputs:
        command: package
        chartPath: $(chartPath)
        chartVersion: $(Build.SourceBranchName)
        arguments: '--app-version $(Build.SourceBranchName)'
    
  7. Envie o pacote Helm para o Registro de Contêiner do Azure (ou outro repositório Helm).

    task: AzureCLI@1
      inputs:
        azureSubscription: $(AzureSubscription)
        scriptLocation: inlineScript
        inlineScript: |
        az acr helm push $(System.ArtifactsDirectory)/$(repositoryName)-$(Build.SourceBranchName).tgz --name $(AzureContainerRegistry);
    

A saída do pipeline de CI é uma imagem de contêiner pronta para produção e um gráfico Helm atualizado para o microsserviço. Neste ponto, o pipeline de liberação pode assumir o controle. Haverá um pipeline de liberação exclusivo para cada microsserviço. O pipeline de liberação será configurado para ter uma fonte de gatilho definida para o pipeline de CI que publicou o artefato. Esse pipeline permite que você tenha implantações independentes de cada microsserviço. O pipeline de liberação executa as seguintes etapas:

  • Implante o gráfico Helm em ambientes de desenvolvimento/QA/preparo. O Helm upgrade comando pode ser usado com o sinalizador --install para suportar a primeira instalação e atualizações subsequentes.
  • Aguarde até que um aprovador aprove ou rejeite a implantação.
  • Marcar novamente a imagem do contêiner para liberação
  • Envie a tag de liberação para o registro do contêiner.
  • Implante o gráfico Helm no cluster de produção.

Para obter mais informações sobre como criar um pipeline de versão, consulte Pipelines de versão, versões de rascunho e opções de versão.

O diagrama a seguir mostra o processo de CI/CD de ponta a ponta descrito neste artigo:

CD/CD pipeline

Contribuidores

Este artigo é mantido pela Microsoft. Foi originalmente escrito pelos seguintes contribuidores.

Autor principal:

Para ver perfis não públicos do LinkedIn, inicie sessão no LinkedIn.

Próximos passos