werf - naš alat za CI/CD u Kubernetesu (pregled i video izvješće)

27. svibnja u glavnoj dvorani konferencije DevOpsConf 2019. koja se održava u sklopu festivala RIT++ 2019, u sklopu rubrike “Continuous Delivery” dano je izvješće “werf - naš alat za CI/CD u Kubernetesu”. Govori o onima probleme i izazove s kojima se svi susreću prilikom postavljanja na Kubernetes, kao io nijansama koje možda neće biti odmah uočljive. Analizirajući moguća rješenja, pokazujemo kako je to implementirano u alatu otvorenog koda werf.

Od predstavljanja, naš uslužni program (ranije poznat kao dapp) dosegao je povijesnu prekretnicu od 1000 zvjezdica na GitHubu — nadamo se da će njegova rastuća zajednica korisnika olakšati život mnogim DevOps inženjerima.

werf - naš alat za CI/CD u Kubernetesu (pregled i video izvješće)

Dakle, predstavljamo video izvještaja (~47 minuta, puno informativnije od članka) i glavni izvadak iz njega u tekstualnom obliku. Ići!

Isporuka koda Kubernetesu

Razgovor više neće biti o werfu, već o CI/CD-u u Kubernetesu, što implicira da je naš softver upakiran u Docker spremnike (O ovome sam govorio u Izvješće za 2016), a K8s će se koristiti za njegovo pokretanje u proizvodnji (više o ovome u 2017 godine).

Kako izgleda isporuka u Kubernetesu?

  • Postoji Git repozitorij s kodom i uputama za njegovu izgradnju. Aplikacija je ugrađena u Docker sliku i objavljena u Docker registru.
  • Isti repozitorij također sadrži upute o tome kako implementirati i pokrenuti aplikaciju. U fazi implementacije, ove upute se šalju Kubernetesu, koji prima željenu sliku iz registra i pokreće je.
  • Osim toga, obično postoje testovi. Neki od njih mogu se učiniti prilikom objavljivanja slike. Također možete (slijedeći iste upute) implementirati kopiju aplikacije (u zasebnom K8s imenskom prostoru ili odvojenom klasteru) i tamo pokrenuti testove.
  • Konačno, potreban vam je CI sustav koji prima događaje iz Gita (ili klikove gumba) i poziva sve određene faze: izrada, objavljivanje, implementacija, testiranje.

werf - naš alat za CI/CD u Kubernetesu (pregled i video izvješće)

Ovdje postoji nekoliko važnih napomena:

  1. Jer imamo nepromjenjivu infrastrukturu (nepromjenjiva infrastruktura), slika aplikacije koja se koristi u svim fazama (uprizorenje, produkcija itd.), mora postojati jedan. O tome sam govorio detaljnije i s primjerima. здесь.
  2. Zato što slijedimo pristup infrastrukture kao koda (IaC), šifra aplikacije, upute za sastavljanje i pokretanje trebaju biti točno u jednom spremištu. Za više informacija o ovome, pogledajte isti izvještaj.
  3. Lanac dostave (dostava) obično vidimo ovako: aplikacija je sastavljena, testirana, puštena (faza otpuštanja) i to je to - isporuka je izvršena. Ali u stvarnosti, korisnik dobiva ono što ste izbacili, ne onda kada ste ga isporučili u proizvodnju, i kada je on mogao otići tamo i kada je ova proizvodnja radila. Stoga vjerujem da lanac isporuke završava samo u operativnoj fazi (trčanje), točnije čak iu trenutku kada je šifra uklonjena iz proizvodnje (zamijenjena novom).

Vratimo se gornjoj shemi isporuke u Kubernetesu: izmislili smo je ne samo mi, već i doslovno svi koji su se bavili ovim problemom. Zapravo, ovaj obrazac se sada zove GitOps (možete pročitati više o pojmu i idejama iza njega здесь). Pogledajmo faze sheme.

Faza izgradnje

Čini se da možete razgovarati o izgradnji Docker slika u 2019., kada svi znaju kako napisati Dockerfiles i pokrenuti docker build?.. Evo nijansi na koje bih želio obratiti pozornost:

  1. Težina slike pitanja, pa koristite višestupanjskiostaviti na slici samo aplikaciju koja je stvarno potrebna za rad.
  2. Broj slojeva mora se minimizirati kombiniranjem lanaca RUN-naredbe prema značenju.
  3. Međutim, to dodaje probleme otklanjanje pogrešaka, jer kada se sklop sruši, morate pronaći pravu naredbu iz lanca koji je uzrokovao problem.
  4. Brzina montaže važno jer želimo brzo uvesti promjene i vidjeti rezultate. Na primjer, ne želite ponovno graditi ovisnosti u knjižnicama jezika svaki put kada gradite aplikaciju.
  5. Često iz jednog Git repozitorija koji vam je potreban mnoge slike, što se može riješiti skupom Dockerfiles (ili imenovanih faza u jednoj datoteci) i Bash skriptom s njihovim sekvencijalnim sklapanjem.

Ovo je bio samo vrh ledenog brijega s kojim se svi suočavaju. Ali postoje i drugi problemi, posebice:

  1. Često nam nešto treba u fazi montaže montirati (na primjer, spremite rezultat naredbe kao što je apt u imenik treće strane).
  2. Mi želimo Ansible umjesto pisanja u ljusci.
  3. Mi želimo izgraditi bez Dockera (zašto nam treba dodatna virtualna mašina u kojoj trebamo sve konfigurirati za to, kada već imamo Kubernetes klaster u kojem možemo pokretati kontejnere?).
  4. Paralelni sklop, što se može razumjeti na različite načine: različite naredbe iz Dockerfilea (ako se koristi multi-stage), nekoliko commitova istog repozitorija, nekoliko Dockerfileova.
  5. Distribuirana montaža: Želimo skupljati stvari u mahune koje su "efemerne" jer njihova predmemorija nestaje, što znači da treba biti pohranjena negdje odvojeno.
  6. Na kraju sam nazvao vrhunac želja automagijski: Idealno bi bilo otići u repozitorij, upisati neku naredbu i dobiti gotovu sliku, sastavljenu s razumijevanjem kako i što učiniti ispravno. No, osobno nisam siguran da se na ovaj način mogu predvidjeti sve nijanse.

A evo i projekata:

  • moby/buildkit — builder tvrtke Docker Inc (već integriran u trenutne verzije Dockera), koji pokušava riješiti sve te probleme;
  • kaniko — graditelj iz Googlea koji vam omogućuje izgradnju bez Dockera;
  • Buildpacks.io — CNCF-ov pokušaj da napravi automatsku magiju i, posebno, zanimljivo rješenje s rebazom za slojeve;
  • i hrpa drugih uslužnih programa, kao što je građenje, originalni alati/img...

...i pogledajte koliko zvjezdica imaju na GitHubu. To jest, s jedne strane, docker build postoji i može nešto učiniti, ali u stvarnosti problem nije potpuno riješen - dokaz za to je paralelni razvoj alternativnih kolektora od kojih svaki rješava neki dio problema.

Montaža u werf

Dakle, moramo werf (ranije slavni kao dapp) — Uslužni program otvorenog koda tvrtke Flant, koji izrađujemo dugi niz godina. Sve je počelo prije 5 godina s Bash skriptama koje su optimizirale asembler Dockerfilesa, a posljednje 3 godine potpuni razvoj odvija se u okviru jednog projekta s vlastitim Git repozitorijem (prvo u Rubyju, a potom prepisan ići, a istovremeno preimenovan). Koji su problemi s montažom riješeni u werf-u?

werf - naš alat za CI/CD u Kubernetesu (pregled i video izvješće)

Problemi osjenčani plavom bojom već su implementirani, paralelna izgradnja je napravljena unutar istog hosta, a problemi označeni žutom bojom planiraju se dovršiti do kraja ljeta.

Faza objave u registru (publish)

Birali smo docker push... - što bi moglo biti teško kod učitavanja slike u registar? I onda se postavlja pitanje: "Koju oznaku trebam staviti na sliku?" Nastaje iz razloga što imamo Gitflow (ili drugu Git strategiju) i Kubernetes, a industrija pokušava osigurati da ono što se događa u Kubernetesu prati ono što se događa u Gitu. Uostalom, Git je naš jedini izvor istine.

Što je tu tako teško? Osigurajte ponovljivost: iz komitiranja u Gitu, koje je po prirodi nepromjenjivo (nepromjenjivo), na Docker sliku, koja bi trebala ostati ista.

Također nam je važno odrediti porijeklo, jer želimo razumjeti iz kojeg je komita izgrađena aplikacija koja se izvodi u Kubernetesu (tada možemo raditi diffove i slične stvari).

Strategije označavanja

Prvi je jednostavan git dan. Imamo registar sa slikom označenom kao 1.0. Kubernetes ima pozornicu i produkciju, gdje se ova slika učitava. U Gitu se obvezujemo iu nekom trenutku označavamo 2.0. Prikupljamo ga prema uputama iz repozitorija i stavljamo u registar s oznakom 2.0. Izbacujemo ga na pozornicu i, ako je sve u redu, onda u produkciju.

werf - naš alat za CI/CD u Kubernetesu (pregled i video izvješće)

Problem s ovim pristupom je što smo prvo stavili oznaku, a tek onda je testirali i izbacili. Zašto? Prvo, jednostavno je nelogično: mi izdajemo verziju softvera koju još nismo ni testirali (ne možemo drugačije, jer da bismo provjerili, moramo staviti tag). Drugo, ovaj put nije kompatibilan s Gitflowom.

Druga mogućnost - git commit + oznaka. Glavna grana ima oznaku 1.0; za to u registru - slika raspoređena u proizvodnju. Osim toga, Kubernetes klaster ima konture za pregled i postavljanje. Zatim slijedimo Gitflow: u glavnoj grani za razvoj (develop) stvaramo nove značajke, što rezultira uvrštavanjem s identifikatorom #c1. Prikupljamo ga i objavljujemo u registru pomoću ovog identifikatora (#c1). S istim identifikatorom izbacujemo ga za pregled. Isto radimo s obvezama #c2 и #c3.

Kada smo shvatili da ima dovoljno mogućnosti, počeli smo sve stabilizirati. Napravite granu u Gitu release_1.1 (na bazi #c3 od develop). Nema potrebe prikupljati ovo izdanje jer... to je učinjeno u prethodnom koraku. Stoga ga možemo jednostavno razvaljati na pozornicu. Ispravljamo greške #c4 i slično razvaljati na uprizorenje. Istodobno, razvoj je u tijeku develop, odakle se povremeno preuzimaju izmjene release_1.1. U nekom trenutku, dobijemo commit kompajliran i prenesen u staging, s čime smo zadovoljni (#c25).

Zatim spajamo (s premotavanjem unaprijed) granu izdanja (release_1.1) u majstoru. Stavili smo oznaku s novom verzijom na ovu predaju (1.1). Ali ova je slika već prikupljena u registru, pa kako je ne bismo ponovno prikupljali, jednostavno dodamo drugu oznaku postojećoj slici (sada ima oznake u registru #c25 и 1.1). Nakon toga ga stavljamo u proizvodnju.

Nedostatak je što se samo jedna slika učitava u prikazivanje (#c25), au proizvodnji je nekako drugačije (1.1), ali znamo da je to “fizički” ista slika iz registra.

werf - naš alat za CI/CD u Kubernetesu (pregled i video izvješće)

Pravi nedostatak je to što nema podrške za spajanje obveza, morate raditi brzo premotavanje unaprijed.

Možemo ići dalje i napraviti trik... Pogledajmo primjer jednostavne Dockerfile datoteke:

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

Izgradimo datoteku od njega prema sljedećem principu:

  • SHA256 iz identifikatora korištenih slika (ruby:2.3 и nginx:alpine), koji su kontrolni zbrojevi njihovog sadržaja;
  • sve ekipe (RUN, CMD i tako dalje.);
  • SHA256 iz datoteka koje su dodane.

... i uzeti kontrolni zbroj (opet SHA256) iz takve datoteke. Ovaj potpis sve što definira sadržaj Docker slike.

werf - naš alat za CI/CD u Kubernetesu (pregled i video izvješće)

Vratimo se dijagramu i umjesto obvezivanja koristit ćemo takve potpise, tj. označavati slike potpisima.

werf - naš alat za CI/CD u Kubernetesu (pregled i video izvješće)

Sada, kada je potrebno, na primjer, spojiti promjene iz izdanja u master, možemo napraviti pravo spajanje: imat će drugačiji identifikator, ali isti potpis. S istim identifikatorom uvest ćemo sliku u proizvodnju.

Nedostatak je što sada neće biti moguće utvrditi koja je vrsta predaje gurnuta u proizvodnju - kontrolni zbrojevi rade samo u jednom smjeru. Ovaj problem rješava dodatni sloj s metapodacima - reći ću vam više kasnije.

Označavanje u werf

U werfu smo otišli još dalje i pripremamo se za distribuiranu izgradnju s predmemorijom koja nije pohranjena na jednom stroju... Dakle, gradimo dvije vrste Docker slika, zovemo ih faza и slika.

Spremište werf Git pohranjuje upute specifične za izgradnju koje opisuju različite faze izgradnje (prije Instaliranja, instalirati, prije postavljanja, postava). Prikupljamo sliku prve faze s potpisom definiranim kao kontrolni zbroj prvih koraka. Zatim dodajemo izvorni kod, za novu sliku pozornice izračunavamo njen kontrolni zbroj... Ove operacije se ponavljaju za sve faze, kao rezultat toga dobivamo skup slika pozornice. Zatim izrađujemo konačnu sliku koja sadrži i metapodatke o svom podrijetlu. I ovu sliku označavamo na različite načine (detalji kasnije).

werf - naš alat za CI/CD u Kubernetesu (pregled i video izvješće)

Pretpostavimo da se nakon ovoga pojavi novi commit u kojem je promijenjen samo kod aplikacije. Što će se dogoditi? Za izmjene koda izradit će se zakrpa i pripremiti nova slika pozornice. Njegov će se potpis odrediti kao kontrolni zbroj stare slike pozornice i nove zakrpe. Od ove slike formirat će se nova konačna slika. Slično će se ponašanje dogoditi s promjenama u drugim fazama.

Stoga su scenske slike predmemorija koja se može distribuirano pohraniti, a slike koje su već stvorene iz nje učitavaju se u Docker registar.

werf - naš alat za CI/CD u Kubernetesu (pregled i video izvješće)

Čišćenje registra

Ne govorimo o brisanju slojeva koji su ostali visjeti nakon izbrisanih oznaka - to je standardna značajka samog Docker registra. Govorimo o situaciji kada se nakupi puno Docker tagova i shvatimo da nam neki od njih više ne trebaju, ali zauzimaju prostor (i/ili mi to plaćamo).

Koje su strategije čišćenja?

  1. Možete jednostavno ne učiniti ništa nemoj čistiti. Ponekad je doista lakše platiti malo za dodatni prostor nego razmrsiti ogromno klupko oznaka. Ali ovo funkcionira samo do određene točke.
  2. Potpuno resetiranje. Ako izbrišete sve slike i ponovno izgradite samo trenutne u CI sustavu, može doći do problema. Ako se spremnik ponovno pokrene u proizvodnji, za njega će se učitati nova slika - ona koju još nitko nije testirao. Ovo ubija ideju o nepromjenjivoj infrastrukturi.
  3. Plavo zeleno. Jedan registar se počeo prelijevati - učitavamo slike u drugi. Isti problem kao u prethodnoj metodi: u kojem trenutku možete očistiti registar koji se počeo prelijevati?
  4. S vremenom. Izbrisati sve slike starije od 1 mjeseca? Ali sigurno će biti usluga koja nije ažurirana mjesec dana...
  5. ručno odrediti što se već može izbrisati.

Postoje dvije doista održive mogućnosti: ne čistiti ili kombinacija plavo-zeleno + ručno. U potonjem slučaju, govorimo o sljedećem: kada shvatite da je vrijeme za čišćenje registra, kreirate novi i dodajete mu sve nove slike tijekom, primjerice, mjesec dana. A nakon mjesec dana pogledajte koji podovi u Kubernetesu još uvijek koriste stari registar i prenesite i njih u novi registar.

Do čega smo došli werf? Sakupljamo:

  1. Git head: sve oznake, sve grane - pod pretpostavkom da nam treba sve što je označeno u Gitu na slikama (a ako nije, onda to trebamo izbrisati u samom Gitu);
  2. sve mahune koje su trenutno ispumpane u Kubernetes;
  3. stare ReplicaSets (ono što je nedavno objavljeno), a također planiramo skenirati izdanja Helma i tamo odabrati najnovije slike.

... i napravimo whitelist iz ovog skupa - popis slika koje nećemo brisati. Čistimo sve ostalo, nakon čega pronalazimo scenske slike siročadi i brišemo ih.

Razmjestiti stupanj

Pouzdana deklarativnost

Prva točka na koju bih želio skrenuti pozornost u implementaciji je uvođenje ažurirane konfiguracije resursa, deklarativno deklarirane. Izvorni YAML dokument koji opisuje Kubernetes resurse uvijek se jako razlikuje od rezultata koji se stvarno izvodi u klasteru. Budući da Kubernetes dodaje konfiguraciji:

  1. identifikatori;
  2. servisne informacije;
  3. mnogo zadanih vrijednosti;
  4. dio s trenutnim statusom;
  5. promjene napravljene kao dio pristupnog webhooka;
  6. rezultat rada raznih kontrolera (i rokovnika).

Stoga, kada se pojavi nova konfiguracija resursa (novi), ne možemo samo uzeti i njime prebrisati trenutnu, "živu" konfiguraciju (živjeti). Da bismo to učinili, morat ćemo usporediti novi sa zadnjom primijenjenom konfiguracijom (zadnji primijenjen) i otkotrljajte se živjeti primljena zakrpa.

Ovaj pristup se zove 2-smjerno spajanje. Koristi se npr. u Helmu.

Postoji također 3-smjerno spajanje, koji se razlikuje po tome što:

  • uspoređujući zadnji primijenjen и novi, gledamo što je izbrisano;
  • uspoređujući novi и živjeti, gledamo što je dodano ili promijenjeno;
  • sumirani patch se primjenjuje na živjeti.

Implementiramo 1000+ aplikacija s Helmom, tako da zapravo živimo s dvosmjernim spajanjem. Međutim, ima niz problema koje smo riješili našim zakrpama, koje Helmu pomažu da radi normalno.

Stvarni status uvođenja

Nakon što naš CI sustav generira novu konfiguraciju za Kubernetes na temelju sljedećeg događaja, on je šalje na korištenje (primijeniti) u klaster - koristeći Helm ili kubectl apply. Zatim dolazi do već opisanog N-way spajanja na što Kubernetes API s odobravanjem odgovara CI sustavu, a on njegovom korisniku.

werf - naš alat za CI/CD u Kubernetesu (pregled i video izvješće)

Međutim, postoji ogroman problem: nakon svega uspješna primjena ne znači i uspješno uvođenje. Ako Kubernetes razumije koje promjene treba primijeniti i primijeni ih, još uvijek ne znamo kakav će biti rezultat. Na primjer, ažuriranje i ponovno pokretanje modula u sučelju može biti uspješno, ali ne i u pozadini, a mi ćemo dobiti različite verzije slika pokrenute aplikacije.

Da bi sve bilo ispravno, ova shema zahtijeva dodatnu vezu - poseban tracker koji će primati informacije o statusu od Kubernetes API-ja i prenositi ih za daljnju analizu stvarnog stanja stvari. Stvorili smo biblioteku otvorenog koda u Gou - kockasti pas (pogledajte njegovu najavu здесь), koji rješava ovaj problem i ugrađen je u werf.

Ponašanje ovog alata za praćenje na razini werf konfigurira se pomoću komentara koji se postavljaju na Deployments ili StatefulSets. Glavna napomena - fail-mode - razumije sljedeća značenja:

  • IgnoreAndContinueDeployProcess — ignoriramo probleme uvođenja ove komponente i nastavljamo s implementacijom;
  • FailWholeDeployProcessImmediately — pogreška u ovoj komponenti zaustavlja proces postavljanja;
  • HopeUntilEndOfDeployProcess — nadamo se da će ova komponenta raditi do kraja implementacije.

Na primjer, ova kombinacija resursa i vrijednosti zabilješki fail-mode:

werf - naš alat za CI/CD u Kubernetesu (pregled i video izvješće)

Kada implementiramo prvi put, baza podataka (MongoDB) možda još nije spremna - implementacije neće uspjeti. Ali možete pričekati trenutak da počne, a implementacija će se i dalje odvijati.

Postoje još dvije napomene za kubedog u werf-u:

  • failures-allowed-per-replica — broj dopuštenih padova za svaku repliku;
  • show-logs-until — regulira trenutak do kojeg werf prikazuje (u stdout-u) dnevnike svih izbačenih mahuna. Zadano je PodIsReady (za ignoriranje poruka koje vjerojatno ne želimo kada promet počne dolaziti do bloka), ali vrijednosti su također važeće: ControllerIsReady и EndOfDeploy.

Što još želimo od implementacije?

Uz dvije već opisane točke, željeli bismo:

  • vidjeti cjepanice - i to samo one neophodne, a ne sve redom;
  • staza napredak, jer ako posao "tiho" visi nekoliko minuta, važno je razumjeti što se tamo događa;
  • иметь automatski rollback u slučaju da je nešto pošlo krivo (i stoga je ključno znati pravi status raspoređivanja). Izvođenje mora biti atomično: ili ide do kraja ili se sve vraća u prethodno stanje.

Rezultati

Za nas kao tvrtku, za implementaciju svih opisanih nijansi u različitim fazama isporuke (izgradnja, objava, implementacija), dovoljan je CI sustav i uslužni program werf.

Umjesto zaključka:

werf - naš alat za CI/CD u Kubernetesu (pregled i video izvješće)

Uz pomoć werfa dobro smo napredovali u rješavanju velikog broja problema za DevOps inženjere i bilo bi nam drago da šira zajednica barem isproba ovaj alat na djelu. Zajedno ćete lakše postići dobar rezultat.

Video zapisi i slajdovi

Video s nastupa (~47 minuta):

Prezentacija izvješća:

PS

Ostali izvještaji o Kubernetesu na našem blogu:

Izvor: www.habr.com

Dodajte komentar