werf - náš nástroj pro CI / CD v Kubernetes (přehled a videoreportáž)

27. května v hlavním sále konference DevOpsConf 2019, konané v rámci festivalu RIT++ 2019, v rámci sekce „Continuous Delivery“ byla vydána zpráva „werf – náš nástroj pro CI/CD v Kubernetes“. O těch se mluví problémy a výzvy, kterým každý čelí při nasazení do Kubernetes, stejně jako o nuancích, které nemusí být okamžitě patrné. Při analýze možných řešení ukazujeme, jak je to implementováno v nástroji Open Source werf.

Od prezentace dosáhla naše utilita (dříve známá jako dapp) historického milníku 1000 hvězdiček na GitHubu — doufáme, že jeho rostoucí komunita uživatelů usnadní život mnoha inženýrům DevOps.

werf - náš nástroj pro CI / CD v Kubernetes (přehled a videoreportáž)

Představujeme tedy video reportáže (~47 minut, mnohem informativnější než článek) a hlavní úryvek z něj v textové podobě. Jít!

Doručování kódu do Kubernetes

Řeč již nebude o werf, ale o CI/CD v Kubernetes, což znamená, že náš software je zabalen v kontejnerech Docker (Mluvil jsem o tom v zpráva za rok 2016), a K8s poslouží k jeho spuštění ve výrobě (více o tom v 2017 rok).

Jak vypadá doručení v Kubernetes?

  • Existuje úložiště Git s kódem a pokyny pro jeho sestavení. Aplikace je zabudována do obrazu Dockeru a publikována v registru Docker.
  • Stejné úložiště obsahuje také pokyny k nasazení a spuštění aplikace. Ve fázi nasazení jsou tyto pokyny odeslány společnosti Kubernetes, která obdrží požadovaný obraz z registru a spustí jej.
  • Navíc tam obvykle probíhají testy. Některé z nich lze provést při publikování obrázku. Můžete také (podle stejných pokynů) nasadit kopii aplikace (v samostatném jmenném prostoru K8s nebo samostatném clusteru) a spustit tam testy.
  • Nakonec potřebujete systém CI, který přijímá události z Gitu (nebo kliknutí na tlačítka) a volá všechny určené fáze: sestavení, publikování, nasazení, testování.

werf - náš nástroj pro CI / CD v Kubernetes (přehled a videoreportáž)

Zde je několik důležitých poznámek:

  1. Protože máme neměnnou infrastrukturu (neměnná infrastruktura), obraz aplikace, který se používá ve všech fázích (staging, produkce atd.), musí tam být jeden. Mluvil jsem o tom podrobněji a s příklady. zde.
  2. Protože sledujeme infrastrukturu jako kódový přístup (IaC), měl by být kód aplikace, pokyny pro sestavení a spuštění přesně v jednom úložišti. Více informací o tomto viz stejná zpráva.
  3. Doručovací řetězec (dodávka) obvykle to vidíme takto: aplikace byla sestavena, otestována, uvolněna (fáze uvolnění) a je to – dodávka proběhla. Ale ve skutečnosti uživatel dostane to, co jste spustili, ne pak, když jste to dodali do výroby, a když tam mohl jít a tato výroba fungovala. Takže věřím, že dodací řetězec končí pouze v provozní fázi (běh), přesněji řečeno ještě v okamžiku, kdy byl kód vyřazen z výroby (nahrazením novým).

Vraťme se k výše uvedenému schématu doručování v Kubernetes: vymysleli jsme ho nejen my, ale doslova každý, kdo se tímto problémem zabýval. Ve skutečnosti se tento vzor nyní nazývá GitOps (můžete si přečíst více o termínu a myšlenkách za ním zde). Podívejme se na fáze schématu.

Stavět jeviště

Zdálo by se, že můžete mluvit o vytváření obrázků Docker v roce 2019, kdy každý ví, jak psát a spouštět Dockerfiles docker build?.. Zde jsou nuance, kterým bych chtěl věnovat pozornost:

  1. Hmotnost obrázku záleží, tak použijte vícestupňovéponechat v obrázku pouze aplikaci, která je pro operaci skutečně nezbytná.
  2. Počet vrstev musí být minimalizováno kombinováním řetězců RUN-příkazy podle významu.
  3. To však přidává problémy ladění, protože když se sestava zhroutí, musíte najít správný příkaz z řetězce, který způsobil problém.
  4. Rychlost montáže důležité, protože chceme rychle zavést změny a vidět výsledky. Například nechcete znovu vytvářet závislosti v jazykových knihovnách pokaždé, když vytváříte aplikaci.
  5. Často z jednoho úložiště Git, které potřebujete mnoho obrázků, což lze vyřešit sadou Dockerfiles (nebo pojmenovaných fází v jednom souboru) a Bash skriptem s jejich sekvenčním sestavováním.

Tohle byla jen špička ledovce, které všichni čelí. Existují však i další problémy, zejména:

  1. Často ve fázi montáže něco potřebujeme namontovat (například uložte do mezipaměti výsledek příkazu jako apt v adresáři třetí strany).
  2. Chceme Možná místo psaní v shellu.
  3. Chceme stavět bez Dockeru (proč potřebujeme další virtuální stroj, ve kterém k tomu potřebujeme vše nakonfigurovat, když už máme cluster Kubernetes, ve kterém můžeme spouštět kontejnery?).
  4. Paralelní montáž, které lze chápat různými způsoby: různé příkazy z Dockerfile (pokud se používá vícestupňový), několik odevzdání stejného úložiště, několik Dockerfile.
  5. Distribuovaná montáž: Chceme sbírat věci do lusků, které jsou „pomíjivé“, protože jejich mezipaměť zmizí, což znamená, že je třeba ji někde uložit samostatně.
  6. Nakonec jsem pojmenoval vrchol tužeb automatika: Ideální by bylo jít do úložiště, napsat nějaký příkaz a získat hotový obrázek, sestavený s pochopením toho, jak a co správně dělat. Osobně si však nejsem jistý, zda lze takto předvídat všechny nuance.

A tady jsou projekty:

  • moby/buildkit — builder od Docker Inc (již integrovaný do aktuálních verzí Dockeru), který se snaží všechny tyto problémy vyřešit;
  • kaniko — stavitel od Google, který vám umožňuje stavět bez Dockeru;
  • Buildpacks.io — pokus CNCF o automatickou magii a zejména zajímavé řešení s rebase pro vrstvy;
  • a spoustu dalších utilit, jako např stavětah, originaltools/img...

...a podívejte se, kolik hvězd mají na GitHubu. To znamená, že na jedné straně docker build existuje a může něco dělat, ale ve skutečnosti problém není zcela vyřešen - důkazem toho je paralelní vývoj alternativních kolektorů, z nichž každý řeší nějakou část problémů.

Montáž ve werf

Takže musíme werf (dříve slavný jako dapp) — Open source utilita od společnosti Flant, kterou vyrábíme již mnoho let. Vše začalo před 5 lety Bash skripty, které optimalizovaly sestavení Dockerfiles a poslední 3 roky probíhal plnohodnotný vývoj v rámci jednoho projektu s vlastním Git repozitářem (nejprve v Ruby a pak přepsat jít a zároveň přejmenovat). Jaké problémy s montáží se řeší ve werf?

werf - náš nástroj pro CI / CD v Kubernetes (přehled a videoreportáž)

Problémy označené modře již byly implementovány, paralelní sestavení bylo provedeno na stejném hostiteli a problémy zvýrazněné žlutě mají být dokončeny do konce léta.

Fáze zveřejnění v registru (publikovat)

Vytočili jsme se docker push... - co může být obtížné na nahrávání obrázku do registru? A pak vyvstává otázka: "Jakou značku bych měl dát na obrázek?" Vzniká z důvodu, který máme Gitflow (nebo jiná strategie Git) a Kubernetes a průmysl se snaží zajistit, aby to, co se děje v Kubernetes, následovalo to, co se děje v Gitu. Koneckonců, Git je náš jediný zdroj pravdy.

co je na tom tak těžkého? Zajistěte reprodukovatelnost: z commitu v Gitu, který je svou povahou neměnný (neměnný), na obrázek Dockeru, který by měl zůstat stejný.

Je to pro nás také důležité určit původ, protože chceme pochopit, ze kterého commitu byla sestavena aplikace běžící v Kubernetes (pak můžeme dělat diffy a podobné věci).

Strategie značkování

První je jednoduchý značka git. Máme registr s obrázkem označeným jako 1.0. Kubernetes má scénu a produkci, kam je tento obrázek nahrán. V Gitu děláme commity a v určitém okamžiku tagujeme 2.0. Sbíráme ho podle pokynů z úložiště a umístíme do registru s visačkou 2.0. Rozjedeme to na jeviště a pokud je vše v pořádku, tak do výroby.

werf - náš nástroj pro CI / CD v Kubernetes (přehled a videoreportáž)

Problém s tímto přístupem je, že jsme nejprve nastavili značku a teprve poté ji otestovali a zavedli. Proč? Zaprvé je to jednoduše nelogické: vydáváme verzi softwaru, kterou jsme ještě ani netestovali (nemůžeme jinak, protože ke kontrole musíme vložit značku). Za druhé, tato cesta není kompatibilní s Gitflow.

Druhá možnost - git commit + tag. Hlavní větev má značku 1.0; pro to v registru - obraz nasazený do produkce. Kromě toho má cluster Kubernetes obrysy náhledu a přípravy. Dále sledujeme Gitflow: v hlavní větvi pro vývoj (develop) vytváříme nové funkce, jejichž výsledkem je potvrzení s identifikátorem #c1. Shromažďujeme a zveřejňujeme v registru pomocí tohoto identifikátoru (#c1). Se stejným identifikátorem spustíme náhled. Totéž děláme se závazky #c2 и #c3.

Když jsme si uvědomili, že funkcí je dostatek, začneme vše stabilizovat. Vytvořte větev v Gitu release_1.1 (na základně #c3 z develop). Toto vydání není potřeba sbírat, protože... to bylo provedeno v předchozím kroku. Proto jej můžeme jednoduše uvést do inscenace. Opravujeme chyby #c4 a podobně se rozvinou do inscenace. Zároveň probíhá vývoj v develop, odkud jsou pravidelně přebírány změny release_1.1. V určitém okamžiku dostaneme revizi zkompilovanou a nahranou do stagingu, s čímž jsme spokojeni (#c25).

Poté sloučíme (rychle vpřed) větev vydání (release_1.1) v masteru. Do tohoto potvrzení jsme vložili značku s novou verzí (1.1). Ale tento obrázek je již shromážděn v registru, takže abychom jej znovu nesbírali, jednoduše přidáme druhou značku k existujícímu obrázku (nyní má značky v registru #c25 и 1.1). Poté jej zavedeme do výroby.

Nevýhodou je, že do stagingu je nahrán pouze jeden obrázek (#c25), a ve výrobě je to trochu jiné (1.1), ale víme, že „fyzicky“ se jedná o stejný obrázek z registru.

werf - náš nástroj pro CI / CD v Kubernetes (přehled a videoreportáž)

Skutečnou nevýhodou je, že neexistuje podpora pro slučovací commity, musíte to udělat rychle vpřed.

Můžeme jít dál a udělat trik... Podívejme se na příklad jednoduchého souboru 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

Vytvořme z něj soubor podle následujícího principu:

  • SHA256 z identifikátorů použitých obrázků (ruby:2.3 и nginx:alpine), což jsou kontrolní součty jejich obsahu;
  • všechny týmy (RUN, CMD a tak dále.);
  • SHA256 ze souborů, které byly přidány.

... a z takového souboru vezměte kontrolní součet (opět SHA256). Tento podpis vše, co definuje obsah obrazu Docker.

werf - náš nástroj pro CI / CD v Kubernetes (přehled a videoreportáž)

Vraťme se ke schématu a místo commitů budeme používat takové podpisy, tj. označte obrázky podpisy.

werf - náš nástroj pro CI / CD v Kubernetes (přehled a videoreportáž)

Nyní, když je například nutné sloučit změny z vydání do hlavního serveru, můžeme provést skutečné začlenění: bude mít jiný identifikátor, ale stejný podpis. Se stejným identifikátorem uvedeme obrázek do výroby.

Nevýhodou je, že nyní nebude možné určit, jaký druh odevzdání byl odeslán do produkce - kontrolní součty fungují pouze jedním směrem. Tento problém řeší další vrstva s metadaty – více vám řeknu později.

Označování ve werf

Ve werf jsme šli ještě dále a připravujeme distribuované sestavení s mezipamětí, která není uložena na jednom počítači... Takže vytváříme dva typy obrazů Docker, říkáme jim fáze и obraz.

Werf Git repozitář ukládá instrukce specifické pro sestavení, které popisují různé fáze sestavení (předInstalovat, instalovat, před nastavením, Nastavení). Shromažďujeme obraz první fáze s podpisem definovaným jako kontrolní součet prvních kroků. Poté přidáme zdrojový kód, pro nový obraz jeviště vypočítáme jeho kontrolní součet... Tyto operace se opakují pro všechny stupně, v důsledku čehož získáme sadu obrazů jeviště. Poté vytvoříme výsledný obrázek, který obsahuje i metadata o jeho původu. A tento obrázek označíme různými způsoby (podrobnosti později).

werf - náš nástroj pro CI / CD v Kubernetes (přehled a videoreportáž)

Předpokládejme, že se poté objeví nové potvrzení, ve kterém byl změněn pouze kód aplikace. Co se bude dít? Pro změny kódu bude vytvořen patch a bude připraven nový obrázek scény. Jeho podpis bude určen jako kontrolní součet obrazu staré fáze a nového patche. Z tohoto obrázku se vytvoří nový konečný obrázek. Podobné chování nastane se změnami v dalších fázích.

Obrazy na scéně jsou tedy mezipamětí, kterou lze distribuovaně ukládat a z ní již vytvořené obrazy se nahrávají do registru Docker.

werf - náš nástroj pro CI / CD v Kubernetes (přehled a videoreportáž)

Čištění registru

Nemluvíme o mazání vrstev, které zůstaly viset po smazaných značkách – to je standardní funkce samotného registru Docker. Bavíme se o situaci, kdy se nahromadí spousta Docker tagů a my pochopíme, že některé už nepotřebujeme, ale zabírají místo (a/nebo za to platíme).

Jaké jsou strategie čištění?

  1. Nemůžeš prostě nic dělat nečistěte. Někdy je opravdu snazší trochu zaplatit za místo navíc, než rozmotávat obrovskou změť visaček. Ale to funguje jen do určitého bodu.
  2. Úplný reset. Pokud smažete všechny snímky a obnovíte pouze ty aktuální v systému CI, může nastat problém. Pokud se kontejner v produkci restartuje, načte se pro něj nový obrázek – takový, který ještě nikdo netestoval. To zabíjí myšlenku neměnné infrastruktury.
  3. Modrá zelená. Jeden registr začal přetékat – obrázky nahráváme do druhého. Stejný problém jako v předchozí metodě: v jakém okamžiku můžete vymazat registr, který začal přetékat?
  4. Časem. Smazat všechny obrázky starší než 1 měsíc? Určitě se ale najde služba, která se měsíc neaktualizovala...
  5. Ručně určit, co již lze smazat.

Existují dvě skutečně schůdné možnosti: nečistit nebo kombinace modro-zelená + ručně. V druhém případě se bavíme o následujícím: když pochopíte, že je čas vyčistit registr, vytvoříte nový a v průběhu třeba měsíce do něj přidáte všechny nové obrázky. A po měsíci se podívejte, které moduly v Kubernetes stále používají starý registr, a přeneste je také do nového registru.

K čemu jsme dospěli werf? Sbíráme:

  1. Git head: všechny značky, všechny větve - za předpokladu, že potřebujeme vše, co je v Gitu označeno v obrázcích (a pokud ne, musíme to v samotném Gitu smazat);
  2. všechny pody, které jsou aktuálně čerpány do Kubernetes;
  3. staré ReplicaSets (co bylo nedávno vydáno) a také plánujeme skenovat vydání Helm a vybrat tam nejnovější obrázky.

... a z této sady udělejte whitelist - seznam obrázků, které nebudeme mazat. Vyčistíme vše ostatní, poté najdeme osiřelé scénické obrazy a také je smažeme.

Fáze nasazení

Spolehlivá deklarativnost

Prvním bodem, na který bych chtěl v nasazení upozornit, je zavedení aktualizované konfigurace prostředků, deklarativně deklarované. Původní dokument YAML popisující prostředky Kubernetes se vždy velmi liší od výsledku, který skutečně běží v clusteru. Protože Kubernetes přidává do konfigurace:

  1. identifikátory;
  2. servisní informace;
  3. mnoho výchozích hodnot;
  4. sekce s aktuálním stavem;
  5. změny provedené v rámci přijímacího webhooku;
  6. výsledek práce různých kontrolérů (a plánovače).

Proto, když se objeví nová konfigurace zdroje (nový), nemůžeme s ním jen tak vzít a přepsat aktuální, „živou“ konfiguraci (žít). Abychom to mohli udělat, budeme muset porovnávat nový s poslední použitou konfigurací (naposledy použito) a převalte se žít obdržel patch.

Tento přístup se nazývá 2-cestné sloučení. Používá se například v Helmu.

Existuje také 3-cestné sloučení, která se liší tím, že:

  • srovnávání naposledy použito и nový, podíváme se na to, co bylo smazáno;
  • srovnávání nový и žít, podíváme se na to, co bylo přidáno nebo změněno;
  • je aplikována souhrnná náplast žít.

S Helm nasazujeme více než 1000 aplikací, takže vlastně žijeme s dvoucestným slučováním. Má však řadu problémů, které jsme vyřešili našimi záplatami, které Helmu pomáhají normálně fungovat.

Skutečný stav zavedení

Poté, co náš systém CI vygeneruje novou konfiguraci pro Kubernetes na základě další události, odešle ji k použití (aplikovat) do clusteru - pomocí Helm popř kubectl apply. Dále dochází k již popsanému N-way merge, na který Kubernetes API reaguje souhlasně CI systému, a to jeho uživateli.

werf - náš nástroj pro CI / CD v Kubernetes (přehled a videoreportáž)

Je tu však obrovský problém: přece úspěšná aplikace neznamená úspěšné zavedení. Pokud Kubernetes pochopí, jaké změny je třeba použít, a použije je, stále nevíme, jaký bude výsledek. Například aktualizace a restartování modulů ve frontendu může být úspěšné, ale ne v backendu a získáme různé verze obrazů běžících aplikací.

Aby bylo vše správně provedeno, toto schéma vyžaduje další odkaz - speciální sledovač, který bude přijímat stavové informace z Kubernetes API a přenášet je pro další analýzu skutečného stavu věcí. Vytvořili jsme knihovnu Open Source v Go - krychlový pes (viz jeho oznámení zde), který tento problém řeší a je zabudován do werf.

Chování tohoto sledovače na úrovni werf se konfiguruje pomocí anotací, které jsou umístěny na Deployments nebo StatefulSets. Hlavní anotace - fail-mode - rozumí následujícím významům:

  • IgnoreAndContinueDeployProcess — ignorujeme problémy se zavedením této komponenty a pokračujeme v nasazování;
  • FailWholeDeployProcessImmediately — chyba v této komponentě zastaví proces nasazení;
  • HopeUntilEndOfDeployProcess — doufáme, že tato součást bude fungovat do konce nasazení.

Například tato kombinace zdrojů a hodnot anotací fail-mode:

werf - náš nástroj pro CI / CD v Kubernetes (přehled a videoreportáž)

Při prvním nasazení nemusí být databáze (MongoDB) ještě připravena – nasazení se nezdaří. Ale můžete počkat na okamžik, kdy se spustí, a nasazení bude stále probíhat.

Existují dvě další anotace pro kubedog ve werf:

  • failures-allowed-per-replica — počet povolených pádů pro každou repliku;
  • show-logs-until — reguluje okamžik, do kterého werf ukazuje (ve stdout) klády ze všech vyvalených lusků. Výchozí nastavení je PodIsReady (abychom ignorovali zprávy, které pravděpodobně nechceme, když provoz začne přicházet do podu), ale hodnoty jsou také platné: ControllerIsReady и EndOfDeploy.

Co ještě chceme od nasazení?

Kromě dvou již popsaných bodů bychom chtěli:

  • vidět protokoly - a pouze ty nezbytné, a ne vše v řadě;
  • dráha pokrok, protože pokud úloha visí „tiše“ několik minut, je důležité pochopit, co se tam děje;
  • иметь automatické vrácení zpět v případě, že se něco pokazilo (a proto je důležité znát skutečný stav nasazení). Zavádění musí být atomické: buď projde až do konce, nebo se vše vrátí do předchozího stavu.

Výsledky

Pro nás jako společnost stačí k implementaci všech popsaných nuancí v různých fázích dodávky (sestavení, publikování, nasazení) systém CI a utility werf.

Místo závěru:

werf - náš nástroj pro CI / CD v Kubernetes (přehled a videoreportáž)

S pomocí werf jsme udělali dobrý pokrok v řešení velkého množství problémů pro inženýry DevOps a byli bychom rádi, kdyby širší komunita tuto utilitu alespoň vyzkoušela v akci. Společně bude snazší dosáhnout dobrého výsledku.

Videa a diapozitivy

Video z představení (~47 minut):

Prezentace zprávy:

PS

Další zprávy o Kubernetes na našem blogu:

Zdroj: www.habr.com

Přidat komentář