Îmbinarea 3-way în werf: desfășurare în Kubernetes cu Helm «pe steroizi»

S-a întâmplat ceea ce noi (și nu doar noi) am așteptat mult timp: werf, utilitarul nostru Open Source pentru construirea aplicațiilor și livrarea acestora în Kubernetes, acum suportă aplicarea modificărilor prin patch-uri de tip 3-way-merge! În plus, a apărut posibilitatea adoptării resurselor K8s existente în lansările Helm fără a recrea aceste resurse.

Îmbinarea 3-way în werf: desfășurare în Kubernetes cu Helm «pe steroizi»

Dacă trebuie să fie foarte pe scurt, atunci setăm WERF_THREE_WAY_MERGE=enabled — obținem un deployment „ca în kubectl apply”, compatibil cu instalările existente pe Helm 2 și chiar puțin mai mult.

Dar să începem cu teoria: ce este, de fapt, patch-ul de tip 3-way-merge, cum au ajuns oamenii la abordarea generării acestuia și de ce sunt importante în procesele CI/CD cu infrastructura bazată pe Kubernetes? Și după aceea, vom vedea ce reprezintă 3-way-merge în werf, ce moduri sunt folosite implicit și cum putem gestiona acest lucru.

Ce este un patch de tip 3-way-merge?

Așadar, să începem cu sarcina de a desfășura resursele descrise în manifestele YAML în Kubernetes.

Pentru a lucra cu resursele Kubernetes, API-ul oferă următoarele operații de bază: create, patch, replace și delete. Se presupune că prin intermediul acestora trebuie să construim un rollout continuu convenabil al resurselor în cluster. Cum?

Comenzile imperativă kubectl

Prima abordare pentru gestionarea obiectelor în Kubernetes este utilizarea comenzilor imperativă kubectl pentru a crea, modifica și șterge aceste obiecte. Cu alte cuvinte:

  • comanda kubectl run poate lansa un Deployment sau Job:
    kubectl run --generator=deployment/apps.v1 DEPLOYMENT_NAME --image=IMAGE
  • comanda kubectl scale — schimbă numărul de replici:
    kubectl scale --replicas=3 deployment/mysql
  • etc.

Această abordare poate părea convenabilă la prima vedere. Cu toate acestea, există probleme:

  1. Este dificil de automatizat.
  2. Cum a reflecta configurația în Git? Cum se face revizuirea modificărilor care au loc cu clusterul?
  3. Cum să asigurăm reproductibilitatea configurației la repornire?
  4. …

E clar că această abordare nu se potrivește bine cu păstrarea împreună cu codul aplicației și infrastructura ca cod (IaC; sau chiar GitOps ca o variantă mai modernă, care câștigă popularitate în ecosistemul Kubernetes). Prin urmare, aceste comenzi din kubectl nu au primit dezvoltări ulterioare.

Operațiile create, obține, înlocuiește și șterge

Cu crearea inițială totul este simplu: trimitem manifestul la operația la kube api și resursa este creată. Reprezentarea YAML a manifestului poate fi păstrată în Git, iar pentru creare se poate folosi comanda create kubectl create -f manifest.yaml ștergerea.

Cu de asemenea, este simplă: introducem același manifest.yaml din Git în comanda kubectl delete -f manifest.yaml replace.

Operația replace permite înlocuirea completă a configurației unei resurse cu una nouă, fără a recrea resursa. Acest lucru înseamnă că, înainte de a efectua modificări asupra resursei, este logic să cerem versiunea curentă printr-o operațiune get, să o modificăm și să actualizăm printr-o operațiune replace. În kube apiserver este integrat optimistic locking , iar dacă obiectul a fost modificat după operațiune get , operațiunea replace nu va reuși.

Pentru a stoca configurația în Git și a actualiza folosind replace, trebuie să efectuezi operațiunea get, să faci un merge al configurației din Git cu ceea ce am obținut și să executăm replace. În mod standard, kubectl permite doar utilizarea comenzii kubectl replace -f manifest.yaml, unde din Git în comanda — un manifest complet pregătit (în cazul nostru, îmbinat) care trebuie instalat. Așadar, utilizatorului îi revine sarcina de a realiza un merge al manifestelor, lucru ce nu este trivial...

De asemenea, merită menționat că, deși din Git în comanda este stocat în Git, nu putem ști dinainte dacă trebuie să creăm obiectul sau să-l actualizăm — acest lucru trebuie să-l facă software-ul utilizatorului.

În total: putem construi un rollout continuu doar cu ajutorul create, replace și delete, asigurând stocarea configurației infrastructurii în Git împreună cu codul și un CI/CD convenabil?

În principiu, putem... Pentru aceasta va trebui să implementăm operațiunea de merge a manifestelor și o anumită interfață care:

  • verifică existența obiectului în cluster,
  • efectuează crearea inițială a resursei,
  • o actualizează sau o șterge.

La actualizare trebuie să ținem cont că resursa ar fi putut fi modificată între timp get și să gestionăm automat cazul de optimistic locking — să facem încercări repetate de actualizare.

Totuși, de ce să inventăm bicicleta, când kube-apiserver oferă o altă modalitate de a actualiza resursele: operațiunea patch, care ia de pe umerii utilizatorului o parte din problemele descrise?

Patch

Iată că am ajuns la patch-uri.

Patch-urile sunt modul principal de a aplica modificări obiectelor existente în Kubernetes. Operațiunea patch funcționează astfel încât:

  • utilizatorul kube-apiserver trebuie să trimită un patch în format JSON și să indice obiectul,
  • iar apiserver-ul se va descurca singur cu starea curentă a obiectului și îl va aduce în forma dorită.

Optimistic locking în acest caz nu este necesar. Această operațiune este mai declarativă în comparație cu replace, deși inițial poate părea invers.

Astfel:

  • prin operațiunea create creăm un obiect conform manifestului din Git,
  • folosind delete — ștergem, dacă obiectul nu mai este necesar,
  • folosind patch — modificăm obiectul, adaptându-l la forma descrisă în Git.

Cu toate acestea, pentru a face acest lucru, trebuie să creăm patch-ul corect!

Cum funcționează patch-urile în Helm 2: 2-way-merge

La prima instalare a release-ului, Helm execută operația create pentru resursele chart-ului.

La actualizarea release-ului Helm pentru fiecare resursă:

  • calculează patch-ul între versiunea resursei din chart-ul anterior și versiunea curentă a chart-ului,
  • aplică acest patch.

Acest patch îl vom numi 2-way-merge patch, pentru că în crearea sa sunt implicate 2 manifestări:

  • manifestarea resursei din release-ul anterior,
  • manifestarea resursei din resursa curentă.

La eliminare, operația delete în kube apiserver este apelată pentru resursele care au fost declarate în release-ul anterior, dar nu sunt declarate în cel curent.

Abordarea cu 2-way merge patch are o problemă: duce la desincronizarea stării reale a resursei în cluster și a manifestului din Git.

Ilustrarea problemei prin exemplu

  • În Git, în chart se păstrează un manifest, în care câmpul imagine la Deployment are valoarea ubuntu:18.04.
  • Utilizatorul a kubectl edit schimbat valoarea acestui câmp în ubuntu:19.04.
  • La redeploy-ul chart-ului Helm, nu se generează patch,deoarece câmpul imagine în versiunea anterioară a release-ului și în chart-ul curent sunt identice.
  • După redeploy, imagine rămâne ubuntu:19.04, deși în chart scrie ubuntu:18.04.

Am obținut desincronizare și am pierdut caracterul declarativ.

Ce este o resursă sincronizată?

În general, completa corespondență a manifestului resursei din cluster-ul activ și a manifestului din Git nu este posibilă. Pentru că în manifestul real pot exista jucării de servicii/etichete, containere adiționale și alte date, adăugate și eliminate din resursă dinamic de către anumiți controleri. Aceste date nu putem și nu vrem să le păstrăm în Git. Totuși, dorim ca în momentul roll-out-ului, câmpurile pe care le-am specificat clar în Git să aibă valorile corespunzătoare.

Se obține o astfel de regulă generală pentru resursa sincronizată: la roll-out-ul resursei se pot schimba sau elimina doar acele câmpuri care sunt specificate clar în manifestul din Git (sau care au fost specificate în versiunea anterioară și acum au fost eliminate).

3-way-merge patch

Ideea principală 3-way-merge patch: generăm un patch între cea mai recent aplicată versiune a manifestului din Git și versiunea țintă a manifestului din Git, având în vedere versiunea curentă a manifestului din cluster-ul activ. Patch-ul final trebuie să respecte regula resursei sincronizate:

  • Câmpurile noi adăugate în versiunea țintă sunt integrate printr-un patch;
  • Câmpurile existente în ultima versiune aplicată și care nu există în cea țintă sunt resetate prin patch;
  • Câmpurile din versiunea curentă a obiectului, diferite de versiunea țintă a manifestului, sunt actualizate prin patch.

Acesta este principiul de bază pe care patch-urile sunt generate kubectl apply:

  • ultima versiune aplicată a manifestului este păstrată în adnotarea obiectului însuși,
  • versiunea țintă este preluată din fișierul YAML specificat,
  • versiunea curentă este din clusterul activ.

Acum că am clarificat teoria, este timpul să discutăm ce am realizat în werf.

Aplicarea modificărilor în werf

Anterior, werf, la fel ca Helm 2, folosea patch-uri de tip 2-way-merge.

Repair patch

Pentru a trece la un nou tip de patch-uri — 3-way-merge — primul pas a fost introducerea așa-numitelor repair-patch-uri.

La desfășurare se folosește un patch standard de tip 2-way-merge, dar werf generează suplimentar un patch care sincronizează starea reală a resursei cu ceea ce este scris în Git (se creează un astfel de patch folosind aceeași regulă de resursă sincronizată, descrisă mai sus).

În caz de desincronizare, la finalul desfășurării, utilizatorul primește un WARNING cu un mesaj corespunzător și un patch care trebuie aplicat pentru a readuce resursa în starea sincronizată. De asemenea, acest patch este înregistrat într-o adnotare specială werf.io/repair-patch. Se preconizează că utilizatorul îl va aplica manual, singur acest patch: werf nu îl va aplica în mod automat.

Generarea repair-patch-urilor este o măsură temporară care permite testarea creării patch-urilor pe baza principiului 3-way-merge, dar aceste patch-uri nu sunt aplicate automat. În prezent, acest mod de funcționare este activat în mod implicit.

Patch-ul 3-way-merge este disponibil doar pentru noi versiuni

Începând cu 1 decembrie 2019, versiunile beta și alpha ale werf încep implicit să folosească patch-uri 3-way-merge complete pentru aplicarea modificărilor doar pentru noile release-uri Helm desfășurate prin werf. Versiunile deja existente vor continua să utilizeze abordarea 2-way-merge + repair-patch-uri.

Acest mod de funcționare poate fi activat explicit prin configurarea WERF_THREE_WAY_MERGE_MODE=onlyNewReleases în prezent.

Notă: funcționalitatea a fost introdusă în werf în mai multe release-uri: în canalul alpha a devenit disponibilă cu versiunea v1.0.5-alpha.19, iar în canalul beta — cu v1.0.4-beta.20.

patch-ul 3-way-merge pentru toate versiunile

Începând cu 15 decembrie 2019, versiunile beta și alpha ale werf vor utiliza implicit patch-uri complete 3-way-merge pentru aplicarea modificărilor în toate versiunile.

Acest mod de funcționare poate fi activat explicit prin configurarea WERF_THREE_WAY_MERGE_MODE=enabled în prezent.

Ce trebuie să facem în legătură cu scalarea automată a resurselor?

În Kubernetes există 2 tipuri de scalare automată: HPA (orizontal) și VPA (vertical).

Scalarea orizontală alege automat numărul de replici, iar scalarea verticală – resursele necesare. Atât numărul de replici, cât și cerințele de resurse sunt specificate în manifestul resursei (vezi. spec.replicas sau spec.containers[].resources.limits.cpu, spec.containers[].resources.limits.memory și altele).

Problema: dacă utilizatorul configurează resursa în chart astfel încât să conțină valori definite pentru resurse sau replici și pentru această resursă sunt activate autoscaler-ii, atunci la fiecare deploy, werf va reseta aceste valori la cele specificate în manifestul chart-ului.

Sunt două soluții pentru această problemă. În primul rând, este cel mai bine să renunțe la specificarea explicită a valorilor scalabile în manifestul chart-ului. Dacă totuși această opțiune nu este potrivită din diverse motive (de exemplu, deoarece este convenabil să definești limitările inițiale ale resurselor și numărul de replici în chart), werf oferă următoarele anotații:

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

În cazul în care există o astfel de anotație, werf nu va reseta valorile corespunzătoare la fiecare deploy, ci le va stabili doar la crearea inițială a resursei.

Mai multe detalii – vezi în documentația proiectului privind HPA și VPA.

Interzice utilizarea patch-urilor 3-way-merge

Utilizatorul poate interzice deocamdată utilizarea noilor patch-uri în werf utilizând variabila de mediu WERF_THREE_WAY_MERGE_MODE=disabled. Cu toate acestea, începând cu 1 martie 2020, această interdicție va înceta să mai funcționeze și va fi posibilă doar utilizarea patch-urilor 3-way-merge.

Adoptarea resurselor în werf

Adoptarea metodei de aplicare a modificărilor prin patch-uri 3-way-merge ne-a permis să implementăm imediat o caracteristică, cum ar fi adoptarea resurselor existente în cluster în release-urile Helm.

Helm 2 are o problemă: nu poți adăuga în manifestele chart-ului o resursă care există deja în cluster, fără a o recrea de la zero (vezi. #6031, #3275). Am învățat werf să accepte resursele existente în release. Pentru aceasta, trebuie să instalezi pe versiunea curentă a resursei din cluster-ul activ o anotație (de exemplu, folosind kubectl edit):

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

Acum resursa trebuie descrisă în chart și la următorul deploy cu werf al release-ului cu numele corespunzător, resursa existentă va fi inclusă în acest release și va rămâne sub gestionarea sa. Mai mult, în procesul de acceptare a resursei în release, werf va aduce starea curentă a resursei din clusterul de lucru în starea descrisă în chart, folosind aceleași patch-uri 3-way-merge și regula resursei sincronizate.

Notă: configurare WERF_THREE_WAY_MERGE_MODE nu afectează adoptarea resurselor — în cazul adoptării se folosește întotdeauna patch-ul 3-way-merge.

Detalii — în documentation.

Concluzii și planuri viitoare

Sper că, după acest articol, este mai clar ce sunt patch-urile 3-way-merge și de ce s-a ajuns la ele. Din perspectiva practică a dezvoltării proiectului werf, implementarea lor a devenit încă un pas pe calea îmbunătățirii deploy-ului similar Helm. Acum putem uita de problemele de sincronizare a configurației, care apăreau adesea când se folosea Helm 2. În același timp, a fost adăugată o nouă caracteristică utilă pentru adoptarea resurselor Kubernetes deja extrase în release-urile Helm.

În deploy-urile similare Helm rămân încă unele probleme și dificultăți, cum ar fi utilizarea template-urilor Go, și vom continua să le rezolvăm.

Informații despre metodele de actualizare a resurselor și adopție pot fi găsite de asemenea pe această pagină de documentație.

Helm 3

Merită menționată în mod special noua versiune majoră a Helm — v3, — care de asemenea utilizează patch-uri 3-way-merge și renunță la Tiller. Noua versiune a Helm necesită instalări existente pentru a le transforma în noul format de stocare a release-urilor. migrare Werf, din partea sa, în prezent, deja a renunțat la utilizarea Tiller, a trecut la 3-way-merge și a adăugat

, rămânând în același timp compatibil cu instalările existente pe Helm 2 (nu este nevoie să se execute scripturi de migrare). Prin urmare, până când werf nu va trece la Helm 3, utilizatorii werf nu pierd avantajele principale ale Helm 3 față de Helm 2 (acestea sunt prezente și în werf). multe alteleCu toate acestea, tranziția werf la baza de cod Helm 3 este inevitabilă și va avea loc în viitorul apropiat. Se estimează că va fi werf 1.1 sau werf 1.2 (în prezent, versiunea principală a werf este 1.0; mai multe detalii despre structurarea versiunilor werf se găsesc

). Până atunci, Helm 3 va avea timp să se stabilizeze. aiciCiclul notelor despre noutățile în werf:

P.S.

Citiți și în blogul nostru:

Sursa: habr.com

Cumpără un hosting fiabil pentru site-uri cu protecție DDoS, servere VPS VDS 🔥 Cumpără un hosting fiabil pentru site-uri cu protecție DDoS, servere VPS VDS | ProHoster