3-way merge u werf: implementacija u Kubernetesu s Helmom "na steroidima"

Dogodilo se ono što smo (i ne samo mi) dugo čekali: werf, naš Open Source alat za izgradnju aplikacija i njihovu isporuku u Kubernetes, sada podržava primjenu promjena korištenjem 3-way-merge patch-eva! Osim toga, uvedena je mogućnost usvajanja postojećih K8s resursa u Helm relize bez ponovnog stvaranja tih resursa.

3-way merge u werf: implementacija u Kubernetesu s Helmom "na steroidima"

Ukratko, postavljamo WERF_THREE_WAY_MERGE=enabled — dobivamo isporuku „kao u kubectl apply“, kompatibilnu s postojećim instalacijama na Helm 2, pa čak i malo više.

Ali hajde da počnemo s teorijom: što su zapravo 3-way-merge patch-evi, kako su ljudi došli do pristupa njihovoj generaciji i zašto su važni u CI/CD procesima s infrastrukturom temeljenom na Kubernetesu? A nakon toga — pogledat ćemo što zapravo predstavlja 3-way-merge u werfu, koji se načini koriste prema zadanim postavkama i kako tim upravljati.

Što je 3-way-merge patch?

Dakle, započnimo s problematikom isporuke resursa opisanih u YAML manifestima, u Kubernetes.

Za rad s resursima Kubernetes API nudi sljedeće osnovne operacije: create, patch, replace i delete. Pretpostavlja se da je njihova svrha olakšati kontinuiranu isporuku resursa u klaster. Kako?

Imperativne komande kubectl

Prvi pristup upravljanju objektima u Kubernetesu — korištenje imperativnih komandi kubectl za kreiranje, promjenu i brisanje tih objekata. Drugim riječima:

  • komandom kubectl run može pokrenuti Deployment ili Job:
    kubectl run --generator=deployment/apps.v1 IME_DEPLOYMENT-a --image=IMAGE
  • komandom kubectl scale — promijeniti broj replik:
    kubectl scale --replicas=3 deployment/mysql
  • itd.

Ovaj pristup može se učiniti jednostavnim na prvi pogled. Međutim, postoje problemi:

  1. Teško je automatizirati.
  2. Kako odraziti konfiguraciju u Git? Kako napraviti pregled promjena, koje se događaju s klasterom?
  3. Kako osigurati reproducibilnost konfiguracije prilikom ponovnog pokretanja?
  4. …

Jasno je da se takav pristup loše usklađuje s pohranjivanjem zajedno s kodom aplikacije i infrastrukturom kao kodom (IaC; ili čak GitOps kao modernija opcija, koja postaje sve popularnija u Kubernetes ekosustavu). Stoga daljnji razvoj ovih komandi u kubectl nije se dogodio.

Operacije create, get, replace i delete

S osnovnim kreiranjem sve je jednostavno: šaljemo manifest u operaciju create kube API-ju i resurs je kreiran. YAML prikaz manifesta može se pohraniti u Git, a za kreiranje — koristiti komandu kubectl create -f manifest.yaml.

S brisanje također je jednostavno: umetnemo isti manifest.yaml iz Gita u komandu kubectl delete -f manifest.yaml.

Operacija replace omogućuje potpuno zamjenu konfiguracije resursa s novom, bez ponovnog stvaranja resursa. To znači da je prije nego što se izvrši promjena resursa logično zatražiti trenutnu verziju operacijom preuzmi, izmijeniti je i ažurirati operacijom replace. U kube apiserveru ugrađen je optimistički zaključavanje i, ako se nakon operacije preuzmi objekt promijenio, tada operacija replace neće proći.

Kako bismo pohranili konfiguraciju u Git i ažurirali je pomoću replace, potrebno je izvršiti operaciju preuzmi, spojiti konfiguraciju iz Gita s onim što smo dobili, i izvršiti replace. Standardno, kubectl omogućuje samo korištenje naredbe kubectl replace -f manifest.yaml, gdje manifest.yaml — već potpuno pripremljen (u našem slučaju — spojeni) manifest koji treba instalirati. Dakle, korisniku je potrebno implementirati spajanje manifestacija, a to nije trivijalno…

Također valja napomenuti da, iako manifest.yaml se pohranjuje u Git, ne možemo unaprijed znati je li potrebno stvoriti objekt ili ga ažurirati — to bi trebao učiniti korisnički softver.

Ukratko: možemo li izgraditi kontinuirani isporučivanje samo pomoću create, replace i delete, osiguravajući pohranu konfiguracije infrastrukture u Gitu zajedno s kodom i prikladnim CI/CD-om?

U načelu, možemo… Za to bit će potrebno implementirati operaciju spajanja manifestacija i neku vrstu omotača koji:

  • provjerava postojanje objekta u klasteru,
  • izvodi inicijalno stvaranje resursa,
  • ažurira ili briše ga.

Prilikom ažuriranja treba uzeti u obzir da resurs je mogao promijeniti od posljednjeg preuzmi i automatski obraditi slučaj optimističkog zaključavanja — izvršavati ponovne pokušaje ažuriranja.

Međutim, zašto izmišljati kotač kada kube-apiserver nudi drugi način ažuriranja resursa: operaciju patch, koja preuzima dio opisanih problema od korisnika?

Patch

Evo nas do zakrpa.

Zakrpe su glavni način primjene promjena postojećim objektima u Kubernetesu. Operacija patch funkcionira tako da:

  • korisniku kube-apiservera treba poslati zakrpu u JSON formatu i navesti objekt,
  • a apiserver će sam razumjeti trenutno stanje objekta i dovesti ga u željeni oblik.

Optimističko zaključavanje u ovom slučaju nije potrebno. Ova operacija je deklarativnija u usporedbi s replace, iako se isprva može činiti obrnuto.

Dakle:

  • uz pomoć operacije create stvaramo objekt prema manifestu iz Gita,
  • pomoću izbriši — brišemo ga ako objekt više nije potreban,
  • pomoću patch — mijenjamo objekt, dovodeći ga u oblik opisano u Gitu.

Međutim, da biste to učinili, potrebno je stvoriti ispravan patch!

Kako rade patch-evi u Helm 2: 2-way-merge

Pri prvoj instalaciji Helm izdanja vrši se operacija create za resurse charta.

Pri ažuriranju izdanja Helm za svaki resurs:

  • uzima patch između verzije resursa iz prethodnog charta i trenutne verzije charta,
  • primjenjuje taj patch.

Takav patch ćemo zvati 2-way-merge patch, jer se u njegovom stvaranju sudjeluje 2 manifesta:

  • manifest resursa iz prethodnog izdanja,
  • manifest resursa iz trenutnog resursa.

Pri brisanju operacija izbriši u kube apiserveru se poziva za resurse koji su bili deklarirani u prethodnom izdanju, ali nisu deklarirani u trenutnom.

Pristup s 2-way merge patch ima problem: dovodi do nesinkronizacije stvarnog stanja resursa u klasteru i manifesta u Gitu.

Ilustracija problema na primjeru

  • U Gitu, u chartu se čuva manifest, u kojem polje image za Deployment ima vrijednost ubuntu:18.04.
  • Korisnik je preko kubectl edit promijenio vrijednost tog polja na ubuntu:19.04.
  • Pri ponovnoj depoymentu charta Helma ne generira patch, jer su polja image u prethodnoj verziji izdanja i trenutnom chartu ista.
  • Nakon ponovnog deploya image ostaje ubuntu:19.04, iako u chartu piše ubuntu:18.04.

Dobili smo nesinkronizaciju i izgubili deklarativnost.

Što je to sinkronizirani resurs?

Općenito, potpuno usklađenost manifesta resursa u aktivnom klasteru i manifesta iz Gita nije moguće postići. Jer u stvarnom manifestu mogu biti sistemske anotacije/oznake, dodatni kontejneri i drugi podaci koji se dinamički dodaju i uklanjaju iz resursa od strane kontrolera. Te podatke ne možemo i ne želimo držati u Gitu. Međutim, želimo da prilikom implementacije ta polja koja smo jasno naveli u Gitu imaju odgovarajuće vrijednosti.

Ispada da je to opće pravilo sinkroniziranog resursa: prilikom implementacije resursa mogu se mijenjati ili brisati samo ona polja koja su jasno navedena u manifestu iz Gita (ili su bila navedena u prethodnoj verziji, a sada su uklonjena).

3-way-merge patch

Osnovna ideja 3-way-merge patch: generira patch između posljednje primijenjene verzije manifesta iz Gita i ciljne verzije manifesta iz Gita uzimajući u obzir trenutnu verziju manifesta iz aktivnog klastera. Konačni patch mora odgovarati pravilu sinkroniziranog resursa:

  • nova polja, dodana u ciljnu verziju, dodaju se pomoću patch-a;
  • polja koja su postojala u posljednjoj primijenjenoj verziji i ne postoje u ciljnome - poništavaju se pomoću zakrpe;
  • polja u trenutnoj verziji objekta koja se razlikuju od ciljne verzije manifesta - ažuriraju se pomoću zakrpe.

Na upravo takvom principu generiraju se zakrpe kubectl apply:

  • posljednja primijenjena verzija manifesta čuva se u anotaciji samog objekta,
  • ciljna - preuzima se iz navedene YAML datoteke,
  • trenutna - iz radnog klastera.

Sada kada smo se pozabavili teorijom, vrijeme je da kažemo što smo napravili u werf.

Primjena promjena u werf

Ranije je werf, kao i Helm 2, koristio 2-way-merge zakrpe.

Repair zakrpa

Kako bismo prešli na novi tip zakrpa - 3-way-merge, prvi korak koji smo poduzeli bio je uvođenje takozvanih repair zakrpa.

Prilikom implementacije koristi se standardna 2-way-merge zakrpa, no werf dodatno generira takvu zakrpu koja bi sinkronizirala stvarno stanje resursa s onim što je napisano u Gitu (stvara se ta zakrpa koristeći isto pravilo sinkroniziranog resursa opisano iznad).

U slučaju nesinkronizacije, na kraju implementacije korisnik dobiva WARNING s odgovarajućom porukom i zakrpom koju treba primijeniti kako bi se resurs doveo u sinkronizirani položaj. Također, ova zakrpa se zapisuje u posebnu anotaciju werf.io/repair-patch. Predviđa se da korisnik ručno samo primijeni tu zakrpu: werf je neće primijeniti principijelno.

Generiranje repair zakrpa je privremena mjera koja omogućava isprobavanje stvaranja zakrpa prema principu 3-way-merge, ali automatski te zakrpe ne primjenjuju. U ovom trenutku ovaj način rada je uključен prema zadanim postavkama.

3-way-merge zakrpa samo za nove izdanja

Počevši od 1. prosinca 2019. beta i alpha verzije werf počinju po defaultu koristiti potpune 3-way-merge zakrpe za primjenu promjena samo za nove Helm izdanja koja se implementiraju putem werf. Već postojeća izdanja nastavit će koristiti pristup s 2-way-merge + repair zakrpama.

Ovaj način rada može se uključiti izrijekom postavkom WERF_THREE_WAY_MERGE_MODE=onlyNewReleases već sada.

Napomena: funkcija se pojavila u werf-u tijekom nekoliko izdanja: u alpha kanalu postala je gotova s verzijom v1.0.5-alpha.19, a u beta kanalu - od v1.0.4-beta.20.

3-way-merge zakrpa za sve verzije

Od 15. prosinca 2019. beta i alpha verzije werf po defaultu koriste potpune 3-way-merge patch-eve za primjenu promjena na svim izdanjima.

Ovaj način rada može se uključiti izrijekom postavkom WERF_THREE_WAY_MERGE_MODE=enabled već sada.

Kako se nositi s automatskim skaliranjem resursa?

U Kubernetesu postoje 2 tipa automatskog skaliranja: HPA (horizontalno) i VPA (vertikalno).

Horizontalno automatski određuje broj replika, dok vertikalno određuje količinu resursa. I broj replika i zahtjevi za resursima su navedeni u manifestu resursa (vidi. spec.replicas ili spec.containers[].resources.limits.cpu, spec.containers[].resources.limits.memory i druge).

Problem: ako korisnik konfiguriše resurs u chartu tako da ima određene vrijednosti za resurse ili replike i za taj resurs su uključeni automatski skaleri, tada će werf pri svakoj implementaciji resetirati ove vrijednosti na ono što je zapisano u manifestu charta.

Postoje dva rješenja za problem. Prvo, najbolje je odustati od eksplicitnog navođenja automatski skaliranih vrijednosti u manifestu charta. Ako ova opcija iz nekog razloga nije pogodna (na primjer, jer je zgodno postaviti početna ograničenja resursa i broj replika unutar charta), werf nudi sljedeće anotacije:

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

U prisustvu takve anotacije, werf neće resetirati odgovarajuće vrijednosti pri svakoj implementaciji, već će ih postaviti samo prilikom prvog kreiranja resursa.

Više informacija — vidi u dokumentaciji projekta o HPA i VPA.

Zabraniti korištenje 3-way-merge patch-a

Korisnik može trenutno zabraniti korištenje novih patch-eva u werf pomoću varijable okruženja WERF_THREE_WAY_MERGE_MODE=disabled. Međutim, od 1. ožujka 2020. ovo pravilo prestaje vrijediti i bit će moguće samo korištenje 3-way-merge patch-eva.

Usvajanje resursa u werf

Uvođenje metode primjene promjena 3-way-merge patch-evima omogućilo nam je odmah implementirati funkciju poput usvajanja postojećih resursa u klasteru u Helm izdanju.

Helm 2 ima problem: nije moguće dodati resurs koji već postoji u klasteru u manifeste charta bez ponovnog stvaranja tog resursa (vidi. #6031, #3275). Naučili smo werf da prihvati postojeće resurse u izdanju. Za to je potrebno na trenutnu verziju resursa iz aktivnog klastera postaviti anotaciju (na primjer, pomoću kubectl edit):

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

Sada resurs treba opisati u chartu, a prilikom sljedeće implementacije werf-a s odgovarajućim imenom, postojeći resurs će biti prihvaćen u tu verziju i ostat će pod njegovim upravljanjem. Štoviše, tijekom prihvaćanja resursa u verziju, werf će dovesti trenutno stanje resursa iz radnog klastera u stanje opisano u chartu, koristeći iste 3-way-merge zakrpe i pravilo sinkroniziranog resursa.

Napomena: konfiguracija WERF_THREE_WAY_MERGE_MODE ne utječe na usvajanje resursa — u slučaju usvajanja uvijek se koristi 3-way-merge zakrpa.

Detalji — u dokumentaciju.

Zaključci i daljnji planovi

Nadam se da je nakon ovog članka jasnije što su 3-way-merge zakrpe i zašto su uvedene. Iz praktične perspektive razvoja projekta werf, njihova implementacija bila je još jedan korak ka poboljšanju Helm-slične implementacije. Sada možemo zaboraviti na probleme sa sinkronizacijom konfiguracija koji su se često javljali pri korištenju Helm 2. Također, dodana je nova korisna značajka usvajanja već preuzetih Kubernetes resursa u Helm verziju.

U Helm-sličnim implementacijama i dalje ostaju neki problemi i poteškoće, poput korištenja Go predložaka, a nastavit ćemo ih rješavati.

Informacije o metodama ažuriranja resursa i usvajanju također se mogu pronaći na ovoj stranici dokumentacije.

Helm 3

Posebnu pažnju zaslužuje nedavno objavljena nova glavna verzija Helma — v3, — koja također koristi 3-way-merge zakrpe i uklanja Tiller. Nova verzija Helma zahtijeva migraciju već postojećih instalacija kako bi ih konvertirala u novi format pohrane verzija.

Werf je sa svoje strane već uklonio korištenje Tillera, prešao na 3-way-merge i dodao mnoge druge stvari, pri čemu je ostao kompatibilan s već postojećim instalacijama na Helmu 2 (nema potrebe za izvođenjem skripti za migraciju). Stoga, dok werf ne pređe na Helm 3, korisnici werfa ne gube osnovne prednosti Helma 3 u odnosu na Helm 2 (one su također prisutne u werfu).

Ipak, prebacivanje werfa na kodnu bazu Helma 3 neizbježno je i dogodit će se u bliskoj budućnosti. Pretpostavlja se da će to biti werf 1.1 ili werf 1.2 (trenutno je glavna verzija werfa — 1.0; više o strukturi verzioniranja werfa pogledajte ovdje). U to vrijeme Helm 3 će se uspjeti stabilizirati.

P.S.

Pročitajte i na našem blogu:

Izvor: habr.com

Kupite pouzdan hosting za web stranice s DDoS zaštitom, VPS VDS servere 🔥 Kupite pouzdan hosting za web stranice s DDoS zaštitom, VPS VDS servere | ProHoster