{"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\/nl\/blog\/administrirovanie\/werf-nash-instrument-dlya-ci-cd-v-kubernetes-obzor-i-video-doklada","title":{"rendered":"werf \u2014 onze tool voor CI\/CD in Kubernetes (overzicht en video van de presentatie)","gt_translate_keys":[{"key":"rendered","format":"text"}]},"content":{"rendered":"<p>Op 27 mei in de grote zaal van de DevOpsConf 2019, die plaatsvindt in het kader van het festival <noindex><a rel=\"nofollow\" href=\"http:\/\/ritfest.ru\/2019\/\">RIT++ 2019<\/a><\/noindex>, tijdens de sessie 'Continue Levering', werd de presentatie 'werf \u2014 onze tool voor CI\/CD in Kubernetes' gepresenteerd. Hierin worden de <b>problemen en uitdagingen besproken waarmee iedereen wordt geconfronteerd bij het implementeren in Kubernetes<\/b>, evenals de nuances die niet meteen opvallen. Door mogelijke oplossingen te bespreken, laten we zien hoe dit is gerealiseerd in onze open-source tool <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/flant\/werf\">werf<\/a><\/noindex>.<\/p>\n<p>Sinds de presentatie heeft onze tool (voorheen bekend als dapp) de historische mijlpaal van <b>1000 sterren op GitHub<\/b> bereikt \u2014 we hopen dat de groeiende gemeenschap van gebruikers het leven voor veel DevOps-engineers zal vergemakkelijken.<\/p>\n<p><img decoding=\"async\" alt=\"werf \u2014 onze tool voor CI\/CD in Kubernetes (overzicht en video van de presentatie)\" src=\"\/wp-content\/uploads\/2019\/08\/c2d1ad5133c0de944b60ae37e3dbe598.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nDus, hier is <noindex><a rel=\"nofollow\" href=\"https:\/\/www.youtube.com\/watch?v=cK3ackGUTLw\"><b>de video van de presentatie<\/b><\/a><\/noindex> (~47 minuten, veel informatief dan het artikel) en de belangrijkste punten in tekstvorm. Laten we beginnen!<noindex><a rel=\"nofollow\" name=\"habracut\"><\/a><\/noindex><\/p>\n<h2>Codelevering naar Kubernetes<\/h2>\n<p>\nIn de presentatie gaat het niet langer alleen over werf, maar over CI\/CD in Kubernetes, met de veronderstelling dat onze software verpakt is in Docker-containers <i>(hierover heb ik gesproken in de <noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/flant\/blog\/322686\/\">presentatie van 2016<\/a><\/noindex>)<\/i>, en K8s zal worden gebruikt voor de productie-implementatie <i>(hierover \u2014 in <noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/flant\/blog\/331188\/\">2017 het bedrag vergelijkbaar was met 2019, en de websites van Arch Linux en FFmpeg<\/a><\/noindex>)<\/i>.<\/p>\n<p>Hoe ziet het leveren in Kubernetes eruit?<\/p>\n<ul>\n<li> Er is een Git-repository met code en instructies voor het bouwen. De applicatie wordt opgebouwd in een Docker-image en gepubliceerd in de Docker Registry.<\/li>\n<li> In dezelfde repository zijn er instructies over hoe de applicatie te implementeren en uit te voeren. Tijdens de implementatiefase worden deze instructies naar Kubernetes gestuurd, dat het benodigde image uit de registry haalt en deze opstart.<\/li>\n<li> Daarnaast zijn er meestal tests. Sommige hiervan kunnen worden uitgevoerd bij het publiceren van het image. Ook kan (aan de hand van dezelfde instructies) een kopie van de applicatie worden uitgerold (in een aparte namespace van K8s of in een aparte cluster) en kunnen tests daar worden uitgevoerd.<\/li>\n<li> Ten slotte is er een CI-systeem nodig dat gebeurtenissen uit Git ontvangt (of knoppenindrukken) en alle aangegeven fasen aanroept: build, publish, deploy, test.<\/li>\n<\/ul>\n<p>\n<img decoding=\"async\" alt=\"werf \u2014 onze tool voor CI\/CD in Kubernetes (overzicht en video van de presentatie)\" src=\"\/wp-content\/uploads\/2019\/08\/ec0d1fd1bf1a68cca92badf7cf1affe1.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nHier zijn een paar belangrijke opmerkingen:<\/p>\n<ol>\n<li> Omdat we ongewijzigde infrastructuur hebben <i>(immutable infrastructure)<\/i>, moet het applicatiebeeld dat in alle fasen wordt gebruikt (staging, productie, enz.) <b>\u00e9\u00e9n zijn<\/b>. <i>Meer hierover, met voorbeelden, heb ik besproken <noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/flant\/blog\/324274\/\">hier<\/a><\/noindex>.<\/i><\/li>\n<li> Omdat we de benadering van infrastructuur als code volgen <i>(IaC)<\/i>, moeten de applicatiecode, de instructies voor het bouwen en uitvoeren ervan <b>precies in \u00e9\u00e9n repository worden opgeslagen<\/b>. <i>Meer hierover \u2014 zie in <noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/flant\/blog\/324274\/\">dezelfde presentatie<\/a><\/noindex>.<\/i><\/li>\n<li> De leveringsketen <i>(delivery)<\/i> We zien meestal het zo: de applicatie is gebouwd, getest en gelanceerd <i>(release stage)<\/i> en dat is het \u2014 de levering heeft plaatsgevonden. Maar in werkelijkheid ontvangt de gebruiker datgene wat je hebt uitgebracht, <b>niet<\/b> op het moment dat je dit in productie hebt gebracht, en wanneer hij toegang heeft gekregen tot die productie en deze werkte. Daarom beschouw ik de leveringsketen als eindigend <b>pas in de uitwerkingsfase<\/b> <i>(run)<\/i>, en als we preciezer willen zijn, zelfs op het moment dat de code uit productie is gehaald (vervangen door een nieuwe).<\/li>\n<\/ol>\n<p>\nLaten we terugkomen op het hierboven beschreven leveringsschema in Kubernetes: dit is niet alleen door ons bedacht, maar letterlijk iedereen die met dit probleem bezig was. In wezen wordt dit patroon nu GitOps genoemd <i>(meer over de term en de idee\u00ebn erachter kun je lezen <noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/flant\/blog\/458878\/\">hier<\/a><\/noindex>)<\/i>. Laten we naar de fases van het schema kijken.<\/p>\n<h2>Bouw fase (build)<\/h2>\n<p>\nHet lijkt misschien vreemd wat er in 2019 te vertellen valt over het bouwen van Docker-images, nu iedereen Dockerfiles kan schrijven en uitvoeren, <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>De grootte van de afbeelding<\/b> doet er toe, dus gebruik <noindex><a rel=\"nofollow\" href=\"https:\/\/docs.docker.com\/develop\/develop-images\/multistage-build\/\">multi-stage<\/a><\/noindex>, om alleen het noodzakelijke voor de werking van de applicatie in de afbeelding te laten.<\/li>\n<li> <b>Het aantal lagen<\/b> moet geminimaliseerd worden door samenhangende ketens van <code>RUN<\/code>-commando's te combineren.<\/li>\n<li> Echter, dit voegt problemen toe <b>met debuggen<\/b>, omdat je bij een mislukte build de juiste opdracht uit de keten moet vinden die het probleem veroorzaakte.<\/li>\n<li> <b>De snelheid van de build<\/b> is belangrijk, omdat we snel wijzigingen willen uitrollen en het resultaat willen zien. Bijvoorbeeld, we willen niet bij elke build de afhankelijkheden in de programmeertaal opnieuw opbouwen.<\/li>\n<li> Vaak zijn er uit \u00e9\u00e9n Git-repository <b>veel afbeeldingen<\/b>, wat kan worden opgelost met een set Dockerfiles (of benoemde stadia in \u00e9\u00e9n bestand) en een Bash-script voor hun opeenvolgende bouw.<\/li>\n<\/ol>\n<p>\nDit was slechts de top van de ijsberg, waarmee iedereen in aanraking komt. Maar er zijn ook andere problemen, met name:<\/p>\n<ol>\n<li> Vaak moeten we iets <b>mounten<\/b> (bijvoorbeeld het cachen van het resultaat van een commando zoals apt in een externe directory).<\/li>\n<li> We willen <b>Ansible<\/b> in plaats van het op shell te schrijven.<\/li>\n<li> We willen <b>bouwen zonder Docker<\/b> (waarom hebben we een extra virtuele machine nodig, waarin we alles moeten instellen als we al een Kubernetes-cluster hebben waarop we containers kunnen draaien?).<\/li>\n<li> <b>Parallel bouwen<\/b>, wat op verschillende manieren kan worden ge\u00efnterpreteerd: verschillende commando's uit Dockerfile (als je multi-stage gebruikt), verschillende commits van dezelfde repository, verschillende Dockerfiles.<\/li>\n<li> <b>Gedistrubeerde bouw<\/b>: we willen iets bouwen in pods, die 'ephemeraal' zijn, omdat hun cache verdwijnt, wat betekent dat het ergens apart moet worden opgeslagen.<\/li>\n<li> Eindelijk noemde ik de top van mijn wensen <b>automagie<\/b>: het zou ideaal zijn om naar de repository te gaan, een bepaalde opdracht in te voeren en een kant-en-klaar beeld te ontvangen, samengesteld met begrip van hoe en wat goed te doen is. Persoonlijk ben ik echter niet zeker of alle nuances zo kunnen worden voorzien.<\/li>\n<\/ol>\n<p>\nEn er zijn projecten:<\/p>\n<ul>\n<li> <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/moby\/buildkit\">moby\/buildkit<\/a><\/noindex> \u2014 de bouwer van het bedrijf Docker Inc (al ge\u00efntegreerd in de actuele versies van Docker), die probeert al deze problemen op te lossen;<\/li>\n<li> <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/GoogleContainerTools\/kaniko\">kaniko<\/a><\/noindex> \u2014 de bouwer van Google, die bouwen zonder Docker mogelijk maakt;<\/li>\n<li> <noindex><a rel=\"nofollow\" href=\"https:\/\/buildpacks.io\/\">Buildpacks.io<\/a><\/noindex> \u2014 een poging van CNCF om automagie te cre\u00ebren en, in het bijzonder, een interessante oplossing met rebase voor lagen;<\/li>\n<li> en nog veel andere tools, zoals <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 en kijk hoeveel sterren ze op GitHub hebben. Dat wil zeggen, aan de ene kant, <code>docker build<\/code> er is iets en kan iets doen, maar feitelijk <b>is de vraag nog niet opgelost<\/b> \u2014 het bewijs daarvoor is de parallelle ontwikkeling van alternatieve bouwers, elk van hen lost een deel van de problemen op.<\/p>\n<h2>Bouwen in werf<\/h2>\n<p>\nZo kwamen we bij <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/flant\/werf\">werf<\/a><\/noindex> <i>(voorheen <noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/flant\/blog\/333682\/\">het beroemde<\/a><\/noindex> zoals dapp)<\/i> \u2014 de Open Source-tool van het bedrijf 'Flant', die we al vele jaren ontwikkelen. Het begon zo'n 5 jaar geleden met Bash-scripts die de bouw van Dockerfiles optimaliseerden, en de laatste 3 jaar is er een volledige ontwikkeling gaande binnen \u00e9\u00e9n project met een eigen Git-repository. <i>(eerder in Ruby, en vervolgens <noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/flant\/blog\/437044\/\">herschreven<\/a><\/noindex> in Go, en daarbij hernoemd)<\/i>. Welke bouwvragen worden opgelost in werf?<\/p>\n<p><img decoding=\"async\" alt=\"werf \u2014 onze tool voor CI\/CD in Kubernetes (overzicht en video van de presentatie)\" src=\"\/wp-content\/uploads\/2019\/08\/ab6aa8831b49977a419fd4cc3543eb83.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nDe in het blauw gemarkeerde problemen zijn al gerealiseerd, de parallelle bouw is uitgevoerd op \u00e9\u00e9n host, en de in het geel gemarkeerde vragen zijn we van plan om tegen het einde van de zomer af te ronden.<\/p>\n<h2>De publicatiefase in de registry (publish)<\/h2>\n<p>\nWe hebben verzameld <code>docker push<\/code>\u2026 \u2014 wat kan er complex zijn aan het uploaden van een beeld naar de registry? En dan rijst de vraag: \"Welke tag moet aan het beeld worden gegeven?\" Dit ontstaat omdat we hebben <b>Gitflow<\/b> (of een andere Git-strategie) en Kubernetes, en de industrie streeft ernaar dat wat er in Kubernetes gebeurt in overeenstemming is met wat er in Git gebeurt. Git is immers onze enige waarheid.<\/p>\n<p>Wat is hier moeilijk aan? <b>Garanderen van reproduceerbaarheid<\/b>: van een commit in Git, dat van nature onveranderlijk is <i>(onveranderlijk)<\/i>, tot een Docker-image, dat hetzelfde moet blijven.<\/p>\n<p>Het is ook belangrijk voor ons <b>de oorsprong te bepalen<\/b>, omdat we willen begrijpen uit welke commit de applicatie die in Kubernetes is aangemaakt, is samengesteld (dan kunnen we diffs en soortgelijke dingen doen).<\/p>\n<h3>Taggingstrategie\u00ebn<\/h3>\n<p>\nDe eerste is een eenvoudige <b>git tag<\/b>. We hebben een registry met een image, getagd als <code>1.0<\/code>. In Kubernetes zijn er stage en production, waar dit image naartoe is gepusht. In Git maken we commits en op een gegeven moment plaatsen we een tag <code>2.0<\/code>. We bouwen het volgens de instructies uit de repository en plaatsen het in de registry met de tag <code>2.0<\/code>. We rollen het uit naar stage en, als alles goed is, daarna naar production.<\/p>\n<p><img decoding=\"async\" alt=\"werf \u2014 onze tool voor CI\/CD in Kubernetes (overzicht en video van de presentatie)\" src=\"\/wp-content\/uploads\/2019\/08\/8545b84bfa63a91773f0d7dc1c2bd49f.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nHet probleem met deze benadering is dat we eerst een tag hebben geplaatst en pas daarna getest en uitgerold. Waarom? Ten eerste is het gewoon niet logisch: we geven een versie van de software uit die we nog niet eens hebben gecontroleerd (we kunnen het niet anders doen, omdat we voor de controle een tag moeten plaatsen). Ten tweede past deze aanpak niet bij Gitflow.<\/p>\n<p>De tweede optie is <b>git commit + tag<\/b>. In de master-branch is er een tag <code>1.0<\/code>; daarvoor is er in de registry een image, uitgerold op production. Bovendien zijn er in de Kubernetes-cluster preview en staging omgevingen. Vervolgens volgen we Gitflow: in de hoofdtak voor ontwikkeling (<code>develop<\/code>) maken we nieuwe features, wat leidt tot een commit met de identificatie <code>#c1<\/code>. We bouwen het en publiceren het in de registry, gebruikmakend van deze identificatie (<code>#c1<\/code>). Met dezelfde identificatie rollen we het uit naar preview. Op dezelfde manier doen we dit met commits <code>#c2<\/code> en <code>#c3<\/code>.<\/p>\n<p>Wanneer we weten dat de features voldoende zijn, beginnen we alles te stabiliseren. In Git maken we een branch <code>release_1.1<\/code> (gebaseerd op <code>#c3<\/code> uit <code>develop<\/code>). Het is niet nodig om deze release te bouwen, omdat dit in de vorige stap is gedaan. Daarom kunnen we het gewoon naar staging uitrollen. We verhelpen bugs in <code>#c4<\/code> en rollen dit op dezelfde manier uit naar staging. Tegelijkertijd is er ontwikkeling in <code>develop<\/code>, waar regelmatig veranderingen uit <code>release_1.1<\/code>worden gehaald. Op een gegeven moment hebben we een commit die is gebouwd en naar staging gepusht, waar we tevreden mee zijn (<code>#c25<\/code>).<\/p>\n<p>Dan maken we een merge (met fast-forward) van de release tak (<code>release_1.1<\/code>) in master. We plaatsen op deze commit een tag met de nieuwe versie (<code>1.1<\/code>). Maar dit image is al gebouwd in de registry, dus om het niet opnieuw te bouwen, voegen we gewoon een tweede tag toe aan het bestaande image (nu heeft het in de registry de tags <code>#c25<\/code> en <code>1.1<\/code>). Daarna rollen we het uit naar production.<\/p>\n<p>Er is een nadeel, dat op staging \u00e9\u00e9n image is uitgerold (<code>#c25<\/code>), terwijl op production een andere is uitgebracht (<code>1.1<\/code>), maar we weten dat het 'fysiek' dezelfde afbeelding uit de registry is.<\/p>\n<p><img decoding=\"async\" alt=\"werf \u2014 onze tool voor CI\/CD in Kubernetes (overzicht en video van de presentatie)\" src=\"\/wp-content\/uploads\/2019\/08\/b3fa2c34442aa401d1e7b30eb593be9a.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nHet echte nadeel is echter dat er geen ondersteuning is voor merge commits, we moeten fast-forward doen.<\/p>\n<p>We kunnen verder gaan en een truc doen... Laten we een voorbeeld bekijken van een eenvoudige 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>\nWe zullen een bestand opbouwen volgens het principe dat we nemen:<\/p>\n<ul>\n<li> SHA256 van de identificaties van de gebruikte afbeeldingen (<code>ruby:2.3<\/code> en <code>nginx:alpine<\/code>), die de controlesommen van hun inhoud zijn;<\/li>\n<li> alle opdrachten (<code>RUN<\/code>, <code>CMD<\/code> enzovoort);<\/li>\n<li> SHA256 van de bestanden die zijn toegevoegd.<\/li>\n<\/ul>\n<p>\n... en we nemen de controlewaarde (opnieuw SHA256) van zo'n bestand. Dit is <b>de handtekening<\/b> van alles wat de inhoud van de Docker-image definieert.<\/p>\n<p><img decoding=\"async\" alt=\"werf \u2014 onze tool voor CI\/CD in Kubernetes (overzicht en video van de presentatie)\" src=\"\/wp-content\/uploads\/2019\/08\/b2af0b7952eefe26ddbb145955e778e8.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nLaten we terugkeren naar het schema en <b>in plaats van commits zullen we zulke handtekeningen gebruiken<\/b>, d.w.z. beelden taggen met handtekeningen.<\/p>\n<p><img decoding=\"async\" alt=\"werf \u2014 onze tool voor CI\/CD in Kubernetes (overzicht en video van de presentatie)\" src=\"\/wp-content\/uploads\/2019\/08\/115f290bd5951614c041b3d510fae38e.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nNu, wanneer we bijvoorbeeld veranderingen uit de release naar master moeten samenvoegen, kunnen we een echte merge commit maken: deze zal een andere identifier hebben, maar dezelfde handtekening. Met dezelfde identifier zullen we het image ook op productie uitrollen.<\/p>\n<p>Het nadeel is dat we nu niet kunnen bepalen welke commit naar productie is gehaald \u2014 controlesommen werken alleen in \u00e9\u00e9n richting. Dit probleem wordt opgelost door een extra laag met metadata \u2014 daarover later meer.<\/p>\n<h3>Tagging in werf<\/h3>\n<p>\nIn werf zijn we nog verder gegaan en bereiden we een gedistribueerde build voor met een cache die niet op \u00e9\u00e9n machine wordt opgeslagen... Dus, we bouwen Docker-images van twee types, we noemen ze <i>stage<\/i> en <i>afbeelding<\/i>.<\/p>\n<p>In de Git-repository van werf zijn specifieke instructies voor de build opgeslagen, die verschillende fasen van de build beschrijven (<i>beforeInstall<\/i>, <i>install<\/i>, <i>beforeSetup<\/i>, <i>setup<\/i>). De eerste stage-image bouwen we met een handtekening, gedefinieerd als de controlewaarde van de eerste stappen. Vervolgens voegen we de broncode toe, voor een nieuwe stage-image berekenen we de controlewaarde... Deze bewerkingen worden herhaald voor alle fasen, waardoor we een set stage-images krijgen. Vervolgens maken we de definitieve image, die ook metadata over zijn oorsprong bevat. En deze image taggen we op verschillende manieren (meer details later).<\/p>\n<p><img decoding=\"async\" alt=\"werf \u2014 onze tool voor CI\/CD in Kubernetes (overzicht en video van de presentatie)\" src=\"\/wp-content\/uploads\/2019\/08\/22df8b6347b45be19ecb889c102b92b5.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nLaten we aannemen dat er een nieuwe commit verschijnt waarin alleen de applicatiecode is gewijzigd. Wat gebeurt er? Voor de codewijzigingen wordt een patch gemaakt en een nieuwe stage-image voorbereid. De handtekening wordt gedefinieerd als de checksum van de oude stage-image en de nieuwe patch. Van deze image wordt een nieuwe finale image gemaakt. Een soortgelijk gedrag zal optreden bij wijzigingen op andere fasen.<\/p>\n<p>Dus, stage-images zijn een cache die gedistribueerd kan worden opgeslagen, en de images die daaruit worden gemaakt, worden in de Docker Registry geladen.<\/p>\n<p><img decoding=\"async\" alt=\"werf \u2014 onze tool voor CI\/CD in Kubernetes (overzicht en video van de presentatie)\" src=\"\/wp-content\/uploads\/2019\/08\/35805eca81605bd64c6910b7965b567e.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<\/p>\n<h3>Registry opschonen<\/h3>\n<p>\nHet gaat hier niet om het verwijderen van lagen die overgebleven zijn na verwijderde tags, dat is een standaardfunctie van Docker Registry zelf. Het gaat om de situatie waarin er veel Docker-tags zich ophopen en we begrijpen dat een deel daarvan niet meer nodig is, terwijl ze ruimte innemen (en\/of we ervoor betalen).<\/p>\n<p>Wat zijn de opschoonstrategie\u00ebn?<\/p>\n<ol>\n<li> Je kunt simpelweg niets doen <b>geen opschoning<\/b>. Soms is het echt gemakkelijker om wat extra ruimte te betalen dan een enorme kluwen van tags te ontrafelen. Maar dit werkt slechts tot op een bepaald punt.<\/li>\n<li> <b>Volledige reset<\/b>. Als je alle images verwijdert en alleen de actuele opnieuw opbouwt in het CI-systeem, kan er een probleem optreden. Als een container in de productie opnieuw wordt gestart, wordt er een nieuwe image gedownload die nog door niemand is getest. Dit verpest het idee van immutable infrastructure.<\/li>\n<li> <b>Blue-green<\/b>. Een registry begint vol te raken \u2014 we laden images naar een andere. Hetzelfde probleem als bij de vorige methode: op welk moment kunnen we de registry die vol raakt opschonen?<\/li>\n<li> <b>Op tijd<\/b>. Verwijder je alle images ouder dan 1 maand? Maar er is altijd wel een service die al een maand niet is bijgewerkt\u2026<\/li>\n<li> <b>Handmatig<\/b> bepalen wat al kan worden verwijderd.<\/li>\n<\/ol>\n<p>\nEr zijn werkelijk levensvatbare opties: niet opruimen of een combinatie van blue-green + handmatig. In het laatste geval gaat het als volgt: wanneer je beseft dat het tijd is om de registry op te ruimen, cre\u00eber je een nieuwe en voeg je alle nieuwe images eraan toe gedurende bijvoorbeeld een maand. Na een maand kijk je welke pods in Kubernetes nog steeds de oude registry gebruiken, en verplaats je ze ook naar de nieuwe registry.<\/p>\n<p>Waar zijn we beland in <b>werf<\/b>? \u041c\u044b \u0441\u043e\u0431\u0438\u0440\u0430\u0435\u043c:<\/p>\n<ol>\n<li> Git head: alle tags, alle branches \u2014 aannemende dat alles wat is getagd in Git ook in de images moet worden meegenomen (en als dat niet het geval is, moet het in Git zelf worden verwijderd);<\/li>\n<li> alle pods die momenteel in Kubernetes worden gedownload;<\/li>\n<li> oude ReplicaSets (wat recentelijk is gedownload), en we zijn ook van plan de Helm-releases te scannen en de laatste images daaruit te selecteren.<\/li>\n<\/ol>\n<p>\n\u2026 en we maken van deze set een whitelist \u2014 een lijst van beelden die we niet zullen verwijderen. Alles andere wordt schoongemaakt, waarna we wezenlijke stage-beelden vinden en ook deze verwijderen.<\/p>\n<h2>Stadium van uitrol (deploy)<\/h2>\n<p><\/p>\n<h3>Betrouwbare declarativiteit<\/h3>\n<p>\nHet eerste punt dat ik wil benadrukken bij de uitrol, is het uitrollen van de vernieuwde configuratie van middelen, die declaratief is verklaard. Het originele YAML-document met de beschrijving van Kubernetes-middelen verschilt altijd aanzienlijk van het resultaat dat daadwerkelijk in het cluster werkt. Dit komt omdat Kubernetes toevoegt aan de configuratie:<\/p>\n<ol>\n<li> identificatoren;<\/li>\n<li> service-informatie;<\/li>\n<li> veel standaardwaarden;<\/li>\n<li> sectie met de huidige status;<\/li>\n<li> wijzigingen gemaakt tijdens het werk van de admission webhook;<\/li>\n<li> resultaat van verschillende controllers (en de planner).<\/li>\n<\/ol>\n<p>\nDaarom, wanneer er een nieuwe configuratie van het middel verschijnt (<i>nieuw<\/i>), kunnen we deze niet eenvoudigweg als de huidige, 'live' configuratie overschrijven (<i>live<\/i>). Hiervoor moeten we vergelijken <i>nieuw<\/i> met de laatst toegepaste configuratie (<i>last-applied<\/i>) en de <i>live<\/i> verkregen patch toepassen.<\/p>\n<p>Deze benadering wordt genoemd <b>2-way merge<\/b>. Hij wordt bijvoorbeeld gebruikt in Helm.<\/p>\n<p>Er is ook een <b>3-way merge<\/b>, die verschilt omdat:<\/p>\n<ul>\n<li> bij het vergelijken <i>last-applied<\/i> en <i>nieuw<\/i>, kijken we naar wat er is verwijderd;<\/li>\n<li> bij het vergelijken <i>nieuw<\/i> en <i>live<\/i>, kijken we naar wat er is toegevoegd of veranderd;<\/li>\n<li> de samengevoegde patch passen we toe op <i>live<\/i>.<\/li>\n<\/ul>\n<p>\nWe deployen meer dan 1000 applicaties met Helm, dus we leven feitelijk met 2-way merge. Echter, dit heeft een aantal problemen, die we hebben opgelost met onze patches om Helm goed te laten functioneren.<\/p>\n<h3>De werkelijke status van de uitrol<\/h3>\n<p>\nNadat ons CI-systeem een nieuwe configuratie voor Kubernetes heeft gegenereerd na een gelegenheid, geeft het deze door voor toepassing <i>(apply)<\/i> in het cluster \u2014 met behulp van Helm of <code>kubectl apply<\/code>. Vervolgens vindt de eerder beschreven N-way merge plaats, waarop de Kubernetes API goedkeuring geeft aan het CI-systeem en deze aan zijn gebruiker.<\/p>\n<p><img decoding=\"async\" alt=\"werf \u2014 onze tool voor CI\/CD in Kubernetes (overzicht en video van de presentatie)\" src=\"\/wp-content\/uploads\/2019\/08\/fcc8251525f32c913f30fbcdc4296198.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nEchter, er is een enorm probleem: want <b>succesvolle toepassing betekent niet succesvolle uitrol<\/b>. Als Kubernetes begrijpt welke wijzigingen moeten worden toegepast, wordt dit toegepast \u2014 we weten nog niet wat het resultaat zal zijn. Bijvoorbeeld, de update en herstart van pods aan de frontend kan succesvol verlopen, maar aan de backend kan dat mislukken, en we krijgen verschillende versies van de uitgerolde applicatie-images.<\/p>\n<p>Om alles goed te doen, is er een extra schakel in dit schema nodig \u2014 een speciale tracker die informatie over de status van de Kubernetes API ontvangt en deze doorgeeft voor verdere analyse van de werkelijke situatie. We hebben een Open Source-bibliotheek gemaakt in Go \u2014 <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/flant\/kubedog\"><b>kubedog<\/b><\/a><\/noindex> <i>(zie de aankondiging <noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/flant\/blog\/434160\/\">hier<\/a><\/noindex>)<\/i>, \u2014 die dit probleem oplost en is ingebouwd in werf.<\/p>\n<p>Het gedrag van deze tracker op het niveau van werf wordt geconfigureerd met behulp van annotaties die worden toegepast op Deployments of StatefulSets. De belangrijkste annotatie is <code>fail-mode<\/code> en begrijpt de volgende waarden:<\/p>\n<ul>\n<li> <code>IgnoreAndContinueDeployProcess<\/code> \u2014 negeer de problemen met het uitrollen van deze component en ga door met de deploy;<\/li>\n<li> <code>FailWholeDeployProcessImmediately<\/code> \u2014 een fout in deze component stopt het deployproces;<\/li>\n<li> <code>HopeUntilEndOfDeployProcess<\/code> \u2014 we hopen dat deze component aan het einde van de deploy werkt.<\/li>\n<\/ul>\n<p>\nBijvoorbeeld, zo'n combinatie van bronnen en waarden van de annotatie <code>fail-mode<\/code>:<\/p>\n<p><img decoding=\"async\" alt=\"werf \u2014 onze tool voor CI\/CD in Kubernetes (overzicht en video van de presentatie)\" src=\"\/wp-content\/uploads\/2019\/08\/40e161710e8a535cd95f1eb66ca8407b.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nWanneer we voor de eerste keer deployen, is de database (MongoDB) mogelijk nog niet klaar \u2014 de Deployments zullen falen. Maar we kunnen wachten tot het opstart, en de deploy zal alsnog slagen.<\/p>\n<p>Er zijn nog twee annotaties voor kubedog in werf:<\/p>\n<ul>\n<li> <code>failures-allowed-per-replica<\/code> \u2014 het aantal toegestane uitval per replica;<\/li>\n<li> <code>show-logs-until<\/code> \u2014 regelt het moment tot wanneer werf logs van alle uitgerolde pods toont (in stdout). Standaard is dit <code>PodIsReady<\/code> (om berichten te negeren die we waarschijnlijk niet nodig hebben wanneer een pod verkeer begint te ontvangen), maar ook de waarden <code>ControllerIsReady<\/code> en <code>EndOfDeploy<\/code>.<\/li>\n<\/ul>\n<p><\/p>\n<h3>Wat willen we nog meer van de deploy?<\/h3>\n<p>\nNaast de twee al beschreven punten willen we graag:<\/p>\n<ul>\n<li> zien <b>logs<\/b> \u2014 en dan alleen de benodigde, en niet allemaal achtereen;<\/li>\n<li> de voortgang <b>volgen, want als een taak 'stil' een paar minuten hangt, is het belangrijk om te begrijpen wat daar aan de hand is;<\/b>automatische rollback<\/li>\n<li> in het geval dat er iets misgaat (en daarom is het cruciaal om de werkelijke status van de deploy te weten). De uitrol moet atomair zijn: of hij is volledig voltooid, of alles gaat terug naar de oude staat. <b>Voor ons als bedrijf hebben we voldoende aan een CI-systeem en een hulpmiddel<\/b> In plaats van een conclusie:<\/li>\n<\/ul>\n<p><\/p>\n<h2>Conclusies<\/h2>\n<p>\nMet werf hebben we aanzienlijke vooruitgang geboekt in het oplossen van een groot aantal problemen voor DevOps-engineers en we zouden blij zijn als een breder publiek deze tool in de praktijk probeert. Samen een goed resultaat bereiken is gemakkelijker. <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/flant\/werf\">werf<\/a><\/noindex>.<\/p>\n<p>Video en dia's<\/p>\n<p><img decoding=\"async\" alt=\"werf \u2014 onze tool voor CI\/CD in Kubernetes (overzicht en video van de presentatie)\" src=\"\/wp-content\/uploads\/2019\/08\/bafba54f2df8740a1a12c93b3476e49a.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nVideo van de presentatie (~47 minuten):<\/p>\n<h2>Video en slides<\/h2>\n<p>\nVideo van de presentatie (~47 minuten):<\/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=\"Video afspelen\" 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>Presentatie van het verslag:<\/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>\nAndere verslagen over Kubernetes op onze blog:<\/p>\n<ul>\n<li> \u00ab<noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/flant\/blog\/459326\/\">Autoscaling en resource management in Kubernetes<\/a><\/noindex>\u00bb <i>(Dmitry Stolyarov; 27 april 2019 op 'Stack')<\/i>;<\/li>\n<li> \u00ab<noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/flant\/blog\/449096\/\">Kubernetes uitbreiden en aanvullen<\/a><\/noindex>\u00bb <i>(Andrey Polovov; 8 april 2019 op Saint HighLoad++)<\/i>;<\/li>\n<li> \u00ab<noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/flant\/blog\/431500\/\">Databases en Kubernetes<\/a><\/noindex>\u00bb <i>(Dmitry Stolyarov; 8 november 2018 op HighLoad++)<\/i>;<\/li>\n<li> \u00ab<noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/flant\/blog\/412901\/\">Monitoring en Kubernetes<\/a><\/noindex>\u00bb <i>(Dmitry Stolyarov; 28 mei 2018 op RootConf)<\/i>;<\/li>\n<li> \u00ab<noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/flant\/blog\/345116\/\">Beste praktijken voor CI\/CD met Kubernetes en GitLab<\/a><\/noindex>\u00bb <i>(Dmitry Stolyarov; 7 november 2017 op HighLoad++)<\/i>;<\/li>\n<li> \u00ab<noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/flant\/blog\/331188\/\">Onze ervaring met Kubernetes in kleine projecten<\/a><\/noindex>\u00bb <i>(Dmitry Stolyarov; 6 juni 2017 op RootConf)<\/i>.<\/li>\n<\/ul>\n<p>Bron: <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.3 - 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\/nl\/blog\/administrirovanie\/werf-nash-instrument-dlya-ci-cd-v-kubernetes-obzor-i-video-doklada\" \/>\n\t\t<meta name=\"generator\" content=\"All in One SEO (AIOSEO) 5.0.3\" \/>\n\t\t<meta property=\"og:locale\" content=\"nl_NL\" \/>\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\/nl\/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 onze tool voor CI\/CD in Kubernetes (overzicht en video van de presentatie) | ProHoster","description":"Op 27 mei in de hoofdzaal van de DevOpsConf 2019, die plaatsvond tijdens het festival RIT++ 2019, tijdens de sessie 'Continue levering', werd dit gepresenteerd.","canonical_url":"https:\/\/prohoster.info\/nl\/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":"nl_NL","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\/nl\/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\/nl\/wp-json\/wp\/v2\/posts\/36754","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/prohoster.info\/nl\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/prohoster.info\/nl\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/prohoster.info\/nl\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/prohoster.info\/nl\/wp-json\/wp\/v2\/comments?post=36754"}],"version-history":[{"count":0,"href":"https:\/\/prohoster.info\/nl\/wp-json\/wp\/v2\/posts\/36754\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/prohoster.info\/nl\/wp-json\/wp\/v2\/media\/27531"}],"wp:attachment":[{"href":"https:\/\/prohoster.info\/nl\/wp-json\/wp\/v2\/media?parent=36754"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/prohoster.info\/nl\/wp-json\/wp\/v2\/categories?post=36754"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/prohoster.info\/nl\/wp-json\/wp\/v2\/tags?post=36754"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}