werf - mūsų CI / CD įrankis Kubernetes (apžvalga ir vaizdo reportažas)

Gegužės 27 d., festivalio metu vykusios konferencijos „DevOpsConf 2019“ pagrindinėje salėje RIT++ 2019 m, kaip dalis "Nuolatinis pristatymas", buvo pateikta ataskaita "werf - mūsų įrankis CI / CD Kubernetes". Kalbama apie tuos problemos ir iššūkiai, su kuriais susiduria visi diegdami Kubernetes, taip pat apie niuansus, kurie gali būti ne iš karto pastebimi. Analizuodami galimus sprendimus, parodome, kaip tai įgyvendinama atvirojo kodo įrankyje werf.

Nuo pristatymo mūsų paslaugų programa (anksčiau vadinta dapp) pasiekė istorinį etapą 1000 žvaigždučių „GitHub“. — Tikimės, kad auganti vartotojų bendruomenė palengvins daugelio „DevOps“ inžinierių gyvenimą.

werf - mūsų CI / CD įrankis Kubernetes (apžvalga ir vaizdo reportažas)

Taigi, mes pristatome reportažo vaizdo įrašas (~47 min., daug informatyvesnis nei straipsnis) ir pagrindinė ištrauka iš jo tekstine forma. Pirmyn!

Kodo pristatymas į Kubernetes

Kalba nebebus apie werf, o apie CI/CD Kubernetes, o tai reiškia, kad mūsų programinė įranga supakuota į Docker konteinerius (Apie tai kalbėjau 2016 metų ataskaita), o K8 bus naudojami jai paleisti gamyboje (daugiau apie tai žr 2017 metų).

Kaip atrodo pristatymas Kubernetes?

  • Yra „Git“ saugykla su kodu ir instrukcijomis, kaip ją sukurti. Programa yra integruota į „Docker“ vaizdą ir paskelbta „Docker“ registre.
  • Toje pačioje saugykloje taip pat yra instrukcijos, kaip įdiegti ir paleisti programą. Diegimo etape šios instrukcijos siunčiamos Kubernetes, kuri gauna norimą vaizdą iš registro ir jį paleidžia.
  • Be to, paprastai yra testai. Kai kuriuos iš jų galima padaryti publikuojant vaizdą. Taip pat (vadovaujantis tomis pačiomis instrukcijomis) galite įdiegti programos kopiją (atskiroje K8s vardų erdvėje arba atskiroje grupėje) ir ten atlikti testus.
  • Galiausiai, jums reikia CI sistemos, kuri gauna įvykius iš Git (arba mygtukų paspaudimus) ir iškviečia visus nurodytus etapus: kūrimo, paskelbimo, diegimo, testavimo.

werf - mūsų CI / CD įrankis Kubernetes (apžvalga ir vaizdo reportažas)

Čia yra keletas svarbių pastabų:

  1. Nes turime nekintamą infrastruktūrą (nekintama infrastruktūra), programos vaizdas, kuris naudojamas visuose etapuose (statybos, gamybos ir kt.), turi būti vienas. Apie tai kalbėjau plačiau ir su pavyzdžiais. čia.
  2. Nes mes laikomės infrastruktūros kaip kodinio metodo (IaC), programos kodas, jo surinkimo ir paleidimo instrukcijos turėtų būti tiksliai vienoje saugykloje. Norėdami gauti daugiau informacijos apie tai, žr ta pati ataskaita.
  3. Pristatymo grandinė (pristatymas) dažniausiai matome taip: programa buvo surinkta, išbandyta, išleista (išleidimo etapas) ir viskas - pristatymas įvyko. Tačiau iš tikrųjų naudotojas gauna tai, ką išleidote, ne tada, kai pristatėte jį į gamybą, ir kai jis galėjo ten nuvykti, ir ši gamyba veikė. Taigi manau, kad pristatymo grandinė baigiasi tik eksploatacijos stadijoje (bėgti), o tiksliau net tuo momentu, kai kodas buvo pašalintas iš gamybos (pakeičiant jį nauju).

Grįžkime prie aukščiau pateiktos pristatymo schemos Kubernetes: ją sugalvojome ne tik mes, bet ir visi, kurie susidūrė su šia problema. Tiesą sakant, šis modelis dabar vadinamas „GitOps“. (daugiau apie terminą ir jo idėjas galite perskaityti čia). Pažvelkime į schemos etapus.

Statyti sceną

Atrodytų, kad galite kalbėti apie „Docker“ vaizdų kūrimą 2019 m., Kai visi žino, kaip rašyti „Dockerfiles“ ir paleisti docker build?.. Štai niuansai, į kuriuos norėčiau atkreipti dėmesį:

  1. Vaizdo svoris svarbu, todėl naudokite kelių pakopųvaizde palikti tik tą aplikaciją, kuri tikrai reikalinga operacijai.
  2. Sluoksnių skaičius turi būti sumažintas derinant grandines RUN-komanda pagal prasmę.
  3. Tačiau tai prideda problemų derinimas, nes kai mazgas sugenda, turite rasti tinkamą komandą iš grandinės, dėl kurios kilo problema.
  4. Surinkimo greitis svarbu, nes norime greitai įgyvendinti pakeitimus ir pamatyti rezultatus. Pavyzdžiui, nenorite atkurti priklausomybių kalbų bibliotekose kiekvieną kartą, kai kuriate programą.
  5. Dažnai reikia iš vienos Git saugyklos daug vaizdų, kurią galima išspręsti naudojant Dockerfiles rinkinį (arba pavadintus etapus viename faile) ir Bash scenarijų su jų nuosekliu surinkimu.

Tai buvo tik ledkalnio viršūnė, su kuria susiduria visi. Tačiau yra ir kitų problemų, visų pirma:

  1. Dažnai surinkimo etape mums kažko reikia kalnas (pavyzdžiui, talpykloje saugokite komandos, pvz., apt, rezultatą trečiosios šalies kataloge).
  2. Mes norime Galimas užuot rašę apvalkalu.
  3. Mes norime statyti be Docker (kodėl mums reikia papildomos virtualios mašinos, kurioje turime viską sukonfigūruoti, kai jau turime Kubernetes klasterį, kuriame galime paleisti konteinerius?).
  4. Lygiagretus surinkimas, kurią galima suprasti įvairiai: skirtingos komandos iš Dockerfile (jei naudojamas daugiapakopis), keli tos pačios saugyklos commitai, keli Dockerfile failai.
  5. Paskirstytas surinkimas: Mes norime rinkti daiktus į ankštis, kurie yra „trumpalaikiai“, nes jų talpykla dingsta, vadinasi, ją reikia saugoti kur nors atskirai.
  6. Galiausiai įvardijau norų viršūnę automatinis: Idealu būtų nueiti į saugyklą, įvesti kokią nors komandą ir gauti paruoštą vaizdą, surinktą su supratimu, kaip ir ką daryti teisingai. Tačiau aš asmeniškai nesu tikras, kad taip galima numatyti visus niuansus.

O štai projektai:

  • moby/buildkit — kūrėjas iš Docker Inc (jau integruotas į dabartines Docker versijas), kuris bando išspręsti visas šias problemas;
  • kaniko — „Google“ kūrėjas, leidžiantis kurti be „Docker“;
  • Buildpacks.io — CNCF bandymas sukurti automatinę magiją ir ypač įdomų sprendimą su sluoksnių perdėjimu;
  • ir daugybė kitų komunalinių paslaugų, pvz statyti, originalūs įrankiai/img...

...ir pažiūrėkite, kiek žvaigždžių jie turi „GitHub“. Tai yra, viena vertus, docker build egzistuoja ir gali ką nors padaryti, bet iš tikrųjų problema nėra visiškai išspręsta - to įrodymas yra lygiagretus alternatyvių kolektorių, kurių kiekvienas išsprendžia tam tikrą dalį problemų, kūrimas.

Surinkimas werf

Taigi mes turime werf (anksčiau garsus kaip dapp) — Atvirojo kodo programa iš bendrovės „Flant“, kurią gaminame daugelį metų. Viskas prasidėjo prieš 5 metus nuo Bash scenarijų, kurie optimizavo Dockerfiles surinkimą, o pastaruosius 3 metus buvo vykdoma visavertė plėtra pagal vieną projektą su savo Git saugykla. (iš pradžių Ruby, o paskui perrašytas eiti ir tuo pačiu pervadintas). Kokios surinkimo problemos išsprendžiamos werf?

werf - mūsų CI / CD įrankis Kubernetes (apžvalga ir vaizdo reportažas)

Mėlyna spalva nuspalvintos problemos jau įgyvendintos, lygiagretus kūrimas buvo atliktas tame pačiame pagrindiniame kompiuteryje, o geltonai paryškintas problemas planuojama užbaigti iki vasaros pabaigos.

Paskelbimo registre etapas (publikavimas)

Mes surinkome docker push... – kas gali būti sudėtinga įkeliant vaizdą į registrą? Ir tada kyla klausimas: „Kokią žymą turėčiau įdėti į vaizdą? Tai kyla dėl tos priežasties, kurią turime „Gitflow“ (ar kita Git strategija) ir Kubernetes, o pramonė stengiasi užtikrinti, kad tai, kas vyksta Kubernetes, atitiktų tai, kas vyksta Git. Juk Gitas yra vienintelis mūsų tiesos šaltinis.

Kas čia tokio sunkaus? Užtikrinti atkuriamumą: iš įsipareigojimo Gite, kuris yra nekintamas (nekeičiamas), į Docker vaizdą, kuris turėtų likti toks pat.

Mums tai taip pat svarbu nustatyti kilmę, nes norime suprasti, iš kurio commit buvo sukurta Kubernetes veikianti programa (tada galime daryti skirtumus ir panašius dalykus).

Žymėjimo strategijos

Pirmasis yra paprastas git žyma. Turime registrą su vaizdu, pažymėtu kaip 1.0. Kubernetes turi sceną ir gamybą, kur įkeliamas šis vaizdas. Git mes atliekame įsipareigojimus ir tam tikru momentu pažymime 2.0. Surenkame jį pagal instrukcijas iš saugyklos ir įdedame į registrą su žyma 2.0. Išvyniojame į sceną ir, jei viskas gerai, tada į gamybą.

werf - mūsų CI / CD įrankis Kubernetes (apžvalga ir vaizdo reportažas)

Šio metodo problema yra ta, kad pirmiausia įdėjome žymą, o tik tada išbandėme ir išleidome. Kodėl? Pirma, tai tiesiog nelogiška: išleidžiame programinės įrangos versiją, kurios dar net neišbandėme (kitaip negalime, nes norint patikrinti, reikia įdėti žymą). Antra, šis kelias nesuderinamas su „Gitflow“.

Antrasis variantas - git commit + žyma. Pagrindinė šaka turi žymą 1.0; už jį registre - vaizdas, įdiegtas gamyboje. Be to, „Kubernetes“ klasteris turi peržiūros ir sustojimo kontūrus. Toliau sekame „Gitflow“: pagrindinėje plėtros šakoje (develop) sukuriame naujas funkcijas, todėl įsipareigojame su identifikatoriumi #c1. Mes jį renkame ir skelbiame registre naudodami šį identifikatorių (#c1). Su tuo pačiu identifikatoriumi išleidžiame peržiūrėti. Tą patį darome ir su įsipareigojimais #c2 и #c3.

Kai supratome, kad funkcijų pakanka, pradedame viską stabilizuoti. Sukurkite filialą „Git“. release_1.1 (ant pagrindo #c3develop). Šio leidinio rinkti nereikia, nes... tai buvo padaryta ankstesniame žingsnyje. Todėl galime jį tiesiog iškelti į sceną. Taisome klaidas #c4 ir panašiai išvynioti į pastatymą. Tuo pačiu metu vyksta plėtra develop, kur periodiškai paimami pakeitimai release_1.1. Tam tikru momentu gauname sudarytą įsipareigojimą ir įkeltą į sceną, kuriuo esame patenkinti (#c25).

Tada sujungiame (su greitu sukimu į priekį) išleidimo šaką (release_1.1) meistre. Šiam įsipareigojimui įdėjome žymą su nauja versija (1.1). Bet šis vaizdas jau yra surinktas registre, todėl norėdami daugiau jo neberinkti, tiesiog pridedame antrą žymą prie esamo vaizdo (dabar jis turi žymas registre #c25 и 1.1). Po to iškeliame į gamybą.

Yra trūkumas, kad į sceną įkeliamas tik vienas vaizdas (#c25), o gamyboje tai kiek kitaip (1.1), tačiau žinome, kad „fiziškai“ tai yra tas pats vaizdas iš registro.

werf - mūsų CI / CD įrankis Kubernetes (apžvalga ir vaizdo reportažas)

Tikrasis trūkumas yra tas, kad nėra palaikymo sujungimo įsipareigojimams, turite atlikti greitį pirmyn.

Galime eiti toliau ir padaryti triuką... Pažvelkime į paprasto Dockerfile pavyzdį:

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

Sukurkime iš jo failą tokiu principu:

  • SHA256 iš naudojamų vaizdų identifikatorių (ruby:2.3 и nginx:alpine), kurios yra jų turinio kontrolinės sumos;
  • visos komandos (RUN, CMD ir taip toliau.);
  • SHA256 iš failų, kurie buvo pridėti.

... ir paimkite kontrolinę sumą (vėl SHA256) iš tokio failo. Tai parašas viskas, kas apibrėžia Docker vaizdo turinį.

werf - mūsų CI / CD įrankis Kubernetes (apžvalga ir vaizdo reportažas)

Grįžkime prie diagramos ir vietoj įsipareigojimų naudosime tokius parašus, t.y. pažymėti paveikslėlius parašais.

werf - mūsų CI / CD įrankis Kubernetes (apžvalga ir vaizdo reportažas)

Dabar, kai reikia, pavyzdžiui, sujungti pakeitimus iš leidimo į pagrindinį, galime atlikti tikrą sujungimo įsipareigojimą: jis turės kitą identifikatorių, bet tą patį parašą. Turėdami tą patį identifikatorių, vaizdą išleisime į gamybą.

Trūkumas yra tas, kad dabar nebus įmanoma nustatyti, koks įsipareigojimas buvo perkeltas į gamybą – kontrolinės sumos veikia tik viena kryptimi. Šią problemą išsprendžia papildomas sluoksnis su metaduomenimis – daugiau papasakosiu vėliau.

Žymėjimas werf

Werf mes nuėjome dar toliau ir ruošiamės daryti paskirstytą kūrimą su talpykla, kuri nėra saugoma viename kompiuteryje... Taigi, mes kuriame dviejų tipų Docker atvaizdus, ​​mes juos vadiname etapas и vaizdas.

„Werf Git“ saugykloje saugomos konkrečios versijos instrukcijos, apibūdinančios skirtingus kūrimo etapus (prieš įdiegiant, įrengti, prieš sąranką, nustatymas). Surenkame pirmojo etapo vaizdą su parašu, apibrėžtu kaip pirmųjų žingsnių kontrolinė suma. Tada pridedame pirminį kodą, naujam sceniniam vaizdui apskaičiuojame jo kontrolinę sumą... Šios operacijos kartojamos visoms stadijoms, ko pasekoje gauname sceninių vaizdų rinkinį. Tada sukuriame galutinį vaizdą, kuriame taip pat yra metaduomenys apie jo kilmę. Ir šį vaizdą pažymime įvairiais būdais (išsamiau).

werf - mūsų CI / CD įrankis Kubernetes (apžvalga ir vaizdo reportažas)

Tarkime, po to atsiranda naujas įsipareigojimas, kuriame buvo pakeistas tik programos kodas. Kas nutiks? Kodo pakeitimams bus sukurtas pataisas ir paruoštas naujas sceninis vaizdas. Jo parašas bus nustatytas kaip senojo sceninio įvaizdžio ir naujojo pataiso kontrolinė suma. Iš šio vaizdo bus suformuotas naujas galutinis vaizdas. Panašus elgesys pasireikš ir pasikeitus kituose etapuose.

Taigi scenos vaizdai yra talpykla, kurią galima saugoti paskirstytu būdu, o iš jos jau sukurti vaizdai įkeliami į Docker registrą.

werf - mūsų CI / CD įrankis Kubernetes (apžvalga ir vaizdo reportažas)

Registro valymas

Mes nekalbame apie sluoksnių, kurie liko kabėti po ištrintų žymų, ištrynimą – tai standartinė paties Docker registro funkcija. Kalbame apie situaciją, kai susikaupia daug Docker žymų ir suprantame, kad kai kurių mums nebereikia, bet jie užima vietą (ir/arba mes už tai mokame).

Kokios yra valymo strategijos?

  1. Galite tiesiog nieko nedaryti nevalyk. Kartais tikrai lengviau šiek tiek sumokėti už papildomą erdvę, nei išnarplioti didžiulį etikečių raizginį. Bet tai veikia tik iki tam tikro taško.
  2. Visiškas atstatymas. Jei ištrinsite visus vaizdus ir atkursite tik esamus CI sistemoje, gali kilti problemų. Jei konteineris bus paleistas iš naujo gamyboje, jam bus įkeltas naujas vaizdas – toks, kurio dar niekas neišbandė. Tai užmuša nekintamos infrastruktūros idėją.
  3. Mėlyna Žalia. Vienas registras pradėjo perpildyti – įkeliame vaizdus į kitą. Ta pati problema, kaip ir ankstesniame metode: kada galite išvalyti registrą, kuris pradėjo perpildyti?
  4. Laiku. Ištrinti visus senesnius nei 1 mėnesio vaizdus? Bet tikrai atsiras jau mėnesį neatnaujinta paslauga...
  5. Rankiniu būdu nustatyti, ką jau galima ištrinti.

Yra du tikrai perspektyvūs variantai: nevalyti arba melsvai žalios + derinys rankiniu būdu. Pastaruoju atveju kalbame apie tai: kai suprantate, kad laikas išvalyti registrą, sukuriate naują ir per, pavyzdžiui, mėnesį, įtraukite į jį visus naujus vaizdus. Ir po mėnesio pažiūrėkite, kurie „Kubernetes“ blokai vis dar naudoja senąjį registrą, ir perkelkite juos į naująjį registrą.

Prie ko priėjome werf? Mes renkame:

  1. Git head: visos žymos, visos šakos – darant prielaidą, kad mums reikia visko, kas pažymėta Git paveikslėliuose (o jei ne, tai reikia ištrinti pačiame Gite);
  2. visos ankštys, kurios šiuo metu išpumpuojamos į Kubernetes;
  3. senų ReplicaSets (kas buvo neseniai išleista), taip pat planuojame nuskaityti Helm leidimus ir ten pasirinkti naujausius vaizdus.

... ir sudaryti baltąjį sąrašą iš šio rinkinio – vaizdų, kurių neištrinsime, sąrašą. Išvalome visa kita, po to randame našlaičių scenos vaizdus ir juos taip pat ištriname.

Diegimo etapas

Patikimas deklaratyvumas

Pirmas dalykas, į kurį norėčiau atkreipti dėmesį diegiant, yra atnaujintos išteklių konfigūracijos įdiegimas, paskelbtas deklaratyviai. Originalus YAML dokumentas, aprašantis Kubernetes išteklius, visada labai skiriasi nuo rezultato, kuris iš tikrųjų veikia klasteryje. Kadangi „Kubernetes“ prideda prie konfigūracijos:

  1. identifikatoriai;
  2. paslaugų informacija;
  3. daug numatytųjų reikšmių;
  4. skyrius su esama būsena;
  5. pakeitimai, padaryti kaip priėmimo „webhook“ dalis;
  6. įvairių valdiklių (ir planuotojo) darbo rezultatas.

Todėl, kai atsiranda nauja išteklių konfigūracija (Naujas produktas), negalime tiesiog paimti ir perrašyti dabartinės „gyvos“ konfigūracijos (gyventi). Norėdami tai padaryti, turėsime palyginti Naujas produktas su paskutine pritaikyta konfigūracija (paskutinį kartą pritaikytas) ir užsukite ant gyventi gavo pleistrą.

Šis požiūris vadinamas Dviejų krypčių sujungimas. Jis naudojamas, pavyzdžiui, Helme.

Taip pat yra Dviejų krypčių sujungimas, kuris skiriasi tuo:

  • lyginant paskutinį kartą pritaikytas и Naujas produktas, žiūrime, kas buvo ištrinta;
  • lyginant Naujas produktas и gyventi, žiūrime, kas buvo pridėta ar pakeista;
  • sumuotas pleistras užklijuojamas gyventi.

Su Helm diegiame daugiau nei 1000 programų, todėl iš tikrųjų gyvename su dviejų krypčių sujungimu. Tačiau jame yra nemažai problemų, kurias išsprendėme naudodami pataisas, kurios padeda Helm normaliai dirbti.

Tikra išleidimo būsena

Kai mūsų CI sistema sugeneruoja naują Kubernetes konfigūraciją pagal kitą įvykį, ji perduoda ją naudoti (taikyti) į klasterį – naudojant Helm arba kubectl apply. Toliau įvyksta jau aprašytas N-way sujungimas, į kurį Kubernetes API pritariamai reaguoja į CI sistemą, o tai – į jos vartotoją.

werf - mūsų CI / CD įrankis Kubernetes (apžvalga ir vaizdo reportažas)

Tačiau yra didžiulė problema: juk sėkminga paraiška nereiškia sėkmingo diegimo. Jei Kubernetes supranta, kokius pakeitimus reikia pritaikyti, ir taiko, mes vis tiek nežinome, koks bus rezultatas. Pvz., Atnaujinti ir iš naujo paleisti blokus priekinėje sistemoje gali būti sėkminga, bet ne užpakalinėje sistemoje, ir mes gausime skirtingas veikiančių programų vaizdų versijas.

Norint viską atlikti teisingai, šiai schemai reikalinga papildoma nuoroda – specialus seklys, kuris gaus informaciją apie būseną iš Kubernetes API ir perduos ją tolesnei tikrosios dalykų būklės analizei. „Go“ sukūrėme atvirojo kodo biblioteką – kubinis šuo (žr. jo skelbimą čia), kuris išsprendžia šią problemą ir yra integruotas į werf.

Šio stebėjimo priemonės veikimas werf lygiu sukonfigūruojamas naudojant komentarus, kurie dedami į diegimus arba „StatefulSets“. Pagrindinė anotacija - fail-mode - supranta šias reikšmes:

  • IgnoreAndContinueDeployProcess — ignoruojame šio komponento diegimo problemas ir tęsiame diegimą;
  • FailWholeDeployProcessImmediately — šio komponento klaida sustabdo diegimo procesą;
  • HopeUntilEndOfDeployProcess — Tikimės, kad šis komponentas veiks iki diegimo pabaigos.

Pavyzdžiui, šis išteklių ir anotacijos reikšmių derinys fail-mode:

werf - mūsų CI / CD įrankis Kubernetes (apžvalga ir vaizdo reportažas)

Kai diegiame pirmą kartą, duomenų bazė (MongoDB) gali būti dar neparengta – diegimas nepavyks. Tačiau galite palaukti, kol jis prasidės, o diegimas vis tiek vyks.

Yra dar dvi kubedog anotacijos werf:

  • failures-allowed-per-replica — kiekvienos kopijos leidžiamų kritimų skaičius;
  • show-logs-until - reguliuoja momentą, iki kurio werf rodo (stdout) rąstus iš visų išvyniotų ankščių. Numatytasis yra PodIsReady (norėdami nepaisyti pranešimų, kurių tikriausiai nenorime, kai srautas pradeda tekėti į bloką), tačiau reikšmės taip pat galioja: ControllerIsReady и EndOfDeploy.

Ko dar norime iš dislokavimo?

Be jau aprašytų dviejų punktų, norėtume:

  • pamatyti rąstų - ir tik būtinus, o ne viską iš eilės;
  • takelis progresas, nes jei darbas „tyliai“ kabo kelias minutes, svarbu suprasti, kas ten vyksta;
  • turėti automatinis atšaukimas jei kas nors nutiko ne taip (todėl labai svarbu žinoti tikrąją dislokavimo būseną). Išleidimas turi būti atominis: arba jis praeina iki galo, arba viskas grįžta į ankstesnę būseną.

rezultatai

Mums, kaip įmonei, norint įgyvendinti visus aprašytus niuansus skirtinguose pristatymo etapuose (sukurti, paskelbti, diegti), pakanka CI sistemos ir paslaugų. werf.

Vietoj išvados:

werf - mūsų CI / CD įrankis Kubernetes (apžvalga ir vaizdo reportažas)

Su werf pagalba padarėme didelę pažangą sprendžiant daugybę „DevOps“ inžinierių problemų ir džiaugiamės, jei platesnė bendruomenė bent jau išbandytų šią priemonę. Kartu bus lengviau pasiekti gerą rezultatą.

Vaizdo įrašai ir skaidrės

Vaizdo įrašas iš spektaklio (~47 min.):

Pranešimo pristatymas:

PS

Kiti pranešimai apie Kubernetes mūsų tinklaraštyje:

Šaltinis: www.habr.com

Добавить комментарий