S-a întâmplat ceea ce noi (și nu doar noi) am așteptat mult timp: , 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.

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 runpoate 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:
- Este dificil de automatizat.
- Cum a reflecta configurația în Git? Cum se face revizuirea modificărilor care au loc cu clusterul?
- Cum să asigurăm reproductibilitatea configurației la repornire?
- …
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 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 , 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
createcreă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
imaginela Deployment are valoareaubuntu:18.04. - Utilizatorul a
kubectl editschimbat valoarea acestui câmp înubuntu: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,
imaginerămâneubuntu:19.04, deși în chart scrieubuntu: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ă : 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 , iar în canalul beta — cu .
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 ).
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 și .
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. , ). 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_NAMEAcum 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 .
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 .
Helm 3
Merită menționată în mod special instalări existente pentru a le transforma în noul format de stocare a release-urilor. 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). Cu 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. Ciclul notelor despre noutățile în werf:
P.S.
Citiți și în blogul nostru:
- Utilizarea werf pentru lansarea chart-urilor complexe Helm
- «»;
- «»;
- «».
- «»;
- «»;
- «».
Sursa: habr.com
