{"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\/it\/blog\/administrirovanie\/werf-nash-instrument-dlya-ci-cd-v-kubernetes-obzor-i-video-doklada","title":{"rendered":"werf \u2014 il nostro strumento per CI\/CD in Kubernetes (panoramica e video della presentazione)","gt_translate_keys":[{"key":"rendered","format":"text"}]},"content":{"rendered":"<p>27 maggio nell'aula principale della conferenza DevOpsConf 2019, che si svolge nell'ambito del festival <noindex><a rel=\"nofollow\" href=\"http:\/\/ritfest.ru\/2019\/\">RIT++ 2019<\/a><\/noindex>, all'interno della sezione \"Continuous Delivery\", \u00e8 stata presentata la relazione \"werf \u2014 il nostro strumento per CI\/CD in Kubernetes\". In essa si parla di quelli <b>problemi e sfide che tutti affrontano durante il deploy in Kubernetes<\/b>, cos\u00ec come delle sfumature che potrebbero non essere subito evidenti. Analizzando possibili soluzioni, mostriamo come ci\u00f2 sia implementato nello strumento Open Source <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/flant\/werf\">werf<\/a><\/noindex>.<\/p>\n<p>Dalla presentazione, il nostro strumento (precedentemente noto come dapp) ha superato una pietra miliare storica di <b>1000 stelle su GitHub<\/b> \u2014 speriamo che la crescente comunit\u00e0 di utenti ne semplifichi la vita a molti ingegneri DevOps.<\/p>\n<p><img decoding=\"async\" alt=\"werf \u2014 il nostro strumento per CI\/CD in Kubernetes (panoramica e video della presentazione)\" src=\"\/wp-content\/uploads\/2019\/08\/c2d1ad5133c0de944b60ae37e3dbe598.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nQuindi, presentiamo <noindex><a rel=\"nofollow\" href=\"https:\/\/www.youtube.com\/watch?v=cK3ackGUTLw\"><b>il video della presentazione<\/b><\/a><\/noindex> (~47 minuti, molto pi\u00f9 informativo dell'articolo) e un riepilogo principale in forma testuale. Andiamo!<noindex><a rel=\"nofollow\" name=\"habracut\"><\/a><\/noindex><\/p>\n<h2>Consegna del codice in Kubernetes<\/h2>\n<p>\nLa relazione parler\u00e0 pi\u00f9 di CI\/CD in Kubernetes, implicando che il nostro software \u00e8 confezionato in container Docker <i>(ne ho parlato nella <noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/flant\/blog\/322686\/\">relazione del 2016<\/a><\/noindex>)<\/i>, con K8s usato per il suo avvio in produzione <i>(di questo ne parlo nella <noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/flant\/blog\/331188\/\">del 2017<\/a><\/noindex>)<\/i>.<\/p>\n<p>Com'\u00e8 la consegna in Kubernetes?<\/p>\n<ul>\n<li> Esiste un repository Git con il codice e le istruzioni per la sua compilazione. L'applicazione viene compilata in un'immagine Docker e pubblicata nel Docker Registry.<\/li>\n<li> Nel medesimo repository ci sono anche istruzioni su come eseguire il deploy e avviare l'applicazione. Durante la fase di deploy, queste istruzioni vengono inviate a Kubernetes, il quale preleva l'immagine necessaria dal registry e la avvia.<\/li>\n<li> Inoltre, di solito ci sono test. Alcuni di essi possono essere eseguiti al momento della pubblicazione dell'immagine. \u00c8 anche possibile (seguendo le stesse istruzioni) distribuire una copia dell'applicazione (in uno spazio dei nomi K8s separato o in un cluster separato) e eseguire test l\u00ec.<\/li>\n<li> Infine, \u00e8 necessaria una CI-system che riceva eventi da Git o da click e chiami tutte le fasi designate: build, publish, deploy, test.<\/li>\n<\/ul>\n<p>\n<img decoding=\"async\" alt=\"werf \u2014 il nostro strumento per CI\/CD in Kubernetes (panoramica e video della presentazione)\" src=\"\/wp-content\/uploads\/2019\/08\/ec0d1fd1bf1a68cca92badf7cf1affe1.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nQui ci sono alcune osservazioni importanti:<\/p>\n<ol>\n<li> Poich\u00e9 abbiamo un'infrastruttura immutabile <i>(immutable infrastructure)<\/i>, l'immagine dell'applicazione, utilizzata in tutte le fasi (staging, production, ecc.), <b>deve essere unica<\/b>. <i>Ho parlato di questo e fornito esempi <noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/flant\/blog\/324274\/\">qui<\/a><\/noindex>.<\/i><\/li>\n<li> Poich\u00e9 seguiamo l'approccio dell'infrastruttura come codice <i>(IaC)<\/i>, il codice dell'applicazione, le istruzioni per la sua compilazione e avvio devono trovarsi <b>esattamente nello stesso repository<\/b>. <i>Maggiore attenzione a questo \u2014 vedi nella <noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/flant\/blog\/324274\/\">stessa relazione<\/a><\/noindex>.<\/i><\/li>\n<li> Catena di consegna <i>(consegna)<\/i> di solito la vediamo cos\u00ec: l'app \u00e8 stata assemblata, testata, rilasciata <i>(fase di rilascio)<\/i> e tutto \u2014 la consegna \u00e8 avvenuta. Ma in realt\u00e0, l'utente riceve ci\u00f2 che hai distribuito, <b>non<\/b> quando l'hai consegnato in produzione, ma quando \u00e8 riuscito a entrarvi e quella produzione ha funzionato. Perci\u00f2 considero che la catena di consegna finisca <b>solo nella fase di sfruttamento<\/b> <i>(esecuzione)<\/i>, e per essere pi\u00f9 precisi, addirittura nel momento in cui il codice \u00e8 stato rimosso dalla produzione (sostituendolo con uno nuovo).<\/li>\n<\/ol>\n<p>\nTorniamo allo schema di consegna descritto sopra in Kubernetes: non lo abbiamo inventato solo noi, ma praticamente chiunque si sia occupato di questo problema. In sostanza, questo modello \u00e8 ora chiamato GitOps <i>(magari puoi leggere di pi\u00f9 sul termine e sulle idee che lo supportano <noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/flant\/blog\/458878\/\">qui<\/a><\/noindex>)<\/i>. Diamo un'occhiata alle fasi dello schema.<\/p>\n<h2>Fase di assemblaggio<\/h2>\n<p>\nA prima vista, cosa si pu\u00f2 dire nel 2019 riguardo alla costruzione di immagini Docker, quando tutti sanno scrivere Dockerfile e avviarli? <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>Il peso dell'immagine<\/b> \u00e8 importante, quindi usa <noindex><a rel=\"nofollow\" href=\"https:\/\/docs.docker.com\/develop\/develop-images\/multistage-build\/\">multi-stage<\/a><\/noindex>, per mantenere nell'immagine solo ci\u00f2 che \u00e8 realmente necessario per il funzionamento dell'applicazione.<\/li>\n<li> <b>Il numero di livelli<\/b> deve essere minimizzato, unendo le catene di <code>comandi RUN<\/code>per significato.<\/li>\n<li> Tuttavia, questo aggiunge problemi <b>al debug<\/b>, poich\u00e9 quando si verifica un errore durante l'assemblaggio \u00e8 necessario trovare il comando giusto nella catena che ha causato il problema.<\/li>\n<li> <b>La velocit\u00e0 di assemblaggio<\/b> \u00e8 importante, perch\u00e9 vogliamo rilasciare rapidamente le modifiche e vedere i risultati. Ad esempio, non vogliamo ricompilare le dipendenze delle librerie del linguaggio ad ogni assemblaggio dell'app.<\/li>\n<li> Spesso da un unico repository Git sono necessari <b>molte immagini<\/b>, che pu\u00f2 essere risolto utilizzando un insieme di Dockerfile (o fasi nominate in un unico file) e uno script Bash con la loro costruzione sequenziale.<\/li>\n<\/ol>\n<p>\nQuesto era solo la punta dell'iceberg con cui tutti si scontrano. Ma ci sono anche altri problemi, in particolare:<\/p>\n<ol>\n<li> Spesso nella fase di assemblaggio abbiamo bisogno di <b>montare qualcosa<\/b> (ad esempio, memorizzare nella cache il risultato di comandi come apt in una directory esterna).<\/li>\n<li> Vogliamo <b>Ansible<\/b> anzich\u00e9 scrivere in shell.<\/li>\n<li> Vogliamo <b>assemblare senza Docker<\/b> (perch\u00e9 dovremmo avere una macchina virtuale aggiuntiva in cui dover configurare tutto questo, quando abbiamo gi\u00e0 un cluster Kubernetes che pu\u00f2 eseguire i contenitori?).<\/li>\n<li> <b>Assemblaggio parallelo<\/b>, che pu\u00f2 essere interpretato in modo diverso: comandi diversi da Dockerfile (se si utilizza multi-stage), pi\u00f9 commit di un unico repository, pi\u00f9 Dockerfile.<\/li>\n<li> <b>Build distribuito<\/b>: vogliamo costruire qualcosa in pod, che sono \"effimeri\", poich\u00e9 perdono la cache, quindi bisogna conservarla altrove.<\/li>\n<li> Infine, ho chiamato il culmine dei desideri <b>automagia<\/b>: sarebbe ideale entrare nel repository, digitare un comando e ricevere un'immagine pronta, costruita comprendendo come e cosa fare correttamente. Tuttavia, personalmente non sono sicuro che tutti i dettagli possano essere previsti in questo modo.<\/li>\n<\/ol>\n<p>\nEcco i progetti:<\/p>\n<ul>\n<li> <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/moby\/buildkit\">moby\/buildkit<\/a><\/noindex> \u2014 un builder della societ\u00e0 Docker Inc (gi\u00e0 integrato nelle versioni attuali di Docker), che cerca di risolvere tutti questi problemi;<\/li>\n<li> <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/GoogleContainerTools\/kaniko\">kaniko<\/a><\/noindex> \u2014 un builder di Google, che consente di costruire senza Docker;<\/li>\n<li> <noindex><a rel=\"nofollow\" href=\"https:\/\/buildpacks.io\/\">Buildpacks.io<\/a><\/noindex> \u2014 un tentativo della CNCF di creare automagia e, in particolare, una soluzione interessante con rebase per i layer;<\/li>\n<li> e un sacco di altre utilit\u00e0, come <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 guarda quante stelle hanno su GitHub. Quindi, da un lato, <code>docker build<\/code> c'\u00e8 e pu\u00f2 fare qualcosa, ma in realt\u00e0 <b>la questione non \u00e8 ancora risolta<\/b> \u2014 la prova di ci\u00f2 \u00e8 lo sviluppo parallelo di builder alternativi, ognuno dei quali affronta una parte dei problemi.<\/p>\n<h2>Costruzione in werf<\/h2>\n<p>\nCos\u00ec siamo arrivati a <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/flant\/werf\">werf<\/a><\/noindex> <i>(precedentemente <noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/flant\/blog\/333682\/\">conosciuta<\/a><\/noindex> come dapp)<\/i> \u2014 uno strumento open source dell'azienda \"Flant\", che stiamo sviluppando da molti anni. Tutto \u00e8 iniziato circa 5 anni fa con script Bash che ottimizzavano la costruzione di Dockerfile, e negli ultimi 3 anni \u00e8 in corso uno sviluppo completo come parte di un progetto con il suo repository Git. <i>(inizialmente in Ruby, poi <noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/flant\/blog\/437044\/\">riscritta<\/a><\/noindex> in Go, e nel frattempo anche rinominata)<\/i>. Quali problemi di costruzione sono stati risolti in werf?<\/p>\n<p><img decoding=\"async\" alt=\"werf \u2014 il nostro strumento per CI\/CD in Kubernetes (panoramica e video della presentazione)\" src=\"\/wp-content\/uploads\/2019\/08\/ab6aa8831b49977a419fd4cc3543eb83.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nI problemi contrassegnati in blu sono gi\u00e0 stati implementati, la costruzione parallela \u00e8 stata realizzata all'interno di un singolo host, e le questioni evidenziate in giallo intendiamo completarle entro la fine dell'estate.<\/p>\n<h2>Fase di pubblicazione nel registry (publish)<\/h2>\n<p>\nAbbiamo raccolto <code>docker push<\/code>\u2026 \u2014 cosa potrebbe esserci di difficile nel caricare un'immagine nel registry? E qui sorge la domanda: \u00abQuale tag assegnare all'immagine?\u00bb Nasce per il motivo che abbiamo <b>Gitflow<\/b> (o un'altra strategia Git) e Kubernetes, e l'industria mira a far s\u00ec che ci\u00f2 che accade in Kubernetes segua ci\u00f2 che viene fatto in Git. Poich\u00e9 Git \u00e8 la nostra unica fonte di verit\u00e0.<\/p>\n<p>Cosa c'\u00e8 di difficile in questo? <b>Garantire la riproducibilit\u00e0<\/b>: dal commit in Git, che per sua natura \u00e8 immutabile <i>(immutabile)<\/i>, all'immagine Docker che deve rimanere la stessa.<\/p>\n<p>\u00c8 anche importante per noi <b>definire l'origine<\/b>, perch\u00e9 vogliamo capire da quale commit \u00e8 stata costruita l'applicazione eseguita in Kubernetes (allora potremo fare diff e cose simili).<\/p>\n<h3>Strategie di tagging<\/h3>\n<p>\nLa prima \u00e8 un semplice <b>git tag<\/b>. Abbiamo un registro con un'immagine etichettata come <code>1.0<\/code>. In Kubernetes ci sono stage e production dove \u00e8 stata distribuita quest'immagine. In Git facciamo commit e a un certo punto impostiamo un tag <code>2.0<\/code>. Lo assemblamo secondo le istruzioni del repository e lo posizioniamo nel registro con il tag <code>2.0<\/code>. Lo distribuiamo su stage e, se va tutto bene, poi su production.<\/p>\n<p><img decoding=\"async\" alt=\"werf \u2014 il nostro strumento per CI\/CD in Kubernetes (panoramica e video della presentazione)\" src=\"\/wp-content\/uploads\/2019\/08\/8545b84bfa63a91773f0d7dc1c2bd49f.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nIl problema di questo approccio \u00e8 che prima abbiamo impostato il tag e solo dopo abbiamo testato e distribuito. Perch\u00e9? Innanzitutto, \u00e8 semplicemente illogico: stiamo rilasciando una versione di software che non abbiamo nemmeno verificato (non possiamo fare diversamente, perch\u00e9 per testare \u00e8 necessario impostare un tag). In secondo luogo, questo percorso non si sposa bene con Gitflow.<\/p>\n<p>La seconda opzione \u00e8 <b>git commit + tag<\/b>. Nel ramo master c'\u00e8 un tag <code>1.0<\/code>; per esso nel registro c'\u00e8 l'immagine distribuita in production. Inoltre, nel cluster Kubernetes ci sono i contorni preview e staging. Successivamente seguiamo Gitflow: nel ramo principale di sviluppo (<code>develop.<\/code>) implementiamo nuove funzionalit\u00e0, creando cos\u00ec un commit con l'identificativo <code>#c1<\/code>. Lo assemblamo e lo pubblichiamo nel registro utilizzando questo identificativo (<code>#c1<\/code>). Con lo stesso identificativo lo distribuiamo in preview. Facciamo la stessa cosa con i commit <code>#c2<\/code> e <code>#c3<\/code>.<\/p>\n<p>Quando capiamo che le funzionalit\u00e0 sono sufficienti, iniziamo a stabilizzare tutto. In Git creiamo un ramo <code>release_1.1<\/code> (basato su <code>#c3<\/code> da <code>develop.<\/code>). Non sar\u00e0 necessario assemblare questa release, poich\u00e9 \u00e8 stata gi\u00e0 completata nella fase precedente. Pertanto, possiamo semplicemente distribuirla su staging. Correggiamo i bug in <code>#c4<\/code> e distribuiamo analogamente su staging. Nel frattempo, nello stesso momento, prosegue lo sviluppo in <code>develop.<\/code>, dove periodicamente vengono apportate modifiche da <code>release_1.1<\/code>. A un certo punto otteniamo un commit assemblato e distribuito su staging, di cui siamo soddisfatti (<code>#c25<\/code>).<\/p>\n<p>Allora facciamo un merge (con fast-forward) del ramo di rilascio (<code>release_1.1<\/code>) in master. Impostiamo su questo commit un tag con una nuova versione (<code>1.1<\/code>). Ma quest'immagine \u00e8 gi\u00e0 stata assemblata nel registro, quindi, per non doverla assemblare di nuovo, aggiungiamo semplicemente un secondo tag all'immagine esistente (ora nel registro ha i tag <code>#c25<\/code> e <code>1.1<\/code>). Dopo di ci\u00f2, la distribuiamo in production.<\/p>\n<p>C'\u00e8 uno svantaggio: su staging \u00e8 stata distribuita un'immagine (<code>#c25<\/code>), mentre in production c'\u00e8 come se fosse un'altra (<code>1.1<\/code>), ma sappiamo che \u00abfisicamente\u00bb \u00e8 la stessa immagine del registry.<\/p>\n<p><img decoding=\"async\" alt=\"werf \u2014 il nostro strumento per CI\/CD in Kubernetes (panoramica e video della presentazione)\" src=\"\/wp-content\/uploads\/2019\/08\/b3fa2c34442aa401d1e7b30eb593be9a.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nIl vero problema \u00e8 che non c'\u00e8 supporto per i merge commit, bisogna fare fast-forward.<\/p>\n<p>Possiamo andare oltre e fare un trucco... Consideriamo l'esempio di un semplice 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>\nCostruiamo un file secondo questo principio, prendendo:<\/p>\n<ul>\n<li> SHA256 degli identificatori delle immagini utilizzate (<code>ruby:2.3<\/code> e <code>nginx:alpine<\/code>), che sono le somme di controllo del loro contenuto;<\/li>\n<li> tutti i comandi (<code>comandi RUN<\/code>, <code>CMD<\/code> ecc.);<\/li>\n<li> SHA256 dei file che sono stati aggiunti.<\/li>\n<\/ul>\n<p>\n... e prendiamo la somma di controllo (ancora una volta SHA256) di tale file. Questa \u00e8 <b>la firma<\/b> di tutto ci\u00f2 che determina il contenuto dell'immagine Docker.<\/p>\n<p><img decoding=\"async\" alt=\"werf \u2014 il nostro strumento per CI\/CD in Kubernetes (panoramica e video della presentazione)\" src=\"\/wp-content\/uploads\/2019\/08\/b2af0b7952eefe26ddbb145955e778e8.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nTorniamo allo schema e <b>invece dei commit utilizzeremo tali firme<\/b>, cio\u00e8 taggheremo le immagini con le firme.<\/p>\n<p><img decoding=\"async\" alt=\"werf \u2014 il nostro strumento per CI\/CD in Kubernetes (panoramica e video della presentazione)\" src=\"\/wp-content\/uploads\/2019\/08\/115f290bd5951614c041b3d510fae38e.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nOra, quando sar\u00e0 necessario, ad esempio, fare il merge delle modifiche dal rilascio in master, possiamo fare un vero merge commit: avr\u00e0 un identificatore diverso, ma la stessa firma. Con lo stesso identificatore, rilasceremo l'immagine anche in produzione.<\/p>\n<p>Lo svantaggio \u00e8 che ora non sar\u00e0 possibile determinare quale commit \u00e8 stato pubblicato in produzione: le somme di controllo funzionano solo in un senso. Questo problema si risolve con uno strato aggiuntivo di metadati \u2014 parler\u00f2 di pi\u00f9 in seguito.<\/p>\n<h3>Tagging in werf<\/h3>\n<p>\nIn werf siamo andati ancora oltre e ci stiamo preparando per una build distribuita con una cache che non \u00e8 memorizzata su un'unica macchina... Quindi, stiamo preparando immagini Docker di due tipi, che chiamiamo <i>stage<\/i> e <i>image<\/i>.<\/p>\n<p>Nel repository Git di werf sono memorizzate istruzioni specifiche per la build, che descrivono diverse fasi della compilazione (<i>beforeInstall<\/i>, <i>install<\/i>, <i>beforeSetup<\/i>, <i>setup<\/i>). La prima immagine stage la costruiamo con una firma definita come somma di controllo dei primi passaggi. Poi aggiungiamo il codice sorgente, per la nuova immagine stage consideriamo la sua somma di controllo... Queste operazioni vengono ripetute per tutte le fasi, risultando in un insieme di immagini stage. Poi creiamo l'immagine finale, contenente anche metadati sulla sua origine. E questa immagine la tagghiamo in vari modi (i dettagli pi\u00f9 avanti).<\/p>\n<p><img decoding=\"async\" alt=\"werf \u2014 il nostro strumento per CI\/CD in Kubernetes (panoramica e video della presentazione)\" src=\"\/wp-content\/uploads\/2019\/08\/22df8b6347b45be19ecb889c102b92b5.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nDopo ci\u00f2, dovrebbe apparire un nuovo commit in cui \u00e8 stato modificato solo il codice dell'applicazione. Cosa succeder\u00e0? Per le modifiche al codice verr\u00e0 creato un patch, e verr\u00e0 preparata una nuova immagine stage. La sua firma sar\u00e0 definita come l'hash dell'immagine stage precedente e del nuovo patch. Da questa immagine, verr\u00e0 formata una nuova immagine finale.<\/p>\n<p>In questo modo, le immagini stage sono una cache che possono essere memorizzate in modo distribuito, mentre le immagini create da esse vengono caricate nel Docker Registry.<\/p>\n<p><img decoding=\"async\" alt=\"werf \u2014 il nostro strumento per CI\/CD in Kubernetes (panoramica e video della presentazione)\" src=\"\/wp-content\/uploads\/2019\/08\/35805eca81605bd64c6910b7965b567e.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<\/p>\n<h3>Pulizia del registry<\/h3>\n<p>\nNon si tratta di eliminare i layer rimasti sospesi dopo la rimozione di tag, questo \u00e8 un'opzione standard del Docker Registry stesso. Si tratta della situazione in cui si accumulano numerosi tag Docker e ci rendiamo conto che una parte di essi non ci serve pi\u00f9, occupando spazio (e\/o di cui paghiamo).<\/p>\n<p>Quali sono le strategie di pulizia?<\/p>\n<ol>\n<li> Si pu\u00f2 semplicemente non <b>pulire<\/b>. A volte \u00e8 davvero pi\u00f9 semplice pagare un po' di pi\u00f9 per spazio extra, piuttosto che districare un grosso gomitolo di tag. Ma questo funziona solo fino a un certo punto.<\/li>\n<li> <b>Reset completo<\/b>. Se si eliminano tutte le immagini e si ricompilano solo quelle attuali nel sistema CI, potrebbe sorgere un problema. Se nel production viene riavviato un contenitore, per esso verr\u00e0 caricato una nuova immagine - che non \u00e8 stata ancora testata da nessuno. Questo stravolge l'idea di un'infrastruttura immutabile.<\/li>\n<li> <b>Blue-green<\/b>. Un registry ha iniziato a riempirsi - carichiamo le immagini in un altro. Stessa problematica del metodo precedente: quando possiamo pulire quel registry che ha iniziato a riempirsi?<\/li>\n<li> <b>Nel tempo<\/b>. Eliminare tutte le immagini pi\u00f9 vecchie di 1 mese? Ma troveremo sicuramente un servizio che non \u00e8 stato aggiornato per un mese intero...<\/li>\n<li> <b>Manualmente<\/b> determinare cosa pu\u00f2 essere gi\u00e0 eliminato.<\/li>\n<\/ol>\n<p>\nCi sono veramente due varianti praticabili: non pulire oppure una combinazione di blue-green + manualmente. In quest'ultimo caso si tratta di quanto segue: quando si capisce che \u00e8 giunto il momento di pulire il registry, si crea un nuovo registry e si aggiungono tutte le nuove immagini a esso per un mese, ad esempio. E dopo un mese si verifica quali pod in Kubernetes stiano ancora utilizzando il vecchio registry e si trasferiscono anche loro nel nuovo registry.<\/p>\n<p>A cosa siamo giunti in <b>werf<\/b>? \u041c\u044b \u0441\u043e\u0431\u0438\u0440\u0430\u0435\u043c:<\/p>\n<ol>\n<li> Git head: tutti i tag, tutti i rami, considerando che tutto ci\u00f2 che \u00e8 etichettato in Git \u00e8 necessario anche nelle immagini (e se non lo \u00e8, deve essere rimosso in Git);<\/li>\n<li> tutti i pod attualmente scaricati in Kubernetes;<\/li>\n<li> vecchi ReplicaSet (quello che \u00e8 stato recentemente scaricato), e pianifichiamo di scansionare i rilasci di Helm e selezionare le ultime immagini da l\u00ec.<\/li>\n<\/ol>\n<p>\n\u2026 e facciamo da questo insieme un whitelist \u2014 un elenco di immagini che non elimineremo. Tutto il resto viene ripulito, dopodich\u00e9 troviamo le immagini stage orfane e le rimuoviamo anche.<\/p>\n<h2>Fase di deploy<\/h2>\n<p><\/p>\n<h3>Affidabile dichiarativit\u00e0<\/h3>\n<p>\nIl primo punto su cui vorrei attirare l'attenzione nel deploy \u00e8 il rilascio della configurazione aggiornata delle risorse, dichiarata in modo dichiarativo. Il documento YAML originale con la descrizione delle risorse Kubernetes \u00e8 sempre molto diverso dal risultato, effettivamente in funzione nel cluster. Perch\u00e9 Kubernetes aggiunge alla configurazione:<\/p>\n<ol>\n<li> identificatori;<\/li>\n<li> informazioni di servizio;<\/li>\n<li> numerosi valori predefiniti;<\/li>\n<li> una sezione con lo stato attuale;<\/li>\n<li> modifiche effettuate nell'ambito del funzionamento del webhook di ammissione;<\/li>\n<li> il risultato del lavoro di vari controller (e del programmatore).<\/li>\n<\/ol>\n<p>\nPertanto, quando appare una nuova configurazione della risorsa (<i>new<\/i>), non possiamo semplicemente riscrivere l'attuale configurazione \"viva\" (<i>live<\/i>). Per questo dobbiamo confrontare <i>new<\/i> con la precedente configurazione applicata (<i>last-applied<\/i>) e applicare <i>live<\/i> la patch ottenuta.<\/p>\n<p>Questo approccio \u00e8 conosciuto come <b>2-way merge<\/b>. Viene utilizzato, ad esempio, in Helm.<\/p>\n<p>C'\u00e8 anche <b>3-way merge<\/b>, che si differenzia per il fatto che:<\/p>\n<ul>\n<li> confrontando <i>last-applied<\/i> e <i>new<\/i>, guardiamo cosa \u00e8 stato rimosso;<\/li>\n<li> confrontando <i>new<\/i> e <i>live<\/i>, guardiamo cosa \u00e8 stato aggiunto o modificato;<\/li>\n<li> applichiamo la patch sommata su <i>live<\/i>.<\/li>\n<\/ul>\n<p>\nDeployiamo oltre 1000 applicazioni con Helm, quindi viviamo effettivamente con il merge a 2 vie. Tuttavia, ha diversi problemi che abbiamo risolto con le nostre patch, aiutando Helm a funzionare correttamente.<\/p>\n<h3>Stato reale del rilascio<\/h3>\n<p>\nDopo che il nostro sistema CI ha generato una nuova configurazione per Kubernetes in seguito a un evento, la invia per l'applicazione <i>(apply)<\/i> nel cluster \u2014 tramite Helm o <code>kubectl apply<\/code>. Successivamente avviene il gi\u00e0 descritto N-way merge, a cui l'API di Kubernetes risponde favorevolmente al sistema CI, e questo \u2014 al suo utente.<\/p>\n<p><img decoding=\"async\" alt=\"werf \u2014 il nostro strumento per CI\/CD in Kubernetes (panoramica e video della presentazione)\" src=\"\/wp-content\/uploads\/2019\/08\/fcc8251525f32c913f30fbcdc4296198.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nTuttavia, c'\u00e8 un enorme problema: infatti <b>un'applicazione riuscita non significa un rilascio riuscito.<\/b>. Se Kubernetes ha capito quali modifiche applicare, le applica \u2014 non sappiamo ancora quale sar\u00e0 il risultato. Ad esempio, l'aggiornamento e il riavvio dei pod nel frontend possono andare a buon fine, mentre nel backend no, e avremo diverse versioni delle immagini dell'applicazione in esecuzione.<\/p>\n<p>Per fare tutto correttamente, in questo schema si rende necessario un ulteriore elemento: un tracker speciale che ricever\u00e0 informazioni sullo stato dall'API di Kubernetes e le trasmetter\u00e0 per un'analisi pi\u00f9 approfondita della situazione reale. Abbiamo creato una libreria Open Source in Go - <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/flant\/kubedog\"><b>kubedog<\/b><\/a><\/noindex> <i>(vedi il suo annuncio <noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/flant\/blog\/434160\/\">qui<\/a><\/noindex>)<\/i>, - che risolve questo problema ed \u00e8 integrata in werf.<\/p>\n<p>Il comportamento di questo tracker a livello di werf \u00e8 configurabile tramite annotazioni che vengono applicate ai Deployments o agli StatefulSets. L'annotazione principale \u00e8 <code>fail-mode<\/code> che comprende i seguenti valori:<\/p>\n<ul>\n<li> <code>IgnoreAndContinueDeployProcess<\/code> si ignorano i problemi di distribuzione di questo componente e si continua con il deploy;<\/li>\n<li> <code>FailWholeDeployProcessImmediately<\/code> un errore in questo componente ferma il processo di deploy;<\/li>\n<li> <code>HopeUntilEndOfDeployProcess<\/code> si spera che questo componente funzioni entro la fine del deploy.<\/li>\n<\/ul>\n<p>\nAd esempio, questa combinazione di risorse e valori dell'annotazione <code>fail-mode<\/code>:<\/p>\n<p><img decoding=\"async\" alt=\"werf \u2014 il nostro strumento per CI\/CD in Kubernetes (panoramica e video della presentazione)\" src=\"\/wp-content\/uploads\/2019\/08\/40e161710e8a535cd95f1eb66ca8407b.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nQuando deployiamo per la prima volta, il database (MongoDB) potrebbe non essere ancora pronto \u2014 i Deployment falliranno. Ma possiamo aspettare che si avvii, e il deploy avr\u00e0 comunque successo.<\/p>\n<p>Ci sono altre due annotazioni per kubedog in werf:<\/p>\n<ul>\n<li> <code>failures-allowed-per-replica<\/code> il numero di fallimenti consentiti per ciascuna replica;<\/li>\n<li> <code>show-logs-until<\/code> \u2014 regola il momento in cui werf mostra (in stdout) i log di tutti i pod distribuiti. Per impostazione predefinita, questo \u00e8 <code>PodIsReady<\/code> (per ignorare i messaggi probabilmente non necessari quando il pod inizia a ricevere traffico), ma sono consentiti anche i seguenti valori <code>ControllerIsReady<\/code> e <code>EndOfDeploy<\/code>.<\/li>\n<\/ul>\n<p><\/p>\n<h3>Cosa vogliamo ancora dal deploy?<\/h3>\n<p>\nOltre ai due punti gi\u00e0 descritti, ci piacerebbe:<\/p>\n<ul>\n<li> vedere <b>log<\/b> ma solo quelli necessari, non tutti in generale;<\/li>\n<li> monitorare <b>il progresso<\/b>perch\u00e9 se un processo di lavoro rimane \"silenzioso\" per diversi minuti, \u00e8 importante capire cosa sta accadendo;<\/li>\n<li> avere <b>un rollback automatico<\/b> nel caso in cui qualcosa vada storto (ed \u00e8 quindi fondamentale conoscere lo stato reale del deploy). Il deploy deve essere atomico: o viene completato fino alla fine, o tutto torna allo stato precedente.<\/li>\n<\/ul>\n<p><\/p>\n<h2>Conclusioni<\/h2>\n<p>\nPer noi come azienda, per implementare tutte queste sfumature in diverse fasi di consegna (build, publish, deploy) \u00e8 sufficiente un sistema CI e uno strumento <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/flant\/werf\">werf<\/a><\/noindex>.<\/p>\n<p>In conclusione:<\/p>\n<p><img decoding=\"async\" alt=\"werf \u2014 il nostro strumento per CI\/CD in Kubernetes (panoramica e video della presentazione)\" src=\"\/wp-content\/uploads\/2019\/08\/bafba54f2df8740a1a12c93b3476e49a.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nGrazie a werf, abbiamo fatto notevoli progressi nella risoluzione di numerosi problemi per gli ingegneri DevOps e saremo lieti se una comunit\u00e0 pi\u00f9 ampia provasse almeno questo strumento nel suo lavoro. Raggiungere buoni risultati insieme sar\u00e0 pi\u00f9 facile.<\/p>\n<h2>Video e slide<\/h2>\n<p>\nVideo della presentazione (~47 minuti):<\/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=\"Guarda il video\" 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>Presentazione della relazione:<\/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>\nAltre relazioni su Kubernetes nel nostro blog:<\/p>\n<ul>\n<li> \u00ab<noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/flant\/blog\/459326\/\">Autoscaler e gestione delle risorse in Kubernetes<\/a><\/noindex>\u00bb <i>(Dmitrij Stolyarov; 27 aprile 2019 a \"Stachka\")<\/i>;<\/li>\n<li> \u00ab<noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/flant\/blog\/449096\/\">Espandere e completare Kubernetes<\/a><\/noindex>\u00bb <i>(Andrey Polovov; 8 aprile 2019 a Saint HighLoad++)<\/i>;<\/li>\n<li> \u00ab<noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/flant\/blog\/431500\/\">Database e Kubernetes<\/a><\/noindex>\u00bb <i>(Dmitrij Stolyarov; 8 novembre 2018 a HighLoad++)<\/i>;<\/li>\n<li> \u00ab<noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/flant\/blog\/412901\/\">Monitoraggio e Kubernetes<\/a><\/noindex>\u00bb <i>(Dmitrij Stolyarov; 28 maggio 2018 a RootConf)<\/i>;<\/li>\n<li> \u00ab<noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/flant\/blog\/345116\/\">Migliori pratiche CI\/CD con Kubernetes e GitLab<\/a><\/noindex>\u00bb <i>(Dmitrij Stolyarov; 7 novembre 2017 a HighLoad++)<\/i>;<\/li>\n<li> \u00ab<noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/flant\/blog\/331188\/\">La nostra esperienza con Kubernetes in piccoli progetti<\/a><\/noindex>\u00bb <i>(Dmitrij Stolyarov; 6 giugno 2017 a 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.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\/it\/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=\"it_IT\" \/>\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\/it\/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 il nostro strumento per CI\/CD in Kubernetes (panoramica e video della relazione) | ProHoster","description":"Il 27 maggio nella sala principale della conferenza DevOpsConf 2019, che si svolge nell'ambito del festival RIT++ 2019, nell'ambito della sezione \"Continuous Delivery\", \u00e8 stato presentato.","canonical_url":"https:\/\/prohoster.info\/it\/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":"it_IT","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\/it\/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\/it\/wp-json\/wp\/v2\/posts\/36754","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/prohoster.info\/it\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/prohoster.info\/it\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/prohoster.info\/it\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/prohoster.info\/it\/wp-json\/wp\/v2\/comments?post=36754"}],"version-history":[{"count":0,"href":"https:\/\/prohoster.info\/it\/wp-json\/wp\/v2\/posts\/36754\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/prohoster.info\/it\/wp-json\/wp\/v2\/media\/27531"}],"wp:attachment":[{"href":"https:\/\/prohoster.info\/it\/wp-json\/wp\/v2\/media?parent=36754"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/prohoster.info\/it\/wp-json\/wp\/v2\/categories?post=36754"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/prohoster.info\/it\/wp-json\/wp\/v2\/tags?post=36754"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}