werf - naše orodje za CI/CD v Kubernetesu (pregled in video poročilo)

27. maja v glavni dvorani konference DevOpsConf 2019, ki poteka v okviru festivala RIT++ 2019, v sklopu razdelka “Continuous Delivery” je bilo podano poročilo “werf - naše orodje za CI/CD v Kubernetesu”. Govori o tistih težave in izzivi, s katerimi se vsi srečujejo pri uvajanju v Kubernetes, pa tudi o niansah, ki morda niso takoj opazne. Z analizo možnih rešitev pokažemo, kako je to implementirano v odprtokodnem orodju werf.

Od predstavitve je naš pripomoček (prej znan kot dapp) dosegel zgodovinski mejnik 1000 zvezdic na GitHubu — upamo, da bo njegova rastoča skupnost uporabnikov olajšala življenje številnim inženirjem DevOps.

werf - naše orodje za CI/CD v Kubernetesu (pregled in video poročilo)

Torej, predstavljamo video poročila (~47 minut, veliko bolj informativno kot članek) in glavni izvleček iz njega v besedilni obliki. Pojdi!

Dostava kode v Kubernetes

Govora ne bo več o werf, ampak o CI/CD v Kubernetesu, kar pomeni, da je naša programska oprema zapakirana v vsebnike Docker (O tem sem govoril v 2016 poročilo), K8s pa bo uporabljen za delovanje v proizvodnji (več o tem v 2017 leto).

Kako izgleda dostava v Kubernetesu?

  • Obstaja repozitorij Git s kodo in navodili za njeno izdelavo. Aplikacija je vgrajena v sliko Docker in objavljena v registru Docker.
  • Isti repozitorij vsebuje tudi navodila za namestitev in zagon aplikacije. V fazi uvajanja se ta navodila pošljejo Kubernetesu, ki prejme želeno sliko iz registra in jo zažene.
  • Poleg tega so ponavadi testi. Nekatere od teh lahko storite ob objavi slike. Prav tako lahko (po enakih navodilih) namestite kopijo aplikacije (v ločen imenski prostor K8s ali ločeno gručo) in tam izvajate teste.
  • Končno potrebujete sistem CI, ki sprejema dogodke iz Gita (ali klike gumbov) in kliče vse določene stopnje: izdelava, objava, uvedba, testiranje.

werf - naše orodje za CI/CD v Kubernetesu (pregled in video poročilo)

Tukaj je nekaj pomembnih opomb:

  1. Ker imamo nespremenljivo infrastrukturo (nespremenljiva infrastruktura), sliko aplikacije, ki se uporablja v vseh fazah (uprizoritev, produkcija itd.), ena mora obstajati. O tem sem govoril podrobneje in s primeri. tukaj.
  2. Ker sledimo pristopu infrastrukture kot kode (IaC), mora biti koda aplikacije, navodila za sestavljanje in zagon točno v enem skladišču. Za več informacij o tem glejte isto poročilo.
  3. Dostavna veriga (dostava) običajno vidimo takole: aplikacija je bila sestavljena, testirana, izdana (stopnja sprostitve) in to je to - dostava je opravljena. Toda v resnici uporabnik dobi tisto, kar ste predstavili, ne takrat, ko si ga dostavil v proizvodnjo in ko je lahko šel tja in je ta proizvodnja delovala. Zato verjamem, da se dobavna veriga konča samo v operativni fazi (teči), ali natančneje, tudi v trenutku, ko je bila koda odstranjena iz produkcije (zamenjana z novo).

Vrnimo se k zgornji shemi dostave v Kubernetesu: izumili smo je ne le mi, ampak dobesedno vsi, ki so se ukvarjali s to težavo. Pravzaprav se ta vzorec zdaj imenuje GitOps (lahko preberete več o izrazu in idejah za njim tukaj). Oglejmo si faze sheme.

Faza gradnje

Zdi se, da lahko govorite o gradnji Dockerjevih slik v letu 2019, ko vsi vedo, kako pisati Dockerfile in izvajati docker build?.. Tu so nianse, na katere bi rad pozoren:

  1. Teža slike zadeve, zato uporabite večstopenjskida na sliki pustimo samo aplikacijo, ki je res potrebna za delovanje.
  2. Število plasti je treba minimizirati s kombiniranjem verig RUN-ukazi po pomenu.
  3. Vendar to dodaja težave odpravljanje napak, ker ko se sklop sesuje, morate najti pravi ukaz iz verige, ki je povzročila težavo.
  4. Hitrost montaže pomembno, ker želimo hitro uvesti spremembe in videti rezultate. Na primer, ne želite znova zgraditi odvisnosti v jezikovnih knjižnicah vsakič, ko zgradite aplikacijo.
  5. Pogosto iz enega repozitorija Git, ki ga potrebujete veliko slik, ki ga je mogoče rešiti z nizom datotek Dockerfiles (ali poimenovanimi stopnjami v eni datoteki) in skriptom Bash z njihovim zaporednim sestavljanjem.

To je bil le vrh ledene gore, s katerim se soočajo vsi. Obstajajo pa še druge težave, zlasti:

  1. Pogosto v fazi montaže nekaj potrebujemo mount (na primer, predpomnite rezultat ukaza, kot je apt, v imenik tretje osebe).
  2. Želimo Možno namesto pisanja v lupini.
  3. Želimo zgraditi brez Dockerja (zakaj potrebujemo dodaten virtualni stroj, v katerem moramo vse konfigurirati za to, ko pa že imamo Kubernetes gručo, v kateri lahko izvajamo kontejnerje?).
  4. Vzporedna montaža, ki jih je mogoče razumeti na različne načine: različni ukazi iz datoteke Dockerfile (če se uporablja večstopenjsko), več objav istega repozitorija, več datotek Dockerfile.
  5. Porazdeljena montaža: Želimo zbirati stvari v strokih, ki so "efemerne", ker njihov predpomnilnik izgine, kar pomeni, da ga je treba shraniti nekje ločeno.
  6. Nazadnje sem poimenovala vrh želja avtomatska magija: Idealno bi bilo iti v repozitorij, vnesti nekaj ukazov in dobiti že pripravljeno sliko, sestavljeno z razumevanjem, kako in kaj narediti pravilno. Vendar osebno nisem prepričan, da je mogoče na ta način predvideti vse nianse.

In tukaj so projekti:

  • moby/buildkit — graditelj podjetja Docker Inc (že integriran v trenutne različice Dockerja), ki poskuša rešiti vse te težave;
  • kaniko — Googlov graditelj, ki omogoča gradnjo brez Dockerja;
  • Buildpacks.io — poskus CNCF, da naredi avtomatsko magijo in še posebej zanimivo rešitev s preosnovo za plasti;
  • in kup drugih pripomočkov, kot npr gradnja, originalna orodja/sl...

...in poglejte, koliko zvezdic imajo na GitHubu. To je po eni strani docker build obstaja in lahko nekaj naredi, ampak v resnici vprašanje ni popolnoma rešeno - dokaz za to je vzporedni razvoj alternativnih kolektorjev, od katerih vsak rešuje del težav.

Montaža v werf

Torej moramo werf (prej slavni kot dapp) — Odprtokodni pripomoček podjetja Flant, ki ga izdelujemo že vrsto let. Vse se je začelo pred 5 leti s skripti Bash, ki so optimizirale sestavljanje datotek Dockerfiles, zadnja 3 leta pa se je celovit razvoj izvajal v okviru enega projekta z lastnim repozitorijem Git (najprej v Rubyju in nato prepisan v Go in hkrati preimenovan). Katere težave pri sestavljanju so rešene v werf?

werf - naše orodje za CI/CD v Kubernetesu (pregled in video poročilo)

Težave, označene z modro barvo, so že implementirane, vzporedna izgradnja je bila izvedena znotraj istega gostitelja, težave, označene z rumeno, pa naj bi bile zaključene do konca poletja.

Faza objave v registru (objava)

Poklicali smo docker push... - kaj bi lahko bilo težavno pri nalaganju slike v register? In potem se pojavi vprašanje: "Kakšno oznako naj dam na sliko?" Nastane zaradi razloga, ki ga imamo Gitflow (ali drugo strategijo Git) in Kubernetes, industrija pa poskuša zagotoviti, da dogajanje v Kubernetesu sledi dogajanju v Gitu. Navsezadnje je Git naš edini vir resnice.

Kaj je tako težko pri tem? Zagotovite ponovljivost: iz objave v Gitu, ki je po naravi nespremenljiva (nespremenljivo), na sliko Docker, ki naj ostane enaka.

Tudi za nas je pomembna določi izvor, ker želimo razumeti, iz katere objave je bila zgrajena aplikacija, ki se izvaja v Kubernetesu (takrat lahko delamo razlike in podobne stvari).

Strategije označevanja

Prvi je preprost git oznaka. Imamo register s sliko, označeno kot 1.0. Kubernetes ima oder in produkcijo, kamor se ta slika naloži. V Gitu naredimo zaveze in na neki točki označimo 2.0. Zberemo ga po navodilih iz repozitorija in ga z oznako vnesemo v register 2.0. Odpeljemo ga na oder in, če je vse v redu, potem v produkcijo.

werf - naše orodje za CI/CD v Kubernetesu (pregled in video poročilo)

Težava pri tem pristopu je, da smo oznako najprej postavili in jo šele nato testirali in uvedli. Zakaj? Prvič, to je preprosto nelogično: izdajamo različico programske opreme, ki je še nismo niti preizkusili (ne moremo drugače, ker moramo za preverjanje postaviti oznako). Drugič, ta pot ni združljiva z Gitflow.

Druga možnost - git commit + oznaka. Glavna veja ima oznako 1.0; zanj v registru - slika, ki je nameščena v produkcijo. Poleg tega ima gruča Kubernetes predogled in uprizoritvene konture. Nato sledimo Gitflowu: v glavni veji za razvoj (develop) naredimo nove funkcije, kar povzroči objavo z identifikatorjem #c1. Zberemo ga in objavimo v registru s tem identifikatorjem (#c1). Z istim identifikatorjem se uvedemo za predogled. Enako storimo z zavezami #c2 и #c3.

Ko ugotovimo, da je funkcij dovolj, začnemo vse stabilizirati. Ustvarite vejo v Gitu release_1.1 (na podlagi #c3 z dne develop). Te objave ni treba zbirati, ker ... to je bilo storjeno v prejšnjem koraku. Zato ga lahko preprosto prenesemo na uprizoritev. Odpravljamo napake #c4 in podobno razviti v uprizoritev. Hkrati poteka razvoj v develop, od koder se občasno vzamejo spremembe release_1.1. Na neki točki dobimo prevedeno objavo in naloženo v pripravo, s katero smo zadovoljni (#c25).

Nato združimo (s hitrim previjanjem naprej) vejo izdaje (release_1.1) v master. Na to obvezo dodamo oznako z novo različico (1.1). Toda ta slika je že zbrana v registru, zato, da je ne bi znova zbirali, preprosto dodamo drugo oznako obstoječi sliki (zdaj ima oznake v registru #c25 и 1.1). Po tem ga uvedemo v proizvodnjo.

Pomanjkljivost je, da se v uprizoritev naloži samo ena slika (#c25), v proizvodnji pa je nekako drugače (1.1), vendar vemo, da je to "fizično" ista slika iz registra.

werf - naše orodje za CI/CD v Kubernetesu (pregled in video poročilo)

Resnična pomanjkljivost je, da ni podpore za potrditev združevanja, morate narediti hitro previjanje naprej.

Lahko gremo še dlje in naredimo trik ... Oglejmo si primer preproste 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

Iz nje sestavimo datoteko po naslednjem principu:

  • SHA256 iz identifikatorjev uporabljenih slik (ruby:2.3 и nginx:alpine), ki so kontrolne vsote njihove vsebine;
  • vse ekipe (RUN, CMD in tako naprej.);
  • SHA256 iz datotek, ki so bile dodane.

... in vzemite kontrolno vsoto (spet SHA256) iz takšne datoteke. to podpis vse, kar določa vsebino slike Docker.

werf - naše orodje za CI/CD v Kubernetesu (pregled in video poročilo)

Vrnimo se k diagramu in namesto zavez bomo uporabili take podpise, tj. označite slike s podpisi.

werf - naše orodje za CI/CD v Kubernetesu (pregled in video poročilo)

Zdaj, ko je treba na primer združiti spremembe iz izdaje v matrico, lahko naredimo pravo združitev: imela bo drugačen identifikator, a enak podpis. Z istim identifikatorjem bomo sliko uvedli v produkcijo.

Pomanjkljivost je, da zdaj ne bo mogoče ugotoviti, kakšna potrditev je bila potisnjena v proizvodnjo - kontrolne vsote delujejo samo v eno smer. Ta problem je rešen z dodatnim slojem z metapodatki - več vam bom povedal kasneje.

Označevanje v werf

V werfu smo šli še dlje in se pripravljamo na porazdeljeno gradnjo s predpomnilnikom, ki ni shranjen na enem računalniku ... Torej gradimo dve vrsti Dockerjevih slik, imenujemo jih stopnja и slika.

Repozitorij werf Git shranjuje navodila, specifična za gradnjo, ki opisujejo različne stopnje gradnje (beforeInstall, namestitev, beforeSetup, nastavitev). Zberemo sliko prve stopnje s podpisom, definiranim kot kontrolna vsota prvih korakov. Nato dodamo izvorno kodo, za novo scensko sliko izračunamo njeno kontrolno vsoto... Te operacije ponovimo za vse stopnje, zaradi česar dobimo nabor scenskih slik. Nato naredimo končno sliko, ki vsebuje tudi metapodatke o izvoru. In to sliko označimo na različne načine (podrobnosti kasneje).

werf - naše orodje za CI/CD v Kubernetesu (pregled in video poročilo)

Recimo, da se po tem pojavi nova potrditev, v kateri je bila spremenjena samo koda aplikacije. Kaj se bo zgodilo? Za spremembe kode bo ustvarjen popravek in pripravljena bo nova odrska slika. Njegov podpis bo določen kot kontrolna vsota stare odrske slike in novega popravka. Iz te slike bo oblikovana nova končna slika. Podobno vedenje se bo zgodilo s spremembami v drugih fazah.

Tako so scenske slike predpomnilnik, ki ga je mogoče distribuirano shraniti, slike, ki so že ustvarjene iz njega, pa se naložijo v register Docker.

werf - naše orodje za CI/CD v Kubernetesu (pregled in video poročilo)

Čiščenje registra

Ne govorimo o brisanju slojev, ki so ostali viseči po izbrisanih oznakah - to je standardna funkcija samega registra Docker Registry. Govorimo o situaciji, ko se nabere veliko Dockerjevih oznak in ugotovimo, da nekaterih ne potrebujemo več, vendar zasedajo prostor (in/ali ga plačamo).

Kakšne so strategije čiščenja?

  1. Lahko preprosto ne narediš nič ne čisti. Včasih je res lažje plačati malo za dodaten prostor kot razvozlati ogromen zaplet oznak. Vendar to deluje le do določene točke.
  2. Popolna ponastavitev. Če izbrišete vse slike in znova sestavite samo trenutne v sistemu CI, lahko pride do težave. Če vsebnik znova zaženete v produkciji, se bo zanj naložila nova slika – tista, ki je še ni nihče preizkusil. To ubija idejo o nespremenljivi infrastrukturi.
  3. Modro zelena. En register se je začel prelivati ​​- slike nalagamo v drugega. Ista težava kot pri prejšnji metodi: na kateri točki lahko počistite register, ki se je začel prelivati?
  4. Sčasoma. Želite izbrisati vse slike, starejše od 1 meseca? Bo pa zagotovo našla storitev, ki ni bila posodobljena že en mesec ...
  5. Ročno določite, kaj je že mogoče izbrisati.

Obstajata dve resnično izvedljivi možnosti: ne čisti ali kombinacija modro-zelena + ročno. V slednjem primeru govorimo o naslednjem: ko ugotovite, da je čas za čiščenje registra, ustvarite novega in vanj dodate vse nove slike v teku, na primer, enega meseca. Po enem mesecu si oglejte, kateri podi v Kubernetesu še vedno uporabljajo stari register, in jih tudi prenesite v novi register.

Do česa smo prišli werf? Zbiramo:

  1. Git head: vse oznake, vse veje, ob predpostavki, da potrebujemo vse, kar je označeno v Gitu na slikah (in če ni, potem moramo to izbrisati v samem Gitu);
  2. vsi podi, ki so trenutno izčrpani v Kubernetes;
  3. stare ReplicaSets (kar je bilo pred kratkim izdano), prav tako nameravamo pregledati Helmove izdaje in tam izbrati najnovejše slike.

... in iz tega niza naredite beli seznam - seznam slik, ki jih ne bomo izbrisali. Vse ostalo počistimo, nakar najdemo osirotele odrske podobe in jih tudi izbrišemo.

Stopnja razmestitve

Zanesljiva deklarativnost

Prva točka, na katero bi rad opozoril pri uvajanju, je uvedba posodobljene konfiguracije virov, ki je deklarativno deklarirana. Izvirni dokument YAML, ki opisuje vire Kubernetes, se vedno zelo razlikuje od rezultata, ki se dejansko izvaja v gruči. Ker Kubernetes konfiguraciji doda:

  1. identifikatorji;
  2. storitvene informacije;
  3. veliko privzetih vrednosti;
  4. razdelek s trenutnim stanjem;
  5. spremembe, narejene kot del spletnega trka za sprejem;
  6. rezultat dela različnih krmilnikov (in rokovnika).

Zato, ko se pojavi nova konfiguracija vira (novo), ne moremo kar vzeti in z njim prepisati trenutne, "žive" konfiguracije (v živo). Za to bomo morali primerjati novo z zadnjo uporabljeno konfiguracijo (nazadnje uporabljeno) in se zakotalite v živo prejel popravek.

Ta pristop se imenuje 2-smerno spajanje. Uporablja se na primer v Helmu.

Je tudi 3-smerno spajanje, ki se razlikuje po tem, da:

  • primerjanje nazadnje uporabljeno и novo, pogledamo, kaj je bilo izbrisano;
  • primerjanje novo и v živo, pogledamo, kaj je bilo dodano ali spremenjeno;
  • povzeti popravek se uporablja za v živo.

S Helmom uvajamo več kot 1000 aplikacij, tako da dejansko živimo z dvosmernim združevanjem. Ima pa številne težave, ki smo jih rešili z našimi popravki, ki Helmu pomagajo pri normalnem delovanju.

Dejansko stanje uvajanja

Ko naš sistem CI ustvari novo konfiguracijo za Kubernetes na podlagi naslednjega dogodka, jo posreduje za uporabo (uporabi) v gručo - z uporabo Helma oz kubectl apply. Sledi že opisano N-way spajanje, na kar se API Kubernetes odzove odobravajoče sistemu CI, ta pa njegovemu uporabniku.

werf - naše orodje za CI/CD v Kubernetesu (pregled in video poročilo)

Vendar pa obstaja velika težava: navsezadnje uspešna aplikacija ne pomeni uspešne uvedbe. Če Kubernetes razume, katere spremembe je treba uporabiti, in jih uporabi, še vedno ne vemo, kakšen bo rezultat. Na primer, posodabljanje in ponovni zagon podov v sprednjem delu je lahko uspešno, v zaledju pa ne, in dobili bomo različne različice slik delujočih aplikacij.

Da bi vse naredili pravilno, ta shema zahteva dodatno povezavo - poseben sledilnik, ki bo prejemal informacije o stanju iz API-ja Kubernetes in jih posredoval za nadaljnjo analizo dejanskega stanja stvari. Ustvarili smo odprtokodno knjižnico v Go - cubedog (glej njeno objavo tukaj), ki rešuje ta problem in je vgrajen v werf.

Vedenje tega sledilnika na ravni werf je konfigurirano z opombami, ki so postavljene na Deployments ali StatefulSets. Glavna opomba - fail-mode - razume naslednje pomene:

  • IgnoreAndContinueDeployProcess — ignoriramo težave pri uvajanju te komponente in nadaljujemo z uvajanjem;
  • FailWholeDeployProcessImmediately — napaka v tej komponenti ustavi postopek uvajanja;
  • HopeUntilEndOfDeployProcess — upamo, da bo ta komponenta delovala do konca uvajanja.

Na primer ta kombinacija virov in vrednosti pripisov fail-mode:

werf - naše orodje za CI/CD v Kubernetesu (pregled in video poročilo)

Ko prvič uvedemo, baza podatkov (MongoDB) morda še ni pripravljena - uvedbe ne bodo uspele. Lahko pa počakate na trenutek, da se začne, in uvedba bo še vedno potekala.

Obstajata še dve opombi za kubedog v werf:

  • failures-allowed-per-replica — število dovoljenih padcev za vsako repliko;
  • show-logs-until — uravnava trenutek, do katerega werf prikazuje (v stdout) dnevnike iz vseh izvaljenih podov. Privzeto je PodIsReady (da ignoriramo sporočila, ki jih verjetno ne želimo, ko promet začne prihajati do bloka), veljavne pa so tudi vrednosti: ControllerIsReady и EndOfDeploy.

Kaj še želimo od uvajanja?

Poleg že opisanih dveh točk želimo:

  • videti dnevniki - in samo tiste potrebne, in ne vse po vrsti;
  • skladba napredek, kajti če delo "tiho" visi nekaj minut, je pomembno razumeti, kaj se tam dogaja;
  • imeti samodejno povrnitev nazaj v primeru, da bi šlo kaj narobe (zato je ključnega pomena vedeti dejanski status uvajanja). Uvajanje mora biti atomsko: ali gre do konca ali pa se vse vrne v prejšnje stanje.

Rezultati

Nam kot podjetju za implementacijo vseh opisanih nians na različnih stopnjah dostave (gradnja, objava, uvedba) zadostujeta sistem CI in pripomoček. werf.

Namesto zaključka:

werf - naše orodje za CI/CD v Kubernetesu (pregled in video poročilo)

S pomočjo werf smo dobro napredovali pri reševanju velikega števila težav za DevOps inženirje in bili bi veseli, če bi širša skupnost ta pripomoček vsaj preizkusila v akciji. Skupaj bomo lažje dosegli dober rezultat.

Video posnetki in diapozitivi

Video z nastopa (~47 minut):

Predstavitev poročila:

PS

Druga poročila o Kubernetesu na našem blogu:

Vir: www.habr.com

Dodaj komentar