{"id":36754,"date":"2019-10-31T22:13:37","date_gmt":"2019-10-31T19:13:37","guid":{"rendered":"https:\/\/prohoster.info\/blog\/werf-nash-instrument-dlya-ci-cd-v-kubernetes-obzor-i-video-doklada\/"},"modified":"2019-10-31T22:13:37","modified_gmt":"2019-10-31T19:13:37","slug":"werf-nash-instrument-dlya-ci-cd-v-kubernetes-obzor-i-video-doklada","status":"publish","type":"post","link":"https:\/\/prohoster.info\/pl\/blog\/administrirovanie\/werf-nash-instrument-dlya-ci-cd-v-kubernetes-obzor-i-video-doklada","title":{"rendered":"werf \u2014 nasze narz\u0119dzie do CI\/CD w Kubernetes (przegl\u0105d i wideo wyk\u0142adu)","gt_translate_keys":[{"key":"rendered","format":"text"}]},"content":{"rendered":"<p>27 maja w g\u0142\u00f3wnym hallu konferencji DevOpsConf 2019, kt\u00f3ra odbywa si\u0119 w ramach festiwalu <noindex><a rel=\"nofollow\" href=\"http:\/\/ritfest.ru\/2019\/\">RIT++ 2019<\/a><\/noindex>, w ramach sekcji \u201eCi\u0105g\u0142e dostarczanie\u201d, wyg\u0142oszono referat \u201ewerf \u2013 nasze narz\u0119dzie do CI\/CD w Kubernetes\u201d. W nim m\u00f3wimy o tych <b>problemach i wyzwaniach, przed kt\u00f3rymi staje ka\u017cdy przy wdra\u017caniu w Kubernetes<\/b>, a tak\u017ce o niuansach, kt\u00f3re mog\u0105 nie by\u0107 od razu zauwa\u017calne. Rozwa\u017caj\u0105c mo\u017cliwe drogi rozwi\u0105zania, pokazujemy, jak to jest zrealizowane w narz\u0119dziu Open Source <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/flant\/werf\">werf<\/a><\/noindex>.<\/p>\n<p>Od momentu wyst\u0105pienia nasza aplikacja (wcze\u015bniej znana jako dapp) przekroczy\u0142a historyczn\u0105 granic\u0119 <b>1000 gwiazdek na GitHubie<\/b> \u2014 mamy nadziej\u0119, \u017ce rosn\u0105ca spo\u0142eczno\u015b\u0107 jej u\u017cytkownik\u00f3w u\u0142atwi \u017cycie wielu in\u017cynierom DevOps.<\/p>\n<p><img decoding=\"async\" alt=\"werf \u2014 nasze narz\u0119dzie do CI\/CD w Kubernetes (przegl\u0105d i wideo wyk\u0142adu)\" src=\"\/wp-content\/uploads\/2019\/08\/c2d1ad5133c0de944b60ae37e3dbe598.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nI tak, przedstawiamy <noindex><a rel=\"nofollow\" href=\"https:\/\/www.youtube.com\/watch?v=cK3ackGUTLw\"><b>wideo z wyk\u0142adem<\/b><\/a><\/noindex> (~47 minut, znacznie bardziej informacyjne ni\u017c artyku\u0142) i g\u0142\u00f3wn\u0105 esencj\u0119 z niego w formie tekstowej. Zaczynajmy!<noindex><a rel=\"nofollow\" name=\"habracut\"><\/a><\/noindex><\/p>\n<h2>Dostarczanie kodu w Kubernetes<\/h2>\n<p>\nW referacie mowa b\u0119dzie ju\u017c nie o werf, a o CI\/CD w Kubernetes, przyjmuj\u0105c, \u017ce nasze oprogramowanie jest zapakowane w kontenery Docker <i>(o tym opowiada\u0142em w <noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/flant\/blog\/322686\/\">referacie z 2016 roku<\/a><\/noindex>)<\/i>, a K8s b\u0119dzie u\u017cywane do uruchamiania go w produkcji <i>(o tym \u2014 w <noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/flant\/blog\/331188\/\">2017 roku<\/a><\/noindex>)<\/i>.<\/p>\n<p>Jak wygl\u0105da dostarczanie w Kubernetes?<\/p>\n<ul>\n<li> Jest repozytorium Git z kodem i instrukcjami do jego budowy. Aplikacja jest kompilowana do obrazu Docker i publikowana w Docker Registry.<\/li>\n<li> W tym samym repozytorium znajduj\u0105 si\u0119 instrukcje dotycz\u0105ce tego, jak aplikacj\u0119 wdro\u017cy\u0107 i uruchomi\u0107. Na etapie wdro\u017cenia te instrukcje s\u0105 wysy\u0142ane do Kubernetes, kt\u00f3ry pobiera potrzebny obraz z registry i go uruchamia.<\/li>\n<li> Ponadto zwykle s\u0105 testy. Niekt\u00f3re z nich mo\u017cna wykona\u0107 przy publikacji obrazu. Mo\u017cna r\u00f3wnie\u017c (wg tych samych instrukcji) wdro\u017cy\u0107 kopi\u0119 aplikacji (w oddzielnej przestrzeni nazw K8s lub oddzielnym klastrze) i uruchamia\u0107 tam testy.<\/li>\n<li> Wreszcie potrzebny jest system CI, kt\u00f3ry odbiera zdarzenia z Git'a (lub przyci\u015bni\u0119cia przycisk\u00f3w) i wywo\u0142uje wszystkie okre\u015blone etapy: build, publish, deploy, test.<\/li>\n<\/ul>\n<p>\n<img decoding=\"async\" alt=\"werf \u2014 nasze narz\u0119dzie do CI\/CD w Kubernetes (przegl\u0105d i wideo wyk\u0142adu)\" src=\"\/wp-content\/uploads\/2019\/08\/ec0d1fd1bf1a68cca92badf7cf1affe1.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nTutaj jest kilka wa\u017cnych uwag:<\/p>\n<ol>\n<li> Poniewa\u017c mamy infrastruktur\u0119 niezmienn\u0105 <i>(immutable infrastructure)<\/i>, obraz aplikacji, kt\u00f3ry jest u\u017cywany na wszystkich etapach (staging, production itd.), <b>musi by\u0107 jeden<\/b>. <i>Wi\u0119cej o tym i z przyk\u0142adami m\u00f3wi\u0142em <noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/flant\/blog\/324274\/\">tutaj<\/a><\/noindex>.<\/i><\/li>\n<li> Poniewa\u017c stosujemy podej\u015bcie infrastruktura jako kod <i>(IaC)<\/i>, kod aplikacji, instrukcje do jego budowy i uruchomienia musz\u0105 le\u017ce\u0107 <b>w\u0142a\u015bnie w jednym repozytorium<\/b>. <i>Wi\u0119cej o tym \u2014 patrz w <noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/flant\/blog\/324274\/\">tym samym referacie<\/a><\/noindex>.<\/i><\/li>\n<li> \u0141a\u0144cuch dostarczania <i>(delivery)<\/i> zwykle widzimy tak: aplikacj\u0119 zbudowano, przetestowano, wydano <i>(etap release)<\/i> i ju\u017c \u2014 dostawa si\u0119 odby\u0142a. W rzeczywisto\u015bci u\u017cytkownik otrzymuje to, co wprowadzili\u015bcie na rynek, <b>nie<\/b> kiedy dostarczyli\u015bcie to do produkcji, a kiedy m\u00f3g\u0142 to zobaczy\u0107 i ta produkcja dzia\u0142a\u0142a. Dlatego uwa\u017cam, \u017ce \u0142a\u0144cuch dostaw ko\u0144czy si\u0119 <b>tylko na etapie eksploatacji<\/b> <i>(uruchom)<\/i>, a m\u00f3wi\u0105c dok\u0142adniej, nawet w momencie, gdy kod zosta\u0142 usuni\u0119ty z produkcji (zast\u0105piony nowym).<\/li>\n<\/ol>\n<p>\nWr\u00f3\u0107my do zaznaczonego wcze\u015bniej schematu dostawy w Kubernetes: wynale\u017ali go nie tylko my, ale dos\u0142ownie ka\u017cdy, kto zajmowa\u0142 si\u0119 tym problemem. W istocie ten wzorzec nazywa si\u0119 teraz GitOps <i>(wi\u0119cej o terminie i ideach, kt\u00f3re za nim stoj\u0105, mo\u017cna przeczyta\u0107 <noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/flant\/blog\/458878\/\">tutaj<\/a><\/noindex>)<\/i>. Przyjrzyjmy si\u0119 etapom schematu.<\/p>\n<h2>Etap budowy (build)<\/h2>\n<p>\nWydaje si\u0119, \u017ce w 2019 roku mo\u017cna powiedzie\u0107 co\u015b wi\u0119cej o budowaniu obraz\u00f3w Docker, gdy wszyscy potrafi\u0105 pisa\u0107 Dockerfile i uruchamia\u0107 <code>docker build<\/code>?.. \u0412\u043e\u0442 \u043d\u044e\u0430\u043d\u0441\u044b, \u043d\u0430 \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0445\u043e\u0442\u0435\u043b\u043e\u0441\u044c \u0431\u044b \u043e\u0431\u0440\u0430\u0442\u0438\u0442\u044c \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435:<\/p>\n<ol>\n<li> <b>Waga obrazu<\/b> ma znaczenie, dlatego u\u017cywajcie <noindex><a rel=\"nofollow\" href=\"https:\/\/docs.docker.com\/develop\/develop-images\/multistage-build\/\">multi-stage<\/a><\/noindex>, aby pozostawi\u0107 w obrazie tylko to, co rzeczywi\u015bcie potrzebne do dzia\u0142ania aplikacji.<\/li>\n<li> <b>Liczba warstw<\/b> powinna by\u0107 minimalizowana, \u0142\u0105cz\u0105c \u0142a\u0144cuchy z <code>RUN<\/code>-komend wed\u0142ug ich znaczenia.<\/li>\n<li> Jednak to dodaje problem\u00f3w <b>w debugowaniu<\/b>, poniewa\u017c przy awarii budowy trzeba znale\u017a\u0107 t\u0119 odpowiedni\u0105 komend\u0119 z \u0142a\u0144cucha, kt\u00f3ra spowodowa\u0142a problem.<\/li>\n<li> <b>Szybko\u015b\u0107 budowy<\/b> jest wa\u017cna, poniewa\u017c chcemy szybko wprowadza\u0107 zmiany i obserwowa\u0107 rezultaty. Na przyk\u0142ad, nie chcemy przebudowywa\u0107 zale\u017cno\u015bci w bibliotekach j\u0119zyka przy ka\u017cdej budowie aplikacji.<\/li>\n<li> Cz\u0119sto z jednego repozytorium Git potrzebne s\u0105 <b>wiele obraz\u00f3w<\/b>, kt\u00f3re mo\u017cna rozwi\u0105za\u0107 za pomoc\u0105 zestawu Dockerfile (lub nazwanych etap\u00f3w w jednym pliku) oraz skryptu Bash z ich sekwencyjnym budowaniem.<\/li>\n<\/ol>\n<p>\nTo by\u0142a tylko wierzcho\u0142ek g\u00f3ry lodowej, z kt\u00f3rym wszyscy si\u0119 mierz\u0105. Ale s\u0105 i inne problemy, a w szczeg\u00f3lno\u015bci:<\/p>\n<ol>\n<li> Cz\u0119sto na etapie budowy potrzebujemy co\u015b <b>zamontowa\u0107<\/b> (na przyk\u0142ad, aby zapisa\u0107 wynik polecenia apt w zewn\u0119trznym katalogu).<\/li>\n<li> Chcemy <b>Ansible<\/b> zamiast pisa\u0107 w shellu.<\/li>\n<li> Chcemy <b>zbiera\u0107 bez Docker<\/b> (po co nam dodatkowa wirtualna maszyna, w kt\u00f3rej trzeba wszystko konfigurowa\u0107, skoro ju\u017c jest klaster Kubernetes, na kt\u00f3rym mo\u017cna uruchamia\u0107 kontenery?).<\/li>\n<li> <b>R\u00f3wnoleg\u0142a budowa<\/b>, kt\u00f3re mo\u017cna interpretowa\u0107 na r\u00f3\u017cne sposoby: r\u00f3\u017cne polecenia z Dockerfile (je\u015bli u\u017cywane jest multi-stage), kilka commit\u00f3w z jednego repozytorium, kilka Dockerfile.<\/li>\n<li> <b>Rozproszona budowa<\/b>: chcemy budowa\u0107 co\u015b w podach, kt\u00f3re s\u0105 \"efemeryczne\", poniewa\u017c ich cache znika, a wi\u0119c musz\u0105 by\u0107 gdzie\u015b przechowywane oddzielnie.<\/li>\n<li> W ko\u0144cu nazwa\u0142em szczyt marze\u0144 <b>automagi\u0105<\/b>: idealnie by\u0142oby wej\u015b\u0107 do repozytorium, wpisa\u0107 jak\u0105\u015b komend\u0119 i otrzyma\u0107 gotowy obraz, zbudowany z zrozumieniem jak i co w\u0142a\u015bciwie zrobi\u0107. Jednak osobi\u015bcie nie jestem pewien, czy wszystkie niuanse mo\u017cna przewidzie\u0107.<\/li>\n<\/ol>\n<p>\nI oto s\u0105 projekty:<\/p>\n<ul>\n<li> <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/moby\/buildkit\">moby\/buildkit<\/a><\/noindex> \u2014 budowniczy od firmy Docker Inc (ju\u017c zintegrowany w aktualnych wersjach Docker), kt\u00f3ry stara si\u0119 rozwi\u0105za\u0107 wszystkie te problemy;<\/li>\n<li> <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/GoogleContainerTools\/kaniko\">kaniko<\/a><\/noindex> \u2014 budowniczy od Google, pozwalaj\u0105cy budowa\u0107 bez Docker;<\/li>\n<li> <noindex><a rel=\"nofollow\" href=\"https:\/\/buildpacks.io\/\">Buildpacks.io<\/a><\/noindex> \u2014 pr\u00f3ba CNCF stworzenia automagii i, w szczeg\u00f3lno\u015bci, ciekawe rozwi\u0105zanie z rebase dla warstw;<\/li>\n<li> i jeszcze wiele innych narz\u0119dzi, takich jak <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/containers\/buildah\">buildah<\/a><\/noindex>, <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/genuinetools\/img\">genuinetools\/img<\/a><\/noindex>\u2026<\/li>\n<\/ul>\n<p>\n\u2026 i zobaczcie, ile maj\u0105 gwiazdek na GitHub. Z jednej strony, <code>docker build<\/code> s\u0105 i mog\u0105 co\u015b zrobi\u0107, ale w rzeczywisto\u015bci <b>problem nie zosta\u0142 do ko\u0144ca rozwi\u0105zany<\/b> \u2014 dowodem na to jest r\u00f3wnoleg\u0142y rozw\u00f3j alternatywnych budowniczych, z kt\u00f3rych ka\u017cdy rozwi\u0105zuje jak\u0105\u015b cz\u0119\u015b\u0107 problem\u00f3w.<\/p>\n<h2>Budowanie w werf<\/h2>\n<p>\nTak dotarli\u015bmy do <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/flant\/werf\">werf<\/a><\/noindex> <i>(wcze\u015bniej <noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/flant\/blog\/333682\/\">znanej<\/a><\/noindex> jak dapp)<\/i> \u2014 Open Source-owa narz\u0119dzie firmy \"Flant\", nad kt\u00f3rym pracujemy ju\u017c od wielu lat. Wszystko zacz\u0119\u0142o si\u0119 oko\u0142o 5 lat temu od skrypt\u00f3w Bash, kt\u00f3re optymalizowa\u0142y budow\u0119 Dockerfile, a przez ostatnie 3 lata prowadzona jest pe\u0142noprawna rozw\u00f3j w ramach jednego projektu z w\u0142asnym repozytorium Git <i>(najpierw w Ruby, a potem <noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/flant\/blog\/437044\/\">przepisali\u015bmy<\/a><\/noindex> na Go, a przy okazji zmienili\u015bmy nazw\u0119)<\/i>. Jakie problemy zwi\u0105zane z budowaniem zosta\u0142y rozwi\u0105zane w werf?<\/p>\n<p><img decoding=\"async\" alt=\"werf \u2014 nasze narz\u0119dzie do CI\/CD w Kubernetes (przegl\u0105d i wideo wyk\u0142adu)\" src=\"\/wp-content\/uploads\/2019\/08\/ab6aa8831b49977a419fd4cc3543eb83.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nProblemy zaznaczone na niebiesko s\u0105 ju\u017c zrealizowane, r\u00f3wnoleg\u0142e budowanie zosta\u0142o zrealizowane w ramach jednego hosta, a wydzielone pytania zaznaczone na \u017c\u00f3\u0142to planujemy uko\u0144czy\u0107 do ko\u0144ca lata.<\/p>\n<h2>Etap publikacji w registry (publish)<\/h2>\n<p>\nWype\u0142nili\u015bmy <code>docker push<\/code>\u2026 \u2014 co mo\u017ce by\u0107 trudnego w tym, aby za\u0142adowa\u0107 obraz do registry? I tutaj pojawia si\u0119 pytanie: \u00abJaki tag nada\u0107 obrazowi?\u00bb Pojawia si\u0119 ono z tego powodu, \u017ce mamy <b>Gitflow<\/b> (lub inna strategia Git'a) i Kubernetes, a bran\u017ca d\u0105\u017cy do tego, aby zdarzenia w Kubernetes by\u0142y zgodne z tym, co dzieje si\u0119 w Git. Przecie\u017c Git jest naszym jedynym \u017ar\u00f3d\u0142em prawdy.<\/p>\n<p>Co w tym trudnego? <b>Gwarantowanie reprodukowalno\u015bci<\/b>: od commita w Git, kt\u00f3ry z natury jest niezmienny <i>(immutable)<\/i>, do obrazu Docker, kt\u00f3ry powinien pozosta\u0107 taki sam.<\/p>\n<p>Dla nas r\u00f3wnie\u017c wa\u017cne jest <b>okre\u015bla\u0107 pochodzenie<\/b>, poniewa\u017c chcemy rozumie\u0107, z kt\u00f3rego commita zbudowano aplikacj\u0119 uruchomion\u0105 w Kubernetes (wtedy b\u0119dziemy mogli robi\u0107 diff'y i podobne rzeczy).<\/p>\n<h3>Strategie tagowania<\/h3>\n<p>\nPierwsza to prosty <b>git tag<\/b>. Mamy registry z obrazem oznaczonym jako <code>1.0<\/code>. W Kubernetes s\u0105 stage i production, do kt\u00f3rych ten obraz zosta\u0142 wys\u0142any. W Git robimy commity i w pewnym momencie stawiamy tag <code>2.0<\/code>. Budujemy go wed\u0142ug instrukcji z repozytorium i umieszczamy w registry z tagiem <code>2.0<\/code>. Wdra\u017camy na stage, a je\u015bli wszystko dobrze, potem na production.<\/p>\n<p><img decoding=\"async\" alt=\"werf \u2014 nasze narz\u0119dzie do CI\/CD w Kubernetes (przegl\u0105d i wideo wyk\u0142adu)\" src=\"\/wp-content\/uploads\/2019\/08\/8545b84bfa63a91773f0d7dc1c2bd49f.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nProblem z takim podej\u015bciem polega na tym, \u017ce najpierw postawili\u015bmy tag, a dopiero potem przetestowali\u015bmy i wdro\u017cyli\u015bmy. Dlaczego? Po pierwsze, to po prostu nielogiczne: wydajemy wersj\u0119 oprogramowania, kt\u00f3re jeszcze nie zosta\u0142o sprawdzone (nie mo\u017cemy zrobi\u0107 inaczej, poniewa\u017c do przeprowadzenia test\u00f3w trzeba postawi\u0107 tag). Po drugie, taki spos\u00f3b nie pasuje do Gitflow.<\/p>\n<p>Druga opcja to <b>git commit + tag<\/b>. W ga\u0142\u0119zi master jest tag <code>1.0<\/code>; dla niego w registry \u2014 obraz wdro\u017cony na production. Ponadto w klastrze Kubernetes s\u0105 kontury preview i staging. Nast\u0119pnie post\u0119pujemy zgodnie z Gitflow: w g\u0142\u00f3wnej ga\u0142\u0119zi do rozwoju (<code>develop<\/code>) wprowadzamy nowe funkcje, co prowadzi do powstania commita o identyfikatorze <code>#c1<\/code>. Budujemy go i publikujemy w registry, u\u017cywaj\u0105c tego identyfikatora (<code>#c1<\/code>). Z tym samym identyfikatorem wdra\u017camy na preview. Podobnie post\u0119pujemy z commitami <code>#c2<\/code> i <code>#c3<\/code>.<\/p>\n<p>Kiedy zrozumiemy, \u017ce funkcji jest wystarczaj\u0105co, zaczynamy wszystko stabilizowa\u0107. W Git tworzymy ga\u0142\u0105\u017a <code>release_1.1<\/code> (opartego na <code>#c3<\/code> z <code>develop<\/code>). Budowanie tej wersji nie b\u0119dzie potrzebne, poniewa\u017c zosta\u0142o to zrobione na poprzednim etapie. Dlatego mo\u017cemy po prostu wdro\u017cy\u0107 j\u0105 na staging. Naprawiamy b\u0142\u0119dy w <code>#c4<\/code> i podobnie wdra\u017camy na staging. R\u00f3wnocze\u015bnie trwa rozw\u00f3j w <code>develop<\/code>, do kt\u00f3rego okresowo wprowadzane s\u0105 zmiany z <code>release_1.1<\/code>. W pewnym momencie otrzymujemy zbudowany i wdro\u017cony na staging commit, kt\u00f3rym jeste\u015bmy zadowoleni (<code>#c25<\/code>).<\/p>\n<p>Wtedy robimy merge (z fast-forward'em) ga\u0142\u0119zi release (<code>release_1.1<\/code>) w master. Stawiamy na ten commit tag z now\u0105 wersj\u0105 (<code>1.1<\/code>). Ale ten obraz ju\u017c zosta\u0142 zbudowany w registry, wi\u0119c aby nie budowa\u0107 go jeszcze raz, po prostu dodajemy drugi tag do istniej\u0105cego obrazu (teraz ma w registry tagi <code>#c25<\/code> i <code>1.1<\/code>). Po tym wdra\u017camy go na production.<\/p>\n<p>Jest niedogodno\u015b\u0107, \u017ce na staging zosta\u0142 wdro\u017cony jeden obraz (<code>#c25<\/code>), a na production \u2014 niejako inny (<code>1.1<\/code>), ale wiemy, \u017ce \u201efizycznie\u201d to ten sam obraz z registry.<\/p>\n<p><img decoding=\"async\" alt=\"werf \u2014 nasze narz\u0119dzie do CI\/CD w Kubernetes (przegl\u0105d i wideo wyk\u0142adu)\" src=\"\/wp-content\/uploads\/2019\/08\/b3fa2c34442aa401d1e7b30eb593be9a.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nPrawdziwym minusem jest to, \u017ce brak wsparcia dla merge commit'\u00f3w, trzeba robi\u0107 fast-forward.<\/p>\n<p>Mo\u017cemy p\u00f3j\u015b\u0107 dalej i wykona\u0107 sztuczk\u0119\u2026 Rozwa\u017cmy przyk\u0142ad prostego Dockerfile:<\/p>\n<pre><code class=\"plaintext\">FROM ruby:2.3 as assets\nRUN mkdir -p \/app\nWORKDIR \/app\nCOPY . .\/\nRUN gem install bundler &amp;&amp; bundle install\nRUN bundle exec rake assets:precompile\nCMD bundle exec puma -C config\/puma.rb\n\nFROM nginx:alpine\nCOPY --from=assets \/app\/public \/usr\/share\/nginx\/www\/public<\/code><\/pre>\n<p>\nZbudujmy z tego plik wed\u0142ug takiej zasady, aby wzi\u0105\u0107:<\/p>\n<ul>\n<li> SHA256 od identyfikator\u00f3w u\u017cywanych obraz\u00f3w (<code>ruby:2.3<\/code> i <code>nginx:alpine<\/code>), kt\u00f3re s\u0105 sumami kontrolnymi ich zawarto\u015bci;<\/li>\n<li> wszystkie komendy (<code>RUN<\/code>, <code>POLECENIE<\/code> itp.);<\/li>\n<li> SHA256 od plik\u00f3w, kt\u00f3re by\u0142y dodawane.<\/li>\n<\/ul>\n<p>\n\u2026 i we\u017amiemy sum\u0119 kontroln\u0105 (ponownie SHA256) z takiego pliku. To <b>sygnatura<\/b> wszystkiego, co definiuje zawarto\u015b\u0107 obrazu Docker.<\/p>\n<p><img decoding=\"async\" alt=\"werf \u2014 nasze narz\u0119dzie do CI\/CD w Kubernetes (przegl\u0105d i wideo wyk\u0142adu)\" src=\"\/wp-content\/uploads\/2019\/08\/b2af0b7952eefe26ddbb145955e778e8.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nWr\u00f3\u0107my do schematu i <b>zamiast commit\u00f3w b\u0119dziemy u\u017cywa\u0107 takich sygnatur<\/b>, tzn. tagowa\u0107 obrazy sygnaturami.<\/p>\n<p><img decoding=\"async\" alt=\"werf \u2014 nasze narz\u0119dzie do CI\/CD w Kubernetes (przegl\u0105d i wideo wyk\u0142adu)\" src=\"\/wp-content\/uploads\/2019\/08\/115f290bd5951614c041b3d510fae38e.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nTeraz, gdy b\u0119dzie konieczne zmergowanie zmian z release do master, mo\u017cemy dokona\u0107 prawdziwego merge commit: b\u0119dzie mia\u0142 inny identyfikator, ale t\u0119 sam\u0105 sygnatur\u0119. Z takim samym identyfikatorem wydamy obraz r\u00f3wnie\u017c na production.<\/p>\n<p>Minusem jest to, \u017ce teraz nie b\u0119dzie mo\u017cna okre\u015bli\u0107, jaki commit zosta\u0142 wprowadzony na produkcj\u0119 \u2014 sumy kontrolne dzia\u0142aj\u0105 tylko w jedn\u0105 stron\u0119. Problem ten rozwi\u0105zuje dodatkowa warstwa z metadanymi \u2014 opowiem o tym wi\u0119cej p\u00f3\u017aniej.<\/p>\n<h3>Tagowanie w werf<\/h3>\n<p>\nW werf poszli\u015bmy jeszcze dalej i przygotowujemy si\u0119 do zbudowania rozproszonego z cachem, kt\u00f3ry nie jest przechowywany na jednej maszynie\u2026 Tak wi\u0119c, mamy Docker obrazy dw\u00f3ch typ\u00f3w, nazywamy je <i>stage<\/i> i <i>image<\/i>.<\/p>\n<p>W repozytorium Git werf przechowywane s\u0105 specyficzne instrukcje do budowy, opisuj\u0105ce r\u00f3\u017cne etapy budowy (<i>beforeInstall<\/i>, <i>install<\/i>, <i>beforeSetup<\/i>, <i>setup<\/i>). Pierwszy obraz etapu budujemy z sygnatur\u0105 okre\u015blon\u0105 jako suma kontrolna pierwszych krok\u00f3w. Nast\u0119pnie dodajemy kod \u017ar\u00f3d\u0142owy, dla nowego obrazu etapu obliczamy jego sum\u0119 kontroln\u0105\u2026 Te operacje powtarzaj\u0105 si\u0119 dla wszystkich etap\u00f3w, w rezultacie czego otrzymujemy zestaw obraz\u00f3w etapu. Nast\u0119pnie tworzymy ko\u0144cowy obraz, kt\u00f3ry zawiera tak\u017ce metadane o jego pochodzeniu. I ju\u017c ten obraz tagujemy na r\u00f3\u017cne sposoby (szczeg\u00f3\u0142y p\u00f3\u017aniej).<\/p>\n<p><img decoding=\"async\" alt=\"werf \u2014 nasze narz\u0119dzie do CI\/CD w Kubernetes (przegl\u0105d i wideo wyk\u0142adu)\" src=\"\/wp-content\/uploads\/2019\/08\/22df8b6347b45be19ecb889c102b92b5.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nNiech po tym pojawi si\u0119 nowy commit, w kt\u00f3rym zmieniono tylko kod aplikacji. Co si\u0119 stanie? Dla zmian kodu zostanie utworzona \u0142atka, przygotowany nowy obraz stage. Jego sygnatura zostanie okre\u015blona jako suma kontrolna starego obrazu stage i nowej \u0142atki. Na podstawie tego obrazu zostanie utworzony nowy ko\u0144cowy obraz image. Podobne zachowanie b\u0119dzie mia\u0142o miejsce przy zmianach na innych etapach.<\/p>\n<p>W ten spos\u00f3b obrazy stage s\u0105 pami\u0119ci\u0105 podr\u0119czn\u0105, kt\u00f3r\u0105 mo\u017cna przechowywa\u0107 rozproszono, a ju\u017c stworzone z niej obrazy image s\u0105 \u0142adowane do Docker Registry.<\/p>\n<p><img decoding=\"async\" alt=\"werf \u2014 nasze narz\u0119dzie do CI\/CD w Kubernetes (przegl\u0105d i wideo wyk\u0142adu)\" src=\"\/wp-content\/uploads\/2019\/08\/35805eca81605bd64c6910b7965b567e.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<\/p>\n<h3>Czyszczenie registry<\/h3>\n<p>\nNie b\u0119dzie mowy o usuwaniu warstw, kt\u00f3re pozosta\u0142y wisz\u0105ce po usuni\u0119tych tagach \u2014 to standardowa funkcjonalno\u015b\u0107 samego Docker Registry. Mowa o sytuacji, gdy nagromadzi si\u0119 wiele tag\u00f3w Docker i zdajemy sobie spraw\u0119, \u017ce ich cz\u0119\u015b\u0107 nie jest nam ju\u017c potrzebna, a zajmuj\u0105 miejsce (i\/lub p\u0142acimy za nie).<\/p>\n<p>Jakie s\u0105 strategie czyszczenia?<\/p>\n<ol>\n<li> Mo\u017cna po prostu nic <b>nie czy\u015bci\u0107<\/b>. Czasami rzeczywi\u015bcie pro\u015bciej jest troch\u0119 zap\u0142aci\u0107 za dodatkow\u0105 przestrze\u0144 ni\u017c rozpl\u0105tywa\u0107 ogromny k\u0142\u0119bek tag\u00f3w. Ale to dzia\u0142a tylko do pewnego momentu.<\/li>\n<li> <b>Pe\u0142ne zresetowanie<\/b>. Je\u015bli usuniemy wszystkie obrazy i zbudujemy tylko aktualne w systemie CI, mo\u017ce wyst\u0105pi\u0107 problem. Je\u015bli na produkcji zostanie ponownie uruchomiony kontener, dla niego za\u0142adowany zostanie nowy obraz \u2014 taki, kt\u00f3ry jeszcze nie by\u0142 testowany. Zabiwa to ide\u0119 niezmiennej infrastruktury.<\/li>\n<li> <b>Blue-green<\/b>. Gdy jeden registry zaczyna by\u0107 zape\u0142niony \u2014 \u0142adujemy obrazy do innego. Ta sama kwestia co w poprzedniej metodzie: w kt\u00f3rym momencie mo\u017cna oczy\u015bci\u0107 ten registry, kt\u00f3ry zacz\u0105\u0142 si\u0119 przepe\u0142nia\u0107?<\/li>\n<li> <b>Na czas<\/b>. Usuwa\u0107 wszystkie obrazy starsze ni\u017c 1 miesi\u0105c? Ale na pewno znajdzie si\u0119 us\u0142uga, kt\u00f3ra nie by\u0142a aktualizowana przez ca\u0142y miesi\u0105c...<\/li>\n<li> <b>R\u0119cznie<\/b> okre\u015bla\u0107, co ju\u017c mo\u017cna usun\u0105\u0107.<\/li>\n<\/ol>\n<p>\nNaprawd\u0119 \u017cycia nadaj\u0105ce si\u0119 warianty s\u0105 dwa: nie czy\u015bci\u0107 albo kombinacja blue-green + r\u0119cznie. W tym ostatnim przypadku chodzi o to, \u017ce gdy rozumiesz, \u017ce czas na oczyszczenie registry, tworzysz nowy i przez miesi\u0105c dodajesz do niego wszystkie nowe obrazy. A po miesi\u0105cu sprawdzasz, jakie pody w Kubernetes wci\u0105\u017c u\u017cywaj\u0105 starego registry, i przenosisz je te\u017c do nowego registry.<\/p>\n<p>Do czego doszli\u015bmy w <b>werf<\/b>? \u041c\u044b \u0441\u043e\u0431\u0438\u0440\u0430\u0435\u043c:<\/p>\n<ol>\n<li> Git head: wszystkie tagi, wszystkie ga\u0142\u0119zie \u2014 zak\u0142adaj\u0105c, \u017ce wszystko, co jest oznaczone w Git, potrzebujemy r\u00f3wnie\u017c w obrazach (a je\u015bli nie, to musimy usun\u0105\u0107 to w samym Gicie);<\/li>\n<li> wszystkie pod'y, kt\u00f3re s\u0105 teraz pobierane w Kubernetes;<\/li>\n<li> stare ReplicaSet'y (to, co niedawno zosta\u0142o pobrane), a tak\u017ce planujemy zeskanowa\u0107 Helm-releasy i wybiera\u0107 tam ostatnie obrazy.<\/li>\n<\/ol>\n<p>\n\u2026 i tworzymy z tego zestawu whitelist\u0119 \u2014 list\u0119 obraz\u00f3w, kt\u00f3re nie b\u0119dziemy usuwa\u0107. Wszystko inne usuwamy, po czym znajdujemy osierocone obrazy stage i tak\u017ce je usuwamy.<\/p>\n<h2>Etap wdro\u017cenia (deploy)<\/h2>\n<p><\/p>\n<h3>Niezawodna deklaratywno\u015b\u0107<\/h3>\n<p>\nPierwsza kwestia, na kt\u00f3r\u0105 chcia\u0142bym zwr\u00f3ci\u0107 uwag\u0119 w wdro\u017ceniu, to wprowadzenie zaktualizowanej konfiguracji zasob\u00f3w, og\u0142oszonej deklaratywnie. Oryginalny dokument YAML z opisem zasob\u00f3w Kubernetes zawsze znacz\u0105co r\u00f3\u017cni si\u0119 od wyniku, kt\u00f3ry rzeczywi\u015bcie dzia\u0142a w klastrze. Poniewa\u017c Kubernetes dodaje do konfiguracji:<\/p>\n<ol>\n<li> identyfikatory;<\/li>\n<li> informacje s\u0142u\u017cbowe;<\/li>\n<li> wiele warto\u015bci domy\u015blnych;<\/li>\n<li> sekcj\u0119 z bie\u017c\u0105cym statusem;<\/li>\n<li> zmiany dokonane w ramach pracy webhooka admission;<\/li>\n<li> wyniki dzia\u0142ania r\u00f3\u017cnych kontroler\u00f3w (i planisty).<\/li>\n<\/ol>\n<p>\nDlatego, gdy pojawia si\u0119 nowa konfiguracja zasobu (<i>nowy<\/i>), nie mo\u017cemy po prostu zaktualizowa\u0107 jej bie\u017c\u0105cej, \u201e\u017cywej\u201d konfiguracji (<i>live<\/i>). Musimy por\u00f3wna\u0107 <i>nowy<\/i> z wcze\u015bniej zastosowan\u0105 konfiguracj\u0105 (<i>last-applied<\/i>) i na\u0142o\u017cy\u0107 na <i>live<\/i> otrzymany patch.<\/p>\n<p>Takie podej\u015bcie nazywa si\u0119 <b>2-way merge<\/b>. Jest stosowane, na przyk\u0142ad, w Helm.<\/p>\n<p>Jest jeszcze <b>3-way merge<\/b>, kt\u00f3ry r\u00f3\u017cni si\u0119 tym, \u017ce:<\/p>\n<ul>\n<li> por\u00f3wnuj\u0105c <i>last-applied<\/i> i <i>nowy<\/i>, sprawdzamy, co zosta\u0142o usuni\u0119te;<\/li>\n<li> por\u00f3wnuj\u0105c <i>nowy<\/i> i <i>live<\/i>, sprawdzamy, co zosta\u0142o dodane lub zmienione;<\/li>\n<li> \u0142\u0105cznie nak\u0142adamy patch na <i>live<\/i>.<\/li>\n<\/ul>\n<p>\nWdr\u0105\u017camy 1000+ aplikacji z Helm, wi\u0119c tak naprawd\u0119 \u017cyjemy z 2-way merge. Jednak ma on szereg problem\u00f3w, kt\u00f3re rozwi\u0105zali\u015bmy naszymi \u0142ataj\u0105cymi, wspieraj\u0105cymi poprawne dzia\u0142anie Helm'a.<\/p>\n<h3>Rzeczywisty status wdro\u017cenia<\/h3>\n<p>\nPo tym, jak nasz system CI wygenerowa\u0142 now\u0105 konfiguracj\u0119 dla Kubernetes w kolejnej akcji, przekazuje j\u0105 do zastosowania <i>(apply)<\/i> w klastrze \u2014 za pomoc\u0105 Helm lub <code>kubectl apply<\/code>. Nast\u0119pnie odbywa si\u0119 opisany ju\u017c N-way merge, na co API Kubernetes pozytywnie odpowiada systemowi CI, a ten \u2014 swojemu u\u017cytkownikowi.<\/p>\n<p><img decoding=\"async\" alt=\"werf \u2014 nasze narz\u0119dzie do CI\/CD w Kubernetes (przegl\u0105d i wideo wyk\u0142adu)\" src=\"\/wp-content\/uploads\/2019\/08\/fcc8251525f32c913f30fbcdc4296198.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nJednak istnieje ogromny problem: poniewa\u017c <b>pomy\u015blne zastosowanie nie oznacza pomy\u015blnego wdro\u017cenia<\/b>. Je\u015bli Kubernetes zrozumia\u0142, jakie zmiany nale\u017cy zastosowa\u0107, to je stosuje \u2014 jeszcze nie wiemy, co wyniknie z tego. Na przyk\u0142ad aktualizacja i ponowne uruchomienie pod'\u00f3w w frontendzie mog\u0105 przebiega\u0107 pomy\u015blnie, a w backendzie \u2014 nie, i otrzymamy r\u00f3\u017cne wersje uruchomionych obraz\u00f3w aplikacji.<\/p>\n<p>Aby wszystko robi\u0107 poprawnie, w tej schemacie potrzebny jest dodatkowy element \u2014 specjalny tracker, kt\u00f3ry b\u0119dzie otrzymywa\u0142 informacje o statusie z API Kubernetes i przekazywa\u0142 je do dalszej analizy rzeczywistej sytuacji. Stworzyli\u015bmy bibliotek\u0119 open source w Go \u2014 <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/flant\/kubedog\"><b>kubedog<\/b><\/a><\/noindex> <i>(zob. jej zapowied\u017a <noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/flant\/blog\/434160\/\">tutaj<\/a><\/noindex>)<\/i>, \u2014 kt\u00f3ra rozwi\u0105zuje ten problem i jest zintegrowana z werf.<\/p>\n<p>Zachowanie tego trackera na poziomie werf jest konfigurowane za pomoc\u0105 adnotacji, kt\u00f3re s\u0105 umieszczane na Deploymentach lub StatefulSets. G\u0142\u00f3wna adnotacja \u2014 <code>fail-mode<\/code> \u2014 rozumie nast\u0119puj\u0105ce warto\u015bci:<\/p>\n<ul>\n<li> <code>IgnoreAndContinueDeployProcess<\/code> \u2014 ignorujemy problemy zwi\u0105zane z wdra\u017caniem tego komponentu i kontynuujemy deploy;<\/li>\n<li> <code>FailWholeDeployProcessImmediately<\/code> \u2014 b\u0142\u0105d w tym komponencie zatrzymuje proces wdra\u017cania;<\/li>\n<li> <code>HopeUntilEndOfDeployProcess<\/code> \u2014 mamy nadziej\u0119, \u017ce ten komponent zadzia\u0142a do ko\u0144ca wdro\u017cenia.<\/li>\n<\/ul>\n<p>\nNa przyk\u0142ad, taka kombinacja zasob\u00f3w i warto\u015bci adnotacji <code>fail-mode<\/code>:<\/p>\n<p><img decoding=\"async\" alt=\"werf \u2014 nasze narz\u0119dzie do CI\/CD w Kubernetes (przegl\u0105d i wideo wyk\u0142adu)\" src=\"\/wp-content\/uploads\/2019\/08\/40e161710e8a535cd95f1eb66ca8407b.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nKiedy wdra\u017camy po raz pierwszy, baza danych (MongoDB) mo\u017ce jeszcze nie by\u0107 gotowa \u2014 Deployment'y si\u0119 nie powiod\u0105. Ale mo\u017cna poczeka\u0107 na moment, aby si\u0119 uruchomi\u0142a, i wdro\u017cenie wci\u0105\u017c si\u0119 powiedzie.<\/p>\n<p>Istniej\u0105 jeszcze dwie adnotacje dla kubedog w werf:<\/p>\n<ul>\n<li> <code>failures-allowed-per-replica<\/code> \u2014 liczba dozwolonych upadk\u00f3w na ka\u017cd\u0105 replik\u0119;<\/li>\n<li> <code>show-logs-until<\/code> \u2014 reguluje moment, do kt\u00f3rego werf pokazuje (w stdout) logi ze wszystkich wdra\u017canych pod'\u00f3w. Domy\u015blnie to <code>PodIsReady<\/code> (aby ignorowa\u0107 komunikaty, kt\u00f3re prawdopodobnie nie s\u0105 nam potrzebne, gdy pod zaczyna otrzymywa\u0107 ruch), jednak dopuszczalne s\u0105 r\u00f3wnie\u017c warto\u015bci <code>ControllerIsReady<\/code> i <code>EndOfDeploy<\/code>.<\/li>\n<\/ul>\n<p><\/p>\n<h3>Czego jeszcze oczekujemy od wdro\u017cenia?<\/h3>\n<p>\nOpr\u00f3cz ju\u017c opisanych dw\u00f3ch punkt\u00f3w chcieliby\u015bmy:<\/p>\n<ul>\n<li> widzie\u0107 <b>logi<\/b> \u2014 i to tylko te potrzebne, a nie wszystkie;<\/li>\n<li> \u015bledzi\u0107 <b>post\u0119p<\/b>, poniewa\u017c je\u015bli zadanie \u201emilczy\u201d przez kilka minut, wa\u017cne jest, aby rozumie\u0107, co si\u0119 dzieje;<\/li>\n<li> mie\u0107 <b>automatyczny rollback<\/b> na wypadek, gdyby co\u015b posz\u0142o nie tak (a zatem krytycznie wiedzie\u0107, jaki jest rzeczywisty status wdro\u017cenia). Wdro\u017cenie powinno by\u0107 atomowe: albo przechodzi do ko\u0144ca, albo wszystko wraca do poprzedniego stanu.<\/li>\n<\/ul>\n<p><\/p>\n<h2>Podsumowanie<\/h2>\n<p>\nJako firma, aby zrealizowa\u0107 wszystkie opisane drobiazgi na r\u00f3\u017cnych etapach dostawy (budowa, publikacja, wdro\u017cenie), wystarcza nam system CI oraz narz\u0119dzie <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/flant\/werf\">werf<\/a><\/noindex>.<\/p>\n<p>Na zako\u0144czenie:<\/p>\n<p><img decoding=\"async\" alt=\"werf \u2014 nasze narz\u0119dzie do CI\/CD w Kubernetes (przegl\u0105d i wideo wyk\u0142adu)\" src=\"\/wp-content\/uploads\/2019\/08\/bafba54f2df8740a1a12c93b3476e49a.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nDzi\u0119ki werf zrobili\u015bmy znaczne post\u0119py w rozwi\u0105zaniu wielu problem\u00f3w in\u017cynier\u00f3w DevOps i z przyjemno\u015bci\u0105, je\u015bli szersze spo\u0142eczno\u015b\u0107 spr\u00f3buje tego narz\u0119dzia w akcji. Osi\u0105gni\u0119cie dobrego wyniku razem b\u0119dzie \u0142atwiejsze.<\/p>\n<h2>Wideo i slajdy<\/h2>\n<p>\nWideo z wyst\u0105pienia (~47 minut):<\/p>\n<p><center><div class=\"youtube-placeholder\" data-id=\"cK3ackGUTLw\" onclick=\"loadVideo(this)\">\r\n        <img decoding=\"async\" src=\"https:\/\/img.youtube.com\/vi\/cK3ackGUTLw\/hqdefault.jpg\" alt=\"Odtwarzaj wideo\" loading=\"lazy\" width=\"480\" height=\"360\" style=\"width:100%;height:auto;\">\r\n        <div class=\"play-button\"><\/div>\r\n    <\/div><\/center><\/p>\n<p>Prezentacja wyst\u0105pienia:<\/p>\n<p><center><iframe loading=\"lazy\" width=\"560\" height=\"315\" src=\"\/\/speakerdeck.com\/player\/2033277984c04900b18940588edf1161\" frameborder=\"0\" allowfullscreen><\/iframe><\/center><\/p>\n<h2>P.S.<\/h2>\n<p>\nInne artyku\u0142y o Kubernetes na naszym blogu:<\/p>\n<ul>\n<li> \u00ab<noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/flant\/blog\/459326\/\">Automatyczne skalowanie i zarz\u0105dzanie zasobami w Kubernetes<\/a><\/noindex>\u00bb <i>(Dmitrij Sto\u0142jarov; 27 kwietnia 2019 na \u201eStaczce\u201d)<\/i>;<\/li>\n<li> \u00ab<noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/flant\/blog\/449096\/\">Rozszerzamy i uzupe\u0142niamy Kubernetes<\/a><\/noindex>\u00bb <i>(Andriej Po\u0142owow; 8 kwietnia 2019 na Saint HighLoad++)<\/i>;<\/li>\n<li> \u00ab<noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/flant\/blog\/431500\/\">Bazy danych i Kubernetes<\/a><\/noindex>\u00bb <i>(Dmitrij Sto\u0142jarov; 8 listopada 2018 na HighLoad++)<\/i>;<\/li>\n<li> \u00ab<noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/flant\/blog\/412901\/\">Monitorowanie i Kubernetes<\/a><\/noindex>\u00bb <i>(Dmitrij Stoljarow; 28 maja 2018 na RootConf)<\/i>;<\/li>\n<li> \u00ab<noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/flant\/blog\/345116\/\">Najlepsze praktyki CI\/CD z Kubernetes i GitLab<\/a><\/noindex>\u00bb <i>(Dmitrij Stoljarow; 7 listopada 2017 na HighLoad++)<\/i>;<\/li>\n<li> \u00ab<noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/flant\/blog\/331188\/\">Nasze do\u015bwiadczenia z Kubernetes w ma\u0142ych projektach<\/a><\/noindex>\u00bb <i>(Dmitrij Stoljarow; 6 czerwca 2017 na RootConf)<\/i>.<\/li>\n<\/ul>\n<p>\u0179r\u00f3d\u0142o: <a content=\"nofollow\" rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/flant\/blog\/460351\/\">habr.com<\/a><\/p>","protected":false,"gt_translate_keys":[{"key":"rendered","format":"html"}]},"excerpt":{"rendered":"<p>27 \u043c\u0430\u044f \u0432 \u0433\u043b\u0430\u0432\u043d\u043e\u043c \u0437\u0430\u043b\u0435 \u043a\u043e\u043d\u0444\u0435\u0440\u0435\u043d\u0446\u0438\u0438 DevOpsConf 2019, \u043f\u0440\u043e\u0445\u043e\u0434\u044f\u0449\u0435\u0439 \u0432 \u0440\u0430\u043c\u043a\u0430\u0445 \u0444\u0435\u0441\u0442\u0438\u0432\u0430\u043b\u044f \u0420\u0418\u0422++ 2019, \u0432 \u0440\u0430\u043c\u043a\u0430\u0445 \u0441\u0435\u043a\u0446\u0438\u0438 \u00ab\u041d\u0435\u043f\u0440\u0435\u0440\u044b\u0432\u043d\u0430\u044f \u043f\u043e\u0441\u0442\u0430\u0432\u043a\u0430\u00bb, \u043f\u0440\u043e\u0437\u0432\u0443\u0447\u0430\u043b \u0434\u043e\u043a\u043b\u0430\u0434 \u00abwerf \u2014 \u043d\u0430\u0448 \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442 \u0434\u043b\u044f CI\/CD \u0432 Kubernetes\u00bb. \u0412 \u043d\u0451\u043c \u0440\u0430\u0441\u0441\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u043e \u0442\u0435\u0445 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u0430\u0445 \u0438 \u0432\u044b\u0437\u043e\u0432\u0430\u0445, \u0441 \u043a\u043e\u0442\u043e\u0440\u044b\u043c\u0438 \u0441\u0442\u0430\u043b\u043a\u0438\u0432\u0430\u0435\u0442\u0441\u044f \u043a\u0430\u0436\u0434\u044b\u0439 \u043f\u0440\u0438 \u0434\u0435\u043f\u043b\u043e\u0435 \u0432 Kubernetes, \u0430 \u0442\u0430\u043a\u0436\u0435 \u043e \u043d\u044e\u0430\u043d\u0441\u0430\u0445, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043c\u043e\u0433\u0443\u0442 \u0431\u044b\u0442\u044c \u0437\u0430\u043c\u0435\u0442\u043d\u044b \u043d\u0435 \u0441\u0440\u0430\u0437\u0443. [&hellip;]<\/p>\n","protected":false,"gt_translate_keys":[{"key":"rendered","format":"html"}]},"author":1,"featured_media":27531,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[688],"tags":[],"class_list":["post-36754","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-administrirovanie"],"aioseo_notices":[],"aioseo_head":"\n\t\t<!-- All in One SEO 5.0.1.1 - aioseo.com -->\n\t<meta name=\"description\" content=\"27 \u043c\u0430\u044f \u0432 \u0433\u043b\u0430\u0432\u043d\u043e\u043c \u0437\u0430\u043b\u0435 \u043a\u043e\u043d\u0444\u0435\u0440\u0435\u043d\u0446\u0438\u0438 DevOpsConf 2019, \u043f\u0440\u043e\u0445\u043e\u0434\u044f\u0449\u0435\u0439 \u0432 \u0440\u0430\u043c\u043a\u0430\u0445 \u0444\u0435\u0441\u0442\u0438\u0432\u0430\u043b\u044f \u0420\u0418\u0422++ 2019, \u0432 \u0440\u0430\u043c\u043a\u0430\u0445 \u0441\u0435\u043a\u0446\u0438\u0438 \u00ab\u041d\u0435\u043f\u0440\u0435\u0440\u044b\u0432\u043d\u0430\u044f \u043f\u043e\u0441\u0442\u0430\u0432\u043a\u0430\u00bb, \u043f\u0440\u043e\u0437\u0432\u0443\u0447\u0430\u043b.\" \/>\n\t<meta name=\"robots\" content=\"max-image-preview:large\" \/>\n\t<meta name=\"author\" content=\"Yuri Gagarin\"\/>\n\t<link rel=\"canonical\" href=\"https:\/\/prohoster.info\/pl\/blog\/administrirovanie\/werf-nash-instrument-dlya-ci-cd-v-kubernetes-obzor-i-video-doklada\" \/>\n\t<meta name=\"generator\" content=\"All in One SEO (AIOSEO) 5.0.1.1\" \/>\n\t\t<meta property=\"og:locale\" content=\"pl_PL\" \/>\n\t\t<meta property=\"og:site_name\" content=\"ProHoster | \u041a\u0443\u043f\u0438\u0442\u044c \u043d\u0430\u0434\u0435\u0436\u043d\u044b\u0439 \u0445\u043e\u0441\u0442\u0438\u043d\u0433 \u0434\u043b\u044f \u0441\u0430\u0439\u0442\u043e\u0432 \u0441 \u0437\u0430\u0449\u0438\u0442\u043e\u0439 \u043e\u0442 DDoS, VPS VDS \u0441\u0435\u0440\u0432\u0435\u0440\u044b\" \/>\n\t\t<meta property=\"og:type\" content=\"article\" \/>\n\t\t<meta property=\"og:title\" content=\"\ud83e\udd47werf \u2014 \u043d\u0430\u0448 \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442 \u0434\u043b\u044f CI\/CD \u0432 Kubernetes (\u043e\u0431\u0437\u043e\u0440 \u0438 \u0432\u0438\u0434\u0435\u043e \u0434\u043e\u043a\u043b\u0430\u0434\u0430) | ProHoster\" \/>\n\t\t<meta property=\"og:description\" content=\"27 \u043c\u0430\u044f \u0432 \u0433\u043b\u0430\u0432\u043d\u043e\u043c \u0437\u0430\u043b\u0435 \u043a\u043e\u043d\u0444\u0435\u0440\u0435\u043d\u0446\u0438\u0438 DevOpsConf 2019, \u043f\u0440\u043e\u0445\u043e\u0434\u044f\u0449\u0435\u0439 \u0432 \u0440\u0430\u043c\u043a\u0430\u0445 \u0444\u0435\u0441\u0442\u0438\u0432\u0430\u043b\u044f \u0420\u0418\u0422++ 2019, \u0432 \u0440\u0430\u043c\u043a\u0430\u0445 \u0441\u0435\u043a\u0446\u0438\u0438 \u00ab\u041d\u0435\u043f\u0440\u0435\u0440\u044b\u0432\u043d\u0430\u044f \u043f\u043e\u0441\u0442\u0430\u0432\u043a\u0430\u00bb, \u043f\u0440\u043e\u0437\u0432\u0443\u0447\u0430\u043b.\" \/>\n\t\t<meta property=\"og:url\" content=\"https:\/\/prohoster.info\/pl\/blog\/administrirovanie\/werf-nash-instrument-dlya-ci-cd-v-kubernetes-obzor-i-video-doklada\" \/>\n\t\t<meta property=\"og:image\" content=\"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg\" \/>\n\t\t<meta property=\"og:image:secure_url\" content=\"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg\" \/>\n\t\t<meta property=\"og:image:width\" content=\"350\" \/>\n\t\t<meta property=\"og:image:height\" content=\"350\" \/>\n\t\t<meta property=\"article:published_time\" content=\"2019-10-31T19:13:37+00:00\" \/>\n\t\t<meta property=\"article:modified_time\" content=\"2019-10-31T19:13:37+00:00\" \/>\n\t\t<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/prohoster\" \/>\n\t\t<meta property=\"article:author\" content=\"https:\/\/www.facebook.com\/prohoster\" \/>\n\t\t<!-- All in One SEO -->\n\n","aioseo_head_json":{"title":"\ud83e\udd47werf \u2014 nasze narz\u0119dzie do CI\/CD w Kubernetes (przegl\u0105d i wideo z prezentacji) | ProHoster","description":"27 maja w g\u0142\u00f3wnym sali konferencji DevOpsConf 2019, kt\u00f3ra odbywa si\u0119 w ramach festiwalu RIT++ 2019, w sekcji \u201eCi\u0105g\u0142e dostarczanie\u201d, wyg\u0142oszono.","canonical_url":"https:\/\/prohoster.info\/pl\/blog\/administrirovanie\/werf-nash-instrument-dlya-ci-cd-v-kubernetes-obzor-i-video-doklada","robots":"max-image-preview:large","keywords":"","webmasterTools":{"miscellaneous":""},"schema":null,"og:locale":"pl_PL","og:site_name":"ProHoster | \u041a\u0443\u043f\u0438\u0442\u044c \u043d\u0430\u0434\u0435\u0436\u043d\u044b\u0439 \u0445\u043e\u0441\u0442\u0438\u043d\u0433 \u0434\u043b\u044f \u0441\u0430\u0439\u0442\u043e\u0432 \u0441 \u0437\u0430\u0449\u0438\u0442\u043e\u0439 \u043e\u0442 DDoS, VPS VDS \u0441\u0435\u0440\u0432\u0435\u0440\u044b","og:type":"article","og:title":"\ud83e\udd47werf \u2014 \u043d\u0430\u0448 \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442 \u0434\u043b\u044f CI\/CD \u0432 Kubernetes (\u043e\u0431\u0437\u043e\u0440 \u0438 \u0432\u0438\u0434\u0435\u043e \u0434\u043e\u043a\u043b\u0430\u0434\u0430) | ProHoster","og:description":"27 \u043c\u0430\u044f \u0432 \u0433\u043b\u0430\u0432\u043d\u043e\u043c \u0437\u0430\u043b\u0435 \u043a\u043e\u043d\u0444\u0435\u0440\u0435\u043d\u0446\u0438\u0438 DevOpsConf 2019, \u043f\u0440\u043e\u0445\u043e\u0434\u044f\u0449\u0435\u0439 \u0432 \u0440\u0430\u043c\u043a\u0430\u0445 \u0444\u0435\u0441\u0442\u0438\u0432\u0430\u043b\u044f \u0420\u0418\u0422++ 2019, \u0432 \u0440\u0430\u043c\u043a\u0430\u0445 \u0441\u0435\u043a\u0446\u0438\u0438 \u00ab\u041d\u0435\u043f\u0440\u0435\u0440\u044b\u0432\u043d\u0430\u044f \u043f\u043e\u0441\u0442\u0430\u0432\u043a\u0430\u00bb, \u043f\u0440\u043e\u0437\u0432\u0443\u0447\u0430\u043b.","og:url":"https:\/\/prohoster.info\/pl\/blog\/administrirovanie\/werf-nash-instrument-dlya-ci-cd-v-kubernetes-obzor-i-video-doklada","og:image":"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg","og:image:secure_url":"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg","og:image:width":350,"og:image:height":350,"article:published_time":"2019-10-31T19:13:37+00:00","article:modified_time":"2019-10-31T19:13:37+00:00","article:publisher":"https:\/\/www.facebook.com\/prohoster","article:author":"https:\/\/www.facebook.com\/prohoster"},"aioseo_meta_data":{"post_id":"36754","title":null,"description":null,"keywords":null,"keyphrases":null,"primary_term":null,"canonical_url":null,"og_title":null,"og_description":null,"og_object_type":"default","og_image_type":"default","og_image_url":null,"og_image_width":null,"og_image_height":null,"og_image_custom_url":null,"og_image_custom_fields":null,"og_video":null,"og_custom_url":null,"og_article_section":null,"og_article_tags":null,"twitter_use_og":false,"twitter_card":"default","twitter_image_type":"default","twitter_image_url":null,"twitter_image_custom_url":null,"twitter_image_custom_fields":null,"twitter_title":null,"twitter_description":null,"schema":{"blockGraphs":[],"customGraphs":[],"default":{"data":{"Article":[],"Course":[],"Dataset":[],"FAQPage":[],"Movie":[],"Person":[],"Product":[],"ProductReview":[],"Car":[],"Recipe":[],"Service":[],"SoftwareApplication":[],"WebPage":[]},"graphName":"","isEnabled":true},"graphs":[]},"schema_type":null,"schema_type_options":null,"pillar_content":false,"robots_default":true,"robots_noindex":false,"robots_noarchive":false,"robots_nosnippet":false,"robots_nofollow":false,"robots_noimageindex":false,"robots_noodp":false,"robots_notranslate":false,"robots_max_snippet":null,"robots_max_videopreview":null,"robots_max_imagepreview":"large","priority":null,"frequency":null,"local_seo":null,"seo_analyzer_scan_date":"2026-01-22 04:44:19","breadcrumb_settings":null,"limit_modified_date":false,"reviewed_by":null,"ai":null,"created":"2021-03-01 01:39:23","updated":"2026-01-22 04:44:19","focus_keyword":null,"additional_keywords":null,"truseo_locale":null},"gt_translate_keys":[{"key":"link","format":"url"}],"_links":{"self":[{"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/posts\/36754","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/comments?post=36754"}],"version-history":[{"count":0,"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/posts\/36754\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/media\/27531"}],"wp:attachment":[{"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/media?parent=36754"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/categories?post=36754"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/tags?post=36754"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}