CI/CD för arkitekturer för mikrotjänster

Azure

Snabbare lanseringscykler är en av de största fördelarna med mikrotjänstarkitekturer. Men utan en bra CI/CD-process uppnår du inte den flexibilitet som mikrotjänster lovar. Den här artikeln beskriver utmaningarna och rekommenderar några metoder för problemet.

Vad är CI/CD?

När vi pratar om CI/CD pratar vi om flera relaterade processer: kontinuerlig integrering, kontinuerlig leverans och kontinuerlig distribution.

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

  • Kontinuerlig leverans. Alla kodändringar som skickar CI-processen publiceras 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 ska vara redo att distribueras till produktion.

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

Här följer några mål för en robust CI/CD-process för en arkitektur för mikrotjänster:

  • 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-/QA-miljöer för validering. Kvalitetsgrindar framtvingas 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.

  • För containerbaserade arbetsbelastningar kan du lita på de containeravbildningar som distribueras till produktion.

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

I ett traditionellt monolitiskt program finns det en enda bygg-pipeline vars utdata är programmets körbara. Allt utvecklingsarbete matas in i den här pipelinen. Om en bugg med hög prioritet hittas måste en korrigering integreras, testas och publiceras, vilket kan fördröja lanseringen av nya funktioner. Du kan åtgärda dessa problem genom att ha välräknade moduler och använda funktionsgrenar för att minimera effekten av kodändringar. Men i takt med att programmet blir mer komplext och fler funktioner läggs till tenderar lanseringsprocessen för en monolit att bli mer spröd och sannolikt bryta.

Efter mikrotjänstfilosofin bör det aldrig finnas ett långt lanseringståg där varje lag måste ställa sig i kö. Teamet som skapar tjänsten "A" kan när som helst släppa en uppdatering, utan att vänta på att ändringar i tjänsten "B" ska sammanfogas, testas och distribueras.

Diagram över en CI/CD-monolit

För att uppnå en hög lanseringshastighet måste versionspipelinen 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 avbrott i tjänsten vara sällsynta. Om en felaktig uppdatering distribueras måste du samtidigt ha ett tillförlitligt sätt att snabbt återställa eller återställa till en tidigare version av en tjänst.

Utmaningar

  • 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 leda till en situation där kunskapen om hur systemet ska byggas sprids mellan team och ingen i organisationen vet hur hela programmet ska distribueras. Vad händer till exempel i ett haveriberedskapsscenario om du snabbt behöver distribuera till ett nytt kluster?

    Åtgärd: Ha en enhetlig och automatiserad pipeline för att skapa och distribuera tjänster, så att den här kunskapen inte är "dold" i varje team.

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

    Åtgärd: Containerisera byggprocessen för varje tjänst. På så sätt behöver byggsystemet bara kunna köra containrarna.

  • Integrering och belastningstestning. Med team som släpper uppdateringar i sin egen takt kan det vara svårt att utforma robust testning från slutpunkt till slutpunkt, särskilt när tjänster har beroenden av andra tjänster. Dessutom kan det vara dyrt att köra ett fullständigt produktionskluster, så det är osannolikt att alla team kommer att köra sitt eget fullständiga kluster i produktionsskalor, bara för testning.

  • Versionshantering. Varje team bör kunna distribuera en uppdatering till produktion. Det betyder inte att alla teammedlemmar har behörighet att göra det. Men att ha en centraliserad Release Manager-roll kan minska distributionshastigheten.

    Åtgärd: Ju mer CI/CD-processen är automatiserad och tillförlitlig, desto mindre bör det finnas ett behov av en central utfärdare. Med det sagt kan du ha olika principer för att släppa viktiga funktionsuppdateringar jämfört med mindre felkorrigeringar. Att vara decentraliserad betyder inte noll styrning.

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

    Åtgärd: Använd distributionstekniker som blågrön eller kanariebaserad version för icke-icke-bakåtkompatibla ändringar. För icke-bakåtkompatibla API-ändringar distribuerar du den nya versionen sida vid sida med den tidigare versionen. På så sätt kan tjänster som använder det tidigare API:et uppdateras och testas för det nya API:et. Se Uppdatera tjänster nedan.

Monorepo jämfört med flera lagringsplatser

Innan du skapar ett CI/CD-arbetsflöde måste du veta hur kodbasen ska struktureras och hanteras.

  • Arbetar team i separata lagringsplatser eller i en monorepo (enskild lagringsplats)?
  • Vilken är din förgreningsstrategi?
  • Vem kan skicka kod till produktion? Finns det en roll som versionshanterare?

Monorepo-metoden har fått fördelar men det finns fördelar och nackdelar med båda.

  Monorepo Flera lagringsplatser
Fördelar Koddelning
Enklare att standardisera kod och verktyg
Enklare att omstrukturera kod
Identifiering – enkel vy av koden
Rensa ä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 skalas till en stor kodbas
Åtkomstkontroll
Mer komplex distributionsprocess
Svårare att dela kod
Svårare att tillämpa kodningsstandarder
Beroendehantering
Diffus kodbas, dålig identifiering
Brist på delad infrastruktur

Uppdatera tjänster

Det finns olika strategier för att uppdatera en tjänst som redan är i produktion. Här diskuterar vi tre vanliga alternativ: Löpande uppdatering, blågrön distribution och kanarieversion.

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 direkt. När de nya instanserna dyker upp tas de tidigare instanserna bort.

Exempel: 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 skalas den nya ReplicaSet upp samtidigt som den gamla skalas ned för att bibehålla det önskade antalet repliker. Den tar inte bort gamla poddar förrän de nya är klara. Kubernetes behåller en historik över uppdateringen, så att du kan återställa en uppdatering om det behövs.

Exempel: Azure Service Fabric använder den löpande uppdateringsstrategin som standard. Den här strategin passar bäst för att distribuera en version av en tjänst med nya funktioner utan att ändra befintliga API:er. Service Fabric startar en uppgraderingsdistribution genom att uppdatera programtypen till en delmängd av noderna eller en uppdateringsdomän. Den återställs sedan till nästa uppdateringsdomän tills alla domäner har uppgraderats. Om en uppgraderingsdomän inte kan uppdateras återställs programtypen till den tidigare versionen i alla domäner. Tänk på att en programtyp med flera tjänster (och om alla tjänster uppdateras som en del av en uppgraderingsdistribution) är utsatt för fel. Om en tjänst inte kan uppdateras återställs hela programmet till den tidigare versionen och de andra tjänsterna uppdateras inte.

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

För icke-bakåtkompatibla API-ändringar är en bra idé att stödja båda versionerna sida vid sida tills alla klienter i den tidigare versionen har uppdaterats. Se API-versionshantering.

Blågrön distribution

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 efter eventuella problem. Om något går fel kan du växla tillbaka till den gamla versionen. Om det inte finns några problem kan du ta bort den gamla versionen.

Med ett mer traditionellt monolitiskt program eller N-nivåprogram innebar den blågröna distributionen vanligtvis att två identiska miljöer etablerades. Du distribuerar den nya versionen till en mellanlagringsmiljö och omdirigerar sedan klienttrafik till mellanlagringsmiljön, till exempel genom att byta VIP-adresser. 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.

Exempel. I Kubernetes behöver du inte etablera ett separat kluster för att utföra blågröna distributioner. I stället kan du dra nytta av väljare. Skapa en ny distributionsresurs med en ny poddspecifikation och en annan uppsättning etiketter. Skapa den här distributionen utan att ta bort den tidigare distributionen eller ändra tjänsten som pekar på den. När de nya poddarna körs kan du uppdatera tjänstens väljare så att den matchar den nya distributionen.

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 kräver mycket CPU- eller minnesresurser kan du behöva skala ut klustret tillfälligt för att hantera resursförbrukningen.

Canary-version

I en canary-version distribuerar du en uppdaterad version till ett litet antal klienter. Sedan övervakar du beteendet för den nya tjänsten innan du distribuerar den till alla klienter. På så sätt kan du göra en långsam distribution på ett kontrollerat sätt, observera verkliga data och upptäcka problem innan alla kunder påverkas.

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

Exempel. 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 ganska grovkornig på grund av hur Kubernetes lastbalanserar mellan poddar. Om du till exempel har totalt 10 repliker kan du bara flytta trafik i steg om 10 %. Om du använder ett tjänstnät kan du använda routningsreglerna för tjänstnät för att implementera en mer sofistikerad strategi för kanariefrisättning.

Nästa steg