werf - meie tööriist CI / CD jaoks Kubernetesis (ülevaade ja videoaruanne)

27. mail festivali raames toimuva DevOpsConf 2019 konverentsi peasaalis RIT++ 2019, osana "Pidev tarnimine" anti aruanne "werf - meie tööriist CI/CD jaoks Kubernetesis". See räägib neist probleemid ja väljakutsed, millega kõik Kubernetesesse juurutades kokku puutuvad, samuti nüansside kohta, mida ei pruugi kohe märgata. Võimalikke lahendusi analüüsides näitame, kuidas seda avatud lähtekoodiga tööriistas rakendatakse werf.

Alates esitlusest on meie utiliit (varem tuntud kui dapp) jõudnud ajaloolise verstapostini 1000 tärni GitHubis — loodame, et selle kasvav kasutajate kogukond muudab paljude DevOpsi inseneride elu lihtsamaks.

werf - meie tööriist CI / CD jaoks Kubernetesis (ülevaade ja videoaruanne)

Niisiis, esitame video raportist (~47 minutit, palju informatiivsem kui artikkel) ja põhiväljavõte sellest teksti kujul. Mine!

Koodi edastamine Kubernetesesse

Jutt ei käi enam werfist, vaid CI/CD-st Kubernetesis, mis annab mõista, et meie tarkvara on pakitud Dockeri konteineritesse (Ma rääkisin sellest aastal 2016. aasta aruanne), ja selle tootmises käitamiseks kasutatakse K8-sid (sellest lähemalt artiklis 2017 aasta).

Kuidas kohaletoimetamine Kubernetesis välja näeb?

  • Seal on Giti hoidla koodi ja juhistega selle loomiseks. Rakendus on sisse ehitatud Dockeri kujutisse ja avaldatud Dockeri registris.
  • Sama hoidla sisaldab ka juhiseid rakenduse juurutamiseks ja käitamiseks. Juurutamisetapis saadetakse need juhised Kubernetesile, mis saab registrist soovitud pildi ja käivitab selle.
  • Lisaks on tavaliselt testid. Mõnda neist saab teha pildi avaldamisel. Samuti saate (sama juhiseid järgides) juurutada rakenduse koopia (eraldi K8-i nimeruumis või eraldi klastris) ja seal testida.
  • Lõpuks vajate CI-süsteemi, mis võtab Gitist sündmusi (või nupuklõpsud) vastu ja kutsub kõiki määratud etappe: looge, avaldage, juurutage, testige.

werf - meie tööriist CI / CD jaoks Kubernetesis (ülevaade ja videoaruanne)

Siin on mõned olulised märkused:

  1. Sest meil on muutumatu infrastruktuur (muutmatu infrastruktuur), rakenduse pilt, mida kasutatakse kõikidel etappidel (lavastamine, tootmine jne), üks peab olema. Rääkisin sellest lähemalt ja näidetega. siin.
  2. Sest me järgime infrastruktuuri kui koodilähenemist (IaC), rakenduse kood, selle kokkupanemise ja käivitamise juhised peaksid olema täpselt ühes hoidlas. Selle kohta lisateabe saamiseks vt sama aruanne.
  3. Tarneahel (tarne) tavaliselt näeme seda nii: rakendus pandi kokku, testiti, vabastati (väljalaske etapp) ja kõik - kohaletoimetamine on toimunud. Kuid tegelikkuses saab kasutaja selle, mille olete välja töötanud, ei siis kui sa selle tootmisse tarnisid ja kui ta sai sinna minna ja see tootmine töötas. Nii et ma usun, et tarneahel lõpeb ainult tööstaadiumis (jookse), või täpsemalt isegi hetkel, mil kood tootmisest eemaldati (asendades uuega).

Naaskem ülaltoodud Kubernetese kohaletoimetamisskeemi juurde: selle leiutasime mitte ainult meie, vaid sõna otseses mõttes kõik, kes selle probleemiga tegelesid. Tegelikult nimetatakse seda mustrit nüüd GitOpsiks (termini ja selle taga olevate ideede kohta saate rohkem lugeda siin). Vaatame skeemi etappe.

Ehitamise etapp

Näib, et saate rääkida Dockeri piltide loomisest 2019. aastal, kui kõik teavad, kuidas Dockerfaile kirjutada ja käivitada docker build?.. Siin on nüansid, millele tahaksin tähelepanu pöörata:

  1. Pildi kaal oluline, nii et kasutage mitmeastmelisejätta pildile ainult see rakendus, mis on operatsiooniks tõesti vajalik.
  2. Kihtide arv tuleb minimeerida, kombineerides ahelaid RUN-käske tähenduse järgi.
  3. See aga lisab probleeme silumine, sest kui koost jookseb kokku, tuleb probleemi tekitanud ketist leida õige käsk.
  4. Montaaži kiirus oluline, sest tahame muudatused kiiresti ellu viia ja tulemusi näha. Näiteks ei soovi te keeleteekides sõltuvusi iga kord rakenduse loomisel uuesti üles ehitada.
  5. Sageli ühest Giti hoidlast, mida vajate palju pilte, mida saab lahendada Docker-failide komplekti (või ühes failis nimetatud etappide) ja Bashi skripti abil koos nende järjestikuse kokkupanekuga.

See oli vaid jäämäe tipp, millega kõik silmitsi seisavad. Kuid on ka muid probleeme, eelkõige:

  1. Sageli vajame kokkupaneku etapis midagi mount (näiteks salvestage vahemällu käsu nagu apt tulemus kolmanda osapoole kataloogis).
  2. Me tahame Võimalik kestas kirjutamise asemel.
  3. Me tahame ehitada ilma Dockerita (miks vajame täiendavat virtuaalmasinat, milles peame selleks kõik konfigureerima, kui meil on juba Kubernetese klaster, milles saame konteinereid käivitada?).
  4. Paralleelne kokkupanek, mida saab mõista erinevalt: erinevad käsud Dockerfile'ist (kui kasutatakse mitmeastmelist), sama hoidla mitu commit'i, mitu Dockerfile'i.
  5. Hajutatud kokkupanek: Tahame koguda kaunadesse asju, mis on "põgusad", sest nende vahemälu kaob, mis tähendab, et see tuleb kuhugi eraldi salvestada.
  6. Lõpetuseks nimetasin soovide tipu automaatne: Ideaalne oleks minna hoidlasse, tippida mingi käsk ja saada valmis pilt, mis on kokku pandud arusaamaga, kuidas ja mida õigesti teha. Samas pole ma isiklikult kindel, et kõiki nüansse saab nii ette näha.

Ja siin on projektid:

  • moby/buildkit — ehitaja ettevõttest Docker Inc (mis on juba integreeritud Dockeri praegustesse versioonidesse), kes püüab kõiki neid probleeme lahendada;
  • kaniko — Google'i ehitaja, mis võimaldab ehitada ilma Dockerita;
  • Buildpacks.io — CNCF-i katse teha automaatset maagiat ja eriti huvitavat lahendust kihtide rebase abil;
  • ja hunnik muid kommunaalteenuseid, nt ehitada, originaaltööriistad/img...

...ja vaadake, kui palju tähti neil GitHubis on. See tähendab, et ühelt poolt docker build on olemas ja saab midagi teha, kuid tegelikkuses probleem pole täielikult lahendatud - selle tõestuseks on alternatiivsete kogujate paralleelne arendamine, millest igaüks lahendab mingi osa probleemidest.

Kokkupanek werfis

Nii et me pidime werf (Varem kuulus nagu dapp) — Ettevõtte Flant avatud lähtekoodiga utiliit, mida oleme teinud juba aastaid. Kõik sai alguse 5 aastat tagasi Bashi skriptidest, mis optimeerisid Dockerfiles'i kokkupanemist ning viimased 3 aastat on ühe projekti raames tehtud täiemahulist arendust koos oma Giti hoidlaga. (kõigepealt Rubys ja siis ümber kirjutatud minna ja samal ajal ümber nimetatud). Milliseid montaažiprobleeme werfis lahendatakse?

werf - meie tööriist CI / CD jaoks Kubernetesis (ülevaade ja videoaruanne)

Sinise värviga varjutatud probleemid on juba ellu viidud, paralleelne ehitamine tehti sama hosti sees ja kollasega esiletõstetud probleemid on plaanis lõpetada suve lõpuks.

Registris avaldamise etapp (avaldamine)

Valisime docker push... - mis võib pildi registrisse üleslaadimisel rasket olla? Ja siis tekib küsimus: "Millise sildi ma peaksin pildile panema?" See tekib põhjusel, mis meil on Gitflow (või muu Giti strateegia) ja Kubernetes ning tööstus püüab tagada, et Kubernetesis toimuv järgiks Gitis toimuvat. Lõppude lõpuks on Git meie ainus tõeallikas.

Mis selles nii rasket on? Tagada reprodutseeritavus: Gitis tehtud kohustusest, mis on olemuselt muutumatu (muutmatu), Dockeri kujutisele, mis tuleks jätta samaks.

See on ka meile oluline päritolu kindlaks teha, sest tahame aru saada, millisest commitist Kuberneteses töötav rakendus on ehitatud (siis saame diffe jms teha).

Sildistamise strateegiad

Esimene on lihtne git silt. Meil on register, mille pilt on märgistatud kui 1.0. Kubernetes on lava ja tootmine, kuhu see pilt üles laaditakse. Gitis teeme kohustusi ja mingil hetkel märgistame 2.0. Kogume selle vastavalt hoidla juhistele ja paigutame koos sildiga registrisse 2.0. Rullime selle lavale ja kui kõik on hästi, siis tootmisse.

werf - meie tööriist CI / CD jaoks Kubernetesis (ülevaade ja videoaruanne)

Selle lähenemisviisi probleem seisneb selles, et esmalt panime sildi üles ja alles siis testisime ja rullisime selle välja. Miks? Esiteks on see lihtsalt ebaloogiline: me anname välja tarkvara versiooni, mida me pole veel isegi testinud (teisiti ei saa, sest kontrollimiseks peame panema sildi). Teiseks ei ühildu see tee Gitflowga.

Teine võimalus - git commit + silt. Põhiharul on silt 1.0; selle jaoks registris – tootmisse juurutatud pilt. Lisaks on Kubernetese klastris eelvaate- ja lavastuskontuurid. Järgmisena järgime Gitflow: arenduse põhiharus (develop) teeme uusi funktsioone, mille tulemuseks on sidumine identifikaatoriga #c1. Kogume selle kokku ja avaldame selle identifikaatori abil registris (#c1). Sama identifikaatoriga käivitame eelvaate. Sama teeme kohustustega #c2 и #c3.

Kui saime aru, et funktsioone on piisavalt, hakkame kõike stabiliseerima. Looge Gitis filiaal release_1.1 (alusel #c3 kohta develop). Seda väljaannet pole vaja koguda, sest... seda tehti eelmises etapis. Seetõttu saame selle lihtsalt lavastada. Parandame vead #c4 ja samamoodi lavastada. Samal ajal on areng käimas develop, kust võetakse perioodiliselt muudatusi release_1.1. Mingil hetkel saame koostatud ja lavastusse üles laaditud kohustuse, millega oleme rahul (#c25).

Seejärel ühendame (kerimisega edasi) vabastusharu (release_1.1) kaptenis. Panime sellele kohustusele uue versiooniga sildi (1.1). Kuid see pilt on juba registris kogutud, nii et selle uuesti kogumise vältimiseks lisame olemasolevale pildile lihtsalt teise sildi (nüüd on sellel registris sildid #c25 и 1.1). Pärast seda rullime selle tootmisse.

Puuduseks on see, et lavastusse laaditakse üles ainult üks pilt (#c25) ja tootmises on see kuidagi teistsugune (1.1), kuid me teame, et "füüsiliselt" on need samad pildid registrist.

werf - meie tööriist CI / CD jaoks Kubernetesis (ülevaade ja videoaruanne)

Tõeline miinus on see, et ühendamise kohustusi ei toetata, peate tegema kiirelt edasi.

Võime minna kaugemale ja teha triki... Vaatame lihtsa Dockerfile'i näidet:

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

Ehitame sellest faili järgmise põhimõtte järgi:

  • SHA256 kasutatud piltide identifikaatoritest (ruby:2.3 и nginx:alpine), mis on nende sisu kontrollsummad;
  • kõik meeskonnad (RUN, CMD ja nii edasi.);
  • SHA256 lisatud failidest.

... ja võta sellisest failist kontrollsumma (jällegi SHA256). See allkiri kõike, mis määrab Dockeri pildi sisu.

werf - meie tööriist CI / CD jaoks Kubernetesis (ülevaade ja videoaruanne)

Läheme tagasi diagrammi juurde ja kohustuste asemel kasutame selliseid allkirju, st. märgistada pilte allkirjadega.

werf - meie tööriist CI / CD jaoks Kubernetesis (ülevaade ja videoaruanne)

Nüüd, kui on vaja näiteks muudatusi liita versioonist masterisse, saame teha tõelise liitmiskohustuse: sellel on erinev identifikaator, kuid sama allkiri. Sama identifikaatoriga paneme pildi tootmisse.

Puuduseks on see, et nüüd ei ole võimalik kindlaks teha, millist kohustust tootmisse suruti - kontrollsummad töötavad ainult ühes suunas. Selle probleemi lahendab täiendav metaandmetega kiht – räägin teile hiljem rohkem.

Sildistamine werfis

werfis läksime veelgi kaugemale ja valmistume tegema hajutatud ehitamist vahemäluga, mida ei salvestata ühes masinas... Niisiis, me ehitame kahte tüüpi Dockeri kujutisi, nimetame neid etapp и pilt.

Werf Giti hoidlas on järgupõhised juhised, mis kirjeldavad ehitamise erinevaid etappe (enne installimist, paigaldama, enne seadistamist, seade). Kogume esimese etapi pildi, mille allkiri on määratletud esimeste sammude kontrollsummana. Seejärel lisame lähtekoodi, uue lavapildi jaoks arvutame selle kontrollsumma... Neid tehteid korratakse kõikide etappide puhul, mille tulemusena saame lavapiltide komplekti. Seejärel teeme lõpliku pildi, mis sisaldab ka metaandmeid selle päritolu kohta. Ja me märgistame selle pildi erineval viisil (üksikasjad hiljem).

werf - meie tööriist CI / CD jaoks Kubernetesis (ülevaade ja videoaruanne)

Oletame, et pärast seda ilmub uus kohustus, milles on muudetud ainult rakenduse koodi. Mis juhtub? Koodimuudatuste jaoks luuakse plaaster ja koostatakse uus lavapilt. Selle allkiri määratakse vana lavapildi ja uue plaastri kontrollsummaks. Sellest pildist moodustatakse uus lõplik pilt. Sarnane käitumine ilmneb ka muude etappide muudatustega.

Seega on lavapildid vahemälu, mida saab laiali salvestada ja millest juba loodud pildid laetakse üles Dockeri registrisse.

werf - meie tööriist CI / CD jaoks Kubernetesis (ülevaade ja videoaruanne)

Registri puhastamine

Me ei räägi kihtide kustutamisest, mis jäid pärast kustutatud silte rippuma – see on Dockeri registri enda standardfunktsioon. Räägime olukorrast, kus Dockeri silte koguneb palju ja saame aru, et mõnda neist pole enam vaja, kuid need võtavad ruumi (ja/või me maksame selle eest).

Millised on puhastusstrateegiad?

  1. Sa ei saa lihtsalt midagi teha ära korista. Mõnikord on tõesti lihtsam maksta veidi lisaruumi eest, kui lahti harutada tohutut siltide sasipundart. Kuid see toimib ainult teatud hetkeni.
  2. Täielik lähtestamine. Kui kustutate kõik pildid ja taastate ainult praegused CI-süsteemis, võib tekkida probleem. Kui konteiner taaskäivitatakse tootmises, siis laaditakse selle jaoks uus pilt – selline, mida pole veel keegi testinud. See tapab muutumatu infrastruktuuri idee.
  3. Sinine Roheline. Üks register hakkas üle ajama – laadime pilte teise üles. Sama probleem, mis eelmise meetodi puhul: millisel hetkel saate tühjendada registri, mis on hakanud ületäituma?
  4. Aja jooksul. Kas kustutada kõik pildid, mis on vanemad kui 1 kuu? Kindlasti tuleb aga teenus, mida pole kuu aega uuendatud...
  5. Käsitsi määrake, mida saab juba kustutada.

On kaks tõeliselt elujõulist võimalust: mitte puhastada või kombinatsioon sinakasroheline + käsitsi. Viimasel juhul räägime järgmisest: kui saate aru, et on aeg registrit puhastada, loote uue ja lisate sinna näiteks kuu aja jooksul kõik uued pildid. Ja kuu aja pärast vaadake, millised Kubernetese kaustad kasutavad endiselt vana registrit, ja teisaldage need ka uude registrisse.

Milleni oleme jõudnud werf? Kogume:

  1. Git head: kõik sildid, kõik harud - eeldades, et vajame kõike, mis on piltidel Gitis märgistatud (ja kui mitte, siis peame selle Gitis endas kustutama);
  2. kõik kaunad, mis praegu Kubernetesesse välja pumbatakse;
  3. vanad ReplicaSets (mis hiljuti välja tuli) ning plaanime ka Helmi väljalaseid skannida ja sealt uusimad pildid välja valida.

... ja koostage sellest komplektist valge nimekiri - piltide loend, mida me ei kustuta. Puhastame kõik muu, misjärel leiame vaeslapse lavapildid ja kustutame ka need.

Juurutamise etapp

Usaldusväärne deklaratiivsus

Esimene punkt, millele tahaksin juurutamisel tähelepanu juhtida, on deklaratiivselt deklareeritud värskendatud ressursikonfiguratsiooni juurutamine. Algne Kubernetese ressursse kirjeldav YAML-dokument on alati väga erinev klastris tegelikult töötavast tulemusest. Kuna Kubernetes lisab konfiguratsioonile:

  1. identifikaatorid;
  2. teenindusteave;
  3. palju vaikeväärtusi;
  4. hetkeseisuga sektsioon;
  5. sisseastumise veebihaagi osana tehtud muudatused;
  6. erinevate kontrollerite (ja planeerija) töö tulemus.

Seega, kui ilmub uus ressursi konfiguratsioon (uus), me ei saa lihtsalt võtta ja praegust "reaalajas" konfiguratsiooni sellega üle kirjutada (elama). Selleks peame võrdlema uus viimase rakendatud konfiguratsiooniga (viimati rakendatud) ja rulli peale elama sai plaastri.

Seda lähenemist nimetatakse 2-suunaline liitmine. Seda kasutatakse näiteks Helmis.

On olemas ka 3-suunaline liitmine, mis erineb selle poolest:

  • võrdlemine viimati rakendatud и uus, vaatame, mis kustutati;
  • võrdlemine uus и elama, vaatame, mida on lisatud või muudetud;
  • summeeritud plaaster paigaldatakse elama.

Juurutame Helmiga üle 1000 rakenduse, nii et tegelikult kasutame kahesuunalist ühendamist. Sellel on aga mitmeid probleeme, mille oleme lahendanud oma plaastritega, mis aitavad Helmil normaalselt töötada.

Tõeline levitamise olek

Pärast seda, kui meie CI-süsteem loob järgmise sündmuse põhjal Kubernetese jaoks uue konfiguratsiooni, edastab see selle kasutamiseks (rakendada) klastrisse - kasutades Helm või kubectl apply. Järgmisena toimub juba kirjeldatud N-suunaline liitmine, millele Kubernetes API reageerib heakskiitvalt CI-süsteemile ja selle kasutajale.

werf - meie tööriist CI / CD jaoks Kubernetesis (ülevaade ja videoaruanne)

Siiski on suur probleem: lõppude lõpuks edukas rakendus ei tähenda edukat levitamist. Kui Kubernetes mõistab, milliseid muudatusi tuleb rakendada, ja rakendab neid, ei tea me ikkagi, mis on tulemus. Näiteks võib esiprogrammis podide värskendamine ja taaskäivitamine olla edukas, kuid taustaprogrammis mitte ning me saame töötavate rakenduste piltidest erinevad versioonid.

Kõige õigeks tegemiseks vajab see skeem täiendavat linki - spetsiaalset jälgijat, mis saab Kubernetes API-lt olekuteavet ja edastab selle asjade tegeliku olukorra edasiseks analüüsiks. Lõime Go's avatud lähtekoodiga teegi - kuubikoer (vaata selle teadaannet siin), mis lahendab selle probleemi ja on werfi sisse ehitatud.

Selle jälgija käitumine werf-tasemel konfigureeritakse juurutustele või StatefulSetsile paigutatud märkuste abil. Peamine annotatsioon - fail-mode - mõistab järgmisi tähendusi:

  • IgnoreAndContinueDeployProcess — ignoreerime selle komponendi kasutuselevõtuga seotud probleeme ja jätkame juurutamist;
  • FailWholeDeployProcessImmediately — selle komponendi viga peatab juurutamisprotsessi;
  • HopeUntilEndOfDeployProcess — loodame, et see komponent hakkab kasutuselevõtu lõpuks tööle.

Näiteks see ressursside ja märkuste väärtuste kombinatsioon fail-mode:

werf - meie tööriist CI / CD jaoks Kubernetesis (ülevaade ja videoaruanne)

Esmakordsel juurutamisel ei pruugi andmebaas (MongoDB) veel valmis olla – juurutamine nurjub. Kuid võite oodata hetke, millal see käivitub, ja kasutuselevõtt toimub ikkagi.

Kubedogi jaoks on werfis veel kaks märkust:

  • failures-allowed-per-replica — iga koopia lubatud kukkumiste arv;
  • show-logs-until — reguleerib hetke, milleni werf näitab (stdout) palke kõigist lahtirullitud kaunadest. Vaikimisi on PodIsReady (et ignoreerida sõnumeid, mida me tõenäoliselt ei soovi, kui liiklus hakkab podisse tulema), kuid kehtivad ka väärtused: ControllerIsReady и EndOfDeploy.

Mida me veel kasutuselevõtult tahame?

Lisaks kahele juba kirjeldatud punktile soovime:

  • näha palgid - ja ainult vajalikud, mitte kõike järjest;
  • rada edenemine, sest kui töö mitu minutit “vaikselt” ripub, on oluline aru saada, mis seal toimub;
  • on automaatne tagasipööramine juhuks, kui midagi läks valesti (ja seetõttu on oluline teada juurutuse tegelikku olekut). Levitamine peab olema tuumakas: kas see läbib lõpuni või naaseb kõik endisesse olekusse.

Tulemused

Meile ettevõttena piisab kõigi kirjeldatud nüansside rakendamiseks erinevates tarneetappides (ehitamine, avaldamine, juurutamine) CI süsteemist ja utiliidist werf.

Järelduse asemel:

werf - meie tööriist CI / CD jaoks Kubernetesis (ülevaade ja videoaruanne)

Oleme werfi abiga saavutanud suuri edusamme paljude DevOpsi inseneride probleemide lahendamisel ja oleks hea meel, kui laiem kogukond vähemalt prooviks seda utiliiti tegevuses. Koos on kergem head tulemust saavutada.

Videod ja slaidid

Video etendusest (~47 minutit):

Aruande esitlus:

PS

Muud aruanded Kubernetese kohta meie ajaveebis:

Allikas: www.habr.com

Lisa kommentaar