werf - vårt verktyg för CI / CD i Kubernetes (översikt och videorapport)

27 maj i huvudsalen på DevOpsConf 2019-konferensen, som hölls som en del av festivalen RIT++ 2019, som en del av avsnittet "Kontinuerlig leverans" gavs en rapport "werf - vårt verktyg för CI/CD i Kubernetes". Den talar om dem problem och utmaningar som alla möter när de distribueras till Kubernetes, samt om nyanser som kanske inte märks direkt. Genom att analysera möjliga lösningar visar vi hur detta implementeras i ett Open Source-verktyg werf.

Sedan presentationen har vårt verktyg (tidigare känt som en dapp) nått en historisk milstolpe av 1000 stjärnor på GitHub — vi hoppas att dess växande gemenskap av användare kommer att göra livet lättare för många DevOps-ingenjörer.

werf - vårt verktyg för CI / CD i Kubernetes (översikt och videorapport)

Så låt oss presentera video av rapporten (~47 minuter, mycket mer informativ än artikeln) och huvudutdraget ur den i textform. Gå!

Levererar kod till Kubernetes

Föredraget kommer inte längre att handla om werf, utan om CI/CD i Kubernetes, vilket innebär att vår programvara är förpackad i Docker-containrar (Jag pratade om detta i 2016 års rapport), och K8s kommer att användas för att köra den i produktion (mer om detta i 2017 år).

Hur ser leveransen ut i Kubernetes?

  • Det finns ett Git-förråd med koden och instruktioner för att bygga det. Applikationen är inbyggd i en Docker-bild och publicerad i Docker-registret.
  • Samma arkiv innehåller också instruktioner om hur du distribuerar och kör programmet. I distributionsstadiet skickas dessa instruktioner till Kubernetes, som tar emot den önskade bilden från registret och startar den.
  • Dessutom finns det oftast tester. Vissa av dessa kan göras när du publicerar en bild. Du kan också (följa samma instruktioner) distribuera en kopia av programmet (i ett separat K8s namnområde eller ett separat kluster) och köra tester där.
  • Slutligen behöver du ett CI-system som tar emot händelser från Git (eller knappklick) och anropar alla utsedda steg: bygga, publicera, distribuera, testa.

werf - vårt verktyg för CI / CD i Kubernetes (översikt och videorapport)

Det finns några viktiga anmärkningar här:

  1. För vi har en oföränderlig infrastruktur (oföränderlig infrastruktur), applikationsbilden som används i alla stadier (scener, produktion, etc.), det måste finnas en. Jag pratade om detta mer ingående och med exempel. här.
  2. Eftersom vi följer infrastrukturen som kodmetoder (IaC), bör applikationskoden, instruktioner för montering och lansering vara exakt i ett förråd. För mer information om detta, se samma rapport.
  3. Leveranskedja (leverans) vi brukar se det så här: applikationen monterades, testades, släpptes (släppstadium) och det är det - leverans har ägt rum. Men i verkligheten får användaren vad du rullade ut, ingen sedan när man levererade den till produktionen, och när han kunde åka dit och den här produktionen fungerade. Så jag tror att leveranskedjan tar slut endast i det operativa skedet (springa), eller mer exakt, även i det ögonblick då koden togs bort från produktionen (ersätter den med en ny).

Låt oss återgå till ovanstående leveransschema i Kubernetes: det uppfanns inte bara av oss utan av bokstavligen alla som hanterade detta problem. Faktum är att detta mönster nu kallas GitOps (du kan läsa mer om termen och idéerna bakom den här). Låt oss titta på stadierna i systemet.

Byggscen

Det verkar som att du kan prata om att bygga Docker-bilder 2019, när alla vet hur man skriver Dockerfiler och kör docker build?.. Här är nyanserna som jag skulle vilja uppmärksamma:

  1. Bildvikt spelar roll, så använd etappvisatt bara lämna den applikation som verkligen är nödvändig för operationen i bilden.
  2. Antal lager måste minimeras genom att kombinera kedjor av RUN-kommandon enligt betydelse.
  3. Detta skapar dock problem felsökning, för när sammansättningen kraschar måste du hitta rätt kommando från kedjan som orsakade problemet.
  4. Bygg fart viktigt eftersom vi snabbt vill rulla ut förändringar och se resultatet. Till exempel vill du inte bygga om beroenden i språkbibliotek varje gång du bygger en applikation.
  5. Ofta från ett Git-förråd du behöver många bilder, som kan lösas med en uppsättning Dockerfiler (eller namngivna stadier i en fil) och ett Bash-skript med deras sekventiella sammansättning.

Detta var bara toppen av isberget som alla står inför. Men det finns andra problem, särskilt:

  1. Ofta på monteringsstadiet behöver vi något montera (till exempel cachelagra resultatet av ett kommando som apt i en tredjepartskatalog).
  2. Vi vill Ansible istället för att skriva i skal.
  3. Vi vill bygga utan Docker (varför behöver vi en extra virtuell maskin där vi måste konfigurera allt för detta, när vi redan har ett Kubernetes-kluster där vi kan köra behållare?).
  4. Parallell montering, som kan förstås på olika sätt: olika kommandon från Dockerfilen (om flersteg används), flera commits av samma arkiv, flera Dockerfiler.
  5. Distribuerad montering: Vi vill samla saker i baljor som är "flyktiga" pga deras cache försvinner, vilket betyder att den måste lagras någonstans separat.
  6. Till sist utnämnde jag höjdpunkten av önskningar automagi: Det skulle vara idealiskt att gå till förvaret, skriva ett kommando och få en färdig bild, sammansatt med en förståelse för hur och vad man ska göra korrekt. Jag är dock personligen inte säker på att alla nyanser kan förutses på detta sätt.

Och här är projekten:

  • moby/buildkit — en byggare från Docker Inc (redan integrerad i nuvarande versioner av Docker), som försöker lösa alla dessa problem;
  • kaniko — en byggare från Google som låter dig bygga utan Docker;
  • Buildpacks.io — CNCF:s försök att skapa automatisk magi och i synnerhet en intressant lösning med rebase för lager;
  • och en massa andra verktyg, som t.ex buildah, genuinetools/img.

...och se hur många stjärnor de har på GitHub. Det vill säga å ena sidan docker build finns och kan göra något, men i verkligheten problemet är inte helt löst – Ett bevis på detta är den parallella utvecklingen av alternativa samlare, som var och en löser en del av problemen.

Montering i werf

Så vi måste werf (tidigare känd som dapp) — Ett verktyg med öppen källkod från företaget Flant, som vi har tillverkat i många år. Allt började för 5 år sedan med Bash-skript som optimerade monteringen av Dockerfiles, och under de senaste 3 åren har fullfjädrad utveckling genomförts inom ramen för ett projekt med ett eget Git-repository (först i Ruby, och sedan skrev om att gå, och samtidigt bytt namn). Vilka monteringsproblem löses i werf?

werf - vårt verktyg för CI / CD i Kubernetes (översikt och videorapport)

Problemen som är skuggade i blått har redan implementerats, parallellbygget gjordes inom samma värd, och de problem som är markerade med gult är planerade att vara klara i slutet av sommaren.

Stadium för publicering i registret (publicera)

Vi ringde docker push... - vad kan vara svårt med att ladda upp en bild till registret? Och då uppstår frågan: "Vilken tagg ska jag sätta på bilden?" Det uppstår av den anledningen som vi har Gitflow (eller annan Git-strategi) och Kubernetes, och branschen försöker se till att det som händer i Kubernetes följer det som händer i Git. När allt kommer omkring är Git vår enda källa till sanning.

Vad är det som är så svårt med det här? Säkerställ reproducerbarhet: från en commit i Git, som är oföränderlig till sin natur (oföränderlig), till en Docker-bild, som bör behållas oförändrad.

Det är också viktigt för oss bestämma ursprung, eftersom vi vill förstå från vilken commit applikationen som körs i Kubernetes byggdes (då kan vi göra diffs och liknande saker).

Taggningsstrategier

Den första är enkel git-tagg. Vi har ett register med en bild taggad som 1.0. Kubernetes har scen och produktion, där denna bild laddas upp. I Git gör vi commits och någon gång taggar vi 2.0. Vi samlar in den enligt instruktionerna från förvaret och placerar den i registret med taggen 2.0. Vi rullar ut den till scenen och, om allt är bra, sedan till produktionen.

werf - vårt verktyg för CI / CD i Kubernetes (översikt och videorapport)

Problemet med detta tillvägagångssätt är att vi först satte taggen och först sedan testade och rullade ut den. Varför? För det första är det helt enkelt ologiskt: vi utfärdar en version av programvara som vi inte ens har testat ännu (vi kan inte göra något annat, för för att kontrollera måste vi sätta en tagg). För det andra är denna väg inte kompatibel med Gitflow.

Det andra alternativet - git commit + tagg. Mastergrenen har en tagg 1.0; för det i registret - en bild som distribueras till produktion. Dessutom har Kubernetes-klustret förhandsgransknings- och iscensättningskonturer. Därefter följer vi Gitflow: i huvudgrenen för utveckling (develop) skapar vi nya funktioner, vilket resulterar i en commit med identifieraren #c1. Vi samlar in den och publicerar den i registret med denna identifierare (#c1). Med samma identifierare rullar vi ut för att förhandsgranska. Vi gör samma sak med commits #c2 и #c3.

När vi insåg att det finns tillräckligt med funktioner börjar vi stabilisera allt. Skapa en filial i Git release_1.1 (på basen #c3 av develop). Det finns inget behov av att samla in den här versionen, eftersom... detta gjordes i föregående steg. Därför kan vi helt enkelt rulla ut det till iscensättning. Vi fixar buggar i #c4 och rulla på liknande sätt ut till iscensättning. Samtidigt pågår utveckling inom develop, där ändringar periodvis hämtas ifrån release_1.1. Vid något tillfälle får vi en commit sammanställd och uppladdad till staging, som vi är nöjda med (#c25).

Sedan slår vi ihop (med snabbspolning framåt) släppgrenen (release_1.1) i master. Vi sätter en tagg med den nya versionen på denna commit (1.1). Men den här bilden är redan samlad i registret, så för att inte samla in den igen lägger vi helt enkelt till en andra tagg till den befintliga bilden (nu har den taggar i registret #c25 и 1.1). Efter det rullar vi ut den till produktion.

Det finns en nackdel att endast en bild laddas upp till staging (#c25), och i produktionen är det lite annorlunda (1.1), men vi vet att dessa "fysiskt" är samma bild från registret.

werf - vårt verktyg för CI / CD i Kubernetes (översikt och videorapport)

Den verkliga nackdelen är att det inte finns något stöd för merge commits, du måste snabbspola framåt.

Vi kan gå längre och göra ett trick... Låt oss titta på ett exempel på en enkel Dockerfil:

FROM ruby:2.3 as assets
RUN mkdir -p /app
WORKDIR /app
COPY . ./
RUN gem install bundler && bundle install
RUN bundle exec rake assets:precompile
CMD bundle exec puma -C config/puma.rb

FROM nginx:alpine
COPY --from=assets /app/public /usr/share/nginx/www/public

Låt oss bygga en fil från den enligt följande princip:

  • SHA256 från identifierarna för de bilder som används (ruby:2.3 и nginx:alpine), som är kontrollsummor av deras innehåll;
  • alla lag (RUN, CMD och så vidare.);
  • SHA256 från filer som lades till.

... och ta kontrollsumman (igen SHA256) från en sådan fil. Detta signatur allt som definierar innehållet i Docker-bilden.

werf - vårt verktyg för CI / CD i Kubernetes (översikt och videorapport)

Låt oss gå tillbaka till diagrammet och istället för att begå kommer vi att använda sådana signaturer, dvs. tagga bilder med signaturer.

werf - vårt verktyg för CI / CD i Kubernetes (översikt och videorapport)

Nu, när det är nödvändigt, till exempel, att slå samman ändringar från en release till master, kan vi göra en riktig sammanfogning: den kommer att ha en annan identifierare, men samma signatur. Med samma identifierare kommer vi att rulla ut bilden till produktion.

Nackdelen är att det nu inte kommer att vara möjligt att avgöra vilken typ av commit som pressades till produktion - kontrollsummor fungerar bara i en riktning. Det här problemet löses av ett extra lager med metadata - jag ska berätta mer senare.

Taggar i werf

I werf gick vi ännu längre och förbereder oss för att göra en distribuerad build med en cache som inte är lagrad på en maskin... Så vi bygger två typer av Docker-bilder, vi kallar dem skede и bild.

werf Git-förvaret lagrar byggspecifika instruktioner som beskriver de olika stegen i bygget (före installation, installera, före installationen, inställning). Vi samlar in första stegsbilden med en signatur som definieras som kontrollsumman för de första stegen. Sedan lägger vi till källkoden, för den nya scenbilden beräknar vi dess kontrollsumma... Dessa operationer upprepas för alla steg, som ett resultat av vilket vi får en uppsättning scenbilder. Sedan gör vi den slutliga bilden, som även innehåller metadata om dess ursprung. Och vi taggar den här bilden på olika sätt (detaljer senare).

werf - vårt verktyg för CI / CD i Kubernetes (översikt och videorapport)

Anta att efter detta en ny commit dyker upp där endast applikationskoden har ändrats. Vad kommer att hända? För kodändringar kommer en patch att skapas och en ny scenbild kommer att förberedas. Dess signatur kommer att bestämmas som kontrollsumman för den gamla scenbilden och den nya patchen. En ny slutlig bild kommer att skapas från denna bild. Liknande beteende kommer att inträffa med förändringar i andra stadier.

Scenbilder är alltså en cache som kan lagras distribuerat, och de bilder som redan skapats från den laddas upp till Docker Registry.

werf - vårt verktyg för CI / CD i Kubernetes (översikt och videorapport)

Rengöring av registret

Vi pratar inte om att ta bort lager som förblev hängande efter borttagna taggar - detta är en standardfunktion i själva Docker Registry. Vi pratar om en situation när många Docker-taggar samlas och vi förstår att vi inte längre behöver några av dem, men de tar plats (och/eller vi betalar för det).

Vilka är städstrategierna?

  1. Du kan bara göra ingenting städa inte. Ibland är det verkligen lättare att betala lite för extra utrymme än att reda ut en enorm härva av taggar. Men detta fungerar bara upp till en viss punkt.
  2. Full återställning. Om du tar bort alla bilder och bygger bara om de nuvarande i CI-systemet kan ett problem uppstå. Om behållaren startas om i produktionen kommer en ny bild att laddas för den - en som ännu inte har testats av någon. Detta dödar idén om oföränderlig infrastruktur.
  3. Blå grön. Ett register började svämma över - vi laddar upp bilder till ett annat. Samma problem som i föregående metod: vid vilken tidpunkt kan du rensa registret som har börjat svämma över?
  4. Med tiden. Ta bort alla bilder äldre än 1 månad? Men det kommer definitivt att finnas en tjänst som inte har uppdaterats på en månad...
  5. manuellt avgöra vad som redan kan raderas.

Det finns två verkligt genomförbara alternativ: rengör inte eller en kombination av blågrön + manuellt. I det senare fallet talar vi om följande: när du förstår att det är dags att rensa registret skapar du ett nytt och lägger till alla nya bilder till det under loppet av till exempel en månad. Och efter en månad, se vilka pods i Kubernetes som fortfarande använder det gamla registret och överför även dem till det nya registret.

Vad har vi kommit fram till werf? Vi samlar in:

  1. Git head: alla taggar, alla grenar - förutsatt att vi behöver allt som är taggat i Git i bilderna (och om inte, så måste vi ta bort det i själva Git);
  2. alla baljor som för närvarande pumpas ut till Kubernetes;
  3. gamla ReplicaSets (som nyligen släpptes), och vi planerar även att skanna Helm-releaser och välja de senaste bilderna där.

... och gör en vitlista från denna uppsättning - en lista över bilder som vi inte kommer att radera. Vi rensar ut allt annat, varefter vi hittar föräldralösa scenbilder och raderar dem också.

Utplaceringsstadiet

Pålitlig deklarativitet

Den första punkten som jag skulle vilja uppmärksamma i utbyggnaden är utrullningen av den uppdaterade resurskonfigurationen, deklarerat. Det ursprungliga YAML-dokumentet som beskriver Kubernetes-resurser skiljer sig alltid mycket från resultatet som faktiskt körs i klustret. Eftersom Kubernetes lägger till i konfigurationen:

  1. identifierare;
  2. serviceinformation;
  3. många standardvärden;
  4. avsnitt med aktuell status;
  5. ändringar gjorda som en del av antagningswebbhooken;
  6. resultatet av olika kontrollers (och schemaläggarens) arbete.

Därför, när en ny resurskonfiguration visas (ny), kan vi inte bara ta och skriva över den nuvarande "live"-konfigurationen med den (lever). För att göra detta måste vi jämföra ny med den senast tillämpade konfigurationen (senast applicerade) och rulla på lever fått patch.

Detta tillvägagångssätt kallas 2-vägs sammanslagning. Det används till exempel i Helm.

Det finns även 3-vägs sammanslagning, som skiljer sig genom att:

  • jämförande senast applicerade и ny, vi tittar på vad som raderades;
  • jämförande ny и lever, vi tittar på vad som har lagts till eller ändrats;
  • den summerade lappen appliceras på lever.

Vi distribuerar 1000+ applikationer med Helm, så vi lever faktiskt med 2-vägs sammanslagning. Den har dock ett antal problem som vi har löst med våra patchar, som hjälper Helm att fungera normalt.

Verklig utbyggnadsstatus

Efter att vårt CI-system har genererat en ny konfiguration för Kubernetes baserat på nästa händelse, överför det den för användning (tillämpa) till ett kluster - med hjälp av Helm eller kubectl apply. Därefter inträffar den redan beskrivna N-vägs sammanslagning, som Kubernetes API svarar godkännande på CI-systemet, och det till dess användare.

werf - vårt verktyg för CI / CD i Kubernetes (översikt och videorapport)

Det finns dock ett stort problem: trots allt framgångsrik ansökan betyder inte framgångsrik lansering. Om Kubernetes förstår vilka ändringar som behöver tillämpas och tillämpar det, vet vi fortfarande inte vad resultatet kommer att bli. Till exempel kan uppdatering och omstart av poddar i frontend vara framgångsrika, men inte i backend, och vi kommer att få olika versioner av de körande programbilderna.

För att göra allt korrekt kräver detta schema en extra länk - en speciell spårare som kommer att ta emot statusinformation från Kubernetes API och överföra den för ytterligare analys av det verkliga tillståndet. Vi skapade ett bibliotek med öppen källkod i Go - kubhund (se dess tillkännagivande här), som löser detta problem och är inbyggt i werf.

Beteendet för den här spåraren på werfnivå konfigureras med anteckningar som placeras på Deployments eller StatefulSets. Huvudkommentar - fail-mode - förstår följande betydelser:

  • IgnoreAndContinueDeployProcess — Vi ignorerar problemen med att rulla ut denna komponent och fortsätter utbyggnaden.
  • FailWholeDeployProcessImmediately — ett fel i denna komponent stoppar driftsättningsprocessen;
  • HopeUntilEndOfDeployProcess — Vi hoppas att den här komponenten kommer att fungera i slutet av utbyggnaden.

Till exempel denna kombination av resurser och anteckningsvärden fail-mode:

werf - vårt verktyg för CI / CD i Kubernetes (översikt och videorapport)

När vi distribuerar för första gången kanske databasen (MongoDB) inte är klar ännu - distributioner kommer att misslyckas. Men du kan vänta tills den startar, och distributionen kommer fortfarande att ske.

Det finns ytterligare två kommentarer för kubedog i werf:

  • failures-allowed-per-replica — Antalet tillåtna fall för varje replik.
  • show-logs-until — reglerar det ögonblick till vilket werf visar (i stdout) stockar från alla utrullade baljor. Standard är PodIsReady (för att ignorera meddelanden som vi egentligen inte vill ha när trafik börjar komma till podden), men värdena är också giltiga: ControllerIsReady и EndOfDeploy.

Vad mer vill vi ha av utbyggnaden?

Utöver de två redan beskrivna punkterna vill vi:

  • att se loggar - och bara de nödvändiga, och inte allt i rad;
  • Spår framsteg, för om jobbet hänger "tyst" i flera minuter är det viktigt att förstå vad som händer där;
  • иметь automatisk återställning om något gick fel (och därför är det viktigt att veta den verkliga statusen för utplaceringen). Utrullningen måste vara atomär: antingen går den igenom till slutet eller så återgår allt till sitt tidigare tillstånd.

Resultat av

För oss som företag, för att implementera alla beskrivna nyanser i olika stadier av leverans (bygga, publicera, distribuera), räcker ett CI-system och ett verktyg werf.

Istället för en slutsats:

werf - vårt verktyg för CI / CD i Kubernetes (översikt och videorapport)

Med hjälp av werf har vi gjort goda framsteg i att lösa ett stort antal problem för DevOps-ingenjörer och skulle vara glada om det bredare samhället åtminstone provade detta verktyg i praktiken. Det blir lättare att nå ett bra resultat tillsammans.

Videor och bilder

Video från föreställningen (~47 minuter):

Presentation av rapporten:

PS

Andra rapporter om Kubernetes på vår blogg:

Källa: will.com

Lägg en kommentar