Skapa en CI/CD-pipeline för mikrotjänster på Kubernetes med Azure DevOps och Helm

Azure Kubernetes Service (AKS)
Azure Container Registry
Azure DevOps

Det kan vara svårt att skapa en tillförlitlig ci/CD-process (kontinuerlig integrering/kontinuerlig leverans) för en mikrotjänstarkitektur. Enskilda team måste kunna släppa tjänster snabbt och tillförlitligt, utan att störa andra team eller destabilisera programmet som helhet.

Den här artikeln beskriver ett exempel på en CI/CD-pipeline för distribution av mikrotjänster till Azure Kubernetes Service (AKS). Varje team och projekt är olika, så ta inte den här artikeln som en uppsättning hårda och snabba regler. I stället är det tänkt att vara en startpunkt för att utforma din egen CI/CD-process.

Målen för en CI/CD-pipeline för Kubernetes-värdbaserade mikrotjänster kan sammanfattas på följande sätt:

  • Teams kan skapa och distribuera sina tjänster oberoende av varandra.
  • Kodändringar som skickar CI-processen distribueras automatiskt till en produktionsliknande miljö.
  • Kvalitetsgrindar framtvingas i varje steg i pipelinen.
  • En ny version av en tjänst kan distribueras sida vid sida med den tidigare versionen.

Mer bakgrund finns i CI/CD för mikrotjänstarkitekturer.

Antaganden

I det här exemplet finns här några antaganden om utvecklingsteamet och kodbasen:

  • Kodlagringsplatsen är en monorepo med mappar ordnade efter mikrotjänst.
  • Teamets förgreningsstrategi bygger på trunkbaserad utveckling.
  • Teamet använder versionsgrenar för att hantera versioner. Separata versioner skapas för varje mikrotjänst.
  • CI/CD-processen använder Azure Pipelines för att skapa, testa och distribuera mikrotjänsterna till AKS.
  • Containeravbildningarna för varje mikrotjänst lagras i Azure Container Registry.
  • Teamet använder Helm-diagram för att paketera varje mikrotjänst.
  • En push-distributionsmodell används, där Azure Pipelines och associerade agenter utför distributioner genom att ansluta direkt till AKS-klustret.

Dessa antaganden driver många av de specifika detaljerna i CI/CD-pipelinen. Den grundläggande metod som beskrivs här anpassas dock för andra processer, verktyg och tjänster, till exempel Jenkins eller Docker Hub.

Alternativ

Följande är vanliga alternativ som kunder kan använda när de väljer en CI/CD-strategi med Azure Kubernetes Service:

  • Som ett alternativ till att använda Helm som pakethanterings- och distributionsverktyg är Kustomize ett inbyggt Kubernetes-konfigurationshanteringsverktyg som introducerar ett mallfritt sätt att anpassa och parametrisera programkonfiguration.
  • Som ett alternativ till att använda Azure DevOps för Git-lagringsplatser och -pipelines kan GitHub-lagringsplatser användas för privata och offentliga Git-lagringsplatser, och GitHub Actions kan användas för CI/CD-pipelines.
  • Som ett alternativ till att använda en push-distributionsmodell kan du hantera Kubernetes-konfiguration i stor skala med GitOps (pull-distributionsmodell), där en Kubernetes-operatör i klustret synkroniserar klustertillståndet baserat på konfigurationen som lagras på en Git-lagringsplats.

Valideringsversioner

Anta att en utvecklare arbetar med en mikrotjänst som kallas leveranstjänsten. När utvecklaren utvecklar en ny funktion kontrollerar han koden i en funktionsgren. Enligt konventionen heter feature/*funktionsgrenar .

CI/CD workflow

Versionsdefinitionsfilen innehåller en utlösare som filtreras efter grennamnet och källsökvägen:

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/

Med den här metoden kan varje team ha en egen bygg-pipeline. Endast kod som är incheckad i /src/shipping/delivery mappen utlöser en version av leveranstjänsten. Push-överföring av incheckningar till en gren som matchar filtret utlöser en CI-version. Nu i arbetsflödet kör CI-versionen en minimal kodverifiering:

  1. Skapa koden.
  2. Kör enhetstester.

Målet är att hålla byggtiden kort så att utvecklaren kan få snabb feedback. När funktionen är redo att slås samman till huvudservern öppnar utvecklaren en PR. Den här åtgärden utlöser en annan CI-version som utför några ytterligare kontroller:

  1. Skapa koden.
  2. Kör enhetstester.
  3. Skapa containeravbildningen runtime.
  4. Kör sårbarhetsgenomsökningar på avbildningen.

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

Kommentar

I Azure DevOps Repos kan du definiera principer för att skydda grenar. Principen kan till exempel kräva ett lyckat CI-bygge plus en signering från en godkännare för att sammanfogas till huvudservern.

Fullständig CI/CD-version

Vid något tillfälle är teamet redo att distribuera en ny version av leveranstjänsten. Versionshanteraren skapar en gren från huvudgrenen med det här namngivningsmönstret: release/<microservice name>/<semver>. Exempel: release/delivery/v1.0.2

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

Skapandet av den här grenen utlöser en fullständig CI-version som kör alla föregående steg plus:

  1. Skicka containeravbildningen till Azure Container Registry. Bilden är taggad med versionsnumret som hämtats från grennamnet.
  2. Kör helm package för att paketera Helm-diagrammet för tjänsten. Diagrammet är också taggat med ett versionsnummer.
  3. Skicka Helm-paketet till Container Registry.

Förutsatt att den här versionen lyckas utlöser den en distributionsprocess (CD) med hjälp av en Azure Pipelines-versionspipeline. Den här pipelinen har följande steg:

  1. Distribuera Helm-diagrammet till en QA-miljö.
  2. En godkännare signerar innan paketet flyttas till produktion. Se Versionsdistributionskontroll med godkännanden.
  3. Lägg till Docker-avbildningen igen för produktionsnamnområdet i Azure Container Registry. Om den aktuella taggen till exempel är myrepo.azurecr.io/delivery:v1.0.2är myrepo.azurecr.io/prod/delivery:v1.0.2produktionstaggen .
  4. Distribuera Helm-diagrammet till produktionsmiljön.

Även i en monorepo kan dessa uppgifter begränsas till enskilda mikrotjänster så att team kan distribuera med hög hastighet. Processen innehåller några manuella steg: Godkänna PR:er, skapa versionsgrenar och godkänna distributioner i produktionsklustret. De här stegen är manuella. de kan automatiseras om organisationen föredrar det.

Isolering av miljöer

Du kommer att ha flera miljöer där du distribuerar tjänster, inklusive miljöer för utveckling, röktestning, integreringstestning, belastningstestning och slutligen produktion. De här miljöerna behöver en viss isoleringsnivå. I Kubernetes kan du välja mellan fysisk isolering och logisk isolering. Fysisk isolering innebär att distribuera till separata kluster. Logisk isolering använder namnområden och principer enligt beskrivningen tidigare.

Vår rekommendation är att skapa ett dedikerat produktionskluster tillsammans med ett separat kluster för dina utvecklings-/testmiljöer. Använd logisk isolering för att avgränsa miljöer i dev/test-klustret. Tjänster som distribueras till dev/test-klustret bör aldrig ha åtkomst till datalager som innehåller affärsdata.

Byggprocess

Paketera byggprocessen i en Docker-container när det är möjligt. Med den här konfigurationen kan du skapa kodartefakter med Docker och utan att konfigurera en byggmiljö på varje byggdator. En containerbaserad byggprocess gör det enkelt att skala ut CI-pipelinen genom att lägga till nya byggagenter. Dessutom kan alla utvecklare i teamet skapa koden helt enkelt genom att köra byggcontainern.

Genom att använda flerstegsversioner i Docker kan du definiera byggmiljön och körningsavbildningen i en enskild Dockerfile. Här är till exempel en Dockerfile som skapar ett .NET-program:

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

Den här Dockerfile definierar flera byggsteg. Observera att fasen med namnet base använder .NET-körningen, medan fasen med namnet build använder hela .NET SDK. build Fasen används för att skapa .NET-projektet. Men den sista körningscontainern är byggd från base, som bara innehåller körningen och är betydligt mindre än den fullständiga SDK-avbildningen.

Skapa en testkörare

En annan bra metod är att köra enhetstester i containern. Här är till exempel en del av en Docker-fil som skapar en testkörare:

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

En utvecklare kan använda den här Docker-filen för att köra testerna lokalt:

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

CI-pipelinen bör också köra testerna som en del av byggverifieringssteget.

Observera att den här filen använder Docker-kommandot ENTRYPOINT för att köra testerna, inte Docker-kommandot RUN .

  • Om du använder RUN kommandot körs testerna varje gång du skapar avbildningen. Med hjälp av ENTRYPOINTär testerna anmälning. De körs bara när du uttryckligen riktar in dig på testrunner fasen.
  • Ett misslyckat test gör inte att Docker-kommandot build misslyckas. På så sätt kan du skilja containerbyggfel från testfel.
  • Testresultat kan sparas på en monterad volym.

Metodtips för containrar

Här följer några andra metodtips för containrar:

  • Definiera organisationsomfattande konventioner för containertaggar, versionshantering och namngivningskonventioner för resurser som distribueras till klustret (poddar, tjänster och så vidare). Det kan göra det enklare att diagnostisera distributionsproblem.

  • Under utvecklings- och testcykeln skapar CI/CD-processen många containeravbildningar. Endast några av dessa bilder är kandidater för lansering, och då kommer endast några av dessa versionskandidater att befordras till produktion. Ha en tydlig versionshanteringsstrategi så att du vet vilka avbildningar som för närvarande distribueras till produktion och för att återställa till en tidigare version om det behövs.

  • Distribuera alltid specifika containerversionstaggar, inte latest.

  • Använd namnområden i Azure Container Registry för att isolera avbildningar som är godkända för produktion från avbildningar som fortfarande testas. Flytta inte en avbildning till produktionsnamnområdet förrän du är redo att distribuera den till produktion. Om du kombinerar den här metoden med semantisk versionshantering av containeravbildningar kan det minska risken för att oavsiktligt distribuera en version som inte har godkänts för lansering.

  • Följ principen om minsta behörighet genom att köra containrar som en icke-privilegierad användare. I Kubernetes kan du skapa en säkerhetsprincip för poddar som förhindrar att containrar körs som rot.

Helm-diagram

Överväg att använda Helm för att hantera att skapa och distribuera tjänster. Här är några av funktionerna i Helm som hjälper dig med CI/CD:

  • Ofta definieras en enskild mikrotjänst av flera Kubernetes-objekt. Med Helm kan dessa objekt paketeras i ett enda Helm-diagram.
  • Ett diagram kan distribueras med ett enda Helm-kommando i stället för en serie kubectl-kommandon.
  • Diagram är uttryckligen versionshanterade. Använd Helm för att släppa en version, visa versioner och återställa till en tidigare version. Spåra uppdateringar och revisioner med hjälp av semantisk versionshantering, tillsammans med möjligheten att återställa till en tidigare version.
  • Helm-diagram använder mallar för att undvika att duplicera information, till exempel etiketter och väljare, i många filer.
  • Helm kan hantera beroenden mellan diagram.
  • Diagram kan lagras på en Helm-lagringsplats, till exempel Azure Container Registry, och integreras i bygg-pipelinen.

Mer information om hur du använder Container Registry som en Helm-lagringsplats finns i Använda Azure Container Registry som en Helm-lagringsplats för dina programdiagram.

En enskild mikrotjänst kan omfatta flera Kubernetes-konfigurationsfiler. Att uppdatera en tjänst kan innebära att du trycker på alla dessa filer för att uppdatera väljare, etiketter och bildtaggar. Helm behandlar dessa som ett enda paket som kallas ett diagram och gör att du enkelt kan uppdatera YAML-filerna med hjälp av variabler. Helm använder ett mallspråk (baserat på Go-mallar) så att du kan skriva parameteriserade YAML-konfigurationsfiler.

Här är till exempel en del av en YAML-fil som definierar en distribution:

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

Du kan se att distributionsnamnet, etiketterna och containerspecifikationen alla använder mallparametrar som tillhandahålls vid distributionstillfället. Till exempel från kommandoraden:

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

Även om CI/CD-pipelinen kan installera ett diagram direkt till Kubernetes rekommenderar vi att du skapar ett diagramarkiv (.tgz-fil) och push-överför diagrammet till en Helm-lagringsplats, till exempel Azure Container Registry. Mer information finns i Paketera Docker-baserade appar i Helm-diagram i Azure Pipelines.

Revideringar

Helm-diagram har alltid ett versionsnummer som måste använda semantisk versionshantering. Ett diagram kan också ha en appVersion. Det här fältet är valfritt och behöver inte vara relaterat till diagramversionen. Vissa team kanske vill programversioner separat från uppdateringar till diagrammen. Men en enklare metod är att använda ett versionsnummer, så det finns en 1:1-relation mellan diagramversion och programversion. På så sätt kan du lagra ett diagram per version och enkelt distribuera önskad version:

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

En annan bra metod är att ange en ändringsorsaksanteckning i distributionsmallen:

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

På så sätt kan du visa fältet ändringsorsak för varje revision med hjälp av kubectl rollout history kommandot . I föregående exempel anges ändringsorsaken som en Helm-diagramparameter.

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

Du kan också använda helm list kommandot för att visa revisionshistoriken:

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

I Azure Pipelines delas pipelines in i bygg-pipelines och versionspipelines. Bygg-pipelinen kör CI-processen och skapar byggartefakter. För en mikrotjänstarkitektur på Kubernetes är dessa artefakter containeravbildningar och Helm-diagram som definierar varje mikrotjänst. Versionspipelinen kör den CD-process som distribuerar en mikrotjänst till ett kluster.

Baserat på CI-flödet som beskrevs tidigare i den här artikeln kan en byggpipeline bestå av följande uppgifter:

  1. Skapa testkörcontainern.

    - task: Docker@1
      inputs:
        azureSubscriptionEndpoint: $(AzureSubscription)
        azureContainerRegistry: $(AzureContainerRegistry)
        arguments: '--pull --target testrunner'
        dockerFile: $(System.DefaultWorkingDirectory)/$(dockerFileName)
        imageName: '$(imageName)-test'
    
  2. Kör testerna genom att anropa Docker-körningen mot testkörcontainern.

    - 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. Publicera testresultaten. Se Skapa en avbildning.

    - task: PublishTestResults@2
      inputs:
        testResultsFormat: 'VSTest'
        testResultsFiles: 'TestResults/*.trx'
        searchFolder: '$(System.DefaultWorkingDirectory)'
        publishRunAttachments: true
    
  4. Skapa körningscontainern.

    - task: Docker@1
      inputs:
        azureSubscriptionEndpoint: $(AzureSubscription)
        azureContainerRegistry: $(AzureContainerRegistry)
        dockerFile: $(System.DefaultWorkingDirectory)/$(dockerFileName)
        includeLatestTag: false
        imageName: '$(imageName)'
    
  5. Skicka containeravbildningen till Azure Container Registry (eller annat containerregister).

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

    - task: HelmDeploy@0
      inputs:
        command: package
        chartPath: $(chartPath)
        chartVersion: $(Build.SourceBranchName)
        arguments: '--app-version $(Build.SourceBranchName)'
    
  7. Skicka Helm-paketet till Azure Container Registry (eller en annan Helm-lagringsplats).

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

Utdata från CI-pipelinen är en produktionsklar containeravbildning och ett uppdaterat Helm-diagram för mikrotjänsten. Nu kan versionspipelinen ta över. Det kommer att finnas en unik versionspipeline för varje mikrotjänst. Versionspipelinen konfigureras för att ha en utlösarkälla inställd på CI-pipelinen som publicerade artefakten. Med den här pipelinen kan du ha oberoende distributioner av varje mikrotjänst. Versionspipelinen utför följande steg:

  • Distribuera Helm-diagrammet till utvecklings-/QA/mellanlagringsmiljöer. Kommandot Helm upgrade kan användas med --install flaggan för att stödja den första installationen och efterföljande uppgraderingar.
  • Vänta tills en godkännare godkänner eller avvisar distributionen.
  • Ändra storlek på containeravbildningen för lansering
  • Push-överför versionstaggen till containerregistret.
  • Distribuera Helm-diagrammet i produktionsklustret.

Mer information om hur du skapar en versionspipeline finns i Versionspipelines, utkastversioner och lanseringsalternativ.

Följande diagram visar ci/CD-processen från slutpunkt till slutpunkt som beskrivs i den här artikeln:

CD/CD pipeline

Deltagare

Den här artikeln underhålls av Microsoft. Det har ursprungligen skrivits av följande medarbetare.

Huvudförfattare:

Om du vill se icke-offentliga LinkedIn-profiler loggar du in på LinkedIn.

Nästa steg