ProHoster > BLOG > administrare > Kubernetes: de ce este atât de important să configurați managementul resurselor sistemului?
Kubernetes: de ce este atât de important să configurați managementul resurselor sistemului?
De regulă, este întotdeauna necesar să se furnizeze un grup dedicat de resurse unei aplicații pentru funcționarea corectă și stabilă. Dar ce se întâmplă dacă mai multe aplicații rulează la aceeași putere? Cum să asigurăm fiecăruia dintre ei resursele minime necesare? Cum poți limita consumul de resurse? Cum se distribuie corect sarcina între noduri? Cum să vă asigurați că mecanismul de scalare orizontală funcționează dacă sarcina aplicației crește?
Trebuie să începeți cu ce tipuri principale de resurse există în sistem - acesta este, desigur, timpul procesorului și RAM. În manifestele k8s, aceste tipuri de resurse sunt măsurate în următoarele unități:
CPU - în nuclee
RAM - în octeți
Mai mult, pentru fiecare resursă este posibil să se stabilească două tipuri de cerințe - cereri de и Limitele. Cereri - descrie cerințele minime pentru resursele gratuite ale unui nod pentru a rula un container (și un pod în ansamblu), în timp ce limitele stabilește o limită strictă a resurselor disponibile pentru container.
Este important să înțelegeți că manifestul nu trebuie să definească în mod explicit ambele tipuri, dar comportamentul va fi după cum urmează:
Dacă doar limitele unei resurse sunt specificate în mod explicit, atunci cererile pentru această resursă iau automat o valoare egală cu limitele (puteți verifica acest lucru apelând entitățile descrie). Acestea. de fapt, containerul va fi limitat la aceeași cantitate de resurse de care are nevoie pentru a rula.
Dacă doar cererile sunt specificate în mod explicit pentru o resursă, atunci nu sunt stabilite restricții superioare pentru această resursă - de exemplu. containerul este limitat doar de resursele nodului însuși.
De asemenea, este posibil să configurați gestionarea resurselor nu numai la nivelul unui anumit container, ci și la nivelul spațiului de nume folosind următoarele entități:
LimitRange — descrie politica de restricție la nivel de container/pod în ns și este necesară pentru a descrie limitele implicite pentru container/pod, precum și pentru a preveni crearea de containere/pod-uri evident grase (sau invers), pentru a limita numărul acestora și determinați posibila diferență a valorilor în limite și solicitări
Cote de resurse — descrie politica de restricție în general pentru toate containerele în ns și este folosită, de regulă, pentru a delimita resursele între medii (utilă atunci când mediile nu sunt strict delimitate la nivel de nod)
Următoarele sunt exemple de manifeste care stabilesc limite de resurse:
Acestea. în acest caz, pentru a rula un container cu nginx, veți avea nevoie de cel puțin 1G de RAM liber și 0.2 CPU pe nod, în timp ce cel mult containerul poate consuma 0.2 CPU și toată RAM disponibilă pe nod.
Acestea. suma tuturor containerelor de solicitare din ns implicit nu poate depăși 300m pentru CPU și 1G pentru OP, iar suma tuturor limitelor este de 700m pentru CPU și 2G pentru OP.
Acestea. în spațiul de nume implicit pentru toate containerele, cererea va fi setată la 100m pentru CPU și 1G pentru OP, limită - 1 CPU și 2G. În același timp, se stabilește și o limită a valorilor posibile în cerere/limită pentru CPU (50m < x < 2) și RAM (500M < x < 4G).
Acestea. pentru fiecare pod din ns implicit va exista o limită de 4 vCPU și 1G.
Acum aș vrea să vă spun ce avantaje ne poate oferi stabilirea acestor restricții.
Mecanism de echilibrare a sarcinii între noduri
După cum știți, componenta k8s este responsabilă pentru distribuția podurilor între noduri, cum ar fi Scheduler, care funcționează conform unui algoritm specific. Acest algoritm trece prin două etape atunci când selectează nodul optim pentru lansare:
filtrare
Variind
Acestea. conform politicii descrise, inițial sunt selectate noduri pe care este posibilă lansarea unui pod pe baza unui set predicate (inclusiv verificarea dacă nodul are suficiente resurse pentru a rula podul - PodFitsResources), și apoi pentru fiecare dintre aceste noduri, conform priorități sunt acordate puncte (inclusiv, cu cât un nod are mai multe resurse libere, cu atât i se alocă mai multe puncte - LeastResourceAllocation/LeastRequestedPriority/BalancedResourceAllocation) și pod-ul este lansat pe nodul cu cele mai multe puncte (dacă mai multe noduri îndeplinesc această condiție deodată, atunci este selectat unul aleatoriu).
În același timp, trebuie să înțelegeți că planificatorul, atunci când evaluează resursele disponibile ale unui nod, este ghidat de datele care sunt stocate în etcd - i.e. pentru cantitatea de resursă solicitată/limită a fiecărui pod care rulează pe acest nod, dar nu pentru consumul real de resurse. Aceste informații pot fi obținute din ieșirea comenzii kubectl describe node $NODE, de exemplu:
Aici vedem toate podurile care rulează pe un anumit nod, precum și resursele pe care fiecare pod le solicită. Și iată cum arată jurnalele de planificare când este lansat pod-ul cronjob-cron-events-1573793820-xt6q9 (aceste informații vor apărea în jurnalul de planificare când este setat al 10-lea nivel de înregistrare în argumentele comenzii de lansare -v=10 ):
Buturuga
I1115 07:57:21.637791 1 scheduling_queue.go:908] About to try and schedule pod nxs-stage/cronjob-cron-events-1573793820-xt6q9
I1115 07:57:21.637804 1 scheduler.go:453] Attempting to schedule pod: nxs-stage/cronjob-cron-events-1573793820-xt6q9
I1115 07:57:21.638285 1 predicates.go:829] Schedule Pod nxs-stage/cronjob-cron-events-1573793820-xt6q9 on Node nxs-k8s-s5 is allowed, Node is running only 16 out of 110 Pods.
I1115 07:57:21.638300 1 predicates.go:829] Schedule Pod nxs-stage/cronjob-cron-events-1573793820-xt6q9 on Node nxs-k8s-s6 is allowed, Node is running only 20 out of 110 Pods.
I1115 07:57:21.638322 1 predicates.go:829] Schedule Pod nxs-stage/cronjob-cron-events-1573793820-xt6q9 on Node nxs-k8s-s3 is allowed, Node is running only 20 out of 110 Pods.
I1115 07:57:21.638322 1 predicates.go:829] Schedule Pod nxs-stage/cronjob-cron-events-1573793820-xt6q9 on Node nxs-k8s-s4 is allowed, Node is running only 17 out of 110 Pods.
I1115 07:57:21.638334 1 predicates.go:829] Schedule Pod nxs-stage/cronjob-cron-events-1573793820-xt6q9 on Node nxs-k8s-s10 is allowed, Node is running only 16 out of 110 Pods.
I1115 07:57:21.638365 1 predicates.go:829] Schedule Pod nxs-stage/cronjob-cron-events-1573793820-xt6q9 on Node nxs-k8s-s12 is allowed, Node is running only 9 out of 110 Pods.
I1115 07:57:21.638334 1 predicates.go:829] Schedule Pod nxs-stage/cronjob-cron-events-1573793820-xt6q9 on Node nxs-k8s-s11 is allowed, Node is running only 11 out of 110 Pods.
I1115 07:57:21.638385 1 predicates.go:829] Schedule Pod nxs-stage/cronjob-cron-events-1573793820-xt6q9 on Node nxs-k8s-s1 is allowed, Node is running only 19 out of 110 Pods.
I1115 07:57:21.638402 1 predicates.go:829] Schedule Pod nxs-stage/cronjob-cron-events-1573793820-xt6q9 on Node nxs-k8s-s2 is allowed, Node is running only 21 out of 110 Pods.
I1115 07:57:21.638383 1 predicates.go:829] Schedule Pod nxs-stage/cronjob-cron-events-1573793820-xt6q9 on Node nxs-k8s-s9 is allowed, Node is running only 16 out of 110 Pods.
I1115 07:57:21.638335 1 predicates.go:829] Schedule Pod nxs-stage/cronjob-cron-events-1573793820-xt6q9 on Node nxs-k8s-s8 is allowed, Node is running only 18 out of 110 Pods.
I1115 07:57:21.638408 1 predicates.go:829] Schedule Pod nxs-stage/cronjob-cron-events-1573793820-xt6q9 on Node nxs-k8s-s13 is allowed, Node is running only 8 out of 110 Pods.
I1115 07:57:21.638478 1 predicates.go:1369] Schedule Pod nxs-stage/cronjob-cron-events-1573793820-xt6q9 on Node nxs-k8s-s10 is allowed, existing pods anti-affinity terms satisfied.
I1115 07:57:21.638505 1 predicates.go:1369] Schedule Pod nxs-stage/cronjob-cron-events-1573793820-xt6q9 on Node nxs-k8s-s8 is allowed, existing pods anti-affinity terms satisfied.
I1115 07:57:21.638577 1 predicates.go:1369] Schedule Pod nxs-stage/cronjob-cron-events-1573793820-xt6q9 on Node nxs-k8s-s9 is allowed, existing pods anti-affinity terms satisfied.
I1115 07:57:21.638583 1 predicates.go:829] Schedule Pod nxs-stage/cronjob-cron-events-1573793820-xt6q9 on Node nxs-k8s-s7 is allowed, Node is running only 25 out of 110 Pods.
I1115 07:57:21.638932 1 resource_allocation.go:78] cronjob-cron-events-1573793820-xt6q9 -> nxs-k8s-s10: BalancedResourceAllocation, capacity 39900 millicores 66620178432 memory bytes, total request 2343 millicores 9640186880 memory bytes, score 9
I1115 07:57:21.638946 1 resource_allocation.go:78] cronjob-cron-events-1573793820-xt6q9 -> nxs-k8s-s10: LeastResourceAllocation, capacity 39900 millicores 66620178432 memory bytes, total request 2343 millicores 9640186880 memory bytes, score 8
I1115 07:57:21.638961 1 resource_allocation.go:78] cronjob-cron-events-1573793820-xt6q9 -> nxs-k8s-s9: BalancedResourceAllocation, capacity 39900 millicores 66620170240 memory bytes, total request 4107 millicores 11307422720 memory bytes, score 9
I1115 07:57:21.638971 1 resource_allocation.go:78] cronjob-cron-events-1573793820-xt6q9 -> nxs-k8s-s8: BalancedResourceAllocation, capacity 39900 millicores 66620178432 memory bytes, total request 5847 millicores 24333637120 memory bytes, score 7
I1115 07:57:21.638975 1 resource_allocation.go:78] cronjob-cron-events-1573793820-xt6q9 -> nxs-k8s-s9: LeastResourceAllocation, capacity 39900 millicores 66620170240 memory bytes, total request 4107 millicores 11307422720 memory bytes, score 8
I1115 07:57:21.638990 1 resource_allocation.go:78] cronjob-cron-events-1573793820-xt6q9 -> nxs-k8s-s8: LeastResourceAllocation, capacity 39900 millicores 66620178432 memory bytes, total request 5847 millicores 24333637120 memory bytes, score 7
I1115 07:57:21.639022 1 generic_scheduler.go:726] cronjob-cron-events-1573793820-xt6q9_nxs-stage -> nxs-k8s-s10: TaintTolerationPriority, Score: (10)
I1115 07:57:21.639030 1 generic_scheduler.go:726] cronjob-cron-events-1573793820-xt6q9_nxs-stage -> nxs-k8s-s8: TaintTolerationPriority, Score: (10)
I1115 07:57:21.639034 1 generic_scheduler.go:726] cronjob-cron-events-1573793820-xt6q9_nxs-stage -> nxs-k8s-s9: TaintTolerationPriority, Score: (10)
I1115 07:57:21.639041 1 generic_scheduler.go:726] cronjob-cron-events-1573793820-xt6q9_nxs-stage -> nxs-k8s-s10: NodeAffinityPriority, Score: (0)
I1115 07:57:21.639053 1 generic_scheduler.go:726] cronjob-cron-events-1573793820-xt6q9_nxs-stage -> nxs-k8s-s8: NodeAffinityPriority, Score: (0)
I1115 07:57:21.639059 1 generic_scheduler.go:726] cronjob-cron-events-1573793820-xt6q9_nxs-stage -> nxs-k8s-s9: NodeAffinityPriority, Score: (0)
I1115 07:57:21.639061 1 interpod_affinity.go:237] cronjob-cron-events-1573793820-xt6q9 -> nxs-k8s-s10: InterPodAffinityPriority, Score: (0)
I1115 07:57:21.639063 1 selector_spreading.go:146] cronjob-cron-events-1573793820-xt6q9 -> nxs-k8s-s10: SelectorSpreadPriority, Score: (10)
I1115 07:57:21.639073 1 interpod_affinity.go:237] cronjob-cron-events-1573793820-xt6q9 -> nxs-k8s-s8: InterPodAffinityPriority, Score: (0)
I1115 07:57:21.639077 1 selector_spreading.go:146] cronjob-cron-events-1573793820-xt6q9 -> nxs-k8s-s8: SelectorSpreadPriority, Score: (10)
I1115 07:57:21.639085 1 interpod_affinity.go:237] cronjob-cron-events-1573793820-xt6q9 -> nxs-k8s-s9: InterPodAffinityPriority, Score: (0)
I1115 07:57:21.639088 1 selector_spreading.go:146] cronjob-cron-events-1573793820-xt6q9 -> nxs-k8s-s9: SelectorSpreadPriority, Score: (10)
I1115 07:57:21.639103 1 generic_scheduler.go:726] cronjob-cron-events-1573793820-xt6q9_nxs-stage -> nxs-k8s-s10: SelectorSpreadPriority, Score: (10)
I1115 07:57:21.639109 1 generic_scheduler.go:726] cronjob-cron-events-1573793820-xt6q9_nxs-stage -> nxs-k8s-s8: SelectorSpreadPriority, Score: (10)
I1115 07:57:21.639114 1 generic_scheduler.go:726] cronjob-cron-events-1573793820-xt6q9_nxs-stage -> nxs-k8s-s9: SelectorSpreadPriority, Score: (10)
I1115 07:57:21.639127 1 generic_scheduler.go:781] Host nxs-k8s-s10 => Score 100037
I1115 07:57:21.639150 1 generic_scheduler.go:781] Host nxs-k8s-s8 => Score 100034
I1115 07:57:21.639154 1 generic_scheduler.go:781] Host nxs-k8s-s9 => Score 100037
I1115 07:57:21.639267 1 scheduler_binder.go:269] AssumePodVolumes for pod "nxs-stage/cronjob-cron-events-1573793820-xt6q9", node "nxs-k8s-s10"
I1115 07:57:21.639286 1 scheduler_binder.go:279] AssumePodVolumes for pod "nxs-stage/cronjob-cron-events-1573793820-xt6q9", node "nxs-k8s-s10": all PVCs bound and nothing to do
I1115 07:57:21.639333 1 factory.go:733] Attempting to bind cronjob-cron-events-1573793820-xt6q9 to nxs-k8s-s10
Aici vedem ca initial planificatorul filtreaza si genereaza o lista de 3 noduri pe care poate fi lansat (nxs-k8s-s8, nxs-k8s-s9, nxs-k8s-s10). Apoi calculează scorurile pe baza mai multor parametri (inclusiv BalancedResourceAllocation, LeastResourceAllocation) pentru fiecare dintre aceste noduri pentru a determina cel mai potrivit nod. În cele din urmă, pod-ul este programat pe nodul cu cel mai mare număr de puncte (aici două noduri simultan au același număr de puncte 100037, deci este selectat unul aleatoriu - nxs-k8s-s10).
Producție: dacă un nod rulează poduri pentru care nu sunt setate restricții, atunci pentru k8s (din punct de vedere al consumului de resurse) acest lucru va fi echivalent cu ca și cum nu ar exista astfel de poduri pe acest nod. Prin urmare, dacă aveți, în mod condiționat, un pod cu un proces lacom (de exemplu, wowza) și nu sunt stabilite restricții pentru acesta, atunci poate apărea o situație când acest pod a consumat de fapt toate resursele nodului, dar pentru k8s acest nod este considerat neîncărcat și i se va acorda același număr de puncte la clasare (mai exact în puncte care evaluează resursele disponibile) ca un nod care nu are pod-uri de lucru, ceea ce în cele din urmă poate duce la o distribuție neuniformă a sarcinii între noduri.
Evacuarea Podului
După cum știți, fiecărui pod îi este atribuită una dintre cele 3 clase QoS:
garantat — este atribuit atunci când pentru fiecare container din pod sunt specificate o solicitare și o limită pentru memorie și CPU, iar aceste valori trebuie să se potrivească
exploziv — cel puțin un container din pod are o cerere și o limită, cu cerere < limită
cel mai bun efort — când niciun container din pod nu are resurse limitate
În același timp, atunci când un nod se confruntă cu o lipsă de resurse (disc, memorie), kubelet începe să clasifice și să elimine podurile conform unui algoritm specific care ține cont de prioritatea pod-ului și a clasei sale QoS. De exemplu, dacă vorbim despre RAM, atunci pe baza clasei QoS, punctele sunt acordate conform următorului principiu:
Acestea. cu aceeași prioritate, kubelet-ul va evacua mai întâi podurile cu cel mai bun efort de clasă QoS din nod.
Producție: dacă doriți să reduceți probabilitatea ca podul dorit să fie evacuat din nod în cazul unei lipse de resurse pe acesta, atunci împreună cu prioritatea, trebuie să vă ocupați și de stabilirea cererii/limitei pentru acesta.
Mecanism pentru scalarea automată orizontală a podurilor de aplicare (HPA)
Când sarcina este să crească și să scadă automat numărul de pod-uri în funcție de utilizarea resurselor (sistem - CPU/RAM sau utilizator - rps), o entitate k8s precum HPA (Horizontal Pod Autoscaler). Algoritmul căruia este următorul:
Sunt determinate citirile curente ale resursei observate (currentMetricValue)
Sunt determinate valorile dorite pentru resursă (desiredMetricValue), care pentru resursele de sistem sunt setate folosind cererea
Numărul curent de replici este determinat (currentReplicas)
Următoarea formulă calculează numărul dorit de replici (desiredReplicas)
doritReplicas = [ currentReplicas * ( currentMetricValue / wantMetricValue )]
În acest caz, scalarea nu va avea loc atunci când coeficientul (currentMetricValue / wantedMetricValue) este aproape de 1 (în acest caz, putem seta singuri eroarea permisă; implicit este 0.1).
Să ne uităm la modul în care funcționează hpa folosind exemplul aplicației de testare a aplicației (descrisă ca Deployment), unde este necesar să se schimbe numărul de replici în funcție de consumul CPU:
Acestea. vedem că aplicația pod este lansată inițial în două instanțe, fiecare dintre ele conține două containere nginx și nginx-exporter, pentru fiecare dintre ele un specificat cereri de pentru CPU.
Acestea. Am creat un hpa care va monitoriza testul aplicației de implementare și va ajusta numărul de poduri cu aplicația pe baza indicatorului CPU (ne așteptăm ca podul să consume 30% din CPU-ul pe care îl solicită), numărul de replici fiind în intervalul 2-10.
Acum, să ne uităm la mecanismul de funcționare a hpa dacă aplicăm o sarcină uneia dintre focare:
# kubectl top pod
NAME CPU(cores) MEMORY(bytes)
app-test-78559f8f44-pgs58 101m 243Mi
app-test-78559f8f44-cj4jz 4m 240Mi
În total avem următoarele:
Valoarea dorită (desiredMetricValue) - conform setărilor hpa, avem 30%
Valoarea curentă (currentMetricValue) - pentru calcul, controler-manager calculează valoarea medie a consumului de resurse în %, adică. în mod condiționat face următoarele:
Primește valori absolute ale valorilor pod de la serverul de metrici, de exemplu. 101 m și 4 m
Calculează valoarea medie absolută, de ex. (101m + 4m) / 2 = 53m
Obține valoarea absolută pentru consumul de resurse dorit (pentru aceasta, solicitările tuturor containerelor sunt însumate) 60m + 30m = 90m
Calculează procentul mediu al consumului CPU în raport cu podul de solicitare, adică 53m / 90m * 100% = 59%
Acum avem tot ce ne trebuie pentru a determina dacă trebuie să schimbăm numărul de replici; pentru a face acest lucru, calculăm coeficientul:
ratio = 59% / 30% = 1.96
Acestea. numărul de replici ar trebui să crească de ~2 ori și să se ridice la [2 * 1.96] = 4.
Concluzie: După cum puteți vedea, pentru ca acest mecanism să funcționeze, o condiție necesară este prezența solicitărilor pentru toate containerele din podul observat.
Mecanism pentru autoscaling orizontal a nodurilor (Cluster Autoscaler)
Pentru a neutraliza impactul negativ asupra sistemului în timpul supratensiunii de sarcină, a avea un hpa configurat nu este suficient. De exemplu, conform setărilor din managerul de controler hpa, acesta decide că numărul de replici trebuie mărit de 2 ori, dar nodurile nu au resurse libere pentru a rula un astfel de număr de pod-uri (adică nodul nu poate oferi resursele solicitate către podul de solicitări) și aceste poduri trec în starea În așteptare.
În acest caz, dacă furnizorul are un IaaS/PaaS corespunzător (de exemplu, GKE/GCE, AKS, EKS etc.), un instrument precum Autoscaler nod. Vă permite să setați numărul maxim și minim de noduri din cluster și să ajustați automat numărul actual de noduri (prin apelarea API-ului furnizorului de cloud pentru a comanda/elimina un nod) atunci când există o lipsă de resurse în cluster și pod-uri nu pot fi programate (sunt în starea În așteptare).
Concluzie: Pentru a putea autoscala nodurile, este necesar să setați cereri în containerele pod, astfel încât k8s să poată evalua corect încărcarea nodurilor și, în consecință, să raporteze că nu există resurse în cluster pentru a lansa următorul pod.
Concluzie
Trebuie remarcat faptul că stabilirea limitelor de resurse pentru container nu este o cerință pentru ca aplicația să ruleze cu succes, dar este totuși mai bine să faceți acest lucru din următoarele motive:
Pentru o funcționare mai precisă a planificatorului în ceea ce privește echilibrarea sarcinii între nodurile k8s
Pentru a reduce probabilitatea apariției unui eveniment de „evacuare a podului”.
Pentru ca scalarea automată orizontală a aplicațiilor (HPA) să funcționeze
Pentru autoscaling orizontal a nodurilor (Cluster Autoscaling) pentru furnizorii de cloud