Combinación de 3 vías para werf: implementación en Kubernetes con Helm "en esteroides"

O que nós (e non só nós) levamos moito tempo esperando pasou: werf, a nosa utilidade de código aberto para crear aplicacións e entregalas a Kubernetes, agora admite a aplicación de cambios mediante parches de combinación de tres vías. Ademais disto, é posible adoptar os recursos existentes de K3s nas versións de Helm sen reconstruír estes recursos.

Combinación de 3 vías para werf: implementación en Kubernetes con Helm "en esteroides"

Se é moi curto, entón poñemos WERF_THREE_WAY_MERGE=enabled - obtemos o despregamento "como en kubectl apply", compatible coas instalacións existentes de Helm 2 e incluso un pouco máis.

Pero comecemos coa teoría: que son exactamente os parches de combinación de 3 vías, como se lle ocorreu a xente para xeralos e por que son importantes nos procesos CI/CD cunha infraestrutura baseada en Kubernetes? E despois diso, vexamos que é a combinación de 3 vías en werf, que modos se usan por defecto e como xestionalo.

Que é un parche de combinación de 3 vías?

Entón, imos comezar coa tarefa de implementar os recursos descritos nos manifestos YAML en Kubernetes.

Para traballar con recursos, a API de Kubernetes ofrece as seguintes operacións básicas: crear, parchear, substituír e eliminar. Suponse que coa súa axuda é necesario construír unha cómoda implementación continua de recursos para o clúster. Como?

comandos imperativos kubectl

O primeiro enfoque para xestionar obxectos en Kubernetes é utilizar comandos imperativos de kubectl para crear, modificar e eliminar estes obxectos. Simplificando:

  • o equipo kubectl run pode executar a implementación ou o traballo:
    kubectl run --generator=deployment/apps.v1 DEPLOYMENT_NAME --image=IMAGE
  • o equipo kubectl scale - cambiar o número de réplicas:
    kubectl scale --replicas=3 deployment/mysql
  • etc

Este enfoque pode parecer conveniente a primeira vista. Non obstante, hai problemas:

  1. É difícil automatizar.
  2. Como reflectir a configuración en Git? Como revisar os cambios que se producen no clúster?
  3. Como proporcionar reproducibilidade configuracións ao reiniciar?
  4. ...

Está claro que este enfoque non encaixa ben co almacenamento de aplicacións e infraestruturas como código (IaC; ou mesmo GitOps como unha opción máis moderna, gañando popularidade no ecosistema de Kubernetes). Polo tanto, estes comandos non recibiron máis desenvolvemento en kubectl.

Crear, obter, substituír e eliminar operacións

Con primaria creación é sinxelo: enviar o manifesto á operación create kube api e creouse o recurso. A representación YAML do manifesto pódese almacenar en Git e crearse mediante o comando kubectl create -f manifest.yaml.

С eliminando tamén sinxelo: substituír o mesmo manifest.yaml de Git a equipo kubectl delete -f manifest.yaml.

Funcionamento replace permítelle substituír completamente a configuración do recurso por unha nova, sen recrear o recurso. Isto significa que antes de facer un cambio nun recurso, é lóxico consultar a versión actual coa operación get, cámbiao e actualízao coa operación replace. kube apiserver está integrado bloqueo optimista e, se despois da cirurxía get o obxecto cambiou, despois a operación replace non funcionará.

Para almacenar a configuración en Git e actualizala mediante substituír, cómpre facer a operación get, combine a configuración de Git co que recibimos e execute replace. Por defecto, kubectl só che permite usar o comando kubectl replace -f manifest.yamlonde manifest.yaml — un manifesto xa totalmente preparado (no noso caso, fusionado) que debe instalarse. Resulta que o usuario necesita implementar manifestos de combinación, e isto non é un asunto trivial...

Tamén cabe destacar que aínda que manifest.yaml e almacénase en Git, non podemos saber de antemán se é necesario crear un obxecto ou actualizalo; isto debe facelo polo software do usuario.

Total: podemos construír un lanzamento continuo só usando crear, substituír e eliminar, garantindo que a configuración da infraestrutura estea almacenada en Git xunto co código e o CI/CD conveniente?

En principio, podemos... Para iso terá que implementar a operación de fusión manifestos e algún tipo de vinculación que:

  • comproba a presenza dun obxecto no clúster,
  • realiza a creación inicial de recursos,
  • actualízao ou elimínao.

Ao actualizar, teña en conta que o recurso pode ter cambiado dende o pasado get e xestionar automaticamente o caso de bloqueo optimista: fai intentos de actualización repetidos.

Non obstante, por que reinventar a roda cando kube-apiserver ofrece outra forma de actualizar recursos: o funcionamento patch, que alivia ao usuario dalgúns dos problemas descritos?

Parches

Agora chegamos aos parches.

Os parches son a forma principal de aplicar cambios aos obxectos existentes en Kubernetes. Operación patch funciona así:

  • o usuario de kube-apiserver debe enviar un parche en formato JSON e especificar o obxecto,
  • e o propio apiserver xestionará o estado actual do obxecto e traerá a forma requirida.

Neste caso non se require un bloqueo optimista. Esta operación é máis declarativa que substituír, aínda que ao principio poida parecer ao revés.

Así:

  • utilizando unha operación create creamos un obxecto segundo o manifesto de Git,
  • coa axuda delete - eliminar se o obxecto xa non é necesario,
  • coa axuda patch — cambiamos o obxecto, levándoo ao formulario descrito en Git.

Non obstante, para facelo, cómpre crear parche correcto!

Como funcionan os parches en Helm 2: fusión bidireccional

Cando instala por primeira vez unha versión, Helm realiza a operación create para recursos gráficos.

Ao actualizar unha versión de Helm para cada recurso:

  • considera o parche entre a versión do recurso do gráfico anterior e a versión do gráfico actual,
  • aplica este parche.

Chamaremos este parche Parche de combinación de 2 vías, porque na súa creación interveñen 2 manifestos:

  • manifesto do recurso da versión anterior,
  • manifesto do recurso do recurso actual.

Ao eliminar a operación delete en kube chámase apiserver para recursos que foron declarados na versión anterior, pero non declarados na actual.

O enfoque de parche de combinación de dúas vías ten un problema: leva a fóra de sincronía co estado real do recurso no clúster e co manifesto en Git.

Ilustración do problema cun exemplo

  • En Git, un gráfico almacena un manifesto no que o campo image Asuntos de despregamento ubuntu:18.04.
  • Usuario vía kubectl edit cambiou o valor deste campo a ubuntu:19.04.
  • Ao volver a despregar o gráfico Helm non xera un parche, porque o campo image na versión anterior do lanzamento e no gráfico actual son os mesmos.
  • Despois do novo despregamento image restos ubuntu:19.04, aínda que o gráfico di ubuntu:18.04.

Conseguimos a desincronización e perdemos a declaratividade.

Que é un recurso sincronizado?

En xeral completo É imposible obter unha coincidencia entre o manifesto do recurso nun clúster en execución e o manifesto de Git. Porque nun manifesto real pode haber anotacións/etiquetas de servizos, contedores adicionais e outros datos que algúns controladores engaden e eliminan dinámicamente do recurso. Non podemos nin queremos manter estes datos en Git. Non obstante, queremos que os campos que especificamos explícitamente en Git adopten os valores adecuados ao lanzar.

Resulta tan xeral regra de recursos sincronizados: ao lanzar un recurso, pode cambiar ou eliminar só aqueles campos que se especifican explícitamente no manifesto desde Git (ou foron especificados nunha versión anterior e agora están eliminados).

Parche de combinación de 3 vías

idea central Parche de combinación de 3 vías: xeramos un parche entre a última versión aplicada do manifesto de Git e a versión de destino do manifesto de Git, tendo en conta a versión actual do manifesto do clúster en execución. O parche resultante debe cumprir a regra de recursos sincronizados:

  • os novos campos engadidos á versión de destino engádense mediante un parche;
  • os campos previamente existentes na última versión aplicada e que non existen na versión de destino restablecen mediante un parche;
  • os campos da versión actual do obxecto que difiren da versión de destino do manifesto actualízanse mediante o parche.

É por este principio que xera parches kubectl apply:

  • a última versión aplicada do manifesto almacénase na anotación do propio obxecto,
  • target - tomado do ficheiro YAML especificado,
  • o actual é dun clúster en execución.

Agora que temos resolto a teoría, é hora de contarvos o que fixemos en werf.

Aplicando cambios a werf

Anteriormente, werf, como Helm 2, utilizaba parches de combinación bidireccional.

Parche de reparación

Para cambiar a un novo tipo de parches - 3-way-merge - o primeiro paso introducimos o chamado reparar parches.

Ao implementar, úsase un parche estándar de combinación de dúas vías, pero werf xera adicionalmente un parche que sincronizaría o estado real do recurso co que está escrito en Git (este parche créase usando a mesma regra de recursos sincronizados descrita anteriormente) .

Se se produce unha desincronización, ao final da implantación o usuario recibe un ADVERTENCIA cunha mensaxe correspondente e un parche que se debe aplicar para levar o recurso a un formulario sincronizado. Este parche tamén se rexistra nunha anotación especial werf.io/repair-patch. Suponse que as mans do usuario se aplicará este parche: werf non o aplicará en absoluto.

A xeración de parches de reparación é unha medida temporal que che permite probar realmente a creación de parches baseándose no principio de combinación de tres vías, pero non aplica automaticamente estes parches. Polo momento, este modo de funcionamento está activado por defecto.

Parche de combinación de 3 vías só para novos lanzamentos

A partir do 1 de decembro de 2019, comezan as versións beta e alfa de werf predeterminado use parches completos de combinación de 3 vías para aplicar cambios só ás novas versións de Helm lanzadas a través de werf. As versións existentes seguirán utilizando o enfoque de fusión bidireccional + parches de reparación.

Este modo de funcionamento pódese activar explícitamente mediante a configuración WERF_THREE_WAY_MERGE_MODE=onlyNewReleases agora.

Nota: a función apareceu en werf durante varios lanzamentos: na canle alfa quedou lista con versión v1.0.5-alfa.19, e na canle beta - con versión 1.0.4-beta.20.

Parche de combinación de 3 vías para todas as versións

A partir do 15 de decembro de 2019, as versións beta e alfa de werf comezan a utilizar parches completos de combinación de tres vías de forma predeterminada para aplicar cambios a todas as versións.

Este modo de funcionamento pódese activar explícitamente mediante a configuración WERF_THREE_WAY_MERGE_MODE=enabled agora.

Que facer co escalado automático de recursos?

Hai 2 tipos de escalado automático en Kubernetes: HPA (horizontal) e VPA (vertical).

Horizontal selecciona automaticamente o número de réplicas, vertical - o número de recursos. Tanto o número de réplicas como os requisitos de recursos especifícanse no manifesto do recurso (consulte o manifesto do recurso). spec.replicas ou spec.containers[].resources.limits.cpu, spec.containers[].resources.limits.memory и outros).

Problema: se un usuario configura un recurso nun gráfico para que especifique certos valores para os recursos ou as réplicas e os escaladores automáticos están habilitados para este recurso, entón con cada implantación werf restablecerá estes valores ao que está escrito no manifesto do gráfico. .

Hai dúas solucións ao problema. Para comezar, o mellor é evitar especificar explícitamente os valores de escala automática no manifesto do gráfico. Se esta opción non é adecuada por algún motivo (por exemplo, porque é conveniente establecer límites de recursos iniciais e o número de réplicas no gráfico), entón werf ofrece as seguintes anotacións:

  • werf.io/set-replicas-only-on-creation=true
  • werf.io/set-resources-only-on-creation=true

Se dita anotación está presente, werf non restablecerá os valores correspondentes en cada despregamento, senón que só os establecerá cando se cree inicialmente o recurso.

Para máis detalles, consulte a documentación do proxecto HPA и VPA.

Prohibir o uso do parche de combinación de 3 vías

Actualmente, o usuario pode prohibir o uso de novos parches en werf mediante unha variable de ambiente WERF_THREE_WAY_MERGE_MODE=disabled. Sen embargo, comezando A partir do 1 de marzo de 2020, esta prohibición deixará de aplicarse. e só será posible utilizar parches de combinación de 3 vías.

Adopción de recursos en werf

Dominar o método de aplicación de cambios con parches de combinación de 3 vías permitiunos implementar inmediatamente unha función como a adopción de recursos existentes no clúster na versión de Helm.

Helm 2 ten un problema: non pode engadir un recurso aos manifestos de gráficos que xa existe no clúster sen recrear este recurso desde cero (ver. # 6031, # 3275). Ensinamos a werf a aceptar os recursos existentes para a súa publicación. Para iso, cómpre instalar unha anotación na versión actual do recurso desde o clúster en execución (por exemplo, usando kubectl edit):

"werf.io/allow-adoption-by-release": RELEASE_NAME

Agora hai que describir o recurso no gráfico e a próxima vez que werf despregue unha versión co nome apropiado, o recurso existente aceptarase nesta versión e permanecerá baixo o seu control. Ademais, no proceso de aceptación dun recurso para o lanzamento, werf levará o estado actual do recurso desde o clúster en execución ao estado descrito no gráfico, utilizando os mesmos parches de combinación de 3 vías e a regra de recursos sincronizados.

Nota: configuración WERF_THREE_WAY_MERGE_MODE non afecta á adopción de recursos; no caso da adopción, sempre se utiliza un parche de combinación de 3 vías.

Detalles - en documentación.

Conclusións e plans de futuro

Espero que despois deste artigo quede máis claro que son os parches de combinación de 3 vías e por que chegaron a eles. Desde o punto de vista práctico do desenvolvemento do proxecto werf, a súa implementación foi un paso máis para mellorar o despregamento tipo Helm. Agora podes esquecerte dos problemas coa sincronización da configuración, que adoitaban xurdir ao usar Helm 2. Ao mesmo tempo, engadiuse á versión de Helm unha nova función útil para adoptar os recursos de Kubernetes xa descargados.

Aínda hai algúns problemas e retos coas implementacións tipo Helm, como o uso de modelos de Go, que seguiremos abordando.

Tamén se pode atopar información sobre os métodos de actualización e adopción de recursos en esta páxina de documentación.

Helmo 3

Digno de mención especial liberado Xusto o outro día unha nova versión principal de Helm - v3 - que tamén usa parches de combinación de 3 vías e elimina Tiller. A nova versión de Helm require migración instalacións existentes para convertelas no formato de almacenamento da nova versión.

Werf, pola súa banda, actualmente desfixouse de usar Tiller, pasou á combinación de 3 vías e engadiu moito máis, aínda que segue sendo compatible coas instalacións existentes de Helm 2 (non é necesario executar scripts de migración). Polo tanto, ata que werf non cambie a Helm 3, os usuarios de werf non perden as principais vantaxes de Helm 3 fronte a Helm 2 (werf tamén as ten).

Non obstante, o cambio de werf á base de código Helm 3 é inevitable e ocorrerá nun futuro próximo. Presumiblemente este será werf 1.1 ou werf 1.2 (de momento, a versión principal de werf é 1.0; para obter máis información sobre o dispositivo de versión de werf, consulte aquí). Durante este tempo, Helm 3 terá tempo para estabilizarse.

PS

Lea tamén no noso blog:

Fonte: www.habr.com

Engadir un comentario