Autoscaling și gestionarea resurselor în Kubernetes (prezentare generală și raport video)

27 aprilie la conferință Grevă 2019, ca parte a secțiunii „DevOps”, a fost dat raportul „Autoscaling și gestionarea resurselor în Kubernetes”. Vorbește despre modul în care puteți utiliza K8-urile pentru a asigura disponibilitatea ridicată a aplicațiilor dvs. și pentru a asigura performanță de vârf.

Autoscaling și gestionarea resurselor în Kubernetes (prezentare generală și raport video)

Prin tradiție, ne face plăcere să vă prezentăm video cu raportul (44 de minute, mult mai informativ decât articolul) și rezumatul principal sub formă de text. Merge!

Să analizăm subiectul raportului cuvânt cu cuvânt și să începem de la sfârșit.

Kubernetes

Să presupunem că avem containere Docker pe gazda noastră. Pentru ce? Pentru a asigura repetabilitatea și izolarea, care la rândul lor permite o implementare simplă și bună, CI/CD. Avem multe astfel de vehicule cu containere.

Ce oferă Kubernetes în acest caz?

  1. Nu ne mai gândim la aceste mașini și începem să lucrăm cu „norul” grup de containere sau păstăi (grupe de recipiente).
  2. În plus, nici măcar nu ne gândim la podurile individuale, ci gestionăm mai multоgrupuri mai mari. Astfel de primitive de nivel înalt permiteți-ne să spunem că există un șablon pentru rularea unei anumite sarcini de lucru și aici este numărul necesar de instanțe pentru a-l rula. Dacă ulterior schimbăm șablonul, toate instanțele se vor schimba.
  3. Cu API declarativ În loc să executăm o secvență de comenzi specifice, descriem „structura lumii” (în YAML), care este creată de Kubernetes. Și din nou: când descrierea se schimbă, afișarea ei reală se va schimba și ea.

Managementul resurselor

Procesor

Să rulăm nginx, php-fpm și mysql pe server. Aceste servicii vor avea de fapt și mai multe procese care rulează, fiecare dintre acestea necesită resurse de calcul:

Autoscaling și gestionarea resurselor în Kubernetes (prezentare generală și raport video)
(numerele de pe diapozitiv sunt „papagali”, nevoia abstractă a fiecărui proces pentru puterea de calcul)

Pentru a facilita lucrul cu aceasta, este logic să combinați procesele în grupuri (de exemplu, toate procesele nginx într-un singur grup „nginx”). O modalitate simplă și evidentă de a face acest lucru este să puneți fiecare grup într-un container:

Autoscaling și gestionarea resurselor în Kubernetes (prezentare generală și raport video)

Pentru a continua, trebuie să vă amintiți ce este un container (în Linux). Apariția lor a fost posibilă datorită a trei caracteristici cheie din kernel, implementate cu destul de mult timp în urmă: capacități, namespace и cgrupuri. Și dezvoltarea ulterioară a fost facilitată de alte tehnologii (inclusiv „shell-uri” convenabile precum Docker):

Autoscaling și gestionarea resurselor în Kubernetes (prezentare generală și raport video)

În contextul raportului, ne interesează doar cgrupuri, deoarece grupurile de control fac parte din funcționalitatea containerelor (Docker, etc.) care implementează managementul resurselor. Procesele combinate în grupuri, așa cum ne-am dorit, sunt grupuri de control.

Să revenim la cerințele CPU pentru aceste procese și acum pentru grupuri de procese:

Autoscaling și gestionarea resurselor în Kubernetes (prezentare generală și raport video)
(Repet că toate numerele sunt o expresie abstractă a nevoii de resurse)

În același timp, procesorul în sine are o anumită resursă finită (în exemplu, acesta este 1000), care poate lipsi tuturor (suma nevoilor tuturor grupurilor este 150+850+460=1460). Ce se va întâmpla în acest caz?

Nucleul începe să distribuie resurse și o face „în mod corect”, oferind aceeași cantitate de resurse fiecărui grup. Dar, în primul caz, sunt mai multe decât este necesar (333>150), deci surplusul (333-150=183) rămâne în rezervă, care este, de asemenea, distribuit în mod egal între alte două containere:

Autoscaling și gestionarea resurselor în Kubernetes (prezentare generală și raport video)

Ca rezultat: primul container avea suficiente resurse, al doilea – nu avea suficiente resurse, al treilea – nu avea suficiente resurse. Acesta este rezultatul acțiunilor programator „cinstit” în Linux - CFS. Funcționarea acestuia poate fi ajustată folosind atribuirea greutate fiecare dintre containere. De exemplu, așa:

Autoscaling și gestionarea resurselor în Kubernetes (prezentare generală și raport video)

Să ne uităm la cazul lipsei de resurse în al doilea container (php-fpm). Toate resursele containerului sunt distribuite în mod egal între procese. Drept urmare, procesul principal funcționează bine, dar toți lucrătorii încetinesc, primind mai puțin de jumătate din ceea ce au nevoie:

Autoscaling și gestionarea resurselor în Kubernetes (prezentare generală și raport video)

Așa funcționează programatorul CFS. Vom numi în continuare greutățile pe care le atribuim containerelor cereri. De ce este așa - vezi mai departe.

Să privim întreaga situație din cealaltă parte. După cum știți, toate drumurile duc la Roma, iar în cazul unui computer, la CPU. Un singur procesor, multe sarcini - aveți nevoie de un semafor. Cel mai simplu mod de a gestiona resursele este „semaforul”: au dat unui proces un timp fix de acces la CPU, apoi următorului etc.

Autoscaling și gestionarea resurselor în Kubernetes (prezentare generală și raport video)

Această abordare se numește cote dure (limitare greu). Să ne amintim pur și simplu ca limite. Cu toate acestea, dacă distribuiți limite pentru toate containerele, apare o problemă: mysql conducea pe drum și la un moment dat nevoia lui de CPU s-a încheiat, dar toate celelalte procese sunt forțate să aștepte până când CPU-ul inactiv.

Autoscaling și gestionarea resurselor în Kubernetes (prezentare generală și raport video)

Să revenim la nucleul Linux și la interacțiunea acestuia cu CPU - imaginea de ansamblu este următoarea:

Autoscaling și gestionarea resurselor în Kubernetes (prezentare generală și raport video)

cgroup are două setări - în esență acestea sunt două „întorsături” simple care vă permit să determinați:

  1. greutatea pentru container (cereri) este parts;
  2. procentul din timpul total al CPU pentru lucrul la sarcini container (limite) este cotă.

Cum se măsoară CPU?

Există diferite moduri:

  1. Ce este papagali, nimeni nu știe - trebuie să negociezi de fiecare dată.
  2. interes mai clar, dar relativ: 50% dintr-un server cu 4 nuclee și cu 20 nuclee sunt lucruri complet diferite.
  3. Le puteți folosi pe cele deja menționate greutate, pe care Linux le cunoaște, dar sunt și relative.
  4. Opțiunea cea mai adecvată este măsurarea resurselor de calcul în secunde. Acestea. în secunde de timp al procesorului în raport cu secunde de timp real: 1 secundă de timp a procesorului a fost dată pe 1 secundă reală - acesta este un întreg nucleu al procesorului.

Pentru a fi și mai ușor de vorbit, au început să măsoare direct în miezuri, adică prin ei același timp CPU față de cel real. Deoarece Linux înțelege greutățile, dar nu atât de mult timp/nuclee ale procesorului, era necesar un mecanism pentru a traduce de la unul la altul.

Să luăm în considerare un exemplu simplu cu un server cu 3 nuclee CPU, în care trei pod-uri vor primi greutăți (500, 1000 și 1500) care sunt ușor convertite în părțile corespunzătoare ale nucleelor ​​alocate acestora (0,5, 1 și 1,5).

Autoscaling și gestionarea resurselor în Kubernetes (prezentare generală și raport video)

Dacă luați un al doilea server, unde vor fi de două ori mai multe nuclee (6), și plasați aceleași poduri acolo, distribuția nucleelor ​​poate fi calculată cu ușurință prin simpla înmulțire cu 2 (1, 2 și, respectiv, 3). Dar un moment important apare atunci când pe acest server apare un al patrulea pod, a cărui greutate, pentru comoditate, va fi de 3000. Acesta ia o parte din resursele CPU (jumătate din nuclee), iar pentru podurile rămase acestea sunt recalculate (înjumătățite):

Autoscaling și gestionarea resurselor în Kubernetes (prezentare generală și raport video)

Kubernetes și resurse CPU

În Kubernetes, resursele CPU sunt de obicei măsurate în miliadrax, adică 0,001 miezuri sunt luate ca greutate de bază. (Același lucru în terminologia Linux/cgroups se numește partajare CPU, deși, mai precis, 1000 milicore = 1024 partajări CPU.) K8s se asigură că nu plasează mai multe pod-uri pe server decât există resurse CPU pentru suma greutăților tuturor pod-urilor.

Cum se întâmplă asta? Când adăugați un server la un cluster Kubernetes, este raportat câte nuclee CPU are disponibile. Și când creează un nou pod, planificatorul Kubernetes știe de câte nuclee va avea nevoie acest pod. Astfel, pod-ul va fi alocat unui server unde există suficiente nuclee.

Ce se va întâmpla dacă nu cererea este specificată (adică podul nu are un număr definit de nuclee de care are nevoie)? Să ne dăm seama cum în general Kubernetes numără resursele.

Pentru un pod puteți specifica atât solicitările (programator CFS) cât și limitele (vă amintiți semaforul?):

  • Dacă sunt specificate egale, pod-ului i se atribuie o clasă QoS garantat. Acest număr de nuclee întotdeauna disponibil pentru el este garantat.
  • Dacă cererea este mai mică decât limita - clasa QoS exploziv. Acestea. Ne așteptăm ca un pod, de exemplu, să folosească întotdeauna 1 nucleu, dar această valoare nu este o limitare pentru el: uneori pod poate folosi mai mult (când serverul are resurse gratuite pentru asta).
  • Există și o clasă QoS cel mai bun efort — include chiar acele poduri pentru care cererea nu este specificată. Resursele le sunt date ultimele.

memorie

Cu memoria, situația este similară, dar ușor diferită - la urma urmei, natura acestor resurse este diferită. În general, analogia este următoarea:

Autoscaling și gestionarea resurselor în Kubernetes (prezentare generală și raport video)

Să vedem cum sunt implementate cererile în memorie. Lăsați podurile să trăiască pe server, modificând consumul de memorie, până când unul dintre ele devine atât de mare încât rămâne fără memorie. În acest caz, ucigașul OOM apare și oprește cel mai mare proces:

Autoscaling și gestionarea resurselor în Kubernetes (prezentare generală și raport video)

Acest lucru nu ne convine întotdeauna, așa că este posibil să reglementăm ce procese sunt importante pentru noi și care nu ar trebui să fie oprite. Pentru a face acest lucru, utilizați parametrul oom_score_adj.

Să revenim la clasele QoS ale procesorului și să facem o analogie cu valorile oom_score_adj care determină prioritățile de consum de memorie pentru pod-uri:

  • Cea mai mică valoare oom_score_adj pentru un pod - -998 - înseamnă că un astfel de pod ar trebui să fie ucis ultima, aceasta garantat.
  • Cel mai mare - 1000 - este cel mai bun efort, astfel de păstăi sunt ucise mai întâi.
  • Pentru a calcula valorile rămase (exploziv) există o formulă, a cărei esență se rezumă la faptul că, cu cât un pod a solicitat mai multe resurse, cu atât este mai puțin probabil să fie ucis.

Autoscaling și gestionarea resurselor în Kubernetes (prezentare generală și raport video)

A doua „întorsătură” - limit_in_bytes - pentru limite. Cu el, totul este mai simplu: pur și simplu atribuim cantitatea maximă de memorie emisă și aici (spre deosebire de CPU) nu se pune problema cum să o măsurăm (memoria).

În total

Fiecare pod din Kubernetes este dat requests и limits - ambii parametri pentru CPU și memorie:

  1. pe baza cererilor funcționează planificatorul Kubernetes, care distribuie pod-uri între servere;
  2. pe baza tuturor parametrilor, se determină clasa QoS a podului;
  3. Greutățile relative sunt calculate pe baza cererilor CPU;
  4. planificatorul CFS este configurat pe baza cererilor CPU;
  5. OOM killer este configurat pe baza cererilor de memorie;
  6. un „semafor” este configurat pe baza limitelor CPU;
  7. Pe baza limitelor de memorie, este configurată o limită pentru cgroup.

Autoscaling și gestionarea resurselor în Kubernetes (prezentare generală și raport video)

În general, această imagine răspunde la toate întrebările despre cum are loc principala parte a gestionării resurselor în Kubernetes.

Autoscaling

Cluster-autoscaler K8s

Să ne imaginăm că întregul cluster este deja ocupat și că trebuie creat un nou pod. În timp ce podul nu poate apărea, acesta rămâne în stare În aşteptare. Pentru ca acesta să apară, putem conecta un nou server la cluster sau... instalați cluster-autoscaler, care o va face pentru noi: comandați o mașină virtuală de la furnizorul de cloud (folosind o solicitare API) și conectați-o la cluster , după care va fi adăugată podul .

Autoscaling și gestionarea resurselor în Kubernetes (prezentare generală și raport video)

Aceasta este scalarea automată a clusterului Kubernetes, care funcționează excelent (din experiența noastră). Cu toate acestea, ca și în alte părți, există câteva nuanțe aici...

Atâta timp cât am mărit dimensiunea clusterului, totul a fost în regulă, dar ce se întâmplă când clusterul a început să se elibereze? Problema este că migrarea podurilor (pentru a elibera gazde) este foarte dificilă din punct de vedere tehnic și costisitoare din punct de vedere al resurselor. Kubernetes folosește o abordare complet diferită.

Luați în considerare un cluster de 3 servere care are Deployment. Are 6 pod-uri: acum sunt 2 pentru fiecare server. Din anumite motive am vrut să oprim unul dintre servere. Pentru a face acest lucru vom folosi comanda kubectl drain, care:

  • va interzice trimiterea de noi poduri la acest server;
  • va șterge podurile existente pe server.

Deoarece Kubernetes este responsabil pentru menținerea numărului de pod-uri (6), pur și simplu va recrea ele pe alte noduri, dar nu pe cel care este dezactivat, deoarece este deja marcat ca indisponibil pentru găzduirea noilor poduri. Aceasta este o mecanică fundamentală pentru Kubernetes.

Autoscaling și gestionarea resurselor în Kubernetes (prezentare generală și raport video)

Cu toate acestea, există și aici o nuanță. Într-o situație similară, pentru StatefulSet (în loc de Deployment), acțiunile vor fi diferite. Acum avem deja o aplicație cu stare - de exemplu, trei pod-uri cu MongoDB, dintre care unul are un fel de problemă (datele au devenit corupte sau o altă eroare care împiedică podul să pornească corect). Și decidem din nou să dezactivăm un server. Ce se va intampla?

Autoscaling și gestionarea resurselor în Kubernetes (prezentare generală și raport video)

MongoDB ar putea mor pentru că are nevoie de cvorum: pentru un cluster de trei instalații, trebuie să funcționeze cel puțin două. Cu toate acestea, aceasta nu se intampla - mulțumită PodDisruptionBuget. Acest parametru determină numărul minim necesar de poduri de lucru. Știind că unul dintre podurile MongoDB nu mai funcționează și văzând că PodDisruptionBudget este setat pentru MongoDB minAvailable: 2, Kubernetes nu vă va permite să ștergeți un pod.

Concluzie: pentru ca mișcarea (și, de fapt, re-crearea) podurilor să funcționeze corect atunci când clusterul este eliberat, este necesar să configurați PodDisruptionBudget.

Scalare orizontală

Să luăm în considerare o altă situație. Există o aplicație care rulează ca Deployment în Kubernetes. Traficul utilizatorilor ajunge la podurile sale (de exemplu, există trei dintre ele) și măsuram un anumit indicator în ele (să zicem, încărcarea procesorului). Când sarcina crește, o înregistrăm într-un program și creștem numărul de pod-uri pentru a distribui cereri.

Astăzi, în Kubernetes, acest lucru nu trebuie făcut manual: o creștere/scădere automată a numărului de poduri este configurată în funcție de valorile indicatorilor de sarcină măsurați.

Autoscaling și gestionarea resurselor în Kubernetes (prezentare generală și raport video)

Principalele întrebări aici sunt: ce anume sa masoare и cum să interpretezi valorile obținute (pentru luarea unei decizii privind schimbarea numărului de păstăi). Puteți măsura multe:

Autoscaling și gestionarea resurselor în Kubernetes (prezentare generală și raport video)

Cum să faceți acest lucru din punct de vedere tehnic - colectați valori etc. — Am vorbit în detaliu în raportul despre Monitorizare și Kubernetes. Iar sfatul principal pentru alegerea parametrilor optimi este experiment!

Există USE metoda (Saturație de utilizare și erori), al cărui sens este următorul. Pe ce bază are sens să scalați, de exemplu, php-fpm? Pe baza faptului că lucrătorii se epuizează, asta este folosire. Și dacă lucrătorii s-au terminat și nu sunt acceptate conexiuni noi, asta este deja saturaţie. Ambii parametri trebuie măsurați și, în funcție de valori, trebuie efectuată scalarea.

În loc de concluzie

Raportul are o continuare: despre scalarea verticală și despre cum să selectați resursele potrivite. Voi vorbi despre asta în videoclipurile viitoare de pe YouTube-ul nostru - abonați-vă pentru a nu rata!

Videoclipuri și diapozitive

Videoclip de la spectacol (44 de minute):

Prezentarea raportului:

PS

Alte rapoarte despre Kubernetes pe blogul nostru:

Sursa: www.habr.com

Adauga un comentariu