Een CI/CD-pijplijn bouwen voor microservices in Kubernetes met Azure DevOps en Helm

Azure Kubernetes Service (AKS)
Azure Container Registry
Azure DevOps

Het kan lastig zijn om een betrouwbaar CI/CD-proces (Continuous Integration/Continuous Delivery) te maken voor een microservicesarchitectuur. Afzonderlijke teams moeten services snel en betrouwbaar kunnen vrijgeven, zonder dat andere teams worden verstoord of de toepassing als geheel wordt gedestabiliseerd.

In dit artikel wordt een voorbeeld van een CI/CD-pijplijn beschreven voor het implementeren van microservices in Azure Kubernetes Service (AKS). Elk team en project zijn anders, dus neem dit artikel niet als een set hard-and-fast regels. In plaats daarvan is het bedoeld als uitgangspunt voor het ontwerpen van uw eigen CI/CD-proces.

De doelstellingen van een CI/CD-pijplijn voor door Kubernetes gehoste microservices kunnen als volgt worden samengevat:

  • Teams kunnen hun services onafhankelijk bouwen en implementeren.
  • Codewijzigingen die het CI-proces doorgeven, worden automatisch geïmplementeerd in een productieachtige omgeving.
  • Kwaliteitspoorten worden afgedwongen in elke fase van de pijplijn.
  • Een nieuwe versie van een service kan naast de vorige versie worden geïmplementeerd.

Zie CI/CD voor microservicesarchitecturen voor meer achtergrondinformatie.

Aannames

In dit voorbeeld vindt u hier enkele veronderstellingen over het ontwikkelteam en de codebasis:

  • De codeopslagplaats is een monorepo, met mappen die zijn georganiseerd door microservice.
  • De vertakkingsstrategie van het team is gebaseerd op de ontwikkeling op basis van trunks.
  • Het team gebruikt releasebranches om releases te beheren. Voor elke microservice worden afzonderlijke releases gemaakt.
  • Het CI/CD-proces maakt gebruik van Azure Pipelines voor het bouwen, testen en implementeren van de microservices in AKS.
  • De containerinstallatiekopieën voor elke microservice worden opgeslagen in Azure Container Registry.
  • Het team gebruikt Helm-grafieken om elke microservice te verpakken.
  • Er wordt een push-implementatiemodel gebruikt, waarbij Azure Pipelines en bijbehorende agents implementaties uitvoeren door rechtstreeks verbinding te maken met het AKS-cluster.

Deze veronderstellingen sturen veel van de specifieke details van de CI/CD-pijplijn aan. De hier beschreven basisbenadering is echter aangepast voor andere processen, hulpprogramma's en services, zoals Jenkins of Docker Hub.

Alternatieven

Hier volgen veelvoorkomende alternatieven die klanten kunnen gebruiken bij het kiezen van een CI/CD-strategie met Azure Kubernetes Service:

  • Als alternatief voor het gebruik van Helm als pakketbeheer- en implementatieprogramma is Kustomize een systeemeigen hulpprogramma voor configuratiebeheer van Kubernetes waarmee een sjabloonvrije manier wordt geïntroduceerd om de toepassingsconfiguratie aan te passen en te parameteriseren.
  • Als alternatief voor het gebruik van Azure DevOps voor Git-opslagplaatsen en -pijplijnen kunnen GitHub-opslagplaatsen worden gebruikt voor privé- en openbare Git-opslagplaatsen, en GitHub Actions kunnen worden gebruikt voor CI/CD-pijplijnen.
  • Als alternatief voor het gebruik van een push-implementatiemodel kan het beheer van de Kubernetes-configuratie op grote schaal worden uitgevoerd met behulp van GitOps (pull-implementatiemodel), waarbij een Kubernetes-operator in het cluster de clusterstatus synchroniseert op basis van de configuratie die is opgeslagen in een Git-opslagplaats.

Validatie-builds

Stel dat een ontwikkelaar werkt aan een microservice met de naam Delivery Service. Tijdens het ontwikkelen van een nieuwe functie controleert de ontwikkelaar code in een functiebranch. Volgens conventie hebben functiebranches de naam feature/*.

CI/CD workflow

Het builddefinitiebestand bevat een trigger die filtert op de naam van de vertakking en het bronpad:

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/

Met deze benadering kan elk team een eigen build-pijplijn hebben. Alleen code die is ingecheckt in de /src/shipping/delivery map activeert een build van de Delivery Service. Doorvoeringen pushen naar een vertakking die overeenkomt met het filter activeert een CI-build. Op dit moment in de werkstroom voert de CI-build een minimale codeverificatie uit:

  1. Bouw de code.
  2. Eenheidstests uitvoeren.

Het doel is om de buildtijden kort te houden, zodat de ontwikkelaar snel feedback kan krijgen. Zodra de functie klaar is om samen te voegen in master, opent de ontwikkelaar een pull-aanvraag. Met deze bewerking wordt een andere CI-build geactiveerd waarmee een aantal extra controles worden uitgevoerd:

  1. Bouw de code.
  2. Eenheidstests uitvoeren.
  3. Bouw de installatiekopieën van de runtimecontainer.
  4. Voer beveiligingsscans uit op de installatiekopie.

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

Notitie

In Azure DevOps-opslagplaatsen kunt u beleidsregels definiëren om vertakkingen te beveiligen. Het beleid kan bijvoorbeeld een geslaagde CI-build plus een afmelding van een fiatteur vereisen om samen te voegen in master.

Volledige CI/CD-build

Op een bepaald moment is het team klaar om een nieuwe versie van de Delivery-service te implementeren. De releasebeheerder maakt een vertakking van de hoofdvertakking met dit naamgevingspatroon: release/<microservice name>/<semver>. Bijvoorbeeld release/delivery/v1.0.2.

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

Het maken van deze vertakking activeert een volledige CI-build die alle vorige stappen uitvoert plus:

  1. Push de containerinstallatiekopieën naar Azure Container Registry. De installatiekopieën worden getagd met het versienummer dat is opgehaald uit de naam van de vertakking.
  2. Voer deze opdracht uit helm package om de Helm-grafiek voor de service te verpakken. De grafiek wordt ook getagd met een versienummer.
  3. Push het Helm-pakket naar Container Registry.

Ervan uitgaande dat deze build slaagt, wordt een implementatieproces (CD) geactiveerd met behulp van een Release-pijplijn van Azure Pipelines. Deze pijplijn heeft de volgende stappen:

  1. Implementeer de Helm-grafiek in een QA-omgeving.
  2. Een fiatteur meldt zich af voordat het pakket naar productie gaat. Zie Release-implementatiebeheer met behulp van goedkeuringen.
  3. Tag de Docker-installatiekopieën voor de productienaamruimte in Azure Container Registry. Als de huidige tag bijvoorbeeld is, is myrepo.azurecr.io/delivery:v1.0.2myrepo.azurecr.io/prod/delivery:v1.0.2de productietag .
  4. Implementeer de Helm-grafiek in de productieomgeving.

Zelfs in een monorepo kunnen deze taken worden afgestemd op afzonderlijke microservices, zodat teams met hoge snelheid kunnen implementeren. Het proces bevat enkele handmatige stappen: goedkeuring van PULL's, het maken van release-vertakkingen en het goedkeuren van implementaties in het productiecluster. Deze stappen zijn handmatig; ze kunnen worden geautomatiseerd als de organisatie de voorkeur geeft.

Isolatie van omgevingen

U hebt meerdere omgevingen waarin u services implementeert, waaronder omgevingen voor ontwikkeling, betrouwbaarheidstests, integratietests, belastingtests en ten slotte productie. Deze omgevingen hebben enige isolatie nodig. In Kubernetes hebt u een keuze tussen fysieke isolatie en logische isolatie. Fysieke isolatie betekent implementeren in afzonderlijke clusters. Logische isolatie maakt gebruik van naamruimten en beleidsregels, zoals eerder beschreven.

We raden u aan om een toegewezen productiecluster te maken, samen met een afzonderlijk cluster voor uw ontwikkel-/testomgevingen. Gebruik logische isolatie om omgevingen binnen het ontwikkel-/testcluster te scheiden. Services die zijn geïmplementeerd in het ontwikkel-/testcluster, mogen nooit toegang hebben tot gegevensarchieven die zakelijke gegevens bevatten.

Bouwproces

Indien mogelijk moet u uw buildproces verpakken in een Docker-container. Met deze configuratie kunt u codeartefacten bouwen met behulp van Docker en zonder dat u een buildomgeving op elke buildcomputer configureert. Met een containerbuildproces kunt u de CI-pijplijn eenvoudig uitschalen door nieuwe buildagents toe te voegen. Bovendien kan elke ontwikkelaar in het team de code bouwen door de buildcontainer uit te voeren.

Met behulp van builds met meerdere fasen in Docker kunt u de buildomgeving en de runtime-installatiekopieën definiëren in één Dockerfile. Hier volgt bijvoorbeeld een Dockerfile waarmee een .NET-toepassing wordt gebouwd:

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"]

Dit Dockerfile definieert verschillende buildfasen. U ziet dat de fase met de naam base gebruikmaakt van de .NET-runtime, terwijl de fase met de naam build gebruikmaakt van de volledige .NET SDK. De build fase wordt gebruikt om het .NET-project te bouwen. Maar de uiteindelijke runtimecontainer is gebouwd op basis van base, die alleen de runtime bevat en aanzienlijk kleiner is dan de volledige SDK-installatiekopie.

Een testloper bouwen

Een andere goede gewoonte is het uitvoeren van eenheidstests in de container. Hier maakt u bijvoorbeeld deel uit van een Docker-bestand waarmee een testrunner wordt gebouwd:

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"]

Een ontwikkelaar kan dit Docker-bestand gebruiken om de tests lokaal uit te voeren:

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

De CI-pijplijn moet ook de tests uitvoeren als onderdeel van de buildverificatiestap.

Houd er rekening mee dat dit bestand de Docker-opdracht ENTRYPOINT gebruikt om de tests uit te voeren, niet de Docker-opdracht RUN .

  • Als u de RUN opdracht gebruikt, worden de tests elke keer uitgevoerd wanneer u de installatiekopieën bouwt. Door gebruik te maken ENTRYPOINT, worden de tests aangemeld. Ze worden alleen uitgevoerd wanneer u de fase expliciet richt testrunner .
  • Een mislukte test zorgt er niet voor dat de Docker-opdracht build mislukt. Op die manier kunt u fouten in de build van containers onderscheiden van testfouten.
  • Testresultaten kunnen worden opgeslagen op een gekoppeld volume.

Best practices voor containers

Hier volgen enkele andere aanbevolen procedures voor containers:

  • Definieer organisatiebrede conventies voor containertags, versiebeheer en naamconventies voor resources die zijn geïmplementeerd in het cluster (pods, services enzovoort). Dit kan het eenvoudiger maken om implementatieproblemen vast te stellen.

  • Tijdens de ontwikkelings- en testcyclus bouwt het CI/CD-proces veel containerinstallatiekopieën. Slechts enkele van deze installatiekopieën zijn kandidaten voor release en vervolgens worden slechts enkele van deze releasekandidaten gepromoveerd naar productie. Zorg voor een duidelijke versiebeheerstrategie zodat u weet welke installatiekopieën momenteel in productie zijn geïmplementeerd en om zo nodig terug te keren naar een eerdere versie.

  • Implementeer altijd specifieke tags voor containerversies, niet latest.

  • Gebruik naamruimten in Azure Container Registry om installatiekopieën te isoleren die zijn goedgekeurd voor productie van installatiekopieën die nog worden getest. Verplaats een installatiekopieën pas naar de productienaamruimte als u klaar bent om deze in productie te implementeren. Als u deze procedure combineert met semantische versiebeheer van containerinstallatiekopieën, kan dit de kans verminderen dat er per ongeluk een versie wordt geïmplementeerd die niet is goedgekeurd voor de release.

  • Volg het principe van minimale bevoegdheden door containers uit te voeren als een niet-gemachtigde gebruiker. In Kubernetes kunt u een beveiligingsbeleid voor pods maken dat voorkomt dat containers als hoofdmap worden uitgevoerd.

Helm-grafieken

Overweeg het gebruik van Helm om het bouwen en implementeren van services te beheren. Hier volgen enkele van de functies van Helm die u helpen met CI/CD:

  • Vaak wordt één microservice gedefinieerd door meerdere Kubernetes-objecten. Met Helm kunnen deze objecten in één Helm-grafiek worden verpakt.
  • Een grafiek kan worden geïmplementeerd met één Helm-opdracht in plaats van een reeks kubectl-opdrachten.
  • Grafieken worden expliciet geversied. Gebruik Helm om een versie vrij te geven, releases weer te geven en terug te keren naar een vorige versie. Updates en revisies bijhouden, met behulp van semantische versiebeheer, samen met de mogelijkheid om terug te keren naar een eerdere versie.
  • Helm-grafieken gebruiken sjablonen om te voorkomen dat gegevens, zoals labels en selectors, in veel bestanden worden gedupliceerd.
  • Helm kan afhankelijkheden tussen grafieken beheren.
  • Grafieken kunnen worden opgeslagen in een Helm-opslagplaats, zoals Azure Container Registry, en kunnen worden geïntegreerd in de build-pijplijn.

Zie Azure Container Registry als Helm-opslagplaats gebruiken als helm-opslagplaats voor uw toepassingsgrafieken voor meer informatie over het gebruik van Container Registry als helm-opslagplaats.

Een enkele microservice kan betrekking hebben op meerdere Kubernetes-configuratiebestanden. Het bijwerken van een service kan betekenen dat al deze bestanden worden aangeraakt om selectors, labels en afbeeldingstags bij te werken. Helm behandelt deze als één pakket dat een grafiek wordt genoemd en stelt u in staat om de YAML-bestanden eenvoudig bij te werken met behulp van variabelen. Helm maakt gebruik van een sjabloontaal (op basis van Go-sjablonen) waarmee u geparameteriseerde YAML-configuratiebestanden kunt schrijven.

Hier maakt u bijvoorbeeld deel uit van een YAML-bestand dat een implementatie definieert:

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 }}

U kunt zien dat de implementatienaam, labels en containerspecificaties allemaal sjabloonparameters gebruiken, die tijdens de implementatie worden opgegeven. Bijvoorbeeld vanaf de opdrachtregel:

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

Hoewel uw CI/CD-pijplijn een grafiek rechtstreeks naar Kubernetes kan installeren, raden we u aan een grafiekarchief (.tgz-bestand) te maken en de grafiek naar een Helm-opslagplaats te pushen, zoals Azure Container Registry. Zie Package Docker-apps in Helm-grafieken in Azure Pipelines voor meer informatie.

Revisies

Helm-grafieken hebben altijd een versienummer, dat semantische versiebeheer moet gebruiken. Een grafiek kan ook een appVersion. Dit veld is optioneel en hoeft niet gerelateerd te zijn aan de grafiekversie. Sommige teams willen mogelijk afzonderlijke toepassingsversies van updates naar de grafieken. Maar een eenvoudigere benadering is om één versienummer te gebruiken, dus er is een 1:1-relatie tussen de grafiekversie en de toepassingsversie. Op die manier kunt u één grafiek per release opslaan en eenvoudig de gewenste release implementeren:

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

Een andere goede gewoonte is om een wijzigingsoorzaakaantekening op te geven in de implementatiesjabloon:

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

Hiermee kunt u het veld wijzigingsoorzaak voor elke revisie weergeven met behulp van de kubectl rollout history opdracht. In het vorige voorbeeld wordt de wijzigingsoorzaak opgegeven als een Helm-grafiekparameter.

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

U kunt ook de helm list opdracht gebruiken om de revisiegeschiedenis weer te geven:

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

In Azure Pipelines zijn pijplijnen onderverdeeld in build-pijplijnen en release-pijplijnen. De build-pijplijn voert het CI-proces uit en maakt buildartefacten. Voor een microservicesarchitectuur in Kubernetes zijn deze artefacten de containerinstallatiekopieën en Helm-grafieken die elke microservice definiëren. Met de release-pijplijn wordt dat CD-proces uitgevoerd waarmee een microservice in een cluster wordt geïmplementeerd.

Op basis van de CI-stroom die eerder in dit artikel is beschreven, kan een build-pijplijn bestaan uit de volgende taken:

  1. Bouw de testrunnercontainer.

    - task: Docker@1
      inputs:
        azureSubscriptionEndpoint: $(AzureSubscription)
        azureContainerRegistry: $(AzureContainerRegistry)
        arguments: '--pull --target testrunner'
        dockerFile: $(System.DefaultWorkingDirectory)/$(dockerFileName)
        imageName: '$(imageName)-test'
    
  2. Voer de tests uit door Docker aan te roepen voor de testrunnercontainer.

    - 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. Publiceer de testresultaten. Zie Een installatiekopieën bouwen.

    - task: PublishTestResults@2
      inputs:
        testResultsFormat: 'VSTest'
        testResultsFiles: 'TestResults/*.trx'
        searchFolder: '$(System.DefaultWorkingDirectory)'
        publishRunAttachments: true
    
  4. Bouw de runtimecontainer.

    - task: Docker@1
      inputs:
        azureSubscriptionEndpoint: $(AzureSubscription)
        azureContainerRegistry: $(AzureContainerRegistry)
        dockerFile: $(System.DefaultWorkingDirectory)/$(dockerFileName)
        includeLatestTag: false
        imageName: '$(imageName)'
    
  5. Push de containerinstallatiekopieën naar Azure Container Registry (of een ander containerregister).

    - task: Docker@1
      inputs:
        azureSubscriptionEndpoint: $(AzureSubscription)
        azureContainerRegistry: $(AzureContainerRegistry)
        command: 'Push an image'
        imageName: '$(imageName)'
        includeSourceTags: false
    
  6. Pak de Helm-grafiek in.

    - task: HelmDeploy@0
      inputs:
        command: package
        chartPath: $(chartPath)
        chartVersion: $(Build.SourceBranchName)
        arguments: '--app-version $(Build.SourceBranchName)'
    
  7. Push het Helm-pakket naar Azure Container Registry (of een andere Helm-opslagplaats).

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

De uitvoer van de CI-pijplijn is een containerinstallatiekopieën die gereed zijn voor productie en een bijgewerkte Helm-grafiek voor de microservice. Op dit moment kan de release-pijplijn worden overgenomen. Er is een unieke release-pijplijn voor elke microservice. De release-pijplijn wordt geconfigureerd voor een triggerbron die is ingesteld op de CI-pijplijn die het artefact heeft gepubliceerd. Met deze pijplijn kunt u onafhankelijke implementaties van elke microservice uitvoeren. De release-pijplijn voert de volgende stappen uit:

  • Implementeer de Helm-grafiek in ontwikkel-/QA-/faseringsomgevingen. De Helm upgrade opdracht kan worden gebruikt met de --install vlag ter ondersteuning van de eerste installatie en volgende upgrades.
  • Wacht totdat een fiatteur de implementatie goedkeurt of weigert.
  • De containerinstallatiekopieën opnieuw tagen voor release
  • Push de releasetag naar het containerregister.
  • Implementeer de Helm-grafiek in het productiecluster.

Zie Release-pijplijnen, conceptversies en releaseopties voor meer informatie over het maken van een release-pijplijn.

In het volgende diagram ziet u het end-to-end CI/CD-proces dat in dit artikel wordt beschreven:

CD/CD pipeline

Bijdragers

Dit artikel wordt onderhouden door Microsoft. De tekst is oorspronkelijk geschreven door de volgende Inzenders.

Hoofdauteur:

Als u niet-openbare LinkedIn-profielen wilt zien, meldt u zich aan bij LinkedIn.

Volgende stappen