{"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\/es\/blog\/administrirovanie\/werf-nash-instrument-dlya-ci-cd-v-kubernetes-obzor-i-video-doklada","title":{"rendered":"werf: nuestra herramienta para CI\/CD en Kubernetes (rese\u00f1a y video de la presentaci\u00f3n)","gt_translate_keys":[{"key":"rendered","format":"text"}]},"content":{"rendered":"<p>El 27 de mayo en la sala principal de la conferencia DevOpsConf 2019, que se lleva a cabo dentro del festival <noindex><a rel=\"nofollow\" href=\"http:\/\/ritfest.ru\/2019\/\">RIT++ 2019<\/a><\/noindex>, en la secci\u00f3n 'Entrega Continua', se present\u00f3 la charla 'werf \u2014 nuestra herramienta para CI\/CD en Kubernetes'. En ella se habla de los <b>problemas y desaf\u00edos que cada uno enfrenta al desplegar en Kubernetes<\/b>, as\u00ed como de los matices que pueden no ser evidentes de inmediato. Al desglosar las posibles soluciones, mostramos c\u00f3mo se implementa en una herramienta de c\u00f3digo abierto <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/flant\/werf\">werf<\/a><\/noindex>.<\/p>\n<p>Desde la presentaci\u00f3n, nuestra utilidad (anteriormente conocida como dapp) ha superado la marca hist\u00f3rica de <b>1000 estrellas en GitHub<\/b> \u2014 esperamos que la creciente comunidad de usuarios facilite la vida a muchos ingenieros DevOps.<\/p>\n<p><img decoding=\"async\" alt=\"werf: nuestra herramienta para CI\/CD en Kubernetes (rese\u00f1a y video de la presentaci\u00f3n)\" src=\"\/wp-content\/uploads\/2019\/08\/c2d1ad5133c0de944b60ae37e3dbe598.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nAs\u00ed que, presentamos <noindex><a rel=\"nofollow\" href=\"https:\/\/www.youtube.com\/watch?v=cK3ackGUTLw\"><b>el video de la presentaci\u00f3n<\/b><\/a><\/noindex> (~47 minutos, mucho m\u00e1s informativo que un art\u00edculo) y un resumen escrito de ello. \u00a1Vamos!<noindex><a rel=\"nofollow\" name=\"habracut\"><\/a><\/noindex><\/p>\n<h2>Entrega de c\u00f3digo en Kubernetes<\/h2>\n<p>\nEn la charla se hablar\u00e1 m\u00e1s sobre CI\/CD en Kubernetes, entendiendo que nuestro software est\u00e1 empaquetado en contenedores Docker <i>(sobre esto habl\u00e9 en <noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/flant\/blog\/322686\/\">la charla de 2016<\/a><\/noindex>)<\/i>, y K8s ser\u00e1 utilizado para su ejecuci\u00f3n en producci\u00f3n <i>(esto \u2014 en <noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/flant\/blog\/331188\/\">2017<\/a><\/noindex>)<\/i>.<\/p>\n<p>\u00bfC\u00f3mo se ve la entrega en Kubernetes?<\/p>\n<ul>\n<li> Hay un repositorio Git con el c\u00f3digo y las instrucciones para su compilaci\u00f3n. La aplicaci\u00f3n se compila en una imagen Docker y se publica en el Docker Registry.<\/li>\n<li> En el mismo repositorio hay instrucciones sobre c\u00f3mo desplegar y ejecutar la aplicaci\u00f3n. En la etapa de despliegue, estas instrucciones se env\u00edan a Kubernetes, que recibe la imagen necesaria del registry y la ejecuta.<\/li>\n<li> Adem\u00e1s, normalmente hay pruebas. Algunas de ellas se pueden ejecutar al publicar la imagen. Tambi\u00e9n se puede (seg\u00fan las mismas instrucciones) desplegar una copia de la aplicaci\u00f3n (en un espacio de nombres K8s separado o en un cl\u00faster separado) y ejecutar pruebas all\u00ed.<\/li>\n<li> Finalmente, se necesita un sistema CI que reciba eventos de Git (o pulsaciones de botones) y llame todas las etapas designadas: construcci\u00f3n, publicaci\u00f3n, implementaci\u00f3n, prueba.<\/li>\n<\/ul>\n<p>\n<img decoding=\"async\" alt=\"werf: nuestra herramienta para CI\/CD en Kubernetes (rese\u00f1a y video de la presentaci\u00f3n)\" src=\"\/wp-content\/uploads\/2019\/08\/ec0d1fd1bf1a68cca92badf7cf1affe1.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nAqu\u00ed hay algunas observaciones importantes:<\/p>\n<ol>\n<li> Dado que tenemos infraestructura inmutable <i>(immutable infrastructure)<\/i>, la imagen de la aplicaci\u00f3n, que se utiliza en todas las etapas (staging, producci\u00f3n, etc.), <b>debe ser una sola<\/b>. <i>Sobre esto y con ejemplos habl\u00e9 <noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/flant\/blog\/324274\/\">aqu\u00ed<\/a><\/noindex>.<\/i><\/li>\n<li> Dado que seguimos el enfoque de infraestructura como c\u00f3digo <i>(IaC)<\/i>, el c\u00f3digo de la aplicaci\u00f3n, las instrucciones para su compilaci\u00f3n y ejecuci\u00f3n deben estar <b>justo en un solo repositorio<\/b>. <i>Sobre esto \u2014 v\u00e9ase en <noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/flant\/blog\/324274\/\">la misma charla<\/a><\/noindex>.<\/i><\/li>\n<li> Cadena de entrega <i>(delivery)<\/i> normalmente vemos as\u00ed: se ensambl\u00f3 la aplicaci\u00f3n, se prob\u00f3, se lanz\u00f3 <i>etapa de release<\/i> y eso es todo: la entrega ocurri\u00f3. Pero en realidad, el usuario recibe lo que ustedes implementaron <b>no<\/b> en el momento que lo entregaron a producci\u00f3n, y cuando pudo acceder a la producci\u00f3n y esta funcion\u00f3. Por eso creo que la cadena de entrega termina <b>solo en la etapa de operaci\u00f3n<\/b> <i>ejecuci\u00f3n<\/i>, y si hablamos con m\u00e1s precisi\u00f3n, incluso en el momento en que se retir\u00f3 el c\u00f3digo de producci\u00f3n (sustituy\u00e9ndolo por uno nuevo).<\/li>\n<\/ol>\n<p>\nVolvamos al esquema de entrega mencionado anteriormente en Kubernetes: no solo lo inventamos nosotros, sino pr\u00e1cticamente todos los que han lidiado con este problema. De hecho, este patr\u00f3n ahora se llama GitOps <i>(puedes leer m\u00e1s sobre el t\u00e9rmino y las ideas que lo respaldan <noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/flant\/blog\/458878\/\">aqu\u00ed<\/a><\/noindex>)<\/i>. Veamos las etapas del esquema.<\/p>\n<h2>Etapa de construcci\u00f3n<\/h2>\n<p>\nParece ser que en 2019 se puede hablar sobre la construcci\u00f3n de im\u00e1genes Docker, cuando todos saben escribir Dockerfiles y ejecutar <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>El peso de la imagen<\/b> es importante, as\u00ed que usa <noindex><a rel=\"nofollow\" href=\"https:\/\/docs.docker.com\/develop\/develop-images\/multistage-build\/\">multi-stage<\/a><\/noindex>, para dejar en la imagen solo lo realmente necesario para el funcionamiento de la aplicaci\u00f3n.<\/li>\n<li> <b>El n\u00famero de capas<\/b> debe minimizarse, combinando cadenas de <code>RUN<\/code>comandos por significado.<\/li>\n<li> Sin embargo, esto a\u00f1ade problemas <b>en la depuraci\u00f3n<\/b>, ya que al fallar la construcci\u00f3n hay que buscar el comando espec\u00edfico en la cadena que caus\u00f3 el problema.<\/li>\n<li> <b>La velocidad de construcci\u00f3n<\/b> es importante, porque queremos implementar cambios r\u00e1pidamente y ver el resultado. Por ejemplo, no queremos volver a compilar dependencias en bibliotecas del lenguaje en cada construcci\u00f3n de la aplicaci\u00f3n.<\/li>\n<li> A menudo, de un solo repositorio de Git se requieren <b>muchas im\u00e1genes<\/b>, que se puede resolver con un conjunto de Dockerfiles (o etapas nombradas en un solo archivo) y un script Bash con su ensamblaje secuencial.<\/li>\n<\/ol>\n<p>\nEsto fue solo la punta del iceberg con la que todos se enfrentan. Pero hay otros problemas, en particular:<\/p>\n<ol>\n<li> A menudo, en la etapa de construcci\u00f3n necesitamos algo <b>montar<\/b> (por ejemplo, almacenar en cach\u00e9 el resultado de un comando como apt en un directorio externo).<\/li>\n<li> Queremos <b>Ansible<\/b> en lugar de escribir en shell.<\/li>\n<li> Queremos <b>construir sin Docker<\/b> (\u00bfpor qu\u00e9 necesitar una m\u00e1quina virtual adicional en la que hay que configurar todo esto cuando ya hay un cl\u00faster de Kubernetes donde se pueden ejecutar contenedores?).<\/li>\n<li> <b>Construcci\u00f3n paralela<\/b>, que se puede entender de diferentes maneras: diferentes comandos de Dockerfile (si se utiliza la multi-etapa), varios commits de un \u00fanico repositorio, varios Dockerfiles.<\/li>\n<li> <b>Construcci\u00f3n distribuida<\/b>: queremos construir algo en pods que son \"ef\u00edmeros\", ya que pierden cach\u00e9, lo que significa que debe almacenarse en alg\u00fan lugar aparte.<\/li>\n<li> Finalmente, llam\u00e9 al pin\u00e1culo de los deseos <b>automagia<\/b>: ser\u00eda ideal entrar en el repositorio, escribir un comando y obtener una imagen lista, compilada con el entendimiento de c\u00f3mo y qu\u00e9 hacer correctamente. Sin embargo, personalmente no estoy seguro de que se puedan prever todos los matices de esta manera.<\/li>\n<\/ol>\n<p>\nY aqu\u00ed hay proyectos:<\/p>\n<ul>\n<li> <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/moby\/buildkit\">moby\/buildkit<\/a><\/noindex> \u2014 compilador de la compa\u00f1\u00eda Docker Inc (ya integrado en las versiones actuales de Docker), que intenta resolver todos estos problemas;<\/li>\n<li> <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/GoogleContainerTools\/kaniko\">kaniko<\/a><\/noindex> \u2014 compilador de Google, que permite construir sin Docker;<\/li>\n<li> <noindex><a rel=\"nofollow\" href=\"https:\/\/buildpacks.io\/\">Buildpacks.io<\/a><\/noindex> \u2014 intento de la CNCF de hacer automagia y, en particular, una soluci\u00f3n interesante con rebase para capas;<\/li>\n<li> y un mont\u00f3n de otras 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 y mira cu\u00e1ntas estrellas tienen en GitHub. Es decir, por un lado, <code>docker build<\/code> hay y puede hacer algo, pero en realidad <b>la cuesti\u00f3n no est\u00e1 completamente resuelta<\/b> \u2014 como prueba de esto est\u00e1 el desarrollo paralelo de compiladores alternativos, cada uno de los cuales resuelve alg\u00fan problema espec\u00edfico.<\/p>\n<h2>Construcci\u00f3n en werf<\/h2>\n<p>\nAs\u00ed llegamos 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\/\">conocida<\/a><\/noindex> como dapp)<\/i> \u2014 una utilidad de c\u00f3digo abierto de la compa\u00f1\u00eda \"Flant\", que hemos estado desarrollando durante muchos a\u00f1os. Todo comenz\u00f3 hace unos 5 a\u00f1os con scripts Bash que optimizaban la construcci\u00f3n de Dockerfiles, y desde hace 3 a\u00f1os se lleva a cabo un desarrollo completo dentro de un solo proyecto con su propio repositorio Git <i>(primero en Ruby, y luego <noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/flant\/blog\/437044\/\">fue reescrito<\/a><\/noindex> en Go, y de paso tambi\u00e9n renombrado)<\/i>. \u00bfQu\u00e9 cuestiones de construcci\u00f3n se resuelven en werf?<\/p>\n<p><img decoding=\"async\" alt=\"werf: nuestra herramienta para CI\/CD en Kubernetes (rese\u00f1a y video de la presentaci\u00f3n)\" src=\"\/wp-content\/uploads\/2019\/08\/ab6aa8831b49977a419fd4cc3543eb83.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nLos problemas marcados en azul ya est\u00e1n implementados, la construcci\u00f3n paralela se ha realizado en un solo host, y las cuestiones destacadas en amarillo planeamos finalizar para finales de verano.<\/p>\n<h2>Etapa de publicaci\u00f3n en el registro (publish)<\/h2>\n<p>\nEscribimos <code>docker push<\/code>\u2026 \u2014 \u00bfqu\u00e9 puede ser complicado en subir una imagen al registro? Y aqu\u00ed surge la pregunta: \"\u00bfQu\u00e9 etiqueta se debe poner a la imagen?\" Surge por el hecho de que tenemos <b>Gitflow<\/b> (o otra estrategia de Git) y Kubernetes, y la industria se esfuerza por hacer que lo que sucede en Kubernetes siga lo que se hace en Git. Despu\u00e9s de todo, Git es nuestra \u00fanica fuente de verdad.<\/p>\n<p>\u00bfQu\u00e9 hay de complicado en esto? <b>Garantizar la reproducibilidad<\/b>: del commit en Git, que por su naturaleza es inmutable <i>(inmutable)<\/i>, hasta la imagen de Docker, que debe mantenerse igual.<\/p>\n<p>Tambi\u00e9n es importante para nosotros <b>definir el origen<\/b>, porque queremos entender de qu\u00e9 commit se construy\u00f3 la aplicaci\u00f3n que se ejecuta en Kubernetes (as\u00ed podremos hacer diffs y cosas similares).<\/p>\n<h3>Estrategias de etiquetado<\/h3>\n<p>\nLa primera es un simple <b>git tag<\/b>. Tenemos un registro con una imagen etiquetada como <code>1.0<\/code>. En Kubernetes hay un stage y producci\u00f3n, donde esta imagen ha sido desplegada. En Git hacemos commits y en alg\u00fan momento ponemos una etiqueta <code>2.0<\/code>. La construimos siguiendo las instrucciones del repositorio y la colocamos en el registro con la etiqueta <code>2.0<\/code>. Desplegamos en stage y, si todo est\u00e1 bien, luego en producci\u00f3n.<\/p>\n<p><img decoding=\"async\" alt=\"werf: nuestra herramienta para CI\/CD en Kubernetes (rese\u00f1a y video de la presentaci\u00f3n)\" src=\"\/wp-content\/uploads\/2019\/08\/8545b84bfa63a91773f0d7dc1c2bd49f.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nEl problema de este enfoque es que primero pusimos la etiqueta y solo despu\u00e9s probamos y desplegamos. \u00bfPor qu\u00e9? En primer lugar, es simplemente il\u00f3gico: estamos entregando una versi\u00f3n de software que ni siquiera hemos revisado (no podemos hacer de otra manera, ya que para verificar es necesario poner una etiqueta). En segundo lugar, este camino no se alinea con Gitflow.<\/p>\n<p>La segunda opci\u00f3n es <b>git commit + tag<\/b>. En la rama master hay una etiqueta <code>1.0<\/code>; para ello en el registro hay una imagen desplegada en producci\u00f3n. Adem\u00e1s, en el cl\u00faster de Kubernetes hay contornos de vista previa y staging. Luego seguimos Gitflow: en la rama principal para el desarrollo (<code>develop<\/code>) hacemos nuevas caracter\u00edsticas, lo que resulta en un commit con el identificador <code>#c1<\/code>. Lo construimos y lo publicamos en el registro, utilizando este identificador (<code>#c1<\/code>). Con el mismo identificador desplegamos en vista previa. Hacemos lo mismo con los commits <code>#c2<\/code> y <code>#c3<\/code>.<\/p>\n<p>Cuando entendemos que hay suficientes caracter\u00edsticas, comenzamos a estabilizar todo. En Git creamos una rama <code>release_1.1<\/code> (basado en <code>#c3<\/code> de <code>develop<\/code>). No ser\u00e1 necesario construir esta versi\u00f3n, ya que se ha hecho en la etapa anterior. Por lo tanto, simplemente podemos desplegarla en staging. Corregimos los errores en <code>#c4<\/code> y tambi\u00e9n los desplegamos en staging. Paralelamente, al mismo tiempo se est\u00e1 desarrollando en <code>develop<\/code>, donde peri\u00f3dicamente se traen cambios de <code>release_1.1<\/code>. En alg\u00fan momento, obtenemos un commit construido y desplegado en staging, con el cual estamos satisfechos (<code>#c25<\/code>).<\/p>\n<p>Entonces hacemos un merge (con fast-forward) de la rama de lanzamiento (<code>release_1.1<\/code>) en master. Ponemos en este commit una etiqueta con la nueva versi\u00f3n (<code>1.1<\/code>). Pero esta imagen ya est\u00e1 construida en el registro, por lo que, para no construirla nuevamente, simplemente a\u00f1adimos una segunda etiqueta a la imagen existente (ahora tiene etiquetas en el registro <code>#c25<\/code> y <code>1.1<\/code>). Despu\u00e9s de esto, la desplegamos en producci\u00f3n.<\/p>\n<p>Hay una desventaja, que en staging se ha desplegado una imagen (<code>#c25<\/code>), mientras que en producci\u00f3n, ser\u00eda como otra (<code>1.1<\/code>), pero sabemos que \u00abf\u00edsicamente\u00bb es la misma imagen del registro.<\/p>\n<p><img decoding=\"async\" alt=\"werf: nuestra herramienta para CI\/CD en Kubernetes (rese\u00f1a y video de la presentaci\u00f3n)\" src=\"\/wp-content\/uploads\/2019\/08\/b3fa2c34442aa401d1e7b30eb593be9a.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nEl verdadero problema es que no hay soporte para merge commits, hay que hacer fast-forward.<\/p>\n<p>Podemos ir un paso m\u00e1s all\u00e1 y hacer un truco\u2026 Consideremos el ejemplo de un Dockerfile simple:<\/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 un archivo seg\u00fan el siguiente principio, es decir, tomaremos:<\/p>\n<ul>\n<li> SHA256 de los identificadores de las im\u00e1genes utilizadas (<code>ruby:2.3<\/code> y <code>nginx:alpine<\/code>), que son los hashes de su contenido;<\/li>\n<li> todas las instrucciones (<code>RUN<\/code>, <code>CMD<\/code> etc.);<\/li>\n<li> SHA256 de los archivos que se a\u00f1adieron.<\/li>\n<\/ul>\n<p>\n\u2026 y tomaremos el hash (nuevamente SHA256) de dicho archivo. Esta es <b>la firma<\/b> de todo lo que define el contenido de la imagen de Docker.<\/p>\n<p><img decoding=\"async\" alt=\"werf: nuestra herramienta para CI\/CD en Kubernetes (rese\u00f1a y video de la presentaci\u00f3n)\" src=\"\/wp-content\/uploads\/2019\/08\/b2af0b7952eefe26ddbb145955e778e8.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nVolvamos al esquema y <b>en lugar de commits utilizaremos estas firmas<\/b>, es decir, etiquetaremos las im\u00e1genes con firmas.<\/p>\n<p><img decoding=\"async\" alt=\"werf: nuestra herramienta para CI\/CD en Kubernetes (rese\u00f1a y video de la presentaci\u00f3n)\" src=\"\/wp-content\/uploads\/2019\/08\/115f290bd5951614c041b3d510fae38e.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nAhora, cuando sea necesario, por ejemplo, fusionar cambios de la versi\u00f3n a master, podemos hacer un verdadero merge commit: tendr\u00e1 un identificador diferente, pero la misma firma. Con el mismo identificador, sacaremos la imagen tambi\u00e9n en producci\u00f3n.<\/p>\n<p>La desventaja es que ahora no se podr\u00e1 determinar qu\u00e9 commit se ha implementado en producci\u00f3n: los hashes solo funcionan en una direcci\u00f3n. Este problema se soluciona con una capa adicional de metadatos, de la que hablar\u00e9 m\u00e1s adelante.<\/p>\n<h3>Etiquetado en werf<\/h3>\n<p>\nEn werf hemos ido un paso m\u00e1s all\u00e1 y nos estamos preparando para hacer una compilaci\u00f3n distribuida con una cach\u00e9 que no se almacena en una sola m\u00e1quina\u2026 As\u00ed que, tenemos im\u00e1genes de Docker de dos tipos, las llamamos <i>stage<\/i> y <i>image<\/i>.<\/p>\n<p>En el repositorio de Git de werf se almacenan instrucciones espec\u00edficas para la compilaci\u00f3n que describen diferentes etapas de la misma (<i>beforeInstall<\/i>, <i>install<\/i>, <i>beforeSetup<\/i>, <i>configuraci\u00f3n<\/i>). La primera imagen de etapa la generamos con una firma definida como el hash de los primeros pasos. Luego a\u00f1adimos el c\u00f3digo fuente, para la nueva imagen de etapa consideramos su hash\u2026 Estas operaciones se repiten para todas las etapas, lo que nos permite obtener un conjunto de im\u00e1genes de etapa. Luego realizamos la imagen final, que tambi\u00e9n contiene metadatos sobre su origen. Y esta imagen la etiquetamos de varias maneras (m\u00e1s detalles m\u00e1s adelante).<\/p>\n<p><img decoding=\"async\" alt=\"werf: nuestra herramienta para CI\/CD en Kubernetes (rese\u00f1a y video de la presentaci\u00f3n)\" src=\"\/wp-content\/uploads\/2019\/08\/22df8b6347b45be19ecb889c102b92b5.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nSupongamos que despu\u00e9s de esto aparece un nuevo commit que solo cambi\u00f3 el c\u00f3digo de la aplicaci\u00f3n. \u00bfQu\u00e9 suceder\u00e1? Se crear\u00e1 un patch para los cambios en el c\u00f3digo y se preparar\u00e1 una nueva imagen de stage. Su firma ser\u00e1 determinada como el checksum de la antigua imagen de stage y del nuevo patch. De esta imagen se formar\u00e1 una nueva imagen final.<\/p>\n<p>Por lo tanto, las im\u00e1genes de stage son un cach\u00e9 que se puede almacenar de manera distribuida, y las im\u00e1genes que se crean a partir de ellas se cargan en el Docker Registry.<\/p>\n<p><img decoding=\"async\" alt=\"werf: nuestra herramienta para CI\/CD en Kubernetes (rese\u00f1a y video de la presentaci\u00f3n)\" src=\"\/wp-content\/uploads\/2019\/08\/35805eca81605bd64c6910b7965b567e.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<\/p>\n<h3>Limpieza del registry<\/h3>\n<p>\nNo se trata de eliminar capas que quedan colgando despu\u00e9s de etiquetas eliminadas, esto es una funci\u00f3n est\u00e1ndar del propio Docker Registry. Se trata de la situaci\u00f3n en la que se acumulan muchas etiquetas de Docker y entendemos que una parte de ellas ya no es necesaria, pero ocupan espacio (y\/o estamos pagando por ello).<\/p>\n<p>\u00bfCu\u00e1les son las estrategias de limpieza?<\/p>\n<ol>\n<li> Se puede simplemente no hacer nada <b>no limpiar<\/b>. A veces realmente es m\u00e1s f\u00e1cil pagar un poco m\u00e1s por el espacio extra que desenredar un gran enredo de etiquetas. Pero esto solo funciona hasta cierto punto.<\/li>\n<li> <b>Restablecimiento completo<\/b>. Si se eliminan todas las im\u00e1genes y se vuelven a construir solo las actuales en el sistema CI, puede surgir un problema. Si se reinicia un contenedor en producci\u00f3n, se cargar\u00e1 una nueva imagen que no ha sido probada por nadie. Esto arruina la idea de infraestructura inmutable.<\/li>\n<li> <b>Blue-green<\/b>. Un registry comenz\u00f3 a sobrecargarse: cargamos im\u00e1genes en otro. El mismo problema que en el m\u00e9todo anterior: \u00bfen qu\u00e9 momento se puede limpiar ese registry que comenz\u00f3 a sobrecargarse?<\/li>\n<li> <b>Por tiempo<\/b>. \u00bfEliminar todas las im\u00e1genes que tengan m\u00e1s de 1 mes? Pero seguramente habr\u00e1 un servicio que no se ha actualizado en todo un mes...<\/li>\n<li> <b>De forma manual<\/b> determinar qu\u00e9 se puede eliminar.<\/li>\n<\/ol>\n<p>\nDe hecho, hay dos opciones viables: no limpiar o una combinaci\u00f3n de blue-green + manualmente. En este \u00faltimo caso, se trata de lo siguiente: cuando comprendas que es hora de limpiar el registro, crear\u00e1s uno nuevo y agregar\u00e1s todas las nuevas im\u00e1genes en \u00e9l durante, por ejemplo, un mes. Despu\u00e9s de un mes, ver\u00e1s qu\u00e9 pods en Kubernetes a\u00fan utilizan el registro antiguo y los trasladar\u00e1s tambi\u00e9n al nuevo registro.<\/p>\n<p>\u00bfA d\u00f3nde llegamos en <b>werf<\/b>? \u041c\u044b \u0441\u043e\u0431\u0438\u0440\u0430\u0435\u043c:<\/p>\n<ol>\n<li> Git head: todas las etiquetas, todas las ramas, suponiendo que todo lo que est\u00e1 etiquetado en Git lo necesitamos tambi\u00e9n en las im\u00e1genes (y si no, debemos eliminarlo en Git);<\/li>\n<li> todas las pods que se est\u00e1n descargando actualmente en Kubernetes;<\/li>\n<li> los ReplicaSets antiguos (lo que se descarg\u00f3 recientemente), as\u00ed como planeamos escanear los lanzamientos de Helm y seleccionar las \u00faltimas im\u00e1genes all\u00ed.<\/li>\n<\/ol>\n<p>\n\u2026 y hacemos de este conjunto una lista blanca \u2014 lista de im\u00e1genes que no vamos a eliminar. Todo lo dem\u00e1s se limpia, despu\u00e9s de lo cual encontramos im\u00e1genes de stage hu\u00e9rfanas y tambi\u00e9n las eliminamos.<\/p>\n<h2>Etapa de despliegue<\/h2>\n<p><\/p>\n<h3>Declaratividad confiable<\/h3>\n<p>\nEl primer punto al que queremos llamar la atenci\u00f3n en el despliegue es la implementaci\u00f3n de la configuraci\u00f3n actualizada de recursos, declarada de forma declarativa. El documento YAML original con la descripci\u00f3n de los recursos de Kubernetes siempre difiere significativamente del resultado que realmente funciona en el cl\u00faster. Esto se debe a que Kubernetes agrega a la configuraci\u00f3n:<\/p>\n<ol>\n<li> identificadores;<\/li>\n<li> informaci\u00f3n de servicio;<\/li>\n<li> m\u00faltiples valores por defecto;<\/li>\n<li> secci\u00f3n con el estado actual;<\/li>\n<li> cambios realizados en el marco del trabajo del webhook de admission;<\/li>\n<li> resultado del trabajo de varios controladores (y programador).<\/li>\n<\/ol>\n<p>\nPor lo tanto, cuando aparece una nueva configuraci\u00f3n de recurso (<i>nuevo<\/i>), no podemos simplemente tomarla y sobrescribir la configuraci\u00f3n actual, \"viva\", (<i>live<\/i>). Para esto, necesitamos comparar <i>nuevo<\/i> con la configuraci\u00f3n aplicada anteriormente (<i>last-applied<\/i>) y aplicar <i>live<\/i> el parche obtenido.<\/p>\n<p>Este enfoque se llama <b>fusi\u00f3n en 2 v\u00edas<\/b>. Se utiliza, por ejemplo, en Helm.<\/p>\n<p>Tambi\u00e9n existe la <b>fusi\u00f3n en 3 v\u00edas<\/b>, que se diferencia en que:<\/p>\n<ul>\n<li> al comparar <i>last-applied<\/i> y <i>nuevo<\/i>, observamos lo que se ha eliminado;<\/li>\n<li> al comparar <i>nuevo<\/i> y <i>live<\/i>, observamos lo que se ha a\u00f1adido o cambiado;<\/li>\n<li> aplicamos el parche sumado a <i>live<\/i>.<\/li>\n<\/ul>\n<p>\nDesplegamos m\u00e1s de 1000 aplicaciones con Helm, por lo que en realidad vivimos con un merge bidireccional. Sin embargo, tiene varios problemas que hemos resuelto con nuestros parches, que ayudan a que Helm funcione correctamente.<\/p>\n<h3>Estado real de la implementaci\u00f3n<\/h3>\n<p>\nDespu\u00e9s de que, en un evento correspondiente, nuestro sistema CI gener\u00f3 una nueva configuraci\u00f3n para Kubernetes, la env\u00eda para aplicar <i>(apply)<\/i> en el cl\u00faster \u2014 usando Helm o <code>kubectl apply<\/code>. Luego ocurre la fusi\u00f3n en N v\u00edas ya descrita, a lo que la API de Kubernetes responde favorablemente a la sistema CI, y esta a su usuario.<\/p>\n<p><img decoding=\"async\" alt=\"werf: nuestra herramienta para CI\/CD en Kubernetes (rese\u00f1a y video de la presentaci\u00f3n)\" src=\"\/wp-content\/uploads\/2019\/08\/fcc8251525f32c913f30fbcdc4296198.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nSin embargo, hay un gran problema: ya que <b>una aplicaci\u00f3n exitosa no significa una implementaci\u00f3n exitosa.<\/b>. Si Kubernetes entiende qu\u00e9 cambios deben aplicarse, los aplica; a\u00fan no sabemos cu\u00e1l ser\u00e1 el resultado. Por ejemplo, la actualizaci\u00f3n y el reinicio de los pods en el frontend pueden completarse con \u00e9xito, mientras que en el backend puede que no, y terminamos con diferentes versiones de las im\u00e1genes de la aplicaci\u00f3n en ejecuci\u00f3n.<\/p>\n<p>Para hacer todo correctamente, en este esquema se sugiere un eslab\u00f3n adicional: un rastreador especial que recibe informaci\u00f3n del estado de la API de Kubernetes y la env\u00eda para un an\u00e1lisis posterior de la situaci\u00f3n real. Hemos creado una biblioteca Open Source en Go \u2014 <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/flant\/kubedog\"><b>kubedog<\/b><\/a><\/noindex> <i>(ver su anuncio <noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/flant\/blog\/434160\/\">aqu\u00ed<\/a><\/noindex>)<\/i>, \u2014 que resuelve este problema y est\u00e1 integrada en werf.<\/p>\n<p>El comportamiento de este rastreador a nivel de werf se configura mediante anotaciones que se colocan en Deployments o StatefulSets. La principal anotaci\u00f3n es <code>fail-mode<\/code> \u2014 entiende los siguientes valores:<\/p>\n<ul>\n<li> <code>IgnoreAndContinueDeployProcess<\/code> \u2014 ignoramos los problemas de implementaci\u00f3n de este componente y continuamos con el despliegue;<\/li>\n<li> <code>FailWholeDeployProcessImmediately<\/code> \u2014 un error en este componente detiene el proceso de despliegue;<\/li>\n<li> <code>HopeUntilEndOfDeployProcess<\/code> \u2014 esperamos que este componente funcione para el final del despliegue.<\/li>\n<\/ul>\n<p>\nPor ejemplo, tal combinaci\u00f3n de recursos y valores de anotaci\u00f3n <code>fail-mode<\/code>:<\/p>\n<p><img decoding=\"async\" alt=\"werf: nuestra herramienta para CI\/CD en Kubernetes (rese\u00f1a y video de la presentaci\u00f3n)\" src=\"\/wp-content\/uploads\/2019\/08\/40e161710e8a535cd95f1eb66ca8407b.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nCuando desplegamos por primera vez, la base de datos (MongoDB) puede que a\u00fan no est\u00e9 lista; los Deployments fallar\u00e1n. Pero podemos esperar a que se inicie y el despliegue a\u00fan se llevar\u00e1 a cabo.<\/p>\n<p>Hay otras dos anotaciones para kubedog en werf:<\/p>\n<ul>\n<li> <code>failures-allowed-per-replica<\/code> \u2014 n\u00famero de fallos permitidos por cada r\u00e9plica;<\/li>\n<li> <code>show-logs-until<\/code> \u2014 regula el momento hasta el cual werf muestra (en stdout) los registros de todas las pods desplegadas. Por defecto, esto es <code>PodIsReady<\/code> (para ignorar mensajes que probablemente no necesitamos cuando comienza a llegar tr\u00e1fico al pod), sin embargo, tambi\u00e9n son v\u00e1lidos los valores <code>ControllerIsReady<\/code> y <code>EndOfDeploy<\/code>.<\/li>\n<\/ul>\n<p><\/p>\n<h3>\u00bfQu\u00e9 m\u00e1s queremos del despliegue?<\/h3>\n<p>\nAdem\u00e1s de los dos puntos ya mencionados, nos gustar\u00eda:<\/p>\n<ul>\n<li> ver <b>registros<\/b> \u2014 pero solo los necesarios, no todos de una vez;<\/li>\n<li> seguir <b>el progreso<\/b>, porque si el trabajo se queda \"silencioso\" durante varios minutos, es importante entender qu\u00e9 est\u00e1 sucediendo;<\/li>\n<li> tener <b>una reversi\u00f3n autom\u00e1tica<\/b> en caso de que algo salga mal (por lo que es cr\u00edtico conocer el estado real del despliegue). La implementaci\u00f3n debe ser at\u00f3mica: o se completa, o todo vuelve a su estado anterior.<\/li>\n<\/ul>\n<p><\/p>\n<h2>Resultados<\/h2>\n<p>\nPara nosotros, como empresa, para implementar todos los matices descritos en diferentes etapas de entrega (build, publish, deploy), es suficiente con un sistema CI y una utilidad <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/flant\/werf\">werf<\/a><\/noindex>.<\/p>\n<p>En conclusi\u00f3n:<\/p>\n<p><img decoding=\"async\" alt=\"werf: nuestra herramienta para CI\/CD en Kubernetes (rese\u00f1a y video de la presentaci\u00f3n)\" src=\"\/wp-content\/uploads\/2019\/08\/bafba54f2df8740a1a12c93b3476e49a.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nCon werf hemos avanzado significativamente en la resoluci\u00f3n de numerosos problemas de los ingenieros DevOps, y nos complace que una comunidad m\u00e1s amplia al menos pruebe esta utilidad en acci\u00f3n. Ser\u00e1 m\u00e1s f\u00e1cil lograr buenos resultados juntos.<\/p>\n<h2>Videos y presentaciones<\/h2>\n<p>\nV\u00eddeo de la presentaci\u00f3n (~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=\"Reproducir 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>Presentaci\u00f3n de la charla:<\/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.D.<\/h2>\n<p>\nOtros informes sobre Kubernetes en nuestro blog:<\/p>\n<ul>\n<li> \u00ab<noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/flant\/blog\/459326\/\">Escalado autom\u00e1tico y gesti\u00f3n de recursos en Kubernetes<\/a><\/noindex>\u00bb <i>(Dmitry Stolyarov; 27 de abril de 2019 en \u00abStachka\u00bb)<\/i>;<\/li>\n<li> \u00ab<noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/flant\/blog\/449096\/\">Ampliando y complementando Kubernetes<\/a><\/noindex>\u00bb <i>(Andrey Polovov; 8 de abril de 2019 en Saint HighLoad++)<\/i>;<\/li>\n<li> \u00ab<noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/flant\/blog\/431500\/\">Bases de datos y Kubernetes<\/a><\/noindex>\u00bb <i>(Dmitry Stolyarov; 8 de noviembre de 2018 en HighLoad++)<\/i>;<\/li>\n<li> \u00ab<noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/flant\/blog\/412901\/\">Monitoreo y Kubernetes<\/a><\/noindex>\u00bb <i>(Dmitry Stolyarov; 28 de mayo de 2018 en RootConf)<\/i>;<\/li>\n<li> \u00ab<noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/flant\/blog\/345116\/\">Mejores pr\u00e1cticas de CI\/CD con Kubernetes y GitLab<\/a><\/noindex>\u00bb <i>(Dmitry Stolyarov; 7 de noviembre de 2017 en HighLoad++)<\/i>;<\/li>\n<li> \u00ab<noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/flant\/blog\/331188\/\">Nuestra experiencia con Kubernetes en proyectos peque\u00f1os<\/a><\/noindex>\u00bb <i>(Dmitry Stolyarov; 6 de junio de 2017 en RootConf)<\/i>.<\/li>\n<\/ul>\n<p>Fuente: <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.2 - 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\/es\/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.2\" \/>\n\t\t<meta property=\"og:locale\" content=\"es_ES\" \/>\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\/es\/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 nuestra herramienta para CI\/CD en Kubernetes (resumen y video de la charla) | ProHoster","description":"El 27 de mayo, en la sala principal de la conferencia DevOpsConf 2019, que forma parte del festival RIT++ 2019, en la secci\u00f3n 'Entrega continua', se present\u00f3.","canonical_url":"https:\/\/prohoster.info\/es\/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":"es_ES","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\/es\/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\/es\/wp-json\/wp\/v2\/posts\/36754","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/prohoster.info\/es\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/prohoster.info\/es\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/prohoster.info\/es\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/prohoster.info\/es\/wp-json\/wp\/v2\/comments?post=36754"}],"version-history":[{"count":0,"href":"https:\/\/prohoster.info\/es\/wp-json\/wp\/v2\/posts\/36754\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/prohoster.info\/es\/wp-json\/wp\/v2\/media\/27531"}],"wp:attachment":[{"href":"https:\/\/prohoster.info\/es\/wp-json\/wp\/v2\/media?parent=36754"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/prohoster.info\/es\/wp-json\/wp\/v2\/categories?post=36754"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/prohoster.info\/es\/wp-json\/wp\/v2\/tags?post=36754"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}