werf - vores værktøj til CI / CD i Kubernetes (oversigt og videorapport)

27. maj i hovedsalen på DevOpsConf 2019-konferencen, afholdt som en del af festivalen RIT++ 2019, som en del af afsnittet "Kontinuerlig levering" blev der givet en rapport "werf - vores værktøj til CI/CD i Kubernetes". Den taler om dem problemer og udfordringer, som alle står over for, når de implementeres til Kubernetes, samt om nuancer, der måske ikke umiddelbart er mærkbare. Ved at analysere mulige løsninger viser vi, hvordan dette implementeres i et Open Source-værktøj werf.

Siden præsentationen har vores værktøj (tidligere kendt som en dapp) nået en historisk milepæl for 1000 stjerner på GitHub — vi håber, at dets voksende fællesskab af brugere vil gøre livet lettere for mange DevOps-ingeniører.

werf - vores værktøj til CI / CD i Kubernetes (oversigt og videorapport)

Så vi præsenterer video af rapporten (~47 minutter, meget mere informativ end artiklen) og hoveduddraget fra den i tekstform. Gå!

Levering af kode til Kubernetes

Foredraget vil ikke længere handle om werf, men om CI/CD i Kubernetes, hvilket betyder, at vores software er pakket i Docker-containere (Jeg talte om dette i 2016 rapport), og K8s vil blive brugt til at køre det i produktion (mere om dette i 2017 år).

Hvordan ser levering ud i Kubernetes?

  • Der er et Git-lager med koden og instruktioner til at bygge det. Applikationen er indbygget i et Docker-billede og udgivet i Docker Registry.
  • Det samme lager indeholder også instruktioner om, hvordan du installerer og kører programmet. På implementeringsstadiet sendes disse instruktioner til Kubernetes, som modtager det ønskede billede fra registreringsdatabasen og starter det.
  • Derudover er der normalt tests. Nogle af disse kan gøres, når du offentliggør et billede. Du kan også (ved at følge de samme instruktioner) installere en kopi af applikationen (i et separat K8s navneområde eller en separat klynge) og køre test der.
  • Endelig har du brug for et CI-system, der modtager hændelser fra Git (eller knapklik) og kalder alle de udpegede stadier: build, publicer, deploy, test.

werf - vores værktøj til CI / CD i Kubernetes (oversigt og videorapport)

Der er et par vigtige bemærkninger her:

  1. Fordi vi har en uforanderlig infrastruktur (uforanderlig infrastruktur), applikationsbilledet, der bruges på alle stadier (iscenesættelse, produktion osv.), der skal være en. Jeg talte om dette mere detaljeret og med eksempler. her.
  2. Fordi vi følger infrastrukturen som kodetilgang (IAC), skal applikationskoden, instruktioner til montering og lancering være nøjagtigt i ét depot. For mere information om dette, se samme rapport.
  3. Leveringskæde (levering) vi ser det normalt sådan her: applikationen blev samlet, testet, frigivet (udgivelsesstadiet) og det er det - levering har fundet sted. Men i virkeligheden får brugeren det, du rullede ud, nej så når du leverede det til produktionen, og når han kunne gå der, og denne produktion virkede. Så jeg tror, ​​at leveringskæden slutter kun på operationsstadiet (løb), eller mere præcist, selv i det øjeblik, hvor koden blev fjernet fra produktionen (erstatter den med en ny).

Lad os vende tilbage til ovenstående leveringsordning i Kubernetes: det blev ikke kun opfundet af os, men af ​​bogstaveligt talt alle, der beskæftigede sig med dette problem. Faktisk kaldes dette mønster nu GitOps (du kan læse mere om begrebet og ideerne bag det her). Lad os se på faserne af ordningen.

Byg scene

Det ser ud til, at du kan tale om at bygge Docker-billeder i 2019, når alle ved, hvordan man skriver Dockerfiler og kører docker build?.. Her er de nuancer, som jeg gerne vil være opmærksom på:

  1. Billedets vægt betyder noget, så brug etapevisfor kun at efterlade den applikation, der virkelig er nødvendig for operationen, i billedet.
  2. Antal lag skal minimeres ved at kombinere kæder af RUN-kommandoer efter betydning.
  3. Dette tilføjer dog problemer fejlretning, for når samlingen går ned, skal du finde den rigtige kommando fra kæden, der forårsagede problemet.
  4. Byg hastighed vigtigt, fordi vi hurtigt vil udrulle ændringer og se resultaterne. For eksempel ønsker du ikke at genopbygge afhængigheder i sprogbiblioteker, hver gang du bygger en applikation.
  5. Ofte fra ét Git-lager, du har brug for mange billeder, som kan løses af et sæt Dockerfiler (eller navngivne stadier i én fil) og et Bash-script med deres sekventielle samling.

Dette var kun toppen af ​​isbjerget, som alle står over for. Men der er andre problemer, især:

  1. Ofte på montagestadiet har vi brug for noget montere (for eksempel cache resultatet af en kommando som apt i en tredjeparts mappe).
  2. Vi vil have Ansible i stedet for at skrive i skal.
  3. Vi vil have bygge uden Docker (hvorfor har vi brug for en ekstra virtuel maskine, hvor vi skal konfigurere alt til dette, når vi allerede har en Kubernetes-klynge, hvor vi kan køre containere?).
  4. Parallel montering, som kan forstås på forskellige måder: forskellige kommandoer fra Dockerfilen (hvis multi-stage bruges), flere commits af det samme lager, flere Dockerfiler.
  5. Distribueret forsamling: Vi vil gerne samle ting i bælg, der er "flygtige" pga deres cache forsvinder, hvilket betyder, at den skal opbevares et sted separat.
  6. Til sidst navngav jeg toppen af ​​begær automagisk: Det ville være ideelt at gå til repository, skrive en kommando og få et færdigt billede, samlet med en forståelse af hvordan og hvad man skal gøre korrekt. Jeg er dog personligt ikke sikker på, at alle nuancerne kan forudses på denne måde.

Og her er projekterne:

  • moby/byggesæt — en builder fra Docker Inc (allerede integreret i de nuværende versioner af Docker), som forsøger at løse alle disse problemer;
  • kaniko — en builder fra Google, der giver dig mulighed for at bygge uden Docker;
  • Buildpacks.io — CNCFs forsøg på at lave automatisk magi og i særdeleshed en interessant løsning med rebase til lag;
  • og en masse andre hjælpeprogrammer, som f.eks buildah, ægte værktøj/img...

...og se hvor mange stjerner de har på GitHub. Det vil sige på den ene side docker build eksisterer og kan noget, men i virkeligheden problemet er ikke helt løst - et bevis på dette er den parallelle udvikling af alternative samlere, som hver især løser en del af problemerne.

Samling i werf

Så det måtte vi werf (tidligere berømt som dapp) — Et open source-værktøj fra firmaet Flant, som vi har lavet i mange år. Det hele startede for 5 år siden med Bash-scripts, der optimerede samlingen af ​​Dockerfiles, og i de sidste 3 år er der gennemført fuld udvikling inden for rammerne af ét projekt med eget Git-lager (først i Ruby, og derefter omskrevet to Go, og samtidig omdøbt). Hvilke monteringsproblemer løses i werf?

werf - vores værktøj til CI / CD i Kubernetes (oversigt og videorapport)

Problemerne med blåt skygge er allerede implementeret, den parallelle opbygning er blevet udført inden for den samme vært, og de problemer, der er fremhævet med gult, er planlagt til at være afsluttet i slutningen af ​​sommeren.

Stadium af offentliggørelse i registreringsdatabasen (publicer)

Vi ringede docker push... - hvad kan være svært ved at uploade et billede til registreringsdatabasen? Og så opstår spørgsmålet: "Hvilket tag skal jeg sætte på billedet?" Det opstår af den grund, at vi har Gitflow (eller anden Git-strategi) og Kubernetes, og industrien forsøger at sikre, at det, der sker i Kubernetes, følger det, der sker i Git. Git er trods alt vores eneste kilde til sandhed.

Hvad er så svært ved det her? Sikre reproducerbarhed: fra en commit i Git, som er uforanderlig i naturen (uforanderlig), til et Docker-billede, som skal bevares det samme.

Det er også vigtigt for os bestemme oprindelsen, fordi vi ønsker at forstå, fra hvilken commit applikationen, der kører i Kubernetes, blev bygget (så kan vi lave diffs og lignende ting).

Tagging strategier

Den første er enkel git tag. Vi har et register med et billede mærket som 1.0. Kubernetes har scene og produktion, hvor dette billede uploades. I Git laver vi commits og på et tidspunkt tagger vi 2.0. Vi indsamler det i henhold til instruktionerne fra depotet og placerer det i registreringsdatabasen med tagget 2.0. Vi ruller det ud til scenen og, hvis alt er vel, så til produktionen.

werf - vores værktøj til CI / CD i Kubernetes (oversigt og videorapport)

Problemet med denne tilgang er, at vi først satte mærket, og først derefter testede og rullede det ud. Hvorfor? For det første er det simpelthen ulogisk: vi udsteder en version af software, som vi ikke engang har testet endnu (vi kan ikke gøre andet, for for at kontrollere, skal vi sætte et mærke). For det andet er denne sti ikke kompatibel med Gitflow.

Den anden mulighed - git commit + tag. Hovedgrenen har et mærke 1.0; for det i registreringsdatabasen - et billede, der er implementeret til produktion. Derudover har Kubernetes-klyngen preview- og iscenesættelseskonturer. Dernæst følger vi Gitflow: i hovedgrenen for udvikling (develop) laver vi nye funktioner, hvilket resulterer i en commit med identifikatoren #c1. Vi indsamler det og offentliggør det i registreringsdatabasen ved hjælp af denne identifikator (#c1). Med den samme identifikator ruller vi ud for at forhåndsvise. Vi gør det samme med commits #c2 и #c3.

Da vi indså, at der er nok funktioner, begynder vi at stabilisere alt. Opret en filial i Git release_1.1 (på basen #c3 af develop). Der er ingen grund til at indsamle denne udgivelse, fordi... dette blev gjort i det foregående trin. Derfor kan vi blot rulle det ud til iscenesættelse. Vi retter fejl i #c4 og ruller tilsvarende ud til iscenesættelse. Samtidig er udviklingen i gang i develop, hvor ændringer med jævne mellemrum hentes fra release_1.1. På et tidspunkt får vi en commit kompileret og uploadet til staging, som vi er tilfredse med (#c25).

Så fletter vi (med hurtig frem) udgivelsesgrenen (release_1.1) i master. Vi sætter et tag med den nye version på denne commit (1.1). Men dette billede er allerede samlet i registreringsdatabasen, så for ikke at samle det igen, tilføjer vi blot et andet tag til det eksisterende billede (nu har det tags i registreringsdatabasen #c25 и 1.1). Derefter ruller vi det ud til produktion.

Der er en ulempe, at kun ét billede uploades til iscenesættelse (#c25), og i produktionen er det lidt anderledes (1.1), men vi ved, at disse "fysisk" er det samme billede fra registreringsdatabasen.

werf - vores værktøj til CI / CD i Kubernetes (oversigt og videorapport)

Den virkelige ulempe er, at der ikke er understøttelse af merge commits, du skal gøre fast-forward.

Vi kan gå videre og lave et trick... Lad os se på et eksempel på en simpel Dockerfile:

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

Lad os bygge en fil ud fra det efter følgende princip:

  • SHA256 fra identifikatorerne for de anvendte billeder (ruby:2.3 и nginx:alpine), som er kontrolsummer af deres indhold;
  • alle hold (RUN, CMD og så videre.);
  • SHA256 fra filer, der blev tilføjet.

... og tag kontrolsummen (igen SHA256) fra sådan en fil. Det her Underskrift alt, hvad der definerer indholdet af Docker-billedet.

werf - vores værktøj til CI / CD i Kubernetes (oversigt og videorapport)

Lad os gå tilbage til diagrammet og i stedet for at forpligte sig, vil vi bruge sådanne signaturer, dvs. tag billeder med signaturer.

werf - vores værktøj til CI / CD i Kubernetes (oversigt og videorapport)

Nu, når det er nødvendigt, for eksempel at flette ændringer fra en udgivelse til master, kan vi lave en ægte flette-commit: den vil have en anden identifikator, men den samme signatur. Med samme identifikator ruller vi billedet ud til produktion.

Ulempen er, at det nu ikke vil være muligt at afgøre, hvilken slags commit der blev skubbet til produktion - kontrolsummer virker kun i én retning. Dette problem er løst af et ekstra lag med metadata - jeg fortæller dig mere senere.

Tagging i werf

I werf gik vi endnu længere og forbereder os på at lave en distribueret build med en cache, der ikke er gemt på én maskine... Så vi bygger to typer Docker-billeder, vi kalder dem etape и billede.

werf Git repository gemmer build-specifikke instruktioner, der beskriver de forskellige stadier af build (før Installer, installere, før opsætning, setup). Vi samler billedet i første fase med en signatur defineret som kontrolsummen af ​​de første trin. Så tilføjer vi kildekoden, for det nye scenebillede beregner vi dets kontrolsum... Disse operationer gentages for alle stadier, som et resultat af hvilket vi får et sæt scenebilleder. Så laver vi det endelige billede, som også indeholder metadata om dets oprindelse. Og vi mærker dette billede på forskellige måder (detaljer senere).

werf - vores værktøj til CI / CD i Kubernetes (oversigt og videorapport)

Antag, at der efter dette dukker en ny commit op, hvor kun applikationskoden er blevet ændret. Hvad vil der ske? For kodeændringer oprettes en patch, og et nyt scenebillede vil blive forberedt. Dens signatur vil blive bestemt som kontrolsummen af ​​det gamle scenebillede og det nye patch. Et nyt endeligt billede vil blive dannet ud fra dette billede. Lignende adfærd vil forekomme med ændringer i andre stadier.

Scenebilleder er således en cache, der kan gemmes distribueret, og de billeder, der allerede er oprettet fra den, uploades til Docker Registry.

werf - vores værktøj til CI / CD i Kubernetes (oversigt og videorapport)

Rensning af registreringsdatabasen

Vi taler ikke om at slette lag, der forblev hængende efter slettede tags - dette er en standardfunktion i selve Docker Registry. Vi taler om en situation, hvor en masse Docker-tags akkumuleres, og vi forstår, at vi ikke længere har brug for nogle af dem, men de optager plads (og/eller vi betaler for det).

Hvad er rengøringsstrategierne?

  1. Du kan bare ingenting gør ikke rent. Nogle gange er det virkelig nemmere at betale lidt for ekstra plads end at optrevle et kæmpe virvar af tags. Men dette virker kun op til et vist punkt.
  2. Fuld nulstilling. Hvis du sletter alle billederne og kun genopbygger de nuværende i CI-systemet, kan der opstå et problem. Hvis containeren genstartes i produktionen, indlæses et nyt billede til den - et som endnu ikke er testet af nogen. Dette dræber ideen om uforanderlig infrastruktur.
  3. Blågrøn. Et register begyndte at flyde over - vi uploader billeder til et andet. Det samme problem som i den foregående metode: på hvilket tidspunkt kan du rydde registreringsdatabasen, der er begyndt at flyde over?
  4. Med tiden. Vil du slette alle billeder, der er ældre end 1 måned? Men der kommer helt sikkert en tjeneste, der ikke er blevet opdateret i en måned...
  5. manuelt bestemme, hvad der allerede kan slettes.

Der er to virkelig levedygtige muligheder: Rengør ikke eller en kombination af blå-grøn + manuelt. I sidstnævnte tilfælde taler vi om følgende: Når du forstår, at det er tid til at rense registreringsdatabasen, opretter du et nyt og tilføjer alle nye billeder til det i løbet af for eksempel en måned. Og efter en måned kan du se, hvilke pods i Kubernetes der stadig bruger det gamle register, og overføre dem også til det nye register.

Hvad er vi kommet til werf? Vi indsamler:

  1. Git head: alle tags, alle brancher - forudsat at vi har brug for alt der er tagget i Git i billederne (og hvis ikke, så skal vi slette det i Git selv);
  2. alle bælg, der i øjeblikket pumpes ud til Kubernetes;
  3. gamle ReplicaSets (hvad der for nylig blev udgivet), og vi planlægger også at scanne Helm-udgivelser og vælge de seneste billeder der.

... og lav en hvidliste fra dette sæt - en liste over billeder, som vi ikke vil slette. Vi rydder ud i alt andet, hvorefter vi finder forældreløse scenebilleder og sletter dem også.

Implementeringsstadiet

Pålidelig deklarativitet

Det første punkt, som jeg gerne vil henlede opmærksomheden på i udrulningen, er udrulningen af ​​den opdaterede ressourcekonfiguration, erklæret deklarativt. Det originale YAML-dokument, der beskriver Kubernetes-ressourcer, er altid meget forskelligt fra det resultat, der rent faktisk kører i klyngen. Fordi Kubernetes tilføjer til konfigurationen:

  1. identifikatorer;
  2. serviceinformation;
  3. mange standardværdier;
  4. afsnit med aktuel status;
  5. ændringer foretaget som en del af adgangswebhooken;
  6. resultatet af forskellige controlleres (og planlæggerens) arbejde.

Derfor, når en ny ressourcekonfiguration vises (ny), kan vi ikke bare tage og overskrive den aktuelle "live" konfiguration med den (leve). For at gøre dette bliver vi nødt til at sammenligne ny med den sidst anvendte konfiguration (sidst anvendte) og rul på leve modtaget patch.

Denne tilgang kaldes 2-vejs fletning. Det bruges for eksempel i Helm.

Der er også 3-vejs fletning, som adskiller sig ved at:

  • sammenligner sidst anvendte и ny, vi ser på, hvad der blev slettet;
  • sammenligner ny и leve, ser vi på, hvad der er tilføjet eller ændret;
  • det summerede plaster påføres leve.

Vi implementerer mere end 1000 applikationer med Helm, så vi lever faktisk med 2-vejs fletning. Det har dog en række problemer, som vi har løst med vores patches, som hjælper Helm til at fungere normalt.

Reel udrulningsstatus

Efter vores CI-system har genereret en ny konfiguration for Kubernetes baseret på den næste hændelse, transmitterer det den til brug (ansøge) til en klynge - ved hjælp af Helm eller kubectl apply. Dernæst sker den allerede beskrevne N-vejs fletning, hvorpå Kubernetes API reagerer godkendende på CI-systemet, og det til dets bruger.

werf - vores værktøj til CI / CD i Kubernetes (oversigt og videorapport)

Der er dog et kæmpe problem: trods alt vellykket ansøgning betyder ikke succesfuld udrulning. Hvis Kubernetes forstår, hvilke ændringer der skal anvendes og anvender dem, ved vi stadig ikke, hvad resultatet bliver. For eksempel kan opdatering og genstart af pods i frontend være vellykket, men ikke i backend, og vi får forskellige versioner af de kørende applikationsbilleder.

For at gøre alt korrekt kræver denne ordning et ekstra link - en speciel tracker, der modtager statusinformation fra Kubernetes API og overfører den til yderligere analyse af tingenes virkelige tilstand. Vi oprettede et Open Source-bibliotek i Go - terninghund (se dens meddelelse her), som løser dette problem og er indbygget i werf.

Opførselen af ​​denne tracker på werf-niveau er konfigureret ved hjælp af annoteringer, der er placeret på Deployments eller StatefulSets. Hovedanmærkning - fail-mode - forstår følgende betydninger:

  • IgnoreAndContinueDeployProcess — vi ignorerer problemerne med at udrulle denne komponent og fortsætter implementeringen;
  • FailWholeDeployProcessImmediately — en fejl i denne komponent stopper implementeringsprocessen;
  • HopeUntilEndOfDeployProcess — vi håber, at denne komponent vil fungere ved afslutningen af ​​implementeringen.

For eksempel denne kombination af ressourcer og annotationsværdier fail-mode:

werf - vores værktøj til CI / CD i Kubernetes (oversigt og videorapport)

Når vi implementerer for første gang, er databasen (MongoDB) muligvis ikke klar endnu - Implementeringer vil mislykkes. Men du kan vente på, at den starter, og implementeringen vil stadig finde sted.

Der er yderligere to kommentarer til kubedog i werf:

  • failures-allowed-per-replica — antallet af tilladte fald for hver replika;
  • show-logs-until — regulerer det øjeblik, indtil hvilket werf viser (i stdout) logfiler fra alle udrullede bælg. Standard er PodIsReady (for at ignorere beskeder, som vi sandsynligvis ikke ønsker, når trafik begynder at komme til poden), men værdierne er også gyldige: ControllerIsReady и EndOfDeploy.

Hvad ønsker vi ellers af implementering?

Ud over de to allerede beskrevne punkter vil vi gerne:

  • at se logs - og kun de nødvendige, og ikke alt på række;
  • spore fremskridt, for hvis jobbet hænger "stille" i flere minutter, er det vigtigt at forstå, hvad der sker der;
  • иметь automatisk tilbagerulning i tilfælde af, at noget gik galt (og derfor er det afgørende at kende den reelle status for implementeringen). Udrulningen skal være atomisk: enten går den igennem til slutningen, eller også vender alt tilbage til sin tidligere tilstand.

Resultaterne af

For os som virksomhed er et CI-system og et hjælpeprogram nok til at implementere alle de beskrevne nuancer på forskellige leveringsstadier (bygge, publicere, implementere) werf.

I stedet for en konklusion:

werf - vores værktøj til CI / CD i Kubernetes (oversigt og videorapport)

Med hjælp fra werf har vi gjort gode fremskridt med at løse en lang række problemer for DevOps-ingeniører og ville være glade, hvis det bredere samfund i det mindste prøvede dette værktøj i aktion. Det bliver nemmere at opnå et godt resultat sammen.

Videoer og dias

Video fra forestillingen (~47 minutter):

Præsentation af rapporten:

PS

Andre rapporter om Kubernetes på vores blog:

Kilde: www.habr.com

Tilføj en kommentar