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

27. maja u glavnoj sali konferencije DevOpsConf 2019, održane u okviru festivala RIT++ 2019, u okviru sekcije „Neprekidna isporuka“, dat je izveštaj „werf - naš alat za CI/CD u Kubernetesu“. Govori o njima problemi i izazovi sa kojima se svi suočavaju prilikom postavljanja na Kubernetes, kao i o nijansama koje možda neće biti odmah uočljive. Analizirajući moguća rješenja, pokazujemo kako se to implementira u Open Source alatu werf.

Od prezentacije, naš uslužni program (ranije poznat kao dapp) dostigao je istorijsku 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štaj)

Dakle, hajde da se predstavimo video izvještaja (~47 minuta, mnogo informativnije od članka) i glavni izvod iz njega u tekstualnom obliku. Idi!

Isporuka koda u Kubernetes

Više neće biti reči o werfu, već o CI/CD-u u Kubernetesu, što implicira da je naš softver upakovan u Docker kontejnere (Pričao sam o ovome u Izvještaj za 2016), a K8s će se koristiti za pokretanje u proizvodnji (više o ovome u 2017 godina).

Kako izgleda dostava u Kubernetesu?

  • Postoji Git repozitorijum sa kodom i uputstvima za njegovu izgradnju. Aplikacija je ugrađena u Docker sliku i objavljena u Docker Registry.
  • Isto spremište također sadrži upute o tome kako postaviti i pokrenuti aplikaciju. U fazi implementacije, ova uputstva se šalju Kubernetesu, koji prima željenu sliku iz registra i pokreće je.
  • Osim toga, obično postoje testovi. Neki od njih se mogu uraditi prilikom objavljivanja slike. Takođe možete (prateći ista uputstva) primeniti kopiju aplikacije (u zasebnom K8s imenskom prostoru ili zasebnom klasteru) i tamo pokrenuti testove.
  • Konačno, potreban vam je CI sistem koji prima događaje iz Gita (ili klikove na dugme) i poziva sve određene faze: izrada, objavljivanje, implementacija, testiranje.

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

Ovdje postoji nekoliko važnih napomena:

  1. Zato što imamo nepromjenjivu infrastrukturu (nepromjenjiva infrastruktura), slika aplikacije koja se koristi u svim fazama (izrada, proizvodnja, itd.), mora postojati. O tome sam govorio detaljnije i sa primjerima. ovdje.
  2. Zato što pratimo infrastrukturu kao kodni pristup (IaC), kod aplikacije, uputstva za sastavljanje i pokretanje treba da budu tačno u jednom spremištu. Za više informacija o tome, pogledajte isti izveštaj.
  3. Lanac isporuke (dostava) obično to vidimo ovako: aplikacija je sastavljena, testirana, puštena (faza puštanja) i to je to - dostava je obavljena. Ali u stvarnosti, korisnik dobija ono što ste izbacili, ne onda kada ste ga isporučili u proizvodnju, i kada je on mogao da ode tamo i ova proizvodnja je proradila. Tako da vjerujem da se lanac isporuke završava samo u operativnoj fazi (trčati), tačnije, čak i u trenutku kada je kod uklonjen iz proizvodnje (zamjena novim).

Vratimo se na gornju šemu isporuke u Kubernetesu: izmislili smo je ne samo mi, već i bukvalno svi koji su se bavili ovim problemom. U stvari, ovaj obrazac se sada zove GitOps (možete pročitati više o terminu i idejama iza njega ovdje). Pogledajmo faze šeme.

Faza izgradnje

Čini se da možete pričati o izgradnji Docker slika 2019. godine, kada svi znaju kako napisati Dockerfiles i pokrenuti docker build?.. Evo nijansi na koje bih želeo da obratim pažnju:

  1. Težina slike stvari, pa koristite višestepeniostaviti na slici samo aplikaciju koja je zaista neophodna za operaciju.
  2. Broj slojeva mora se minimizirati kombinovanjem lanaca RUN-naredbe prema značenju.
  3. Međutim, ovo dodaje probleme otklanjanje greš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 ponovo izgraditi ovisnosti u knjižnicama jezika svaki put kada napravite aplikaciju.
  5. Često vam je potrebno iz jednog Git spremišta mnogo slika, koji se može riješiti skupom Dockerfile-a (ili imenovanim fazama u jednoj datoteci) i Bash skriptom sa njihovim sekvencijalnim sklapanjem.

Ovo je bio samo vrh ledenog brega sa kojim se svi suočavaju. Ali postoje i drugi problemi, posebno:

  1. Često nam u fazi montaže nešto treba mount (na primjer, keširajte rezultat naredbe kao što je apt u direktoriju treće strane).
  2. Mi želimo Ansible umjesto pisanja u ljusci.
  3. Mi želimo build bez Docker-a (zašto nam je potrebna dodatna virtuelna mašina u kojoj moramo sve da konfigurišemo za ovo, kada već imamo Kubernetes klaster u kojem možemo da pokrećemo kontejnere?).
  4. Paralelni sklop, što se može shvatiti na različite načine: različite komande iz Dockerfile-a (ako se koristi višestepena), nekoliko urezivanja istog spremišta, nekoliko Dockerfile-a.
  5. Distribuirana montaža: Želimo sakupljati stvari u mahunama koje su "efemerne" jer njihova keš memorija nestaje, što znači da treba biti pohranjena negdje odvojeno.
  6. Konačno sam nazvao vrhunac želja automagic: Idealno bi bilo otići u spremište, ukucati neku komandu i dobiti gotovu sliku, sastavljenu sa razumijevanjem kako i šta treba raditi ispravno. Međutim, lično nisam siguran da se na ovaj način mogu predvidjeti sve nijanse.

A evo i projekata:

  • moby/buildkit — builder kompanije Docker Inc (već integrisan u trenutne verzije Docker-a), koji pokušava da reši sve ove probleme;
  • kaniko — builder iz Google-a koji vam omogućava da gradite bez Docker-a;
  • Buildpacks.io — CNCF-ov pokušaj da napravi automatsku magiju i, posebno, zanimljivo rješenje sa ponovnom bazom za slojeve;
  • i gomila drugih uslužnih programa, kao npr buildah, originalni alati/img...

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

Montaža u werf

Pa smo morali werf (ranije slavna kao dapp) — Uslužni program otvorenog koda kompanije Flant, koji proizvodimo dugi niz godina. Sve je počelo prije 5 godina sa Bash skriptama koje su optimizirale montažu Dockerfiles-a, a posljednje 3 godine potpuni razvoj se odvijao u okviru jednog projekta sa vlastitim Git repozitorijumom (prvo u Rubyju, a zatim prepisano to Go, i istovremeno preimenovan). Koje probleme sa montažom rješava werf?

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

Problemi označeni plavom bojom su već implementirani, paralelna gradnja je urađena u okviru istog hosta, a planirano je da problemi označeni žutom bojom budu gotovi do kraja ljeta.

Faza objave u registru (objavi)

Zvali smo docker push... - šta bi moglo biti teško u vezi sa postavljanjem slike u registar? I onda se postavlja pitanje: "Koju oznaku da stavim na sliku?" To nastaje iz razloga koji imamo Gitflow (ili neku drugu Git strategiju) i Kubernetes, a industrija pokušava da osigura da ono što se dešava u Kubernetesu prati ono što se dešava u Gitu. Na kraju krajeva, Git je naš jedini izvor istine.

Šta je tu tako teško? Osigurajte ponovljivost: iz urezivanja u Gitu, koji je po prirodi nepromjenjiv (nepromjenjiv), na Docker sliku, koja bi trebala ostati ista.

To nam je takođe važno odrediti porijeklo, jer želimo da shvatimo iz kojeg urezivanja je napravljena aplikacija koja radi u Kubernetesu (onda možemo raditi diffs i slične stvari).

Strategije označavanja

Prvi je jednostavan git tag. Imamo registar sa slikom označenom kao 1.0. Kubernetes ima pozornicu i produkciju, gdje se ova slika postavlja. U Gitu pravimo urezivanje i u nekom trenutku označavamo 2.0. Prikupljamo ga prema uputama iz spremišta i stavljamo u registar sa oznakom 2.0. Prebacujemo ga na scenu i, ako je sve u redu, onda u produkciju.

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

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

Druga opcija - git urezivanje + 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 staging. Zatim slijedimo Gitflow: u glavnoj grani za razvoj (develop) pravimo nove karakteristike, što rezultira urezivanjem sa identifikatorom #c1. Prikupljamo ga i objavljujemo u registru koristeći ovaj identifikator (#c1). Sa istim identifikatorom prelazimo na pregled. Isto radimo i sa urezivanje #c2 и #c3.

Kada smo shvatili da ima dovoljno funkcija, počinjemo sve da stabilizujemo. Kreirajte granu u Gitu release_1.1 (na bazi #c3 из develop). Nema potrebe za prikupljanjem ovog izdanja, jer... ovo je urađeno u prethodnom koraku. Stoga ga možemo jednostavno prebaciti u inscenaciju. Popravljamo greške #c4 i na sličan način prešli na inscenaciju. Istovremeno, razvoj je u toku develop, odakle se periodično preuzimaju promjene release_1.1. U nekom trenutku dobijamo urezivanje sastavljeno i učitano u inscenaciju, čime smo zadovoljni (#c25).

Zatim spajamo (uz brzo premotavanje) granu za oslobađanje (release_1.1) u master. Stavili smo oznaku sa novom verzijom na ovo urezivanje (1.1). Ali ova slika je već prikupljena u registru, tako da je ne bismo ponovo prikupili, jednostavno dodamo drugu oznaku postojećoj slici (sada ima oznake u registru #c25 и 1.1). Nakon toga ga stavljamo u proizvodnju.

Postoji nedostatak što se samo jedna slika učitava u inscenaciju (#c25), a u proizvodnji je nekako drugačije (1.1), ali znamo da su to „fizički“ iste slike iz registra.

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

Pravi nedostatak je što ne postoji podrška za spajanje urezivanja, morate raditi brzo naprijed.

Možemo ići dalje i napraviti trik... Pogledajmo primjer jednostavnog Dockerfile-a:

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

Napravimo fajl iz njega prema sljedećem principu:

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

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

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

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

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

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

Nedostatak je što sada neće biti moguće utvrditi kakva je vrsta urezivanja gurnuta u proizvodnju - kontrolni sumi rade samo u jednom smjeru. Ovaj problem je riješen dodatnim slojem s metapodacima - reći ću vam više kasnije.

Označavanje u werf

U werf-u smo otišli još dalje i spremamo se da napravimo distribuiranu gradnju sa keš memorijom koja nije pohranjena na jednoj mašini... Dakle, gradimo dvije vrste Docker slika, nazivamo ih faza и slika.

werf Git spremište pohranjuje upute specifične za izgradnju koje opisuju različite faze izgradnje (prije instalacije, instalirajte, pre podešavanja, postaviti). Prikupljamo sliku prve faze s potpisom definiranim kao kontrolni zbroj prvih koraka. Zatim dodajemo izvorni kod, za novu sliku pozornice izračunavamo njen kontrolni zbir... Ove operacije se ponavljaju za sve faze, kao rezultat toga dobijamo skup slika pozornice. Zatim pravimo konačnu sliku, koja takođe sadrži metapodatke o svom poreklu. I ovu sliku označavamo na različite načine (detalji kasnije).

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

Pretpostavimo da se nakon ovoga pojavi novo urezivanje u kojem je promijenjen samo kod aplikacije. Šta će se desiti? Za promjene koda, kreirat će se zakrpa i pripremiti nova slika pozornice. Njegov potpis će biti određen kao kontrolni zbroj stare slike pozornice i nove zakrpe. Nova konačna slika će se formirati od ove slike. Slično ponašanje će se dogoditi i s promjenama u drugim fazama.

Dakle, scenske slike su keš memorija koja se može distribuirati pohranjivati, a slike koje su već kreirane iz njega se učitavaju u Docker Registry.

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

Čišćenje registra

Ne govorimo o brisanju slojeva koji su ostali visjeti nakon obrisanih oznaka - ovo je standardna karakteristika samog Docker registra. Govorimo o situaciji kada se nakupi puno Docker oznaka i razumijemo da nam neki od njih više nisu potrebni, ali zauzimaju prostor (i/ili mi to plaćamo).

Koje su strategije čišćenja?

  1. Ne možeš ništa učiniti ne čisti. Ponekad je zaista lakše platiti malo za dodatni prostor nego razmrsiti ogroman splet oznaka. Ali ovo funkcionira samo do određene tačke.
  2. Potpuno resetovanje. Ako izbrišete sve slike i ponovo izgradite samo one trenutne u CI sistemu, može nastati problem. Ako se kontejner ponovo pokrene u proizvodnji, za njega će se učitati nova slika - ona koju još niko nije testirao. Ovo ubija ideju nepromjenjive infrastrukture.
  3. Plavo zeleno. Jedan registar je počeo da se prelijeva - slike učitavamo u drugi. Isti problem kao u prethodnoj metodi: u kom trenutku možete izbrisati registar koji je počeo da se prelijeva?
  4. Vremenom. Izbrisati sve slike starije od 1 mjeseca? Ali sigurno će postojati servis koji nije ažuriran mjesec dana...
  5. Ručno odredite šta se već može izbrisati.

Postoje dvije zaista održive opcije: ne čistiti ili kombinacija plavo-zelenog + 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 u toku, na primjer, mjesec dana. I nakon mjesec dana, pogledajte koji podovi u Kubernetesu još uvijek koriste stari registar i prebacite ih također u novi registar.

Do čega smo došli werf? prikupljamo:

  1. Git glava: sve oznake, sve grane - pod pretpostavkom da nam je potrebno sve što je označeno u Gitu na slikama (a ako nije, onda moramo to izbrisati u samom Gitu);
  2. sve podove koji se trenutno ispumpavaju u Kubernetes;
  3. stare ReplicaSets (ono što je nedavno objavljeno), a također planiramo skenirati Helm izdanja i tamo odabrati najnovije slike.

... i napravite bijelu listu od ovog skupa - listu slika koje nećemo brisati. Sve ostalo čistimo, nakon čega pronalazimo slike siročeta i brišemo ih.

Deploy stage

Pouzdana deklarativnost

Prva tačka na koju bih želeo da skrenem pažnju prilikom implementacije je uvođenje ažurirane konfiguracije resursa, deklarativno deklarisane. Originalni YAML dokument koji opisuje Kubernetes resurse je uvijek veoma različit od rezultata koji se stvarno izvodi u klasteru. Zato što Kubernetes dodaje u konfiguraciju:

  1. identifikatori;
  2. servisne informacije;
  3. mnoge zadane vrijednosti;
  4. odjeljak sa trenutnim statusom;
  5. promjene napravljene kao dio webhooka za prijem;
  6. rezultat rada raznih kontrolera (i planera).

Stoga, kada se pojavi nova konfiguracija resursa (novi), ne možemo samo uzeti i prepisati trenutnu, "živu" konfiguraciju s njom (živjeti). Da bismo to uradili, moraćemo da uporedimo novi sa posljednjom primijenjenom konfiguracijom (zadnja primijenjena) i otkotrljajte na živjeti primljena zakrpa.

Ovaj pristup se zove Dvosmjerno spajanje. Koristi se, na primjer, u Helmu.

Postoji također Dvosmjerno spajanje, koji se razlikuje po tome:

  • poređenje zadnja primijenjena и novi, gledamo šta je obrisano;
  • poređenje novi и živjeti, gledamo šta je dodato ili promenjeno;
  • sumirani patch je primijenjen živjeti.

Mi implementiramo 1000+ aplikacija sa Helm-om, tako da zapravo živimo sa dvosmjernim spajanjem. Međutim, ima niz problema koje smo riješili našim zakrpama, koje pomažu Helmu da normalno radi.

Pravi status uvođenja

Nakon što naš CI sistem generiše novu konfiguraciju za Kubernetes na osnovu sledećeg događaja, on je prenosi na upotrebu (primijeniti) u klaster - koristeći Helm ili kubectl apply. Zatim dolazi do već opisanog N-smjernog spajanja, na koje Kubernetes API s odobravanjem odgovara CI sistemu, a to i svom korisniku.

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

Međutim, postoji ogroman problem: na kraju krajeva uspješna primjena ne znači uspješno uvođenje. Ako Kubernetes shvati koje promjene treba primijeniti i primjenjuje ih, još uvijek ne znamo kakav će biti rezultat. Na primjer, ažuriranje i ponovno pokretanje podova u frontendu može biti uspješno, ali ne u backendu, i dobićemo različite verzije slika pokrenutih aplikacija.

Da bi sve uradila ispravno, ova šema zahteva dodatnu vezu - poseban tracker koji će primati informacije o statusu od Kubernetes API-ja i prenositi ih na dalju analizu stvarnog stanja stvari. Napravili smo biblioteku otvorenog koda u Go - cubedog (pogledajte njegovu najavu ovdje), koji rješava ovaj problem i ugrađen je u werf.

Ponašanje ovog tragača na werf nivou je konfigurisano korišćenjem napomena koje se postavljaju na Deployments ili StatefulSets. Glavna napomena - fail-mode - razumije sljedeća značenja:

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

Na primjer, ova kombinacija resursa i vrijednosti napomena fail-mode:

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

Kada implementiramo po prvi put, baza podataka (MongoDB) možda još nije spremna - implementacije neće uspjeti. Ali možete sačekati trenutak da počne, a raspoređivanje će se ipak održati.

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

  • failures-allowed-per-replica — broj dozvoljenih padova za svaku repliku;
  • show-logs-until — regulira trenutak do kojeg werf prikazuje (u stdout) zapise iz svih izbačenih podova. Podrazumevano je PodIsReady (da ignorišemo poruke koje verovatno ne želimo kada saobraćaj počne da dolazi u pod), ali vrednosti su takođe važeće: ControllerIsReady и EndOfDeploy.

Šta još želimo od implementacije?

Pored dvije već opisane tačke, željeli bismo:

  • vidjeti logs - i to samo one neophodne, a ne sve po redu;
  • track napredak, jer ako posao visi “tiho” nekoliko minuta, važno je razumjeti šta se tu dešava;
  • imam automatsko vraćanje unazad u slučaju da nešto pođe po zlu (i stoga je ključno znati pravi status implementacije). Uvođenje mora biti atomsko: ili ide do kraja, ili se sve vraća u svoje prethodno stanje.

Ishodi

Za nas kao kompaniju, za implementaciju svih opisanih nijansi u različitim fazama isporuke (izrada, objavljivanje, implementacija), dovoljni su CI sistem i uslužni program werf.

Umesto zaključka:

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

Uz pomoć werfa, napravili smo dobar napredak u rješavanju velikog broja problema za DevOps inženjere i bilo bi nam drago da šira zajednica barem isproba ovaj uslužni program na djelu. Zajedno će biti lakše postići dobar rezultat.

Video zapisi i slajdovi

Video sa nastupa (~47 minuta):

Prezentacija izvještaja:

PS

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

izvor: www.habr.com

Dodajte komentar