Merge a tre in werf: deployment in Kubernetes con Helm «potenziato»

È successo ciò che noi (e non solo noi) aspettavamo da tempo: werf, il nostro strumento Open Source per la creazione di applicazioni e la loro distribuzione in Kubernetes ora supporta l'applicazione delle modifiche tramite patch di 3-way-merge! In aggiunta, è stata introdotta la possibilità di adottare risorse K8s esistenti nei rilasci Helm senza dover ricreare queste risorse.

Merge a tre in werf: deployment in Kubernetes con Helm «potenziato»

In breve, impostiamo WERF_THREE_WAY_MERGE=enabled — otteniamo un deploy "come in kubectl apply", compatibile con le installazioni esistenti su Helm 2 e anche un po' di più.

Ma iniziamo con la teoria: che cos'è esattamente una patch 3-way-merge, come si è arrivati a questo approccio per la loro generazione e perché sono importanti nei processi CI/CD basati su un'infrastruttura Kubernetes? Dopo di che, daremo un'occhiata a cosa rappresenta il 3-way-merge in werf, quali modalità sono utilizzate per impostazione predefinita e come gestirle.

Che cos'è una patch 3-way-merge?

Iniziamo con il compito di distribuire le risorse descritte nei manifest YAML in Kubernetes.

Per lavorare con le risorse, l'API di Kubernetes offre le seguenti operazioni fondamentali: create, patch, replace e delete. Si presume che queste siano utilizzate per costruire una distribuzione continua delle risorse nel cluster. Come?

Comandi imperativi kubectl

Il primo approccio per gestire gli oggetti in Kubernetes è l'uso di comandi imperativi kubectl per creare, modificare e cancellare questi oggetti. In parole povere:

  • comando kubectl run può avviare un Deployment o un Job:
    kubectl run --generator=deployment/apps.v1 NOME_DEPLOYMENT --image=IMMAGINE
  • comando kubectl scale — cambia il numero di repliche:
    kubectl scale --replicas=3 deployment/mysql
  • ecc.

Questo approccio può sembrare conveniente a prima vista. Tuttavia, ci sono dei problemi:

  1. È difficile automatizzare.
  2. Come riflettere la configurazione in Git? Come revisionare le modifiche che avvengono nel cluster?
  3. Come garantire riproducibilità le configurazioni durante il riavvio?

È chiaro che questo approccio si adatta male all'archiviazione insieme al codice dell'applicazione e all'infrastruttura come codice (IaC; o persino GitOps come una versione più moderna che sta guadagnando popolarità nell'ecosistema Kubernetes). Pertanto, queste comandi non hanno avuto ulteriore sviluppo.

Operazioni create, get, replace e delete

Con la creazione iniziale è tutto semplice: inviamo il manifesto all'operazione create al kube api e la risorsa viene creata. La rappresentazione YAML del manifesto può essere archiviata in Git, e per la creazione si può usare il comando kubectl create -f manifest.yaml.

C la cancellazione è altrettanto semplice: inseriamo lo stesso manifest.yaml da Git nel comando kubectl delete -f manifest.yaml.

Operazione replace permette di sostituire completamente la configurazione della risorsa con una nuova, senza dover ricreare la risorsa. Questo significa che prima di apportare una modifica alla risorsa, è logico richiedere la versione corrente con l'operazione get, modificarla e aggiornarla con l'operazione replace. Nel kube apiserver è integrato l'ottimistic locking e se dopo l'operazione get l'oggetto è stato modificato, allora l'operazione replace non andrà a buon fine.

Per archiviare la configurazione in Git e aggiornare usando replace, è necessario eseguire l'operazione get, unire la configurazione da Git con quella che abbiamo ottenuto e quindi eseguire replace. Standardmente, kubectl consente solo di utilizzare il comando kubectl replace -f manifest.yaml, dove manifest.yaml — già il manifesto completamente preparato (nel nostro caso — unito) che deve essere installato. Pertanto, l'utente deve implementare la fusione dei manifesti, che non è un compito semplice...

Vale anche la pena notare che, sebbene manifest.yaml sia archiviato in Git, non possiamo sapere in anticipo se bisogna creare l'oggetto o aggiornarlo — questo deve farlo il software dell'utente.

In totale: possiamo costruire un rollout continuo solo con create, replace e delete, garantendo l'archiviazione della configurazione dell'infrastruttura in Git insieme al codice e un CI/CD conveniente?

In linea di principio, possiamo… Per questo dovremmo implementare un'operazione di fusione dei manifesti e un certo tipo di interfaccia che:

  • verifica l'esistenza dell'oggetto nel cluster,
  • esegue la creazione iniziale della risorsa,
  • aggiorna o la elimina.

Durante l'aggiornamento, occorre tenere presente che la risorsa potrebbe essere cambiata dall'ultimo get e gestire automaticamente il caso di ottimistic locking — effettuare tentativi ripetuti di aggiornamento.

Ma perché reinventare la ruota, quando kube-apiserver offre un altro modo per aggiornare le risorse: l'operazione patch, che allevia l'utente da alcune delle problematiche descritte?

Patch

Eccoci arrivati ai patch.

Le patch sono il principale metodo per applicare modifiche agli oggetti esistenti in Kubernetes. L'operazione patch funziona in modo tale che:

  • all'utente del kube-apiserver è richiesto di inviare una patch in formato JSON e specificare l'oggetto,
  • e l'apiserver si occuperà dello stato attuale dell'oggetto e lo porterà allo stato desiderato.

In questo caso, l'ottimistic locking non è necessario. Questa operazione è più dichiarativa rispetto a replace, anche se inizialmente può sembrare il contrario.

Pertanto:

  • con l'operazione create creiamo l'oggetto secondo il manifesto da Git,
  • utilizzando elimina — lo eliminiamo, se l'oggetto non è più necessario,
  • utilizzando patch — modifichiamo l'oggetto, portandolo allo stato descritto in Git.

Tuttavia, per fare ciò, è necessario creare patch corretto!

Come funzionano i patch in Helm 2: 2-way-merge

Durante la prima installazione del rilascio, Helm esegue l'operazione create per le risorse del chart.

Durante l'aggiornamento del rilascio, Helm per ogni risorsa:

  • calcola il patch tra la versione della risorsa del chart precedente e la versione attuale del chart,
  • applica questo patch.

Questo patch lo chiameremo 2-way-merge patch, perché nella sua creazione sono coinvolti 2 manifesti:

  • il manifesto della risorsa del rilascio precedente,
  • il manifesto della risorsa del rilascio attuale.

Durante la rimozione, l'operazione elimina viene chiamata nel kube apiserver per risorse che sono state dichiarate nel rilascio precedente, ma non dichiarate in quello attuale.

L'approccio con il 2-way-merge patch presenta un problema: porta a una desincronizzazione tra lo stato reale della risorsa nel cluster e il manifesto in Git.

Un'illustrazione del problema è il seguente esempio

  • In Git, nel chart, viene conservato un manifesto in cui il campo image del Deployment ha il valore ubuntu:18.04.
  • L'utente tramite kubectl edit ha cambiato il valore di questo campo in ubuntu:19.04.
  • Durante il ridispiegamento del chart Helm non genera un patch, perché il campo image nella versione precedente del rilascio e nel chart attuale sono identici.
  • Dopo il ridispiegamento image rimane ubuntu:19.04, anche se nel chart è indicato ubuntu:18.04.

Abbiamo ottenuto una desincronizzazione e perso la dichiaratività.

Cos'è una risorsa sincronizzata?

In generale, la piena corrispondenza del manifesto della risorsa nel cluster in esecuzione e del manifesto in Git non può essere ottenuta. Perché nel manifesto reale possono esserci annotazioni/etichette di servizio, contenitori aggiuntivi e altri dati, aggiunti e rimossi dalla risorsa dinamicamente da alcuni controller. Questi dati non possiamo e non vogliamo tenerli in Git. Tuttavia, vogliamo che durante il rollout i campi che abbiamo esplicitamente indicato in Git assumano i valori corrispondenti.

Ne deriva una regola generale per una risorsa sincronizzata : durante il rollout di una risorsa si possono modificare o rimuovere solo quei campi che sono esplicitamente riportati nel manifesto di Git (o erano riportati nella versione precedente e ora sono stati rimossi).3-way-merge patch

: genera un patch tra l'ultima versione applicata del manifesto di Git e la versione obiettivo del manifesto di Git, tenendo conto dell'attuale versione del manifesto del cluster in esecuzione. Il patch finale deve conformarsi alla regola della risorsa sincronizzata:

L'idea principale : genera un patch tra l'ultima versione applicata del manifesto di Git e la versione obiettivo del manifesto di Git, tenendo conto dell'attuale versione del manifesto del cluster in esecuzione. Il patch finale deve conformarsi alla regola della risorsa sincronizzata:i nuovi campi, aggiunti nella versione obiettivo, vengono aggiunti tramite il patch;

  • i campi precedentemente esistenti nell'ultima versione applicata e non esistenti nella versione obiettivo — vengono azzerati tramite il patch;
  • i campi nell'attuale versione dell'oggetto, che differiscono dalla versione obiettivo del manifesto, vengono aggiornati tramite il patch.
  • E proprio su questo principio vengono generati i patch

l'ultima versione applicata del manifesto è conservata nell'annotazione dell'oggetto stesso, kubectl apply:

  • l'obiettivo è preso dal file YAML specificato,
  • l'attuale — dal cluster in esecuzione.
  • attuale — da un cluster attivo.

Ora che abbiamo chiarito la teoria, è tempo di raccontare cosa abbiamo fatto in werf.

Applicazione delle modifiche in werf

In precedenza, werf, come Helm 2, utilizzava patch 2-way-merge.

Repair patch

Per passare a un nuovo tipo di patch — 3-way-merge, — come primo passo abbiamo introdotto i cosiddetti repair patch.

Durante il deployment viene utilizzato il patch standard 2-way-merge, ma werf genera anche un patch che sincronizza lo stato reale della risorsa con ciò che è scritto in Git (viene creato un patch utilizzando la stessa regola della risorsa sincronizzata descritta sopra).

In caso di desincronizzazione, alla fine del deployment l'utente riceve un WARNING con un messaggio pertinente e un patch che deve essere applicato per riportare la risorsa a uno stato sincronizzato. Inoltre, questo patch viene registrato in un'annotazione speciale werf.io/repair-patch. Si presume che l'utente applicherà manualmente questo patch: werf non lo applicherà per principio. applicherà questa patch: werf non la applicherà in modo decisivo.

La generazione di repair patch è una misura temporanea che consente di testare concretamente la creazione di patch secondo il principio del 3-way-merge, ma questi patch non vengono applicati automaticamente. Al momento, questa modalità di funzionamento è abilitata di default.

3-way-merge patch solo per nuovi rilasci

A partire dal 1 dicembre 2019, le versioni beta e alpha di werf iniziano predefinito a utilizzare patch 3-way-merge completi per l'applicazione delle modifiche solo per i nuovi rilasci di Helm, distribuiti tramite werf. I rilasci già esistenti continueranno a utilizzare l'approccio con patch 2-way-merge + patch di riparazione.

Questa modalità di funzionamento può essere abilitata esplicitamente impostando WERF_THREE_WAY_MERGE_MODE=onlyNewReleases adesso.

Nota: la funzione è stata introdotta in werf nel corso di diversi rilasci: nel canale alpha è stata completata dalla versione v1.0.5-alpha.19, e nel canale beta — da v1.0.4-beta.20.

3-way-merge patch per tutti i rilasci

A partire dal 15 dicembre 2019, le versioni beta e alpha di werf iniziano a utilizzare di default patch 3-way-merge completi per l'applicazione delle modifiche per tutti i rilasci.

Questa modalità di funzionamento può essere abilitata esplicitamente impostando WERF_THREE_WAY_MERGE_MODE=enabled adesso.

Come gestire il ridimensionamento automatico delle risorse?

In Kubernetes ci sono 2 tipi di autoscalamento: HPA (orizzontale) e VPA (verticale).

L'orizzontale sceglie automaticamente il numero di repliche, il verticale – la quantità di risorse. Sia il numero di repliche che i requisiti delle risorse sono specificati nel manifesto delle risorse (vedi spec.replicas o spec.containers[].resources.limits.cpu, spec.containers[].resources.limits.memory e altri).

Problema: se un utente configura una risorsa nel chart in modo da specificare valori definiti per le risorse o le repliche e per questa risorsa vengono abilitati gli autoscalatori, ad ogni deploy werf reimposterà questi valori a quelli riportati nel manifesto del chart.

Ci sono due soluzioni al problema. Inizialmente, è meglio evitare di specificare esplicitamente i valori autoscalabili nel manifesto del chart. Se questa opzione non è praticabile per qualche motivo (ad esempio, perché nel chart è comodo impostare i limiti iniziali delle risorse e il numero di repliche), werf offre le seguenti annotazioni:

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

Con tale annotazione, werf non ridurrà i valori corrispondenti ad ogni deploy, ma li imposterà solo alla creazione iniziale della risorsa.

Maggiore approfondimento – vedere nella documentazione del progetto su HPA e VPA.

Impedire l'uso del patch 3-way-merge

L'utente può attualmente impedire l'uso di nuovi patch in werf tramite la variabile d'ambiente WERF_THREE_WAY_MERGE_MODE=disabled. Tuttavia, a partire dal 1 marzo 2020, questo divieto smetterà di funzionare e sarà possibile utilizzare solo patch 3-way-merge.

Adozione delle risorse in werf

L'apprendimento del metodo di applicazione delle modifiche tramite patch 3-way-merge ci ha permesso subito di implementare una funzionalità come l'adozione delle risorse esistenti nel cluster in un rilascio Helm.

Helm 2 ha un problema: non è possibile aggiungere una risorsa che esiste già nel cluster ai manifesti del chart senza ricreare da zero questa risorsa (vedi #6031, #3275). Abbiamo insegnato a werf ad accettare risorse esistenti nel rilascio. Per fare ciò, è necessario installare sull'attuale versione della risorsa nel cluster attivo un'annotazione (ad esempio, tramite kubectl edit):

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

Ora la risorsa deve essere descritta nel chart e al prossimo deploy del rilascio con il nome corrispondente, la risorsa esistente sarà accettata in questo rilascio e rimarrà sotto il suo controllo. Inoltre, durante il processo di adozione, werf porterà lo stato attuale della risorsa dal cluster attivo allo stato descritto nel chart, utilizzando le stesse patch 3-way-merge e la regola della risorsa sincronizzata.

Nota: configurazione WERF_THREE_WAY_MERGE_MODE non influisce sull'adozione delle risorse – in caso di adozione viene sempre utilizzata una patch 3-way-merge.

Dettagli – in documentazione.

Conclusioni e piani futuri

Spero che dopo questo articolo risulti più chiaro cosa siano le patch 3-way-merge e perchè sono state integrate. Dal punto di vista pratico dello sviluppo del progetto werf, la loro implementazione rappresenta un ulteriore passo verso il miglioramento del deploy simile a Helm. Ora possiamo dimenticare i problemi di sincronizzazione della configurazione che spesso si presentano con Helm 2. Inoltre, è stata aggiunta una nuova funzionalità utile per l'adozione delle risorse Kubernetes già esistenti nei rilasci Helm.

Nel deploy simile a Helm permangono comunque alcuni problemi e difficoltà, come l'uso dei template Go, e continueremo a risolverli.

Informazioni sui metodi di aggiornamento delle risorse e sull'adozione si possono anche trovare su questa pagina di documentazione.

Helm 3

Un'ulteriore nota merita la recente uscita della nuova versione principale di Helm – v3, – che utilizza anche patch 3-way-merge ed elimina Tiller. La nuova versione di Helm richiede migrazione installazioni già esistenti per convertirle nel nuovo formato di archiviazione dei rilasci.

Da parte sua, werf attualmente ha già eliminato l'uso di Tiller, ha adottato le patch 3-way-merge e ha aggiunto molto altro, rimanendo comunque compatibile con le installazioni già esistenti su Helm 2 (non è necessario eseguire script di migrazione). Pertanto, finché werf non è stato migrato a Helm 3, gli utenti di werf non perdono i principali vantaggi di Helm 3 rispetto a Helm 2 (essi sono presenti anche in werf).

Tuttavia, la migrazione di werf sulla base di codice di Helm 3 è inevitabile e avverrà nel prossimo futuro. Si prevede che sarà werf 1.1 o werf 1.2 (attualmente, la principale versione di werf è 1.0; per ulteriori dettagli sul sistema di versionamento di werf vedi qui). Nel frattempo, Helm 3 avrà il tempo di stabilizzarsi.

P.S.

Leggete anche nel nostro blog:

Fonte: habr.com

Acquista hosting affidabile per siti web con protezione DDoS, server VPS VDS 🔥 Acquista hosting affidabile per siti web con protezione DDoS, server VPS VDS | ProHoster