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

In sintesi, impostiamo WERF_THREE_WAY_MERGE=enabled — otteniamo un deployment «come in kubectl apply», compatibile con le installazioni esistenti su Helm 2 e anche un po' di più.
Ma cominciamo con la teoria: cosa sono esattamente le patch 3-way-merge, come le persone sono arrivate all'approccio per la loro generazione e perché sono importanti nei processi CI/CD con un'infrastruttura basata su Kubernetes? E dopo di questo, vedremo cos'è il 3-way-merge in werf, quali modalità vengono utilizzate per impostazione predefinita e come gestirle.
Cosa è una patch 3-way-merge?
Quindi, cominciamo con l'argomento del rollout delle risorse descritte nei manifest YAML in Kubernetes.
Per lavorare con le risorse, l'API Kubernetes offre le seguenti operazioni principali: create, patch, replace e delete. Si presume che con esse si debba costruire un rollout continuo delle risorse nel cluster. Come?
Comandi imperativi kubectl
Il primo approccio alla gestione degli oggetti in Kubernetes è l'uso di comandi imperativi kubectl per creare, modificare ed eliminare questi oggetti. In termini semplici:
- comando
kubectl runpuò 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 problemi:
- È difficile automatizzare.
- Come riflettere la configurazione in Git? Come fare una revisione delle modifiche che avvengono nel cluster?
- Come garantire riproducibilità la configurazione al riavvio?
- …
È chiaro che questo approccio si integra male con la memorizzazione insieme al codice dell'applicazione e l'infrastruttura come codice (IaC; o anche come una versione più moderna che sta acquisendo popolarità nell'ecosistema Kubernetes). Pertanto, non ha ottenuto ulteriore sviluppo in kubectl.
Operazioni create, get, replace e delete
Per la creazione iniziale è tutto semplice: inviamo il manifesto all'operazione a kube api e la risorsa è creata. La rappresentazione YAML del manifesto può essere memorizzata in Git, e per la creazione — utilizzare il comando create kubectl create -f manifest.yaml eliminazione.
C è semplice anche: sostituiamo lo stesso manifest.yaml da Git nel comando kubectl delete -f manifest.yaml kubectl delete -f manifest.yaml.
Operazione replace consente di sostituire completamente la configurazione della risorsa con una nuova, senza dover ricreare la risorsa. Ciò significa che, prima di apportare una modifica alla risorsa, è logico richiedere la versione attuale tramite l'operazione get, modificarla e aggiornare tramite l'operazione replace. All'interno di kube apiserver è integrato , e, se dopo l'operazione get l'oggetto è cambiato, allora l'operazione replace non avrà successo.
Per memorizzare la configurazione in Git e aggiornare utilizzando replace, bisogna eseguire l'operazione get, unire la configurazione da Git con ciò che abbiamo ricevuto e procedere con replace. In modo predefinito, kubectl consente solo di utilizzare il comando kubectl replace -f manifest.yaml, dove da Git nel comando — un manifesto già completamente preparato (nel nostro caso — unito) che deve essere installato. Dunque, l'utente deve implementare la fusione dei manifesti, il che non è un compito banale...
Vale anche la pena notare che, anche se da Git nel comando è memorizzato in Git, non possiamo sapere in anticipo se bisogna creare un oggetto o aggiornarlo — questo deve essere fatto dal software dell'utente.
In totale: possiamo costruire un rilascio continuo solo con create, replace e delete, garantendo il mantenimento della configurazione dell'infrastruttura in Git insieme al codice e un CI/CD conveniente?
In linea di principio, possiamo... Per questo è necessario implementare l'operazione di fusione dei manifesti e qualche tipo di interfaccia, che:
- verifica la presenza dell'oggetto nel cluster,
- esegue la creazione iniziale della risorsa,
- la aggiorna o la elimina.
Durante l'aggiornamento è necessario considerare che la risorsa potrebbe essere cambiata dalla volta precedente get e gestire automaticamente il caso del blocco ottimistico — effettuando tentativi ripetuti di aggiornamento.
Tuttavia, perché inventare la ruota, quando kube-apiserver offre un altro modo per aggiornare le risorse: l'operazione patch, che solleva l'utente da parte dei problemi descritti?
Patch
Ecco che arriviamo ai patch.
I patch sono il modo principale per applicare modifiche agli oggetti esistenti in Kubernetes. L'operazione patch funziona in modo tale che:
- l'utente del kube-apiserver deve inviare un patch in formato JSON e specificare l'oggetto,
- e l'apiserver si occuperà dello stato attuale dell'oggetto e lo porterà allo stato richiesto.
In questo caso non è necessario il blocco ottimistico. Questa operazione è più dichiarativa rispetto a replace, anche se inizialmente potrebbe sembrare il contrario.
Pertanto:
- tramite l'operazione
createcreiamo un oggetto dal manifesto di Git, - utilizzando
delete— lo eliminiamo, se l'oggetto non è più necessario, - utilizzando
patch— modifichiamo l'oggetto, portandolo allo stato descritto in Git.
Tuttavia, per farlo, è necessario creare una patch corretta!
Come funzionano le 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 la patch tra la versione della risorsa del chart precedente e la versione attuale del chart,
- applica questa patch.
Questa patch la chiameremo 2-way-merge patch, perché nella sua creazione sono coinvolti 2 manifesti:
- il manifesto della risorsa dal rilascio precedente,
- il manifesto della risorsa dall'attuale risorsa.
Durante la cancellazione, l'operazione delete nel kube apiserver viene chiamata per le risorse che sono state dichiarate nel rilascio precedente, ma non sono dichiarate in quello attuale.
L'approccio della 2 way merge patch presenta un problema: porta a una desincronizzazione dello stato reale della risorsa nel cluster e del manifesto in Git.
Illustrazione del problema con un esempio
- In Git, nel chart è memorizzato un manifesto, in cui il campo
imagedel Deployment ha il valoreubuntu:18.04. - L'utente tramite
kubectl editha cambiato il valore di questo campo inubuntu:19.04. - Durante il ridispiegamento del chart Helm non genera una patch, perché il campo
imagenella versione precedente del rilascio e nell'attuale chart sono uguali. - Dopo il ridispiegamento
imagerimaneubuntu:19.04, anche se nel chart è scrittoubuntu:18.04.
Abbiamo ottenuto una desincronizzazione e perso la dichiaratività.
Che cos'è una risorsa sincronizzata?
In generale, è impossibile ottenere una corrispondenza completa tra il manifesto della risorsa nel cluster in funzione e il manifesto in Git. Perché nel manifesto reale potrebbero esserci annotazioni/etichette di servizio, contenitori aggiuntivi e altri dati, aggiunti e rimossi dalla risorsa in modo dinamico da alcuni controller. Questi dati non possiamo e non vogliamo tenerli in Git. Tuttavia, vogliamo che quando viene distribuita, i campi che abbiamo esplicitamente indicato in Git assumano i valori corrispondenti.
Si ottiene quindi una regola generale per le risorse sincronizzate: durante la distribuzione di una risorsa è possibile modificare o rimuovere solo quei campi che sono esplicitamente indicati nel manifesto di Git (o che erano stati indicati nella versione precedente e ora sono stati rimossi).
3-way-merge patch
L'idea principale : genera una patch tra l'ultima versione applicata del manifesto da Git e la versione target del manifesto da Git, tenendo conto dell'attuale versione del manifesto nel cluster in funzione. La patch finale deve rispettare la regola della risorsa sincronizzata:
- i nuovi campi aggiunti alla versione target vengono aggiunti tramite patch;
- I campi precedentemente esistenti nell'ultima versione applicata e non esistenti nella versione target vengono azzerati con la patch;
- I campi nella versione attuale dell'oggetto, diversi dalla versione target del manifesto, vengono aggiornati tramite patch.
Questo è esattamente il principio secondo cui vengono generate le patch kubectl apply:
- l'ultima versione applicata del manifesto viene salvata nell'annotazione dell'oggetto stesso,
- la target viene presa dal file YAML specificato,
- l'attuale viene dal cluster in uso.
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 di unione a 2 vie.
Patch di riparazione
Per passare a un nuovo tipo di patch — unione a 3 vie — abbiamo prima introdotto così chiamate patch di riparazione.
Durante il deployment viene utilizzata una patch standard di unione a 2 vie, ma werf genera inoltre una patch che sincronizza lo stato reale della risorsa con ciò che è scritto in Git (viene creata una patch utilizzando la stessa regola di risorsa sincronizzata descritta sopra).
In caso di disallineamento, al termine del deployment l'utente riceve un AVVISO con un messaggio pertinente e una patch che deve essere applicata per riportare la risorsa a uno stato sincronizzato. Questa patch viene inoltre registrata in un'annotazione speciale werf.io/repair-patch. Si presuppone che l'utente la applicherà manualmente: werf non la applicherà in modo principiale.
La generazione di patch di riparazione è una misura temporanea che consente di testare la creazione di patch secondo il principio dell'unione a 3 vie, ma queste patch non vengono applicate automaticamente. Attualmente, questa modalità operativa è abilitata per impostazione predefinita.
Patch di unione a 3 vie solo per nuove versioni
A partire dal 1 dicembre 2019, le versioni beta e alpha di werf iniziano predefinito a utilizzare patch di unione a 3 vie complete per l'applicazione delle modifiche solo per le nuove versioni Helm distribuite tramite werf. Le versioni già esistenti continueranno a utilizzare l'approccio con patch a 2 vie + patch di riparazione.
Questa modalità operativa può essere attivata esplicitamente impostando WERF_THREE_WAY_MERGE_MODE=onlyNewReleases già adesso.
Nota: la funzionalità è stata presente in werf per diverse versioni: nel canale alpha è diventata stabile con la versione , mentre nel canale beta — da .
patch di unione a 3 vie per tutte le versioni
A partire dal 15 dicembre 2019, le versioni beta e alpha di werf iniziano a utilizzare per impostazione predefinita patch 3-way-merge complete per applicare modifiche a tutte le release.
Questa modalità operativa può essere attivata esplicitamente impostando WERF_THREE_WAY_MERGE_MODE=enabled già adesso.
Come gestire l'autoscaling delle risorse?
In Kubernetes esistono 2 tipi di autoscaling: HPA (orizzontale) e VPA (verticale).
Quello orizzontale seleziona automaticamente il numero di repliche, quello verticale il numero di risorse. Sia il numero di repliche che i requisiti delle risorse sono specificati nel manifesto della risorsa (vedi spec.replicas o spec.containers[].resources.limits.cpu, spec.containers[].resources.limits.memory e ).
Problema: se l'utente configura una risorsa nel chart in modo tale che siano specificati valori per risorse o repliche e per questa risorsa sono abilitati gli autoscaler, ad ogni deploy werf ripristinerà questi valori a quelli registrati nel manifesto del chart.
Ci sono due soluzioni al problema. Per iniziare, è meglio evitare di specificare esplicitamente i valori autoscalabili nel manifesto del chart. Se questa opzione non è praticabile per qualche motivo (ad esempio, perché è comodo definire le limitazioni iniziali delle risorse e il numero di repliche nel chart), 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 ripristinerà i valori corrispondenti ad ogni deploy, ma li setterà solo al momento della creazione iniziale della risorsa.
Per maggiori dettagli - vedi nella documentazione del progetto su e .
Impedire l'uso di 3-way-merge patch
L'utente può attualmente vietare l'uso di nuove patch in werf tramite la variabile di 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.
Adoption delle risorse in werf
L'adozione del metodo applicativo delle modifiche tramite patch 3-way-merge ci ha permesso di implementare immediatamente una funzionalità come l'adozione delle risorse esistenti nel cluster in una release di Helm.
Helm 2 ha un problema: non è possibile aggiungere al manifesto del chart una risorsa che esiste già nel cluster senza ricreare quella risorsa da zero (vedi , ). Abbiamo insegnato a werf ad accettare risorse esistenti nella release. Per farlo, è necessario impostare sul versione attuale della risorsa nel cluster in esecuzione un'annotazione (ad esempio, tramite kubectl edit):
"werf.io/allow-adoption-by-release": RELEASE_NAMEOra è necessario descrivere la risorsa nella chart e al prossimo deploy con werf del rilascio con il nome corrispondente, la risorsa esistente verrà accettata in questo rilascio e rimarrà sotto il suo controllo. Inoltre, nel processo di accettazione della risorsa nel rilascio, werf porterà lo stato attuale della risorsa dal cluster funzionante allo stato descritto nella chart, utilizzando gli stessi patch di 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 utilizzato il patch di 3-way-merge.
Dettagli — in .
Conclusioni e futuri piani
Spero che dopo questo articolo sia più chiaro cosa siano i patch di 3-way-merge e perché siano stati adottati. Dal punto di vista pratico dello sviluppo del progetto werf, la loro implementazione è stato un ulteriore passo verso il miglioramento del deploy simile a Helm. Ora possiamo dimenticare i problemi di sincronizzazione della configurazione che spesso si presentavano con l'uso di Helm 2. Inoltre, è stata aggiunta una nuova utile funzionalità di adozione delle risorse Kubernetes già estratte nel rilascio Helm.
Nel deploy simile a Helm rimangono comunque alcuni problemi e difficoltà, come l'uso dei template Go, e continueremo a risolverli.
Le informazioni sui metodi di aggiornamento delle risorse e sull'adozione possono essere trovate anche in .
Helm 3
Merita una nota separata nuova versione principale di Helm — v3, — che usa anch'essa i patch di 3-way-merge e si libera di Tiller. La nuova versione di Helm richiede delle installazioni esistenti per convertirle nel nuovo formato di archiviazione dei rilasci.
Werf, da parte sua, ha già eliminato l'uso di Tiller, è passato ai patch di 3-way-merge e ha aggiunto , restando compatibile con le installazioni esistenti su Helm 2 (non è necessario eseguire script di migrazione). Pertanto, finché werf non passerà a Helm 3, gli utenti di werf non perderanno i principali vantaggi di Helm 3 rispetto a Helm 2 (essi sono presenti anche in werf).
Tuttavia, il passaggio di werf alla base di codice di Helm 3 è inevitabile e avverrà nel prossimo futuro. Presumibilmente sarà werf 1.1 o werf 1.2 (attualmente, la versione principale di werf è 1.0; ulteriori dettagli sul sistema di versioning di werf si trovano ). Nel frattempo, Helm 3 avrà il tempo di stabilizzarsi.
P.S.
Leggi anche nel nostro blog:
- Ciclo di note sulle novità in werf:
- «»;
- «»;
- «».
- «»;
- «»;
- «».
Fonte: habr.com
