werf - náš nástroj pre CI / CD v Kubernetes (prehľad a video správa)

27. mája v hlavnej sále konferencie DevOpsConf 2019, ktorá sa koná v rámci festivalu RIT++ 2019, v rámci sekcie „Nepretržité doručovanie“ bola uvedená správa „werf – náš nástroj pre CI/CD v Kubernetes“. Hovorí o nich problémy a výzvy, ktorým každý čelí pri nasadzovaní do Kubernetes, ako aj o nuansách, ktoré nemusia byť okamžite viditeľné. Pri analýze možných riešení ukážeme, ako sa to implementuje v nástroji Open Source werf.

Od prezentácie naša pomôcka (predtým známa ako dapp) dosiahla historický míľnik 1000 hviezdičiek na GitHub — dúfame, že jeho rastúca komunita používateľov uľahčí život mnohým inžinierom DevOps.

werf - náš nástroj pre CI / CD v Kubernetes (prehľad a video správa)

Takže, predstavujeme video zo správy (~47 minút, oveľa informatívnejšie ako článok) a hlavný úryvok z neho v textovej podobe. Choď!

Doručenie kódu do Kubernetes

Hovoriť sa už nebude o werf, ale o CI/CD v Kubernetes, čo znamená, že náš softvér je zabalený v kontajneroch Docker (Hovoril som o tom v správa za rok 2016), a na jeho spustenie vo výrobe budú použité K8 (viac o tom v 2017 rok).

Ako vyzerá doručenie v Kubernetes?

  • Existuje úložisko Git s kódom a pokynmi na jeho zostavenie. Aplikácia je zabudovaná do obrazu Docker a zverejnená v registri Docker.
  • Rovnaké úložisko obsahuje aj návod na nasadenie a spustenie aplikácie. Vo fáze nasadenia sa tieto pokyny odošlú spoločnosti Kubernetes, ktorá dostane požadovaný obrázok z registra a spustí ho.
  • Navyše, zvyčajne existujú testy. Niektoré z nich je možné vykonať pri publikovaní obrázka. Môžete tiež (podľa rovnakých pokynov) nasadiť kópiu aplikácie (v samostatnom mennom priestore K8s alebo v samostatnom klastri) a spustiť tam testy.
  • Nakoniec potrebujete systém CI, ktorý prijíma udalosti z Gitu (alebo kliknutia na tlačidlá) a volá všetky určené fázy: zostavenie, zverejnenie, nasadenie, testovanie.

werf - náš nástroj pre CI / CD v Kubernetes (prehľad a video správa)

Tu je niekoľko dôležitých poznámok:

  1. Pretože máme nemennú infraštruktúru (nezmeniteľná infraštruktúra), obrázok aplikácie, ktorý sa používa vo všetkých fázach (predstavenie, výroba atď.), musí tam byť jeden. Hovoril som o tom podrobnejšie a s príkladmi. tu.
  2. Pretože sledujeme infraštruktúru ako kódový prístup (IaC), mal by byť kód aplikácie, pokyny na jej zostavenie a spustenie presne v jednom úložisku. Viac informácií o tom nájdete v časti rovnaká správa.
  3. Doručovací reťazec (doručenie) zvyčajne to vidíme takto: aplikácia bola zostavená, otestovaná, uvoľnená (fáza uvoľnenia) a je to – doručenie prebehlo. V skutočnosti však používateľ dostane to, čo ste zaviedli, nie potom, keď ste to dodali do výroby, a keď tam mohol ísť a táto výroba fungovala. Takže verím, že doručovací reťazec končí len v prevádzkovom štádiu (beh), presnejšie povedané ešte v momente, keď bol kód stiahnutý z výroby (nahradený novým).

Vráťme sa k vyššie uvedenej schéme doručovania v Kubernetes: vymysleli sme ju nielen my, ale doslova každý, kto sa s týmto problémom zaoberal. V skutočnosti sa tento vzor teraz nazýva GitOps (môžete si prečítať viac o termíne a myšlienkach za ním tu). Pozrime sa na fázy schémy.

Stavať javisko

Zdalo by sa, že môžete hovoriť o vytváraní obrázkov Docker v roku 2019, keď každý vie, ako napísať a spustiť Dockerfiles docker build?.. Tu sú nuansy, ktorým by som chcel venovať pozornosť:

  1. Hmotnosť obrázka záleží, tak použite viacstupňovéponechať na obrázku len tú aplikáciu, ktorá je pre operáciu skutočne potrebná.
  2. Počet vrstiev sa musia minimalizovať kombinovaním reťazcov RUN-príkazy podľa významu.
  3. To však pridáva problémy ladenie, pretože keď sa zostava zrúti, musíte nájsť správny príkaz z reťazca, ktorý spôsobil problém.
  4. Rýchlosť montáže dôležité, pretože chceme rýchlo zaviesť zmeny a vidieť výsledky. Napríklad nechcete pri každom vytváraní aplikácie prestavovať závislosti v jazykových knižniciach.
  5. Často z jedného úložiska Git, ktoré potrebujete veľa obrázkov, ktoré je možné vyriešiť pomocou sady Dockerfiles (alebo pomenovaných fáz v jednom súbore) a skriptu Bash s ich sekvenčným zostavovaním.

Toto bola len špička ľadovca, ktorému čelia všetci. Existujú však aj iné problémy, najmä:

  1. Často vo fáze montáže niečo potrebujeme namontovať (napríklad uložte do vyrovnávacej pamäte výsledok príkazu ako apt v adresári tretej strany).
  2. Chceme Ansible namiesto písania v shelli.
  3. Chceme stavať bez Dockera (prečo potrebujeme ďalší virtuálny stroj, v ktorom musíme na to všetko nakonfigurovať, keď už máme klaster Kubernetes, v ktorom môžeme spúšťať kontajnery?).
  4. Paralelná montáž, ktoré možno chápať rôznymi spôsobmi: rôzne príkazy zo súboru Dockerfile (ak sa používa viacstupňový), niekoľko odovzdaní toho istého úložiska, niekoľko súborov Dockerfile.
  5. Distribuovaná montáž: Chceme zbierať veci do strukov, ktoré sú „pominuteľné“, pretože ich vyrovnávacia pamäť zmizne, čo znamená, že ju treba niekde uložiť oddelene.
  6. Nakoniec som pomenoval vrchol túžob automatika: Ideálne by bolo ísť do úložiska, napísať nejaký príkaz a získať hotový obrázok zostavený s pochopením toho, ako a čo robiť správne. Osobne si však nie som istý, či sa takto dajú predvídať všetky nuansy.

A tu sú projekty:

  • moby/buildkit — staviteľ od Docker Inc (už integrovaný do aktuálnych verzií Docker), ktorý sa snaží vyriešiť všetky tieto problémy;
  • kaniko — staviteľ od spoločnosti Google, ktorý vám umožňuje stavať bez Dockera;
  • Buildpacks.io — pokus CNCF o automatickú mágiu a najmä zaujímavé riešenie s rebázou pre vrstvy;
  • a kopu ďalších utilít, ako napr buildah, originaltools/img...

...a pozrite sa, koľko hviezdičiek majú na GitHub. To znamená, že na jednej strane docker build existuje a môže niečo urobiť, ale v skutočnosti problém nie je úplne vyriešený - dôkazom toho je paralelný vývoj alternatívnych kolektorov, z ktorých každý rieši nejakú časť problémov.

Montáž vo werf

Takže musíme werf (predtým slávny ako dapp) — Open source utilita od spoločnosti Flant, ktorú vyrábame už mnoho rokov. Všetko to začalo pred 5 rokmi skriptami Bash, ktoré optimalizovali zostavovanie Dockerfiles a posledné 3 roky prebiehal plnohodnotný vývoj v rámci jedného projektu s vlastným úložiskom Git. (najskôr v Ruby a potom prepísané ísť a zároveň premenovať). Aké montážne problémy sa riešia vo werf?

werf - náš nástroj pre CI / CD v Kubernetes (prehľad a video správa)

Problémy označené modrou farbou už boli implementované, paralelná zostava bola vykonaná v rámci toho istého hostiteľa a problémy označené žltou farbou sa plánujú dokončiť do konca leta.

Stupeň zverejnenia v registri (zverejniť)

Vytočili sme sa docker push... - čo môže byť zložité na nahrávaní obrázka do registra? A potom vyvstáva otázka: „Akú značku mám dať na obrázok? Vzniká z dôvodu, ktorý máme Gitflow (alebo iná stratégia Git) a Kubernetes a toto odvetvie sa snaží zabezpečiť, aby to, čo sa deje v Kubernetes, nasledovalo to, čo sa deje v Gite. Koniec koncov, Git je náš jediný zdroj pravdy.

Čo je na tom také ťažké? Zabezpečte reprodukovateľnosť: z odovzdania v systéme Git, ktorý je svojou povahou nemenný (nezmeniteľné), na obrázok Docker, ktorý by mal zostať rovnaký.

Je to dôležité aj pre nás určiť pôvod, pretože chceme pochopiť, z akého commitu bola zostavená aplikácia bežiaca v Kubernetes (potom môžeme robiť diffy a podobné veci).

Stratégie označovania

Prvý je jednoduchý git tag. Máme register s obrázkom označeným ako 1.0. Kubernetes má scénu a produkciu, kde je tento obrázok nahraný. V Git robíme commity a v určitom bode tagujeme 2.0. Zhromažďujeme ho podľa pokynov z úložiska a umiestňujeme do registra s visačkou 2.0. Rozvinieme to na javisko a ak je všetko v poriadku, tak do výroby.

werf - náš nástroj pre CI / CD v Kubernetes (prehľad a video správa)

Problém s týmto prístupom je, že najprv sme nastavili značku a až potom ju otestovali a zaviedli. prečo? Po prvé, je to jednoducho nelogické: vydávame verziu softvéru, ktorú sme ešte ani netestovali (nemôžeme inak, pretože na kontrolu musíme vložiť značku). Po druhé, táto cesta nie je kompatibilná s Gitflow.

Druhá možnosť - git commit + tag. Hlavná vetva má štítok 1.0; pre to v registri - obraz nasadený do produkcie. Okrem toho má klaster Kubernetes obrysy náhľadu a prípravy. Ďalej sledujeme Gitflow: v hlavnej vetve pre vývoj (develop) vytvárame nové funkcie, ktorých výsledkom je potvrdenie s identifikátorom #c1. Zhromažďujeme ho a zverejňujeme v registri pomocou tohto identifikátora (#c1). S rovnakým identifikátorom spustíme ukážku. To isté robíme so záväzkami #c2 и #c3.

Keď sme si uvedomili, že funkcií je dosť, začneme všetko stabilizovať. Vytvorte pobočku v Git release_1.1 (na základni #c3 z develop). Toto vydanie nie je potrebné zbierať, pretože... toto bolo vykonané v predchádzajúcom kroku. Preto ho môžeme jednoducho rozvinúť do inscenácie. Opravujeme chyby #c4 a podobne rozvinúť na inscenáciu. Zároveň prebieha vývoj v develop, odkiaľ sa pravidelne preberajú zmeny release_1.1. V určitom okamihu dostaneme odovzdanie skompilované a nahrané do stagingu, s čím sme spokojní (#c25).

Potom zlúčime (s rýchlym posunom vpred) uvoľňovaciu vetvu (release_1.1) v predlohe. Do tohto odovzdania sme vložili značku s novou verziou (1.1). Ale tento obrázok je už zhromaždený v registri, takže aby sme ho znova nezozbierali, jednoducho pridáme druhú značku k existujúcemu obrázku (teraz má značky v registri #c25 и 1.1). Potom ho rozvinieme do výroby.

Nevýhodou je, že do inscenácie sa nahrá iba jeden obrázok (#c25) a vo výrobe je to trochu iné (1.1), ale vieme, že „fyzicky“ ide o rovnaký obrázok z registra.

werf - náš nástroj pre CI / CD v Kubernetes (prehľad a video správa)

Skutočnou nevýhodou je, že neexistuje podpora pre zlučovacie commity, musíte to urobiť rýchlo dopredu.

Môžeme ísť ďalej a urobiť trik... Pozrime sa na príklad jednoduchého súboru 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

Zostavme z neho súbor podľa nasledujúceho princípu:

  • SHA256 z identifikátorov použitých obrázkov (ruby:2.3 и nginx:alpine), ktoré sú kontrolnými súčtami ich obsahu;
  • všetky tímy (RUN, CMD atď.);
  • SHA256 zo súborov, ktoré boli pridané.

... a z takého súboru vezmite kontrolný súčet (opäť SHA256). Toto podpis všetko, čo definuje obsah obrazu Docker.

werf - náš nástroj pre CI / CD v Kubernetes (prehľad a video správa)

Vráťme sa k diagramu a namiesto commitov budeme používať takéto podpisy, t.j. označte obrázky podpismi.

werf - náš nástroj pre CI / CD v Kubernetes (prehľad a video správa)

Teraz, keď je napríklad potrebné zlúčiť zmeny z vydania do hlavného, ​​môžeme urobiť skutočný komitát zlúčenia: bude mať iný identifikátor, ale rovnaký podpis. S rovnakým identifikátorom uvedieme obrázok do výroby.

Nevýhodou je, že teraz nebude možné určiť, aký druh commitu bol zatlačený do produkcie - kontrolné súčty fungujú len jedným smerom. Tento problém rieši ďalšia vrstva s metadátami – viac vám poviem neskôr.

Označovanie vo werf

Vo werf sme zašli ešte ďalej a pripravujeme distribuované zostavenie s vyrovnávacou pamäťou, ktorá nie je uložená na jednom počítači... Takže vytvárame dva typy obrazov Docker, nazývame ich stupeň и obraz.

Repozitár werf Git ukladá inštrukcie špecifické pre zostavenie, ktoré popisujú rôzne fázy zostavovania (predInštalovať, inštalovať, pred nastavením, nastavenie). Zhromažďujeme obrázok prvej fázy s podpisom definovaným ako kontrolný súčet prvých krokov. Potom pridáme zdrojový kód, pre nový scénický obrázok vypočítame jeho kontrolný súčet... Tieto operácie sa opakujú pre všetky štádiá, výsledkom čoho je množina scénických obrázkov. Potom urobíme výsledný obrázok, ktorý obsahuje aj metadáta o jeho pôvode. A tento obrázok označujeme rôznymi spôsobmi (podrobnosti neskôr).

werf - náš nástroj pre CI / CD v Kubernetes (prehľad a video správa)

Predpokladajme, že sa potom objaví nové potvrdenie, v ktorom sa zmenil iba kód aplikácie. Čo sa bude diať? Pre zmeny kódu sa vytvorí záplata a pripraví sa nový obrázok scény. Jeho podpis bude určený ako kontrolný súčet obrazu starého štádia a nového patchu. Z tohto obrázka sa vytvorí nový konečný obrázok. Podobné správanie nastane pri zmenách v iných fázach.

Obrázky fázy sú teda vyrovnávacou pamäťou, ktorú možno distribuovane ukladať a obrázky, ktoré z nej už boli vytvorené, sa nahrávajú do registra Docker.

werf - náš nástroj pre CI / CD v Kubernetes (prehľad a video správa)

Čistenie registra

Nehovoríme o odstránení vrstiev, ktoré zostali visieť po odstránených značkách - to je štandardná funkcia samotného registra Docker. Hovoríme o situácii, keď sa hromadí veľa Docker tagov a my chápeme, že niektoré už nepotrebujeme, ale zaberajú miesto (a/alebo za to platíme).

Aké sú stratégie čistenia?

  1. Nemôžeš robiť nič nečistite. Niekedy je naozaj jednoduchšie zaplatiť trochu za priestor navyše, než rozmotať obrovskú spleť štítkov. Ale to funguje len do určitého bodu.
  2. Úplný reset. Ak vymažete všetky obrázky a v systéme CI znova vytvoríte len tie aktuálne, môže nastať problém. Ak sa kontajner reštartuje vo výrobe, načíta sa mu nový obrázok – taký, ktorý ešte nikto netestoval. To zabíja myšlienku nemennej infraštruktúry.
  3. Modro zelená. Jeden register sa začal prepĺňať – obrázky nahrávame do druhého. Rovnaký problém ako v predchádzajúcej metóde: v akom bode môžete vyčistiť register, ktorý začal pretekať?
  4. Časom. Odstrániť všetky obrázky staršie ako 1 mesiac? Ale určite sa nájde služba, ktorá už mesiac nebola aktualizovaná...
  5. ručné určiť, čo už možno vymazať.

Existujú dve skutočne životaschopné možnosti: nečistiť alebo kombinácia modro-zelená + ručne. V druhom prípade hovoríme o nasledujúcom: keď pochopíte, že je čas vyčistiť register, vytvoríte si nový a v priebehu napríklad mesiaca doň pridáte všetky nové obrázky. A po mesiaci sa pozrite, ktoré moduly v Kubernetes stále používajú starý register, a preneste ich tiež do nového registra.

K čomu sme dospeli werf? Zbierame:

  1. Hlava Git: všetky značky, všetky vetvy - za predpokladu, že na obrázkoch potrebujeme všetko, čo je označené v Git (a ak nie, musíme to odstrániť v samotnom Gite);
  2. všetky moduly, ktoré sa momentálne čerpajú do Kubernetes;
  3. staré ReplicaSets (čo bolo nedávno vydané) a plánujeme tiež skenovať vydania Helm a vybrať tam najnovšie obrázky.

... a z tejto sady urobte whitelist - zoznam obrázkov, ktoré nevymažeme. Vyčistíme všetko ostatné, potom nájdeme osirelé scénické obrazy a tiež ich vymažeme.

Fáza nasadenia

Spoľahlivá deklaratívnosť

Prvým bodom, na ktorý by som chcel upozorniť pri nasadzovaní, je zavedenie aktualizovanej konfigurácie prostriedkov, deklaratívne deklarované. Pôvodný dokument YAML popisujúci zdroje Kubernetes sa vždy veľmi líši od výsledku, ktorý skutočne beží v klastri. Pretože Kubernetes pridáva do konfigurácie:

  1. identifikátory;
  2. servisné informácie;
  3. veľa predvolených hodnôt;
  4. sekcia s aktuálnym stavom;
  5. zmeny vykonané v rámci prijímacieho webhooku;
  6. výsledok práce rôznych ovládačov (a plánovača).

Preto, keď sa objaví nová konfigurácia prostriedkov (nový), nemôžeme s ním len vziať a prepísať aktuálnu „živú“ konfiguráciu (žiť). Aby sme to dosiahli, budeme musieť porovnávať nový s poslednou použitou konfiguráciou (naposledy aplikovaný) a rolujte na žiť prijatá náplasť.

Tento prístup sa nazýva 2-cestné zlúčenie. Používa sa napríklad v Helme.

Je tu tiež 3-cestné zlúčenie, ktorý sa líši tým, že:

  • porovnávanie naposledy aplikovaný и nový, pozrieme sa na to, čo bolo vymazané;
  • porovnávanie nový и žiť, pozrieme sa na to, čo bolo pridané alebo zmenené;
  • sa aplikuje súhrnná náplasť žiť.

S Helm nasadzujeme viac ako 1000 aplikácií, takže v skutočnosti žijeme s obojsmerným zlučovaním. Má však množstvo problémov, ktoré sme vyriešili našimi záplatami, ktoré pomáhajú Helmu normálne fungovať.

Skutočný stav zavádzania

Keď náš systém CI vygeneruje novú konfiguráciu pre Kubernetes na základe ďalšej udalosti, odošle ju na použitie (použiť) do klastra - pomocou Helm resp kubectl apply. Ďalej nastáva už popísané N-way merge, na ktorý Kubernetes API odpovedá súhlasne CI systému, a to jeho užívateľovi.

werf - náš nástroj pre CI / CD v Kubernetes (prehľad a video správa)

Je tu však obrovský problém: predsa úspešná aplikácia neznamená úspešné zavedenie. Ak Kubernetes pochopí, aké zmeny je potrebné použiť, a použije ich, stále nevieme, aký bude výsledok. Napríklad aktualizácia a reštart modulov vo frontende môže byť úspešný, ale nie v backende a získame rôzne verzie obrazov spustených aplikácií.

Aby sa všetko urobilo správne, táto schéma vyžaduje ďalší odkaz - špeciálny sledovač, ktorý bude prijímať informácie o stave z rozhrania Kubernetes API a prenášať ich na ďalšiu analýzu skutočného stavu vecí. Vytvorili sme knižnicu Open Source v Go - kockatý pes (pozri jeho oznámenie tu), ktorá tento problém rieši a je zabudovaná do werf.

Správanie tohto sledovača na úrovni werf sa konfiguruje pomocou anotácií, ktoré sú umiestnené na Deployments alebo StatefulSets. Hlavná anotácia - fail-mode - rozumie nasledujúcim významom:

  • IgnoreAndContinueDeployProcess — ignorujeme problémy spojené so zavádzaním tohto komponentu a pokračujeme v nasadzovaní;
  • FailWholeDeployProcessImmediately — chyba v tomto komponente zastaví proces nasadenia;
  • HopeUntilEndOfDeployProcess — dúfame, že tento komponent bude fungovať do konca nasadenia.

Napríklad táto kombinácia zdrojov a hodnôt anotácií fail-mode:

werf - náš nástroj pre CI / CD v Kubernetes (prehľad a video správa)

Keď nasadíme prvýkrát, databáza (MongoDB) ešte nemusí byť pripravená – nasadenia zlyhajú. Môžete však počkať na chvíľu, kým sa spustí, a nasadenie bude stále prebiehať.

Existujú dve ďalšie anotácie pre kubedog vo werf:

  • failures-allowed-per-replica — počet povolených pádov pre každú repliku;
  • show-logs-until — reguluje moment, do ktorého werf ukazuje (v stdout) polená zo všetkých vyvalených strukov. Predvolená hodnota je PodIsReady (aby sme ignorovali správy, ktoré v skutočnosti nechceme, keď návštevnosť začne prichádzať do podu), ale hodnoty sú tiež platné: ControllerIsReady и EndOfDeploy.

Čo ešte chceme od nasadenia?

Okrem už opísaných dvoch bodov by sme chceli:

  • vidieť protokoly - a iba tie potrebné, a nie všetko v rade;
  • trať pokrok, pretože ak úloha visí „potichu“ niekoľko minút, je dôležité pochopiť, čo sa tam deje;
  • иметь automatický návrat späť v prípade, že sa niečo pokazilo (a preto je dôležité poznať skutočný stav nasadenia). Zavedenie musí byť atómové: buď prejde až do konca, alebo sa všetko vráti do predchádzajúceho stavu.

Výsledky

Pre nás ako spoločnosť stačí na implementáciu všetkých popísaných nuancií v rôznych fázach dodávky (zostavenie, zverejnenie, nasadenie) systém CI a nástroj. werf.

Namiesto záveru:

werf - náš nástroj pre CI / CD v Kubernetes (prehľad a video správa)

S pomocou werf sme urobili dobrý pokrok pri riešení veľkého množstva problémov pre inžinierov DevOps a boli by sme radi, keby širšia komunita aspoň vyskúšala túto pomôcku v akcii. Spoločne bude ľahšie dosiahnuť dobrý výsledok.

Videá a diapozitívy

Video z predstavenia (~47 minút):

Prezentácia správy:

PS

Ďalšie správy o Kubernetes na našom blogu:

Zdroj: hab.com

Pridať komentár