werf - eszközünk CI / CD-hez Kubernetesben (áttekintés és videóriport)

május 27-én a fesztivál részeként megrendezett DevOpsConf 2019 konferencia nagytermében RIT++ 2019, a „Folyamatos kézbesítés” rész részeként egy jelentést kaptunk „werf – eszközünk a Kubernetes CI/CD-hez”. Azokról szól problémák és kihívások, amelyekkel mindenki szembesül a Kubernetes rendszerbe helyezéskor, valamint olyan árnyalatokról, amelyek nem biztos, hogy azonnal észrevehetők. A lehetséges megoldásokat elemezve megmutatjuk, hogyan valósul meg ez egy nyílt forráskódú eszközben werf.

A bemutató óta a segédprogramunk (korábbi nevén dapp) történelmi mérföldkőhöz érkezett: 1000 csillag a GitHubon — Reméljük, hogy a felhasználók növekvő közössége sok DevOps mérnök életét megkönnyíti majd.

werf - eszközünk CI / CD-hez Kubernetesben (áttekintés és videóriport)

Szóval, mutassuk be videó a riportról (~47 perc, sokkal informatívabb, mint a cikk) és a fő kivonat belőle szöveges formában. Megy!

Kód kézbesítése a Kubernetesnek

A beszéd már nem a werf-ről fog szólni, hanem a Kubernetes CI/CD-ről, ami arra utal, hogy szoftverünk Docker konténerekbe van csomagolva. (Erről már beszéltem 2016-os jelentés), és a K8-asokat fogják használni a futtatáshoz a termelésben (erről bővebben itt 2017 év).

Hogyan néz ki a szállítás Kubernetesben?

  • Van egy Git-tárház a kóddal és az elkészítéséhez szükséges utasításokkal. Az alkalmazás egy Docker-képbe van beépítve, és közzéteszik a Docker Registry-ben.
  • Ugyanez a lerakat az alkalmazás üzembe helyezésére és futtatására vonatkozó utasításokat is tartalmaz. A telepítési szakaszban ezeket az utasításokat elküldik a Kubernetesnek, amely megkapja a kívánt lemezképet a rendszerleíró adatbázisból, és elindítja azt.
  • Ráadásul általában vannak tesztek. Ezek egy része megtehető egy kép közzétételekor. Ezenkívül (ugyanazokat az utasításokat követve) telepítheti az alkalmazás egy példányát (egy külön K8s névtérben vagy egy külön fürtben), és ott teszteket futtathat.
  • Végül szükség van egy CI-rendszerre, amely fogadja az eseményeket a Gittől (vagy a gombkattintásoktól), és meghívja az összes kijelölt szakaszt: build, publish, deploy, test.

werf - eszközünk CI / CD-hez Kubernetesben (áttekintés és videóriport)

Itt van néhány fontos megjegyzés:

  1. Mert változhatatlan infrastruktúránk van (változhatatlan infrastruktúra), az alkalmazás képét, amelyet minden szakaszban (stádium, gyártás stb.) használnak, kell lennie egynek. Erről részletesebben és példákkal beszéltem. itt.
  2. Mert mi az infrastruktúrát mint kódszemléletet követjük (IaC), az alkalmazás kódja, az összeszerelési és elindítási útmutató legyen pontosan egy tárolóban. Erről további információért lásd ugyanaz a jelentés.
  3. Szállítási lánc (szállítás) általában így látjuk: az alkalmazást összeállították, tesztelték, megjelentették (kiadási szakasz) és ennyi - a szállítás megtörtént. De a valóságban a felhasználó azt kapja, amit kiadott, nincs majd amikor szállítottad a gyártásba, és amikor ő mehetett oda, és ez a produkció működött. Szóval szerintem a szállítási lánc véget ér csak a működési szakaszban (fuss), pontosabban még abban a pillanatban, amikor a kódot eltávolították a termelésből (újra cserélve).

Térjünk vissza a fenti szállítási sémához Kubernetesben: nem csak mi találtuk ki, hanem szó szerint mindenki, aki ezzel a problémával foglalkozott. Valójában ezt a mintát GitOpsnak hívják (a kifejezésről és a mögötte rejlő ötletekről bővebben olvashat itt). Nézzük a séma szakaszait.

Építési színpad

Úgy tűnik, hogy 2019-ben beszélhetünk Docker-képek létrehozásáról, amikor mindenki tudja, hogyan kell Docker-fájlokat írni és futtatni docker build?.. Itt vannak azok az árnyalatok, amelyekre szeretnék figyelni:

  1. Kép súlya számít, ezért használd többlépcsőshogy a képen csak a művelethez valóban szükséges alkalmazás maradjon.
  2. Rétegek száma láncok kombinálásával minimálisra kell csökkenteni RUN-parancsol a jelentés szerint.
  3. Ez azonban problémákat okoz hibakeresés, mert a szerelvény összeomlásakor meg kell találni a megfelelő parancsot a problémát okozó láncból.
  4. Összeszerelési sebesség fontos, mert gyorsan szeretnénk végrehajtani a változtatásokat, és látni szeretnénk az eredményeket. Például nem akarja újraépíteni a függőségeket a nyelvi könyvtárakban minden alkalommal, amikor egy alkalmazást készít.
  5. Gyakran egy Git-tárból, amire szüksége van sok kép, amelyet Docker-fájlok halmazával (vagy egy fájlban elnevezett szakaszokkal) és egy Bash-szkripttel lehet megoldani a szekvenciális összeállításukkal.

Ez csak a jéghegy csúcsa volt, amellyel mindenki szembesül. De vannak más problémák is, különösen:

  1. Gyakran az összeszerelési szakaszban szükségünk van valamire hegy (például gyorsítótárazza az apt-hoz hasonló parancs eredményét egy harmadik féltől származó könyvtárban).
  2. Mi akarunk Ansible ahelyett, hogy héjban írna.
  3. Mi akarunk épít Docker nélkül (miért kell egy további virtuális gép, amiben ehhez mindent be kell állítani, amikor már van egy Kubernetes-fürt, amelyben konténereket tudunk futtatni?).
  4. Párhuzamos összeszerelés, amely többféleképpen is értelmezhető: különböző parancsok a Dockerfile-ból (ha többlépcsős használat van), több commit ugyanannak a tárolónak, több Docker-fájl.
  5. Elosztott összeszerelés: Olyan dolgokat akarunk tokba gyűjteni, amik "múlékonyak", mert a gyorsítótáruk eltűnik, ami azt jelenti, hogy valahol külön kell tárolni.
  6. Végül a vágyak csúcsát neveztem meg automagic: Ideális lenne bemenni a tárolóba, begépelni egy parancsot, és kész képet kapni, összerakva, hogy megértsük, hogyan és mit kell helyesen csinálni. Én azonban személy szerint nem vagyok benne biztos, hogy minden árnyalatot előre lehet látni így.

És itt vannak a projektek:

  • moby/buildkit — a Docker Inc építője (már integrálva a Docker jelenlegi verzióiba), amely megpróbálja megoldani ezeket a problémákat;
  • kaniko — egy építő a Google-tól, amely lehetővé teszi a Docker nélküli építkezést;
  • Buildpacks.io — a CNCF kísérlete automatikus varázslat létrehozására, és különösen érdekes megoldásra rétegek újrabázisával;
  • és egy csomó más segédprogram, mint pl buildah, eredeti szerszámok/img...

...és nézze meg, hány csillaguk van a GitHubon. Azaz egyrészt docker build létezik és tehet valamit, de a valóságban a probléma nincs teljesen megoldva - ennek bizonyítéka az alternatív kollektorok párhuzamos fejlesztése, amelyek mindegyike megoldja a problémák egy részét.

Összeszerelés werfben

Tehát meg kell tennünk werf (korábban híres mint a dapp) — Nyílt forráskódú segédprogram a Flant cégtől, amelyet évek óta készítünk. Az egész 5 éve a Dockerfiles összeállítását optimalizáló Bash szkriptekkel kezdődött, az elmúlt 3 évben pedig egy projekt keretében teljes értékű fejlesztés zajlott, saját Git tárolóval. (először Rubyban, majd átírta to Go, és egyben átnevezték). Milyen összeszerelési problémákat oldanak meg a werf-ben?

werf - eszközünk CI / CD-hez Kubernetesben (áttekintés és videóriport)

A kékkel színezett problémák már megvalósultak, a párhuzamos építés ugyanazon a hoston belül történt, a sárgával kiemelt problémák pedig a tervek szerint nyár végére elkészülnek.

Nyilvántartásban való közzététel szakasza (közzététel)

Tárcsáztunk docker push... - mi okozhat nehézséget egy kép feltöltésében a registry-be? És akkor felmerül a kérdés: "Milyen címkét tegyek a képre?" Ez azért merül fel, mert mi Gitflow (vagy más Git-stratégia) és a Kubernetes, és az iparág igyekszik biztosítani, hogy ami a Kubernetesben történik, az kövesse azt, ami a Gitben történik. Hiszen Git az egyetlen igazságforrásunk.

Mi olyan nehéz ebben? Biztosítsa a reprodukálhatóságot: a Gitben történt elköteleződésből, ami változhatatlan természetű (változhatatlan), egy Docker-képre, amelyet változatlannak kell tartani.

Nekünk is fontos meghatározni az eredetet, mert szeretnénk megérteni, hogy a Kubernetesben futó alkalmazás melyik commit-ból épült fel (akkor tudunk diffeket és hasonlókat csinálni).

Címkézési stratégiák

Az első egyszerű git tag. Van egy rendszerleíró adatbázisunk a következővel címkézett képpel 1.0. A Kubernetesnek van színpada és produkciója, ahová ez a kép feltöltődik. A Gitben commit-ot hajtunk végre, és egy bizonyos ponton címkézzük 2.0. A repository utasításai szerint összegyűjtjük, és a címkével együtt elhelyezzük a registry-ben 2.0. Kigurítjuk a színpadra, és ha minden rendben, akkor a gyártásba.

werf - eszközünk CI / CD-hez Kubernetesben (áttekintés és videóriport)

Ezzel a megközelítéssel az a probléma, hogy először beállítottuk a címkét, majd csak azután teszteltük és dobtuk ki. Miért? Először is egyszerűen logikátlan: olyan szoftververziót adunk ki, amelyet még nem is teszteltünk (nem tehetünk másként, mert az ellenőrzéshez címkét kell elhelyezni). Másodszor, ez az útvonal nem kompatibilis a Gitflow-val.

A második lehetőség - git commit + tag. A fő ágnak van egy címkéje 1.0; ehhez a rendszerleíró adatbázisban – éles rendszerbe telepített kép. Ezenkívül a Kubernetes-fürt rendelkezik előnézeti és állomási kontúrokkal. Ezután követjük a Gitflow-t: a fejlesztés fő ágában (develop) új funkciókat készítünk, ami az azonosítóval történő véglegesítést eredményezi #c1. Összegyűjtjük és közzétesszük a rendszerleíró adatbázisban ezzel az azonosítóval (#c1). Ugyanazzal az azonosítóval tesszük közzé az előnézetet. Ugyanezt tesszük a kötelezettségvállalásokkal is #c2 и #c3.

Amikor rájöttünk, hogy van elég funkció, elkezdünk mindent stabilizálni. Hozzon létre egy ágat a Gitben release_1.1 (az alapon #c3 A develop). Ezt a kiadást nem kell begyűjteni, mert... ez megtörtént az előző lépésben. Ezért egyszerűen kiteregethetjük a színpadra. Javítjuk a hibákat #c4 és hasonlóképpen gördítsd ki a színpadra. Ezzel párhuzamosan a fejlesztés is folyamatban van develop, ahol a változások időszakosan származnak release_1.1. Egy ponton összeállítunk és feltöltünk egy commit-ot, amivel elégedettek vagyunk (#c25).

Ezután egyesítjük (gyors előretekeréssel) a kioldó ágat (release_1.1) a mesterben. Az új verziót tartalmazó címkét helyeztünk el erre a véglegesítésre (1.1). De ez a kép már össze van gyűjtve a rendszerleíró adatbázisban, így annak érdekében, hogy ne gyűjtsük újra, egyszerűen hozzáadunk egy második címkét a meglévő képhez (most már vannak címkék a rendszerleíró adatbázisban #c25 и 1.1). Ezt követően gyártásba gurítjuk.

Van egy hátránya, hogy csak egy kép kerül fel a színpadra (#c25), és a gyártásban ez valahogy más (1.1), de tudjuk, hogy „fizikailag” ezek ugyanazok a képek a rendszerleíró adatbázisból.

werf - eszközünk CI / CD-hez Kubernetesben (áttekintés és videóriport)

Az igazi hátrány az, hogy nincs támogatás a merge commitokhoz, gyorsan előre kell lépni.

Mehetünk tovább, és csinálhatunk egy trükköt... Nézzünk egy példát egy egyszerű Dockerfile-ra:

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

Építsünk belőle egy fájlt a következő elv szerint:

  • SHA256 a felhasznált képek azonosítóiból (ruby:2.3 и nginx:alpine), amelyek tartalmuk ellenőrző összegei;
  • minden csapat (RUN, CMD stb.);
  • SHA256 a hozzáadott fájlokból.

... és vegye ki az ellenőrző összeget (ismét SHA256) egy ilyen fájlból. Ez aláírás mindent, ami meghatározza a Docker kép tartalmát.

werf - eszközünk CI / CD-hez Kubernetesben (áttekintés és videóriport)

Térjünk vissza a diagramhoz és commit helyett ilyen aláírásokat fogunk használni, azaz jelölje meg a képeket aláírásokkal.

werf - eszközünk CI / CD-hez Kubernetesben (áttekintés és videóriport)

Most, amikor például egy kiadásról a mesterre kell egyesíteni a változtatásokat, akkor valódi összevonási commitot hajthatunk végre: más lesz az azonosítója, de ugyanaz az aláírás. Ugyanazzal az azonosítóval a képet gyártásba tesszük.

Hátránya, hogy most nem lehet majd meghatározni, hogy milyen commit került a gyártásba – az ellenőrző összegek csak egy irányban működnek. Ezt a problémát egy további metaadatokat tartalmazó réteg oldja meg – majd később elmondom.

Címkézés a werf-ben

A werf-ben még tovább mentünk, és egy elosztott buildet készülünk csinálni olyan gyorsítótárral, ami nem egy gépen van tárolva... Tehát kétféle Docker image-et építünk, nevezzük őket. szakasz и kép.

A werf Git lerakata build-specifikus utasításokat tárol, amelyek leírják a build különböző szakaszait (Telepítés előtt, telepíteni, a beállítás előtt, felépítés). Az első szakaszképet az első lépések ellenőrző összegeként meghatározott aláírással gyűjtjük össze. Ezután adjuk hozzá a forráskódot, az új színpadképhez kiszámoljuk az ellenőrző összegét... Ezek a műveletek minden szakaszra megismétlődnek, aminek eredményeként színpadképek halmazát kapjuk. Ezután elkészítjük a végső képet, amely metaadatokat is tartalmaz az eredetéről. És ezt a képet különböző módon címkézzük (részletek később).

werf - eszközünk CI / CD-hez Kubernetesben (áttekintés és videóriport)

Tegyük fel, hogy ezután egy új véglegesítés jelenik meg, amelyben csak az alkalmazás kódja módosult. Mi fog történni? A kódmódosításokhoz egy patch jön létre, és egy új színpadkép készül. Aláírása a régi színpadkép és az új folt ellenőrző összege lesz. Ebből a képből egy új végleges kép alakul ki. Hasonló viselkedés lép fel más szakaszok változásaival is.

Így a színpadképek egy elosztottan tárolható gyorsítótár, amelyből a már létrehozott képek feltöltődnek a Docker Registry-be.

werf - eszközünk CI / CD-hez Kubernetesben (áttekintés és videóriport)

A rendszerleíró adatbázis tisztítása

Nem a törölt címkék után függő rétegek törléséről beszélünk – ez magának a Docker Registry-nek az alapfunkciója. Olyan helyzetről beszélünk, amikor sok Docker címke halmozódik fel, és megértjük, hogy egy részükre már nincs szükségünk, de helyet foglalnak (és/vagy fizetünk érte).

Mik a tisztítási stratégiák?

  1. Egyszerűen nem tehetsz semmit ne takaríts. Néha tényleg könnyebb fizetni egy keveset a plusz helyért, mint egy hatalmas cédula-gubancot kibogozni. De ez csak egy bizonyos pontig működik.
  2. Teljes visszaállítás. Ha törli az összes képet, és csak a jelenlegieket építi újra a CI-rendszerben, probléma léphet fel. Ha a tárolót újraindítják az éles üzemben, akkor egy új kép töltődik be hozzá – olyat, amelyet még senki nem tesztelt. Ez megöli a megváltoztathatatlan infrastruktúra gondolatát.
  3. Kékeszöld. Az egyik rendszerleíró adatbázis túlcsordulni kezdett - képeket töltünk fel egy másikba. Ugyanaz a probléma, mint az előző módszernél: mikor lehet törölni a rendszerleíró adatbázist, amely túlcsordulni kezdett?
  4. Idővel. Törli az összes 1 hónapnál régebbi képet? De biztosan lesz olyan szolgáltatás, amit egy hónapja nem frissítettek...
  5. manuálisan határozza meg, hogy mi törölhető már.

Két igazán életképes lehetőség van: ne tisztítsa, vagy a kék-zöld + manuális kombinációja. Ez utóbbi esetben a következőkről beszélünk: amikor megérti, hogy ideje megtisztítani a rendszerleíró adatbázist, hozzon létre egy újat, és például egy hónap alatt hozzáadja az összes új képet. Egy hónap múlva pedig nézze meg, hogy a Kubernetes mely podjai használják még mindig a régi beállításjegyzéket, és vigye át őket is az új rendszerleíró adatbázisba.

Mire jutottunk werf? Gyűjtjük:

  1. Git fej: minden címke, minden ág - feltételezve, hogy mindenre szükségünk van, ami a Gitben meg van jelölve a képeken (és ha nem, akkor magában a Gitben kell törölnünk);
  2. az összes jelenleg a Kubernetesbe szivattyúzott hüvely;
  3. régi ReplicaSets (ami a közelmúltban jelent meg), és tervezzük a Helm kiadások beszkennelését is, és ott kiválasztjuk a legújabb képeket.

... és készítsen egy fehérlistát ebből a készletből - egy listát azokról a képekről, amelyeket nem fogunk törölni. Minden mást kitisztítunk, utána árva színpadképeket találunk és azokat is töröljük.

Telepítési szakasz

Megbízható deklarativitás

Az első pont, amelyre a telepítés során fel szeretném hívni a figyelmet, a frissített erőforrás-konfiguráció deklaratív módon történő bevezetése. A Kubernetes-erőforrásokat leíró eredeti YAML-dokumentum mindig nagyon különbözik a fürtben ténylegesen futó eredménytől. Mivel a Kubernetes hozzáadja a konfigurációt:

  1. azonosítók;
  2. szolgáltatási információk;
  3. sok alapértelmezett érték;
  4. szakasz aktuális állapottal;
  5. a felvételi webhook részeként végrehajtott változtatások;
  6. a különböző vezérlők (és az ütemező) munkájának eredménye.

Ezért amikor egy új erőforrás-konfiguráció jelenik meg (új), nem tudjuk csak úgy átvenni és felülírni vele az aktuális, „élő” konfigurációt (él). Ehhez össze kell hasonlítanunk új az utoljára alkalmazott konfigurációval (utoljára alkalmazva) és görgessen rá él kapott tapaszt.

Ezt a megközelítést az ún 2 irányú összevonás. Használják például a Helmben.

Vannak még 3 irányú összevonás, ami abban különbözik:

  • összehasonlítása utoljára alkalmazva и új, megnézzük, mit töröltek;
  • összehasonlítása új и él, megnézzük, mi lett hozzáadva vagy változtatva;
  • az összesített tapasz rá van helyezve él.

Több mint 1000 alkalmazást telepítünk a Helmmel, így valójában kétirányú egyesítéssel élünk. Azonban számos problémája van, amelyeket a javításainkkal megoldottunk, amelyek segítik a Helmet a normális működésben.

Valódi bevezetési állapot

Miután CI-rendszerünk a következő esemény alapján új konfigurációt generál a Kubernetes számára, azt továbbítja használatra (alkalmaz) fürtbe - Helm ill kubectl apply. Ezután megtörténik a már leírt N-irányú összevonás, amelyre a Kubernetes API helyeslően válaszol a CI rendszernek, az pedig a felhasználónak.

werf - eszközünk CI / CD-hez Kubernetesben (áttekintés és videóriport)

Van azonban egy óriási probléma: végül is a sikeres alkalmazás nem jelenti a sikeres bevezetést. Ha a Kubernetes megérti, hogy milyen változtatásokat kell alkalmazni, és alkalmazza azokat, még mindig nem tudjuk, mi lesz az eredmény. Például a podok frissítése és újraindítása a frontendben sikeres lehet, de a háttérben nem, és a futó alkalmazásképek különböző verzióit kapjuk meg.

Minden helyes végrehajtásához ehhez a sémához további hivatkozásra van szükség - egy speciális nyomkövetőre, amely állapotinformációkat kap a Kubernetes API-tól, és továbbítja azt a dolgok valós állapotának további elemzéséhez. Nyílt forráskódú könyvtárat hoztunk létre a Go-ban - kockakutya (lásd a közleményét itt), amely megoldja ezt a problémát, és be van építve a werf-be.

Ennek a nyomkövetőnek a werf-szintű viselkedése a Deployments vagy StatefulSets-ben elhelyezett megjegyzésekkel van konfigurálva. Fő annotáció - fail-mode - érti a következő jelentéseket:

  • IgnoreAndContinueDeployProcess — figyelmen kívül hagyjuk az komponens bevezetésével kapcsolatos problémákat, és folytatjuk a telepítést;
  • FailWholeDeployProcessImmediately — az összetevő hibája leállítja a telepítési folyamatot;
  • HopeUntilEndOfDeployProcess — reméljük, hogy ez az összetevő a telepítés végére működni fog.

Például az erőforrások és a megjegyzésértékek ezen kombinációja fail-mode:

werf - eszközünk CI / CD-hez Kubernetesben (áttekintés és videóriport)

Az első üzembe helyezéskor előfordulhat, hogy az adatbázis (MongoDB) még nem áll készen – a telepítések meghiúsulnak. De meg lehet várni a pillanatot, amíg elindul, és a telepítés továbbra is megtörténik.

Van még két megjegyzés a kubedoghoz a werf-ben:

  • failures-allowed-per-replica — az egyes replikák megengedett esések száma;
  • show-logs-until — szabályozza azt a pillanatot, ameddig a werf (stdout) naplókat jelenít meg az összes kigöngyölt tokból. Az alapértelmezett PodIsReady (hogy figyelmen kívül hagyjuk azokat az üzeneteket, amelyeket valószínűleg nem akarunk, amikor a forgalom megindul a podba), de az értékek is érvényesek: ControllerIsReady и EndOfDeploy.

Mit akarunk még a telepítéstől?

A már leírt két ponton kívül szeretnénk még:

  • látni rönkök - és csak a szükségeseket, és nem mindent egymás után;
  • vágány haladás, mert ha a munka több percig „némán” lóg, fontos megérteni, mi történik ott;
  • van automatikus visszaállítás arra az esetre, ha valami rosszul sülne el (és ezért kritikus fontosságú a telepítés valós állapotának ismerete). A kiterjesztésnek atomosnak kell lennie: vagy végigmegy a végéig, vagy minden visszaáll a korábbi állapotába.

Eredményei

Nekünk, mint cégnek az összes leírt árnyalat megvalósításához a szállítás különböző szakaszaiban (építés, közzététel, telepítés) elegendő egy CI rendszer és segédprogram. werf.

Következtetés helyett:

werf - eszközünk CI / CD-hez Kubernetesben (áttekintés és videóriport)

A werf segítségével jó előrehaladást értünk el a DevOps mérnökök számos problémájának megoldásában, és örülnénk, ha a szélesebb közösség legalább gyakorlatban kipróbálná ezt a segédprogramot. Könnyebb lesz közösen jó eredményt elérni.

Videók és diák

Videó az előadásról (~47 perc):

A jelentés bemutatása:

PS

További beszámolók a Kubernetesről a blogunkon:

Forrás: will.com

Hozzászólás