{"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\/pt\/blog\/administrirovanie\/werf-nash-instrument-dlya-ci-cd-v-kubernetes-obzor-i-video-doklada","title":{"rendered":"werf \u2014 nossa ferramenta para CI\/CD no Kubernetes (vis\u00e3o geral e v\u00eddeo da apresenta\u00e7\u00e3o)","gt_translate_keys":[{"key":"rendered","format":"text"}]},"content":{"rendered":"<p>27 de maio no sal\u00e3o principal da confer\u00eancia DevOpsConf 2019, realizada durante o festival <noindex><a rel=\"nofollow\" href=\"http:\/\/ritfest.ru\/2019\/\">RIT++ 2019<\/a><\/noindex>, no \u00e2mbito da se\u00e7\u00e3o \"Entrega Cont\u00ednua\", foi apresentada a palestra \"werf \u2014 nossa ferramenta para CI\/CD no Kubernetes\". Nela, falamos sobre os <b>problemas e desafios que todos enfrentam ao implantar no Kubernetes<\/b>, assim como as nuances que podem n\u00e3o ser evidentes de imediato. Ao explorar poss\u00edveis solu\u00e7\u00f5es, mostramos como isso \u00e9 implementado na ferramenta de c\u00f3digo aberto <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/flant\/werf\">werf<\/a><\/noindex>.<\/p>\n<p>Desde a apresenta\u00e7\u00e3o, nossa utilidade (anteriormente conhecida como dapp) ultrapassou a marca hist\u00f3rica de <b>1000 estrelas no GitHub<\/b> \u2014 esperamos que a comunidade crescente de usu\u00e1rios facilite a vida de muitos engenheiros de DevOps.<\/p>\n<p><img decoding=\"async\" alt=\"werf \u2014 nossa ferramenta para CI\/CD no Kubernetes (vis\u00e3o geral e v\u00eddeo da apresenta\u00e7\u00e3o)\" src=\"\/wp-content\/uploads\/2019\/08\/c2d1ad5133c0de944b60ae37e3dbe598.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nPortanto, apresentamos <noindex><a rel=\"nofollow\" href=\"https:\/\/www.youtube.com\/watch?v=cK3ackGUTLw\"><b>o v\u00eddeo da palestra<\/b><\/a><\/noindex> (~47 minutos, muito mais informativa que o artigo) e um resumo principal em formato de texto. Vamos l\u00e1!<noindex><a rel=\"nofollow\" name=\"habracut\"><\/a><\/noindex><\/p>\n<h2>Entrega de c\u00f3digo no Kubernetes<\/h2>\n<p>\nO foco da palestra n\u00e3o ser\u00e1 mais sobre werf, mas sobre CI\/CD no Kubernetes, assumindo que nosso software est\u00e1 empacotado em cont\u00eaineres Docker <i>(sobre isso falei na <noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/flant\/blog\/322686\/\">palestra de 2016<\/a><\/noindex>)<\/i>, e o K8s ser\u00e1 usado para execut\u00e1-lo em produ\u00e7\u00e3o <i>(sobre isso \u2014 na <noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/flant\/blog\/331188\/\">aceitaram<\/a><\/noindex>)<\/i>.<\/p>\n<p>Como \u00e9 a entrega no Kubernetes?<\/p>\n<ul>\n<li> H\u00e1 um reposit\u00f3rio Git com o c\u00f3digo e instru\u00e7\u00f5es para sua constru\u00e7\u00e3o. O aplicativo \u00e9 constru\u00eddo em uma imagem Docker e publicado no Docker Registry.<\/li>\n<li> Nesse mesmo reposit\u00f3rio, est\u00e3o as instru\u00e7\u00f5es sobre como implantar e executar o aplicativo. Na fase de implanta\u00e7\u00e3o, essas instru\u00e7\u00f5es s\u00e3o enviadas ao Kubernetes, que obt\u00e9m a imagem necess\u00e1ria do registry e a executa.<\/li>\n<li> Al\u00e9m disso, geralmente h\u00e1 testes. Alguns deles podem ser executados ao publicar a imagem. Tamb\u00e9m \u00e9 poss\u00edvel (com as mesmas instru\u00e7\u00f5es) implantar uma c\u00f3pia do aplicativo (em um namespace separado do K8s ou em um cluster separado) e executar testes l\u00e1.<\/li>\n<li> Finalmente, \u00e9 necess\u00e1rio um sistema de CI que receba eventos do Git (ou cliques de bot\u00e3o) e acione todas as etapas definidas: build, publish, deploy, test.<\/li>\n<\/ul>\n<p>\n<img decoding=\"async\" alt=\"werf \u2014 nossa ferramenta para CI\/CD no Kubernetes (vis\u00e3o geral e v\u00eddeo da apresenta\u00e7\u00e3o)\" src=\"\/wp-content\/uploads\/2019\/08\/ec0d1fd1bf1a68cca92badf7cf1affe1.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nAqui est\u00e3o algumas observa\u00e7\u00f5es importantes:<\/p>\n<ol>\n<li> Como temos uma infraestrutura imut\u00e1vel <i>(immutable infrastructure)<\/i>, a imagem do aplicativo, que \u00e9 utilizada em todas as etapas (staging, production, etc.), <b>deve ser \u00fanica<\/b>. <i>Mais detalhes sobre isso e exemplos foram apresentados <noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/flant\/blog\/324274\/\">aqui<\/a><\/noindex>.<\/i><\/li>\n<li> Como seguimos a abordagem de infraestrutura como c\u00f3digo <i>(IaC)<\/i>, o c\u00f3digo do aplicativo, as instru\u00e7\u00f5es para sua constru\u00e7\u00e3o e execu\u00e7\u00e3o devem estar <b>justamente em um \u00fanico reposit\u00f3rio<\/b>. <i>Mais sobre isso \u2014 consulte a <noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/flant\/blog\/324274\/\">mesma palestra<\/a><\/noindex>.<\/i><\/li>\n<li> A cadeia de entrega <i>(delivery)<\/i> geralmente a vemos assim: o aplicativo foi constru\u00eddo, testado, lan\u00e7ado <i>(fase de release)<\/i> e pronto \u2014 a entrega foi realizada. Mas, na verdade, o usu\u00e1rio recebe o que voc\u00ea lan\u00e7ou, <b>n\u00e3o<\/b> no momento em que voc\u00ea o enviou para produ\u00e7\u00e3o, e quando ele conseguiu acessar e essa produ\u00e7\u00e3o funcionou. Portanto, eu considero que a cadeia de entrega termina <b>somente na fase de opera\u00e7\u00e3o<\/b> <i>(execu\u00e7\u00e3o)<\/i>, e se quisermos ser mais precisos, at\u00e9 o momento em que o c\u00f3digo foi removido da produ\u00e7\u00e3o (substitu\u00eddo por uma nova vers\u00e3o).<\/li>\n<\/ol>\n<p>\nVamos voltar ao esquema de entrega mencionado acima no Kubernetes: ele foi criado n\u00e3o apenas por n\u00f3s, mas por praticamente todos que lidaram com esse problema. Na verdade, esse padr\u00e3o agora \u00e9 chamado de GitOps <i>(voc\u00ea pode ler mais sobre o termo e as ideias por tr\u00e1s dele <noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/flant\/blog\/458878\/\">aqui<\/a><\/noindex>)<\/i>. Vamos olhar para as etapas do esquema.<\/p>\n<h2>Fase de constru\u00e7\u00e3o (build)<\/h2>\n<p>\nPode parecer que h\u00e1 pouco a dizer em 2019 sobre a constru\u00e7\u00e3o de imagens Docker, quando todos j\u00e1 sabem escrever Dockerfiles e execut\u00e1-los. <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>O tamanho da imagem<\/b> \u00e9 importante, por isso use <noindex><a rel=\"nofollow\" href=\"https:\/\/docs.docker.com\/develop\/develop-images\/multistage-build\/\">multi-stage<\/a><\/noindex>, para manter na imagem apenas o que realmente \u00e9 necess\u00e1rio para o funcionamento do aplicativo.<\/li>\n<li> <b>O n\u00famero de camadas<\/b> deve ser minimizado, combinando cadeias de <code>RUN<\/code>-comandos por significado.<\/li>\n<li> No entanto, isso traz problemas <b>para a depura\u00e7\u00e3o<\/b>, j\u00e1 que, ao falhar a constru\u00e7\u00e3o, \u00e9 preciso encontrar o comando necess\u00e1rio na cadeia que causou o problema.<\/li>\n<li> <b>A velocidade da constru\u00e7\u00e3o<\/b> \u00e9 importante, porque queremos lan\u00e7ar mudan\u00e7as rapidamente e ver o resultado. Por exemplo, n\u00e3o queremos recompilar depend\u00eancias nas bibliotecas da linguagem a cada constru\u00e7\u00e3o do aplicativo.<\/li>\n<li> Frequentemente, de um \u00fanico reposit\u00f3rio Git, s\u00e3o necess\u00e1rios <b>muitas imagens<\/b>, que pode ser resolvido por um conjunto de Dockerfiles (ou est\u00e1gios nomeados em um \u00fanico arquivo) e um script Bash para sua constru\u00e7\u00e3o sequencial.<\/li>\n<\/ol>\n<p>\nIsso foi apenas a ponta do iceberg, que todos enfrentam. Mas h\u00e1 outros problemas, como:<\/p>\n<ol>\n<li> Frequentemente, na fase de constru\u00e7\u00e3o, precisamos <b>montar<\/b> (por exemplo, cachear o resultado de um comando como apt em um diret\u00f3rio externo).<\/li>\n<li> Queremos <b>Ansible<\/b> em vez de escrever em shell.<\/li>\n<li> Queremos <b>construir sem Docker<\/b> (por que precisamos de uma m\u00e1quina virtual adicional, onde tudo precisa ser configurado, quando j\u00e1 temos um cluster Kubernetes no qual podemos executar cont\u00eaineres?).<\/li>\n<li> <b>Constru\u00e7\u00e3o paralela<\/b>, que pode ser interpretado de maneiras diferentes: diferentes comandos de um Dockerfile (se multi-stage for usado), v\u00e1rios commits de um mesmo reposit\u00f3rio, v\u00e1rios Dockerfiles.<\/li>\n<li> <b>Constru\u00e7\u00e3o distribu\u00edda<\/b>: queremos construir algo em pods que s\u00e3o \"ef\u00eameros\", j\u00e1 que eles perdem o cache, o que significa que ele precisa ser armazenado em algum lugar separado.<\/li>\n<li> Finalmente, eu chamava o auge dos desejos <b>automagia<\/b>: seria ideal acessar o reposit\u00f3rio, digitar um comando qualquer e obter uma imagem pronta, montada com o entendimento de como e o que fazer corretamente. No entanto, pessoalmente, n\u00e3o estou certo de que todos os detalhes podem ser previstos assim.<\/li>\n<\/ol>\n<p>\nE aqui est\u00e3o os projetos:<\/p>\n<ul>\n<li> <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/moby\/buildkit\">moby\/buildkit<\/a><\/noindex> \u2014 um montador da empresa Docker Inc (j\u00e1 integrado nas vers\u00f5es mais recentes do Docker), que tenta resolver todos esses problemas;<\/li>\n<li> <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/GoogleContainerTools\/kaniko\">kaniko<\/a><\/noindex> \u2014 um montador do Google, que permite montar sem Docker;<\/li>\n<li> <noindex><a rel=\"nofollow\" href=\"https:\/\/buildpacks.io\/\">Buildpacks.io<\/a><\/noindex> \u2014 uma tentativa do CNCF de fazer automagia e, em particular, uma solu\u00e7\u00e3o interessante com rebase para camadas;<\/li>\n<li> e mais um monte de outras utilidades, como <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 e veja quantas estrelas eles t\u00eam no GitHub. Portanto, por um lado, <code>docker build<\/code> existe e pode fazer algo, mas na realidade <b>a quest\u00e3o n\u00e3o est\u00e1 totalmente resolvida<\/b> \u2014 a prova disso \u00e9 o desenvolvimento paralelo de montadores alternativos, cada um dos quais resolve uma parte dos problemas.<\/p>\n<h2>Montagem no werf<\/h2>\n<p>\nAssim, chegamos a <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/flant\/werf\">werf<\/a><\/noindex> <i>(anteriormente <noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/flant\/blog\/333682\/\">conhecida<\/a><\/noindex> como dapp)<\/i> \u2014 utilit\u00e1rio Open Source da empresa \"Flant\", que estamos desenvolvendo h\u00e1 muitos anos. Tudo come\u00e7ou h\u00e1 cerca de 5 anos com scripts Bash que otimizaram a constru\u00e7\u00e3o de Dockerfiles, e nos \u00faltimos 3 anos temos trabalhado em um projeto com seu pr\u00f3prio reposit\u00f3rio Git. <i>(primeiro em Ruby, e depois <noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/flant\/blog\/437044\/\">rewrote<\/a><\/noindex> em Go, e tamb\u00e9m mudamos o nome)<\/i>. Quais problemas de montagem foram resolvidos no werf?<\/p>\n<p><img decoding=\"async\" alt=\"werf \u2014 nossa ferramenta para CI\/CD no Kubernetes (vis\u00e3o geral e v\u00eddeo da apresenta\u00e7\u00e3o)\" src=\"\/wp-content\/uploads\/2019\/08\/ab6aa8831b49977a419fd4cc3543eb83.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nOs problemas marcados em azul j\u00e1 foram implementados, e a montagem paralela foi feita dentro de um \u00fanico host, enquanto as quest\u00f5es destacadas em amarelo planejamos finalizar at\u00e9 o final do ver\u00e3o.<\/p>\n<h2>Fase de publica\u00e7\u00e3o no registry (publish)<\/h2>\n<p>\nColetamos <code>docker push<\/code>\u2026 \u2014 o que pode ser complicado em carregar uma imagem no registry? E aqui surge a pergunta: \"Qual tag atribuir \u00e0 imagem?\" Surge porque temos <b>Gitflow<\/b> (ou outra estrat\u00e9gia do Git) e Kubernetes, e a ind\u00fastria busca fazer com que o que acontece no Kubernetes siga o que \u00e9 feito no Git. Afinal, o Git \u00e9 nossa \u00fanica fonte de verdade.<\/p>\n<p>Qual \u00e9 a complica\u00e7\u00e3o nisso? <b>Garantir a reprodutibilidade<\/b>: do commit no Git, que por natureza \u00e9 imut\u00e1vel <i>(immutable),<\/i>, para a imagem Docker, que deve ser preservada da mesma forma.<\/p>\n<p>Tamb\u00e9m \u00e9 importante para n\u00f3s <b>definir a origem<\/b>, porque queremos entender de qual commit a aplica\u00e7\u00e3o em Kubernetes foi constru\u00edda (assim seremos capazes de fazer diffs e coisas semelhantes).<\/p>\n<h3>Estrat\u00e9gias de versionamento<\/h3>\n<p>\nA primeira \u00e9 um simples <b>git tag<\/b>. Temos um registro com a imagem marcada como <code>1.0<\/code>. No Kubernetes, existem os ambientes de stage e produ\u00e7\u00e3o, onde essa imagem foi implantada. No Git, fazemos commits e em algum momento aplicamos a tag <code>2.0<\/code>. Coletamos seguindo as instru\u00e7\u00f5es do reposit\u00f3rio e colocamos no registro com a tag <code>2.0<\/code>. Implantamos no stage e, se tudo estiver bom, depois na produ\u00e7\u00e3o.<\/p>\n<p><img decoding=\"async\" alt=\"werf \u2014 nossa ferramenta para CI\/CD no Kubernetes (vis\u00e3o geral e v\u00eddeo da apresenta\u00e7\u00e3o)\" src=\"\/wp-content\/uploads\/2019\/08\/8545b84bfa63a91773f0d7dc1c2bd49f.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nO problema desse abordagem \u00e9 que primeiro aplicamos a tag e s\u00f3 depois testamos e implantamos. Por qu\u00ea? Primeiro, isso \u00e9 simplesmente il\u00f3gico: estamos lan\u00e7ando uma vers\u00e3o de um software que ainda n\u00e3o verificamos (n\u00e3o podemos fazer diferente, pois para testar \u00e9 necess\u00e1rio aplicar a tag). Em segundo lugar, esse caminho n\u00e3o se alinha com o Gitflow.<\/p>\n<p>A segunda op\u00e7\u00e3o \u00e9 <b>git commit + tag<\/b>. Na branch master, h\u00e1 uma tag <code>1.0<\/code>; para ela no registro - uma imagem implantada na produ\u00e7\u00e3o. Al\u00e9m disso, h\u00e1 contornos de preview e staging no cluster Kubernetes. Em seguida, seguimos o Gitflow: na branch principal de desenvolvimento (<code>develop<\/code>) fazemos novas funcionalidades, resultando em um commit com o identificador <code>#c1<\/code>. Coletamos e publicamos no registro, usando esse identificador (<code>#c1<\/code>). Com o mesmo identificador, implantamos no preview. Fazemos o mesmo com os commits <code>#c2<\/code> e <code>#c3<\/code>.<\/p>\n<p>Quando entendemos que temos funcionalidades suficientes, come\u00e7amos a estabilizar tudo. No Git, criamos uma branch <code>release_1.1<\/code> (baseado em <code>#c3<\/code> do <code>develop<\/code>). N\u00e3o ser\u00e1 necess\u00e1rio coletar essa vers\u00e3o, pois j\u00e1 foi feita na etapa anterior. Portanto, podemos simplesmente implant\u00e1-la no staging. Corrigimos bugs em <code>#c4<\/code> e implantamos no staging de forma similar. Paralelamente, est\u00e1 ocorrendo desenvolvimento em <code>develop<\/code>, onde periodicamente absorvemos altera\u00e7\u00f5es de <code>release_1.1<\/code>. Em algum momento, obtemos um commit coletivo e implantado no staging do qual estamos satisfeitos (<code>#c25<\/code>).<\/p>\n<p>Nesse caso, fazemos um merge (com fast-forward) da branch de release (<code>release_1.1<\/code>) na master. Aplicamos uma tag com a nova vers\u00e3o a esse commit (<code>1.1<\/code>). Mas essa imagem j\u00e1 foi coletada no registro, ent\u00e3o, para n\u00e3o colet\u00e1-la novamente, simplesmente adicionamos uma segunda tag \u00e0 imagem existente (agora ela no registro tem as tags <code>#c25<\/code> e <code>1.1<\/code>). Ap\u00f3s isso, implantamos na produ\u00e7\u00e3o.<\/p>\n<p>H\u00e1 uma desvantagem, pois no staging foi implantada uma imagem (<code>#c25<\/code>), enquanto na produ\u00e7\u00e3o parece que \u00e9 outra (<code>1.1<\/code>), mas sabemos que \u201cfisicamente\u201d \u00e9 a mesma imagem do registro.<\/p>\n<p><img decoding=\"async\" alt=\"werf \u2014 nossa ferramenta para CI\/CD no Kubernetes (vis\u00e3o geral e v\u00eddeo da apresenta\u00e7\u00e3o)\" src=\"\/wp-content\/uploads\/2019\/08\/b3fa2c34442aa401d1e7b30eb593be9a.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nO verdadeiro por\u00e9m \u00e9 que n\u00e3o h\u00e1 suporte para merge commits, \u00e9 necess\u00e1rio fazer um fast-forward.<\/p>\n<p>Podemos ir mais longe e fazer um truque\u2026 Vamos considerar um exemplo de um Dockerfile simples:<\/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>\nConstruiremos a partir dele um arquivo com o seguinte princ\u00edpio, que pegaremos:<\/p>\n<ul>\n<li> SHA256 dos identificadores das imagens utilizadas (<code>ruby:2.3<\/code> e <code>nginx:alpine<\/code>), que s\u00e3o as somas de verifica\u00e7\u00e3o de seu conte\u00fado;<\/li>\n<li> todos os comandos (<code>RUN<\/code>, <code>CMD<\/code> etc.);<\/li>\n<li> SHA256 dos arquivos que foram adicionados.<\/li>\n<\/ul>\n<p>\n\u2026 e pegaremos a soma de verifica\u00e7\u00e3o (novamente SHA256) desse arquivo. Isso \u00e9 <b>a assinatura<\/b> de tudo que define o conte\u00fado da imagem Docker.<\/p>\n<p><img decoding=\"async\" alt=\"werf \u2014 nossa ferramenta para CI\/CD no Kubernetes (vis\u00e3o geral e v\u00eddeo da apresenta\u00e7\u00e3o)\" src=\"\/wp-content\/uploads\/2019\/08\/b2af0b7952eefe26ddbb145955e778e8.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nVoltando ao esquema, <b>em vez de commits, usaremos essas assinaturas,<\/b>, ou seja, marcarmos imagens com assinaturas.<\/p>\n<p><img decoding=\"async\" alt=\"werf \u2014 nossa ferramenta para CI\/CD no Kubernetes (vis\u00e3o geral e v\u00eddeo da apresenta\u00e7\u00e3o)\" src=\"\/wp-content\/uploads\/2019\/08\/115f290bd5951614c041b3d510fae38e.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nAgora, quando precisarmos, por exemplo, fazer o merge das mudan\u00e7as da release no master, podemos fazer um verdadeiro merge commit: ele ter\u00e1 um identificador diferente, mas a mesma assinatura. Com o mesmo identificador, iremos liberar a imagem em produ\u00e7\u00e3o.<\/p>\n<p>A desvantagem \u00e9 que agora n\u00e3o conseguiremos determinar qual commit foi implantado na produ\u00e7\u00e3o \u2014 as somas de verifica\u00e7\u00e3o funcionam apenas em uma dire\u00e7\u00e3o. Este problema \u00e9 resolvido com uma camada adicional de metadados \u2014 explicarei mais adiante.<\/p>\n<h3>Marca\u00e7\u00e3o no werf<\/h3>\n<p>\nNo werf, fomos ainda mais longe e estamos nos preparando para uma constru\u00e7\u00e3o distribu\u00edda com um cache que n\u00e3o \u00e9 armazenado em uma \u00fanica m\u00e1quina\u2026 Ent\u00e3o, estamos construindo imagens Docker de dois tipos, a quais chamamos de <i>stage<\/i> e <i>image<\/i>.<\/p>\n<p>No reposit\u00f3rio Git do werf, armazenamos instru\u00e7\u00f5es espec\u00edficas para a constru\u00e7\u00e3o, descrevendo diferentes etapas da constru\u00e7\u00e3o (<i>beforeInstall<\/i>, <i>install<\/i>, <i>beforeSetup<\/i>, <i>setup<\/i>). O primeiro est\u00e1gio da imagem \u00e9 constru\u00eddo com a assinatura, definida como a soma de verifica\u00e7\u00e3o dos primeiros passos. Em seguida, adicionamos o c\u00f3digo-fonte; para a nova imagem do est\u00e1gio, consideramos a sua soma de verifica\u00e7\u00e3o\u2026 Essas opera\u00e7\u00f5es se repetem para todas as etapas, resultando em um conjunto de imagens de est\u00e1gio. Em seguida, fazemos a imagem final, que cont\u00e9m tamb\u00e9m metadados sobre sua origem. E essa imagem \u00e9 marcada de v\u00e1rias maneiras (detalhes depois).<\/p>\n<p><img decoding=\"async\" alt=\"werf \u2014 nossa ferramenta para CI\/CD no Kubernetes (vis\u00e3o geral e v\u00eddeo da apresenta\u00e7\u00e3o)\" src=\"\/wp-content\/uploads\/2019\/08\/22df8b6347b45be19ecb889c102b92b5.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nSuponha que depois disso aparece um novo commit, onde apenas o c\u00f3digo do aplicativo foi alterado. O que acontecer\u00e1? Um patch ser\u00e1 criado para as altera\u00e7\u00f5es de c\u00f3digo, um novo stage-image ser\u00e1 preparado. A sua assinatura ser\u00e1 determinada como o checksum da antiga stage-image e do novo patch. A partir dessa imagem, um novo final image ser\u00e1 formado. Um comportamento semelhante ocorrer\u00e1 nas mudan\u00e7as em outras etapas.<\/p>\n<p>Assim, os stage-images s\u00e3o um cache que pode ser armazenado de forma distribu\u00edda, enquanto as image-images criadas a partir dele s\u00e3o carregadas no Docker Registry.<\/p>\n<p><img decoding=\"async\" alt=\"werf \u2014 nossa ferramenta para CI\/CD no Kubernetes (vis\u00e3o geral e v\u00eddeo da apresenta\u00e7\u00e3o)\" src=\"\/wp-content\/uploads\/2019\/08\/35805eca81605bd64c6910b7965b567e.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<\/p>\n<h3>Limpeza do registry<\/h3>\n<p>\nA quest\u00e3o n\u00e3o \u00e9 sobre a remo\u00e7\u00e3o de camadas que ficaram penduradas ap\u00f3s tags removidas \u2014 essa \u00e9 uma funcionalidade padr\u00e3o do pr\u00f3prio Docker Registry. Trata-se da situa\u00e7\u00e3o em que se acumula um grande n\u00famero de tags Docker e percebemos que parte delas n\u00e3o nos \u00e9 mais necess\u00e1ria, ocupando espa\u00e7o (e\/ou pagando por isso).<\/p>\n<p>Quais s\u00e3o as estrat\u00e9gias de limpeza?<\/p>\n<ol>\n<li> Pode-se simplesmente n\u00e3o fazer nada <b>n\u00e3o limpar<\/b>. \u00c0s vezes, realmente \u00e9 mais f\u00e1cil pagar um pouco a mais por espa\u00e7o extra do que desfazer um emaranhado enorme de tags. Mas isso s\u00f3 funciona at\u00e9 certo ponto.<\/li>\n<li> <b>Reset completo<\/b>. Se todos os images forem deletados e apenas os atuais forem reconstru\u00eddos no sistema CI, isso pode causar um problema. Se um container for reiniciado em produ\u00e7\u00e3o, ele carregar\u00e1 uma nova imagem \u2014 uma que ainda n\u00e3o foi testada por ningu\u00e9m. Isso compromete a ideia de infraestrutura imut\u00e1vel.<\/li>\n<li> <b>Blue-green<\/b>. Um registry come\u00e7ou a ficar cheio \u2014 carregamos imagens em outro. O mesmo problema que na abordagem anterior: em que momento podemos limpar o registry que come\u00e7ou a ficar cheio?<\/li>\n<li> <b>Baseado no tempo<\/b>. Remover todas as imagens com mais de 1 m\u00eas? Mas sempre haver\u00e1 algum servi\u00e7o que n\u00e3o foi atualizado por um m\u00eas inteiro...<\/li>\n<li> <b>Manual<\/b> determinar o que j\u00e1 pode ser deletado.<\/li>\n<\/ol>\n<p>\nExistem duas variantes realmente vi\u00e1veis: n\u00e3o limpar ou uma combina\u00e7\u00e3o de blue-green + manual. Nesta \u00faltima, trata-se do seguinte: quando voc\u00ea entender que \u00e9 hora de limpar o registro, cria um novo e adiciona todas as novas imagens a ele ao longo de um m\u00eas, por exemplo. E ap\u00f3s um m\u00eas, veja quais pods no Kubernetes ainda usam o registro antigo e os transfira para o novo registro.<\/p>\n<p>Aonde chegamos em <b>werf<\/b>? \u041c\u044b \u0441\u043e\u0431\u0438\u0440\u0430\u0435\u043c:<\/p>\n<ol>\n<li> Git head: todas as tags, todos os ramos, considerando que tudo o que est\u00e1 tagueado no Git, precisamos nos imagens (e se n\u00e3o, deve ser removido no pr\u00f3prio Git);<\/li>\n<li> todos os pods que est\u00e3o sendo baixados agora no Kubernetes;<\/li>\n<li> ReplicaSets antigos (o que foi baixado recentemente), al\u00e9m de planejarmos escanear as libera\u00e7\u00f5es do Helm e selecionar as \u00faltimas imagens l\u00e1.<\/li>\n<\/ol>\n<p>\n\u2026 e fazemos dessa cole\u00e7\u00e3o uma lista branca \u2014 uma lista de imagens que n\u00e3o iremos deletar. Todo o resto \u00e9 eliminado, ap\u00f3s o qual encontramos imagens orf\u00e3s e tamb\u00e9m as deletamos.<\/p>\n<h2>Fase de implanta\u00e7\u00e3o (deploy)<\/h2>\n<p><\/p>\n<h3>Declaratividade confi\u00e1vel<\/h3>\n<p>\nO primeiro ponto que gostaria de destacar na implanta\u00e7\u00e3o \u00e9 a aplica\u00e7\u00e3o de uma nova configura\u00e7\u00e3o de recursos, declarada de forma declarativa. O documento YAML original que descreve os recursos do Kubernetes costuma ser bastante diferente do resultado que realmente opera no cluster. Porque o Kubernetes adiciona \u00e0 configura\u00e7\u00e3o:<\/p>\n<ol>\n<li> identificadores;<\/li>\n<li> informa\u00e7\u00f5es de sistema;<\/li>\n<li> v\u00e1rios valores padr\u00e3o;<\/li>\n<li> se\u00e7\u00e3o com o status atual;<\/li>\n<li> altera\u00e7\u00f5es feitas no contexto do admission webhook;<\/li>\n<li> resultado do trabalho de v\u00e1rios controladores (e do scheduler).<\/li>\n<\/ol>\n<p>\nPortanto, quando uma nova configura\u00e7\u00e3o de recurso aparece (<i>novo<\/i>), n\u00e3o podemos simplesmente sobrescrever a configura\u00e7\u00e3o atual, \"viva\", (<i>live<\/i>). Para isso, teremos que compar\u00e1-la <i>novo<\/i> com a \u00faltima configura\u00e7\u00e3o aplicada (<i>\"last-applied\"<\/i>) e aplicar o patch obtido. <i>live<\/i> Essa abordagem \u00e9 chamada de<\/p>\n<p>mesclagem de 2 vias <b>. Ela \u00e9 usada, por exemplo, no Helm.<\/b>Existe tamb\u00e9m a<\/p>\n<p>mesclagem de 3 vias <b>, que se diferencia pelo fato de que:<\/b>ao comparar<\/p>\n<ul>\n<li> , observamos o que foi removido; <i>\"last-applied\"<\/i> e <i>novo<\/i>, observamos o que foi adicionado ou modificado;<\/li>\n<li> , observamos o que foi removido; <i>novo<\/i> e <i>live<\/i>aplicamos o patch resumido em<\/li>\n<li> Implantamos mais de 1000 aplicativos com o Helm, portanto, na pr\u00e1tica, vivemos com a mesclagem de 2 vias. No entanto, ela possui uma s\u00e9rie de problemas, que resolvemos com nossos patches, ajudando o Helm a funcionar corretamente. <i>live<\/i>.<\/li>\n<\/ul>\n<p>\nEstamos implantando mais de 1000 aplica\u00e7\u00f5es com Helm, portanto, na verdade, vivemos com merge bidirecional. No entanto, ele tem uma s\u00e9rie de problemas que resolvemos com nossos patches, ajudando o Helm a funcionar corretamente.<\/p>\n<h3>Ap\u00f3s um evento, nosso sistema CI gerou uma nova configura\u00e7\u00e3o para o Kubernetes, que \u00e9 enviada para aplica\u00e7\u00e3o<\/h3>\n<p>\n(apply) <i>no cluster \u2014 usando Helm ou<\/i> . Em seguida, ocorre a mesclagem N-way j\u00e1 descrita, \u00e0 qual a API do Kubernetes responde de forma positiva ao sistema CI, e este, ao seu usu\u00e1rio. <code>kubectl apply<\/code>No entanto, h\u00e1 um enorme problema: porque<\/p>\n<p><img decoding=\"async\" alt=\"werf \u2014 nossa ferramenta para CI\/CD no Kubernetes (vis\u00e3o geral e v\u00eddeo da apresenta\u00e7\u00e3o)\" src=\"\/wp-content\/uploads\/2019\/08\/fcc8251525f32c913f30fbcdc4296198.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\na aplica\u00e7\u00e3o bem-sucedida n\u00e3o significa uma implanta\u00e7\u00e3o bem-sucedida. <b>Se o Kubernetes entendeu quais altera\u00e7\u00f5es devem ser aplicadas e as aplica \u2014 ainda n\u00e3o sabemos qual ser\u00e1 o resultado. Por exemplo, a atualiza\u00e7\u00e3o e rein\u00edcio de pods no frontend podem ocorrer com sucesso, enquanto no backend pode n\u00e3o, e acabamos tendo diferentes vers\u00f5es das imagens do aplicativo em execu\u00e7\u00e3o.<\/b>. Se o Kubernetes entendeu quais mudan\u00e7as precisam ser aplicadas, ele as aplica \u2014 ainda n\u00e3o sabemos o que resultar\u00e1 disso. Por exemplo, a atualiza\u00e7\u00e3o e rein\u00edcio dos pods no frontend pode ser bem-sucedida, enquanto no backend pode n\u00e3o ser, e obteremos diferentes vers\u00f5es das imagens da aplica\u00e7\u00e3o em execu\u00e7\u00e3o.<\/p>\n<p>Para fazer tudo corretamente, neste esquema surge um elo adicional \u2014 um rastreador especial que receber\u00e1 informa\u00e7\u00f5es do Kubernetes API sobre o status e as transmitir\u00e1 para uma an\u00e1lise mais profunda da situa\u00e7\u00e3o real. Criamos uma biblioteca Open Source em Go \u2014 <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/flant\/kubedog\"><b>kubedog<\/b><\/a><\/noindex> <i>(veja seu an\u00fancio <noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/flant\/blog\/434160\/\">aqui<\/a><\/noindex>)<\/i>, \u2014 que resolve esse problema e est\u00e1 integrada ao werf.<\/p>\n<p>O comportamento desse rastreador no n\u00edvel do werf \u00e9 configurado por meio de anota\u00e7\u00f5es que s\u00e3o aplicadas em Deployments ou StatefulSets. A principal anota\u00e7\u00e3o \u00e9 <code>fail-mode<\/code> \u2014 que compreende os seguintes valores:<\/p>\n<ul>\n<li> <code>IgnoreAndContinueDeployProcess<\/code> \u2014 ignoramos os problemas na implanta\u00e7\u00e3o deste componente e continuamos o deploy;<\/li>\n<li> <code>FailWholeDeployProcessImmediately<\/code> \u2014 um erro neste componente interrompe o processo de deploy;<\/li>\n<li> <code>HopeUntilEndOfDeployProcess<\/code> \u2014 esperamos que este componente funcione at\u00e9 o final do deploy.<\/li>\n<\/ul>\n<p>\nPor exemplo, essa combina\u00e7\u00e3o de recursos e valores da anota\u00e7\u00e3o <code>fail-mode<\/code>:<\/p>\n<p><img decoding=\"async\" alt=\"werf \u2014 nossa ferramenta para CI\/CD no Kubernetes (vis\u00e3o geral e v\u00eddeo da apresenta\u00e7\u00e3o)\" src=\"\/wp-content\/uploads\/2019\/08\/40e161710e8a535cd95f1eb66ca8407b.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nQuando implantamos pela primeira vez, o banco de dados (MongoDB) pode n\u00e3o estar pronto \u2014 os Deployments falhar\u00e3o. Mas podemos aguardar at\u00e9 que ele inicie, e a implanta\u00e7\u00e3o ainda assim ser\u00e1 realizada.<\/p>\n<p>Existem mais duas anota\u00e7\u00f5es para kubedog no werf:<\/p>\n<ul>\n<li> <code>failures-allowed-per-replica<\/code> \u2014 o n\u00famero de falhas permitidas por r\u00e9plica;<\/li>\n<li> <code>show-logs-until<\/code> \u2014 regula o momento at\u00e9 o qual o werf exibe (no stdout) os logs de todos os pods sendo implantados. Por padr\u00e3o, isso \u00e9 <code>PodIsReady<\/code> (para ignorar mensagens que provavelmente n\u00e3o precisamos quando o pod come\u00e7a a receber tr\u00e1fego), no entanto, tamb\u00e9m s\u00e3o aceitos os valores <code>ControllerIsReady<\/code> e <code>EndOfDeploy<\/code>.<\/li>\n<\/ul>\n<p><\/p>\n<h3>O que mais queremos da implanta\u00e7\u00e3o?<\/h3>\n<p>\nAl\u00e9m dos dois pontos j\u00e1 descritos, gostar\u00edamos de:<\/p>\n<ul>\n<li> ver <b>logs<\/b> \u2014 e apenas os necess\u00e1rios, n\u00e3o todos eles;<\/li>\n<li> monitorar <b>o progresso<\/b>, porque se uma job \"silenciosamente\" estiver travada por v\u00e1rios minutos, \u00e9 importante entender o que est\u00e1 acontecendo;<\/li>\n<li> ter <b>rollback autom\u00e1tico<\/b> caso algo d\u00ea errado (da\u00ed a import\u00e2ncia de saber o status real do deploy). A implanta\u00e7\u00e3o deve ser at\u00f4mica: ou ocorre at\u00e9 o final, ou tudo volta ao estado anterior.<\/li>\n<\/ul>\n<p><\/p>\n<h2>Resultados<\/h2>\n<p>\nComo empresa, para implementar todas essas nuances em diferentes etapas da entrega (build, publish, deploy), precisamos apenas de um sistema CI e da ferramenta <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/flant\/werf\">werf<\/a><\/noindex>.<\/p>\n<p>Em conclus\u00e3o:<\/p>\n<p><img decoding=\"async\" alt=\"werf \u2014 nossa ferramenta para CI\/CD no Kubernetes (vis\u00e3o geral e v\u00eddeo da apresenta\u00e7\u00e3o)\" src=\"\/wp-content\/uploads\/2019\/08\/bafba54f2df8740a1a12c93b3476e49a.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nCom o werf, avan\u00e7amos consideravelmente na resolu\u00e7\u00e3o de uma s\u00e9rie de problemas enfrentados por engenheiros DevOps e ficaremos felizes se uma comunidade mais ampla pelo menos experimentar essa ferramenta na pr\u00e1tica. Alcan\u00e7ar bons resultados juntos ser\u00e1 mais f\u00e1cil.<\/p>\n<h2>V\u00eddeo e slides<\/h2>\n<p>\nV\u00eddeo da apresenta\u00e7\u00e3o (~47 minutos):<\/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=\"Por que no hosting comum a administra\u00e7\u00e3o do PrestaShop \u00e9 lenta, e no seu n\u00e3o \u00e9?\" 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>Apresenta\u00e7\u00e3o da palestra:<\/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>\nOutros relat\u00f3rios sobre Kubernetes em nosso blog:<\/p>\n<ul>\n<li> \u00ab<noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/flant\/blog\/459326\/\">Autoescalonamento e gerenciamento de recursos no Kubernetes<\/a><\/noindex>\u00bb <i>(Dmitry Stolyarov; 27 de abril de 2019 no \"Stachka\")<\/i>;<\/li>\n<li> \u00ab<noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/flant\/blog\/449096\/\">Expandindo e complementando o Kubernetes<\/a><\/noindex>\u00bb <i>(Andrey Polovov; 8 de abril de 2019 no Saint HighLoad++)<\/i>;<\/li>\n<li> \u00ab<noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/flant\/blog\/431500\/\">Bancos de dados e Kubernetes<\/a><\/noindex>\u00bb <i>(Dmitry Stolyarov; 8 de novembro de 2018 no HighLoad++)<\/i>;<\/li>\n<li> \u00ab<noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/flant\/blog\/412901\/\">Monitoramento e Kubernetes<\/a><\/noindex>\u00bb <i>(Dmitry Stolyarov; 28 de maio de 2018 no RootConf)<\/i>;<\/li>\n<li> \u00ab<noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/flant\/blog\/345116\/\">Melhores pr\u00e1ticas de CI\/CD com Kubernetes e GitLab<\/a><\/noindex>\u00bb <i>(Dmitry Stolyarov; 7 de novembro de 2017 no HighLoad++)<\/i>;<\/li>\n<li> \u00ab<noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/flant\/blog\/331188\/\">Nossa experi\u00eancia com Kubernetes em pequenos projetos<\/a><\/noindex>\u00bb <i>(Dmitry Stolyarov; 6 de junho de 2017 no RootConf)<\/i>.<\/li>\n<\/ul>\n<p>Fonte: <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\/pt\/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=\"pt_PT\" \/>\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\/pt\/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 nossa ferramenta para CI\/CD no Kubernetes (resumo e v\u00eddeo da apresenta\u00e7\u00e3o) | ProHoster","description":"No dia 27 de maio, no sal\u00e3o principal da confer\u00eancia DevOpsConf 2019, realizada durante o festival RIT++ 2019, na se\u00e7\u00e3o \"Entrega Cont\u00ednua\", foi apresentado.","canonical_url":"https:\/\/prohoster.info\/pt\/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":"pt_PT","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\/pt\/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\/pt\/wp-json\/wp\/v2\/posts\/36754","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/prohoster.info\/pt\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/prohoster.info\/pt\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/prohoster.info\/pt\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/prohoster.info\/pt\/wp-json\/wp\/v2\/comments?post=36754"}],"version-history":[{"count":0,"href":"https:\/\/prohoster.info\/pt\/wp-json\/wp\/v2\/posts\/36754\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/prohoster.info\/pt\/wp-json\/wp\/v2\/media\/27531"}],"wp:attachment":[{"href":"https:\/\/prohoster.info\/pt\/wp-json\/wp\/v2\/media?parent=36754"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/prohoster.info\/pt\/wp-json\/wp\/v2\/categories?post=36754"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/prohoster.info\/pt\/wp-json\/wp\/v2\/tags?post=36754"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}