CI/CD för mikrotjänster

Snabbare lanseringscykler är en stor fördel med mikrotjänstarkitekturer. Utan en tillförlitlig process för kontinuerlig integrering och kontinuerlig leverans (CI/CD) förlorar du den flexibilitet som mikrotjänster ger. Den här artikeln beskriver vanliga CI/CD-utmaningar i mikrotjänstarkitekturer och rekommenderar metoder för att skapa, validera, skydda och distribuera tjänster oberoende av varandra.

Vad är CI/CD?

CI/CD refererar till flera relaterade processer: kontinuerlig integrering, kontinuerlig leverans och kontinuerlig distribution.

  • Kontinuerlig integrering (CI): Kodändringar sammanfogas ofta i huvudgrenen. Automatiserade bygg- och testprocesser säkerställer att koden i huvudgrenen alltid är produktionskvalitet.

  • Kontinuerlig leverans (CD): Kodändringar som klarar CI-processen distribueras automatiskt till en produktionsliknande miljö. Distribution till liveproduktionsmiljön kan kräva manuellt godkännande, men är annars automatiserad. Målet är att koden alltid är redo att distribueras till produktion.

  • Kontinuerlig distribution: Kodändringar som passerar de föregående två stegen distribueras automatiskt till produktion.

Överväg följande mål för en robust CI/CD-process för en mikrotjänstarkitektur:

  • Varje team kan skapa och distribuera de tjänster som de äger oberoende av varandra, utan att påverka eller störa andra team.

  • Innan en ny version av en tjänst distribueras till produktion distribueras den till utvecklings-/test- och QA-miljöer för validering. Kvalitetsgrindar tillämpas i varje steg.

  • En ny version av en tjänst kan distribueras sida vid sida med den tidigare versionen.

  • Det finns tillräckligt med principer för åtkomstkontroll. Pipelines autentiserar till Azure med federerade, kortlivade autentiseringsuppgifter i stället för långlivade hemligheter.

  • För containerbaserade arbetsbelastningar kan du lita på de containeravbildningar som distribueras till produktion. Det förtroendet upprättas genom signerade avbildningar, SBOM-intyg (Software Bill of Materials) och sårbarhetsgenomsökning som framtvingas i pipelinen.

Varför en robust CI/CD-pipeline är viktig

I ett traditionellt monolitiskt program skapar en enda byggpipeline det körbara programmet. Allt utvecklingsarbete matas in i den här pipelinen. Om teamet hittar en bugg med hög prioritet måste korrigeringen integreras, testas och publiceras, vilket kan fördröja lanseringen av nya funktioner. Du kan minska dessa problem genom att använda välräknade moduler och funktionsgrenar för att begränsa effekten av kodändringar. Men när programmet blir mer komplext och fler funktioner läggs till tenderar lanseringsprocessen för en monolit att bli mer komplicerad och kommer sannolikt att misslyckas.

Enligt mikrotjänstfilosofin bör det aldrig finnas ett långt lanseringståg där varje lag måste komma i linje. Teamet som skapar tjänst A kan släppa en uppdatering när den väljer och behöver inte vänta på att ändringar i tjänst B ska slås samman, testas och distribueras.

Diagram som jämför CI/CD för monolitisk arkitektur och mikrotjänstarkitektur.

För att uppnå en hög utgivningstakt måste utgivningspipelinen vara automatiserad och mycket tillförlitlig för att minimera risken. Om du släpper till produktion en eller flera gånger dagligen måste regressioner eller tjänststörningar vara sällsynta. Samtidigt gäller att om du driftsätter en felaktig uppdatering måste du ha ett tillförlitligt sätt att snabbt rulla tillbaka eller framåt till en tidigare version av en tjänst.

Challenges

  • Många små oberoende kodbaser: Varje team ansvarar för att skapa en egen tjänst med en egen bygg-pipeline. I vissa organisationer kan team använda separata kodlagringsplatser. Separata lagringsplatser kan sprida kunskapen om hur du skapar systemet mellan team. Därför vet ingen i organisationen hur hela programmet ska distribueras.

    Åtgärd: Ha en enhetlig och automatiserad pipeline, eller åtminstone en gemensam pipelineinfrastruktur, för att bygga och driftsätta tjänster så att den här kunskapen inte blir dold inom varje team. Återanvändbara pipelinemallar, till exempel GitHub Actions återanvändbara arbetsflöden eller Azure-pipelines mallar, hjälper till att standardisera stegen för att skapa, testa, skanna och distribuera i varje tjänst.

  • Flera språk och ramverk: Varje team använder sin egen blandning av tekniker, så det kan vara svårt att skapa en enda byggprocess som fungerar i hela arbetsbelastningen. Byggprocessen måste vara tillräckligt flexibel för att varje team ska kunna anpassa den efter sitt valda språk eller ramverk.

    Åtgärd: Containerisera byggprocessen för varje tjänst så att byggsystemet endast behöver köra containrarna. Plattformar som GitHub Actions, Azure-pipelines och Azure Container Registry uppgifter kan skapa och publicera containeravbildningar konsekvent oavsett källspråk.

  • Integrerings- och belastningstestning: Teams släpper uppdateringar i sin egen takt, så det kan vara svårt att utforma robust testning från slutpunkt till slutpunkt, särskilt när tjänster har beroenden för andra tjänster. Det kan vara kostsamt att köra ett fullständigt produktionskluster, så det är osannolikt att varje team endast kör ett eget fullständigt kluster i produktionsskala för testning.

    Åtgärd: Använd tillfälliga förhandsvisningsmiljöer, till exempel namnrymder per pull request i Kubernetes eller Azure Container Apps-miljöer som skapas på begäran. Använd kontrakttester så att integrationsproblem upptäcks tidigt utan att behöva en fullskalig kopia av produktionsmiljön.

  • Versionshantering: Varje team bör kunna distribuera en uppdatering till produktion. Det kravet innebär inte att varje gruppmedlem har behörighet att distribuera. En centraliserad roll för versionshanteraren kan minska distributionshastigheten.

    Motåtgärd: Ju mer din CI/CD-process är automatiserad och tillförlitlig, desto mindre behöver du en central instans. Du kanske fortfarande har olika principer för att släppa viktiga funktionsuppdateringar jämfört med mindre felkorrigeringar. En decentraliserad metod innebär inte noll styrning. Framtvinga godkännanden med hjälp av Azure-pipelines miljöer och godkännanden eller GitHub Actions distributionsmiljöer och nödvändiga granskare och kodifiera klustersidans princip med hjälp av Azure Policy för Azure Kubernetes Service (AKS) eller OPA Gatekeeper.

  • Tjänstuppdateringar: När du uppdaterar en tjänst till en ny version bör uppdateringen inte orsaka att andra tjänster som är beroende av den misslyckas.

    Mitigering: Använd driftsättningstekniker som blue-green-distribution eller canary-releaser för icke-brytande ändringar. För förändringar i API som bryter kompatibilitet distribuerar du den nya versionen sida vid sida med den tidigare versionen. Med den här metoden kan tjänster som använder det tidigare API:et uppdateras och testas för det nya API:et. Mer information finns i Uppdatera tjänster.

  • Identitets- och hemlighetshantering i pipelines: Långlivade hemligheter för tjänsthuvudnamn som lagras i pipelines är en vanlig källa till kompromettering och driftarbete. Tjänstens huvudnamnshemligheter upphör att gälla, kan läcka och kräva rotation i många oberoende mikrotjänstpipelines.

    Åtgärd: Autentisera pipelines mot Azure med federering av arbetsbelastningsidentiteter, som använder OpenID Connect (OIDC), så att ingen klienthemlighet lagras i pipelinen. Mer information finns i Workload identities for Azure-pipelines and Configure OpenID Connect in Azure for GitHub Actions. Lagra eventuella återstående hemligheter i Azure Key Vault och referera till dem vid körning.

  • Säkerhet i leveranskedjan: Allt som du skickar till produktion måste kunna spåras till koden och beroendena som den skapades från. Mikrotjänster ökar antalet bilder, register och pipelines, vilket ökar din attackyta i leveranskedjan.

    Riskreducerande åtgärd: Signera containeravbildningar med Notation and Key Vault och verifiera signaturer vid tillträdeskontroll med hjälp av AKS image integrity eller Ratify. Generera en SBOM som en byggartefakt. Genomsök kod, beroenden och pipelines med hjälp av Microsoft Defender för molnet DevOps-säkerhet och GitHub Avancerad säkerhet. Sök igenom körningsbilder med Microsoft Defender for Containers. Kräv att alla skanningar godkänns innan en lansering kan fortsätta.

Monorepo jämfört med multirepo

Innan du skapar ett CI/CD-arbetsflöde måste du veta hur kodbasen är strukturerad och hanterad, inklusive:

  • Oavsett om teamen arbetar i separata lagringsplatser eller i en monorepo.
  • Din förgreningsstrategi.
  • Vem som kan driftsätta kod i produktion och om det finns en releaseansvarig.

Team använder ofta båda metoderna i produktion. Ditt val beror på teamets topologi, verktygsmognad och hur mycket kod som delas mellan tjänster.

  Monorepo Flera repos
Fördelar – Koddelning

– Enklare att standardisera kod och verktyg

– Enklare att omstrukturera kod

– Hittbarhet (en samlad vy av koden)
– Tydligt ägarskap per team

– Potentiellt färre sammanslagningskonflikter

– Hjälper till att framtvinga avkoppling av mikrotjänster
utmaningar – Ändringar i delad kod kan påverka flera mikrotjänster

– Större potential för sammanslagningskonflikter

– Verktyg måste kunna skala till stora kodbaser

– Åtkomstkontroll

– En mer komplex driftsättningsprocess
– Svårare att dela kod

- Svårare att framtvinga kodningsstandarder

– Beroendehantering

- Diffus kodbas, dålig identifiering

– Brist på delad infrastruktur

Oavsett vilken modell du väljer, använd utlösare med sökvägsomfång i dina pipelines, till exempel sökvägsfilter i GitHub Actions eller utlösarsökvägar i Azure-pipelines. Sökvägsbaserade utlösare bidrar till att säkerställa att endast berörda mikrotjänster byggs om och driftsätts om vid varje incheckning.

Uppdatera tjänster

Det finns olika strategier för att uppdatera en tjänst som redan finns i produktion, inklusive löpande uppdatering, blågrön distribution och kanarieversion. Dessa mönster koordineras ofta via ett GitOps-arbetsflöde. Mer information finns i GitOps och progressiv leverans.

Löpande uppdateringar

I en löpande uppdatering distribuerar du nya instanser av en tjänst och de nya instanserna börjar ta emot begäranden omedelbart. När de nya instanserna blir klara tas de tidigare instanserna bort.

Exempel i Kubernetes: I Kubernetes är löpande uppdateringar standardbeteendet när du uppdaterar poddspecifikationen för en distribution. Distributionskontrollanten skapar en ny ReplicaSet för de uppdaterade poddarna. Sedan skalar den upp den nya Replikuppsättningen samtidigt som den tidigare Replikuppsättningen skalas ned för att behålla det önskade antalet repliker. Den tar inte bort tidigare poddar förrän de nya poddarna är klara. Kubernetes behåller en historik över uppdateringen så att du kan återställa en uppdatering om det behövs.

Exempel i Container Apps: Container Apps använder revisioner för att hantera löpande uppdateringar. När du distribuerar en ny revision kan Container Apps gradvis flytta trafik från föregående revision till den nya revisionen med hjälp av regler för trafikdelning. Om den nya revisionen stöter på problem kan du återställa genom att omdirigera trafik till föregående revision. Du kan konfigurera flera aktiva revisioner samtidigt och styra procentandelen trafik som varje revision tar emot.

En utmaning med löpande uppdateringar är att en blandning av tidigare och nya versioner körs och tar emot trafik under uppdateringsprocessen. Under den här perioden kan systemet dirigera alla begäranden till någon av versionerna.

För icke-bakåtkompatibla API-ändringar är det bra att stödja båda versionerna sida vid sida tills alla klienter i den tidigare versionen har uppdaterats. Mer information finns i API-versionshantering.

Blågrön driftsättning

I en blågrön distribution distribuerar du den nya versionen tillsammans med den tidigare versionen. När du har verifierat den nya versionen växlar du all trafik samtidigt från den tidigare versionen till den nya versionen. Efter växeln övervakar du programmet för eventuella problem. Om det uppstår ett problem kan du växla trafik tillbaka till den tidigare versionen. Om det inte finns några problem kan du ta bort den tidigare versionen.

Med ett mer traditionellt monolitiskt program eller N-nivåprogram innebär blågrön distribution vanligtvis att du skapar två identiska miljöer. Du distribuerar den nya versionen till en mellanlagringsmiljö och omdirigerar sedan klienttrafik till den miljön, till exempel genom att byta en virtuell IP-adress. I en mikrotjänstarkitektur sker uppdateringar på mikrotjänstnivå, så du distribuerar vanligtvis uppdateringen till samma miljö och använder en mekanism för tjänstidentifiering för att växla trafik.

Exempel i Kubernetes: I Kubernetes behöver du inte skapa ett separat kluster för att utföra blågröna distributioner. I stället kan du dra nytta av selektorer. Skapa en ny distributionsresurs med en ny poddspecifikation och en annan uppsättning etiketter. Skapa den här distributionen, men ta inte bort den tidigare distributionen eller ändra den tjänst som pekar på den. När de nya poddarna har startats kan du uppdatera tjänstens selektor så att den matchar den nya driftsättningen.

En nackdel med blågrön distribution är att du under uppdateringen kör dubbelt så många poddar för tjänsten (aktuell och nästa). Om poddarna använder betydande processor- eller minnesresurser kan du tillfälligt behöva skala ut klustret för att möta den högre resursefterfrågan.

Kanarieutgåva

I en kanarieversion distribuerar du en uppdaterad version till en liten delmängd klienter och övervakar sedan beteendet för den nya tjänsten innan du distribuerar den till alla klienter. Med den här metoden kan du distribuera gradvis på ett kontrollerat sätt, övervaka verkliga data och identifiera problem innan de påverkar alla kunder.

En kanarieversion är mer komplex att hantera än en blågrön eller rullande uppdatering eftersom du måste dirigera begäranden dynamiskt till olika versioner av tjänsten.

Exempel i Kubernetes: I Kubernetes kan du konfigurera en tjänst så att den sträcker sig över två replikuppsättningar (en för varje version) och justera antalet repliker manuellt. Den här metoden är dock grovkornig eftersom Kubernetes lastbalanserar mellan poddar. Om du till exempel har totalt 10 repliker kan du bara flytta trafik i 10% steg. Om du använder ett tjänstnät kan du använda routningsreglerna för service mesh för att implementera en mer avancerad strategi för kanariefrisättning.

Exempel i Container Apps: I Container Apps kan du använda trafikdelning för att skicka en definierad procentandel av trafiken till en ny revision (t.ex. 10% till v2 medan 90% förblir på v1) och flytta vikterna när konfidensen växer, utan att det krävs något externt servicenät.

Progressiv leverans och GitOps

För team som driver många mikrotjänster på Kubernetes kompletterar en GitOps-pull-baserad modell de tidigare push-baserade exemplen. Det önskade klustertillståndet finns i Git och en operatör i klustret avstämer klustret till det tillståndet. CI bygger, testar, skannar, signerar och skickar upp imagen. CD synkroniserar klustret med manifestet. Denna uppdelning ger revisionsspår och enklare katastrofåterställning (DR). Det eliminerar också behovet av att CI-runnern har direkta autentiseringsuppgifter till klustret.

Nästa steg