Nouă sfaturi de performanță Kubernetes

Nouă sfaturi de performanță Kubernetes

Salutare tuturor! Numele meu este Oleg Sidorenkov, lucrez la DomClick ca șef al echipei de infrastructură. Folosim Kubik în producție de mai bine de trei ani și, în acest timp, am trăit multe momente interesante cu el. Astăzi vă voi spune cum, cu abordarea corectă, puteți obține și mai multă performanță din vanilla Kubernetes pentru clusterul dvs. Gata, continuu!

Știți cu toții foarte bine că Kubernetes este un sistem open source scalabil pentru orchestrarea containerelor; bine, sau 5 binare care funcționează magic prin gestionarea ciclului de viață al microserviciilor dvs. într-un mediu de server. În plus, este un instrument destul de flexibil care poate fi asamblat ca Lego pentru personalizare maximă pentru diferite sarcini.

Și totul pare să fie în regulă: aruncați serverele în cluster ca lemnele de foc într-un focar și nu veți cunoaște nicio durere. Dar dacă sunteți pentru mediul înconjurător, vă veți gândi: „Cum pot să țin focul aprins și să cruț pădurea?” Cu alte cuvinte, cum să găsiți modalități de a îmbunătăți infrastructura și de a reduce costurile.

1. Monitorizați echipa și resursele aplicației

Nouă sfaturi de performanță Kubernetes

Una dintre cele mai comune, dar eficiente metode este introducerea de cereri/limite. Împărțiți aplicațiile pe spații de nume și spațiile de nume pe echipe de dezvoltare. Înainte de implementare, setați valorile aplicației pentru consumul de timp al procesorului, memorie și stocare efemeră.

resources:
   requests:
     memory: 2Gi
     cpu: 250m
   limits:
     memory: 4Gi
     cpu: 500m

Prin experiență, am ajuns la concluzia: nu trebuie să umflați cererile din limite de mai mult de două ori. Volumul cluster-ului este calculat pe baza solicitărilor, iar dacă aplicații o diferență de resurse, de exemplu, de 5-10 ori, atunci imaginați-vă ce se va întâmpla cu nodul dvs. atunci când este umplut cu pod și primește brusc încărcare. Nimic bun. Cel puțin, stropit, și la maximum, vă veți lua la revedere de la lucrător și veți obține o sarcină ciclică pe nodurile rămase după ce podurile încep să se miște.

În plus, cu ajutorul limitranges La început, puteți seta valorile resurselor pentru container - minim, maxim și implicit:

➜  ~ kubectl describe limitranges --namespace ops
Name:       limit-range
Namespace:  ops
Type        Resource           Min   Max   Default Request  Default Limit  Max Limit/Request Ratio
----        --------           ---   ---   ---------------  -------------  -----------------------
Container   cpu                50m   10    100m             100m           2
Container   ephemeral-storage  12Mi  8Gi   128Mi            4Gi            -
Container   memory             64Mi  40Gi  128Mi            128Mi          2

Nu uitați să limitați resursele spațiului de nume, astfel încât o echipă să nu preia toate resursele clusterului:

➜  ~ kubectl describe resourcequotas --namespace ops
Name:                   resource-quota
Namespace:              ops
Resource                Used          Hard
--------                ----          ----
limits.cpu              77250m        80
limits.memory           124814367488  150Gi
pods                    31            45
requests.cpu            53850m        80
requests.memory         75613234944   150Gi
services                26            50
services.loadbalancers  0             0
services.nodeports      0             0

După cum se vede din descriere resourcequotas, dacă echipa operațională dorește să implementeze pod-uri care vor consuma încă 10 cpu, planificatorul nu va permite acest lucru și va arunca o eroare:

Error creating: pods "nginx-proxy-9967d8d78-nh4fs" is forbidden: exceeded quota: resource-quota, requested: limits.cpu=5,requests.cpu=5, used: limits.cpu=77250m,requests.cpu=53850m, limited: limits.cpu=10,requests.cpu=10

Pentru a rezolva o astfel de problemă, puteți scrie un instrument, de exemplu, cum ar fi acest, capabil să stocheze și să comite starea resurselor de comandă.

2. Alegeți stocarea optimă a fișierelor

Nouă sfaturi de performanță Kubernetes

Aici aș dori să abordez subiectul volumelor persistente și subsistemul de disc al nodurilor de lucru Kubernetes. Sper că nimeni nu folosește „Cube” pe un HDD în producție, dar uneori un SSD obișnuit nu mai este suficient. Am întâlnit o problemă în care jurnalele ucideau discul din cauza operațiunilor I/O și nu există multe soluții:

  • Utilizați SSD-uri de înaltă performanță sau treceți la NVMe (dacă vă gestionați propriul hardware).

  • Reduceți nivelul de înregistrare.

  • Faceți o echilibrare „inteligentă” a păstăilor care violează discul (podAntiAffinity).

Ecranul de mai sus arată ce se întâmplă sub nginx-ingress-controller pe disc atunci când accesul_logs este activat (~ 12 mii de loguri/sec). Această condiție, desigur, poate duce la degradarea tuturor aplicațiilor de pe acest nod.

Cat despre PV, vai, nu am incercat totul tipuri Volume persistente. Utilizați cea mai bună opțiune care vi se potrivește. Din punct de vedere istoric, s-a întâmplat în țara noastră ca o mică parte a serviciilor să necesite volume RWX și cu mult timp în urmă au început să folosească stocarea NFS pentru această sarcină. Ieftin și... suficient. Bineînțeles, el și cu mine am mâncat rahat - binecuvântați-vă, dar am învățat să ne descurcăm și nu mă mai doare capul. Și dacă este posibil, treceți la stocarea obiectelor S3.

3. Colectați imagini optimizate

Nouă sfaturi de performanță Kubernetes

Cel mai bine este să folosiți imagini optimizate pentru container, astfel încât Kubernetes să le poată prelua mai rapid și să le execute mai eficient. 

Optimizat înseamnă că imaginile:

  • conține o singură aplicație sau îndeplinește o singură funcție;

  • dimensiuni mici, deoarece imaginile mari sunt transmise mai rău prin rețea;

  • să aibă puncte finale de sănătate și pregătire care să permită lui Kubernetes să ia măsuri în caz de nefuncționare;

  • utilizați sisteme de operare prietenoase cu containerele (cum ar fi Alpine sau CoreOS), care sunt mai rezistente la erorile de configurare;

  • utilizați versiuni în mai multe etape, astfel încât să puteți implementa numai aplicații compilate și nu sursele însoțitoare.

Există multe instrumente și servicii care vă permit să verificați și să optimizați imaginile din mers. Este important să le ții mereu la zi și să fie testate pentru siguranță. Ca rezultat, obțineți:

  1. Sarcina redusă de rețea pe întregul cluster.

  2. Reducerea timpului de pornire a containerului.

  3. Dimensiunea mai mică a întregului registru Docker.

4. Folosiți memoria cache DNS

Nouă sfaturi de performanță Kubernetes

Dacă vorbim despre sarcini mari, atunci viața este destul de proastă fără a regla sistemul DNS al clusterului. Cândva, dezvoltatorii Kubernetes și-au susținut soluția kube-dns. A fost implementat și aici, dar acest software nu a fost reglat în mod special și nu a produs performanța necesară, deși părea a fi o sarcină simplă. Apoi au apărut coredns, la care am trecut și nu am avut nicio durere; mai târziu a devenit serviciul DNS implicit în K8s. La un moment dat, am crescut la 40 de mii de rps la sistemul DNS, iar această soluție a devenit și ea insuficientă. Dar, din noroc, a apărut Nodelocaldns, alias node local cache, aka NodeLocal DNSCache.

De ce folosim asta? Există o eroare în nucleul Linux care, atunci când apeluri multiple prin conntrack NAT peste UDP, duc la o condiție de concurență pentru intrările în tabelele conntrack și o parte din traficul prin NAT se pierde (fiecare călătorie prin Serviciu este NAT). Nodelocaldns rezolvă această problemă eliminând NAT și upgradând conexiunea la TCP la DNS în amonte, precum și prin memorarea în cache local a interogărilor DNS din amonte (inclusiv un cache negativ scurt de 5 secunde).

5. Scalați automat podurile orizontal și vertical

Nouă sfaturi de performanță Kubernetes

Puteți spune cu încredere că toate microserviciile dumneavoastră sunt pregătite pentru o creștere de două până la trei ori a încărcăturii? Cum să alocați corect resursele aplicațiilor dvs.? Păstrarea câtorva pod-uri care rulează dincolo de volumul de lucru poate fi redundantă, dar păstrarea lor spate în spate duce la riscul de nefuncționare de la o creștere bruscă a traficului către serviciu. Servicii precum Autoscaler pod orizontal и Autoscaler vertical Pod.

VPA vă permite să ridicați automat cererile/limitele containerelor dvs. din pod în funcție de utilizarea reală. Cum poate fi util? Dacă aveți pod-uri care nu pot fi scalate orizontal dintr-un motiv oarecare (care nu este în întregime de încredere), atunci puteți încerca să încredințați modificări ale resurselor sale către VPA. Caracteristica sa este un sistem de recomandare bazat pe datele istorice și actuale de la metric-server, așa că dacă nu doriți să modificați automat cererile/limitele, puteți pur și simplu să monitorizați resursele recomandate pentru containerele dvs. și să optimizați setările pentru a economisi CPU și memorie în cluster.

Nouă sfaturi de performanță KubernetesImagine preluată de la https://levelup.gitconnected.com/kubernetes-autoscaling-101-cluster-autoscaler-horizontal-pod-autoscaler-and-vertical-pod-2a441d9ad231

Planificatorul din Kubernetes se bazează întotdeauna pe solicitări. Indiferent de valoarea pe care o puneți acolo, planificatorul va căuta un nod potrivit pe baza acestuia. Valorile limită sunt necesare pentru ca cubetul să înțeleagă când să clasifice sau să omoare păstaia. Și deoarece singurul parametru important este valoarea solicitărilor, VPA va funcționa cu el. Ori de câte ori scalați o aplicație pe verticală, definiți care ar trebui să fie cererile. Ce se va întâmpla atunci cu limitele? Acest parametru va fi, de asemenea, scalat proporțional.

De exemplu, iată setările obișnuite ale podului:

resources:
   requests:
     memory: 250Mi
     cpu: 200m
   limits:
     memory: 500Mi
     cpu: 350m

Motorul de recomandare determină că aplicația dvs. necesită 300 m CPU și 500 Mi pentru a rula corect. Veți obține următoarele setări:

resources:
   requests:
     memory: 500Mi
     cpu: 300m
   limits:
     memory: 1000Mi
     cpu: 525m

După cum sa menționat mai sus, aceasta este o scalare proporțională bazată pe raportul cereri/limite din manifest:

  • CPU: 200m → 300m: raport 1:1.75;

  • Memorie: 250Mi → 500Mi: raport 1:2.

cu privire la HPA, atunci mecanismul de funcționare este mai transparent. Metrici precum CPU și memoria sunt limitate, iar dacă media tuturor replicilor depășește pragul, aplicația este scalată cu +1 sub până când valoarea scade sub prag sau până când este atins numărul maxim de replici.

Nouă sfaturi de performanță KubernetesImagine preluată de la https://levelup.gitconnected.com/kubernetes-autoscaling-101-cluster-autoscaler-horizontal-pod-autoscaler-and-vertical-pod-2a441d9ad231

În plus față de valorile obișnuite, cum ar fi CPU și memoria, puteți seta praguri pentru valorile personalizate de la Prometheus și puteți lucra cu ele dacă credeți că acesta este cel mai precis indiciu cu privire la momentul în care să scalați aplicația. Odată ce aplicația se stabilizează sub pragul metric specificat, HPA va începe să reducă podurile până la numărul minim de replici sau până când sarcina atinge pragul specificat.

6. Nu uitați de Node Affinity și Pod Affinity

Nouă sfaturi de performanță Kubernetes

Nu toate nodurile rulează pe același hardware și nu toate pod-urile trebuie să ruleze aplicații intensive în calcul. Kubernetes vă permite să setați specializarea nodurilor și a podurilor folosind Afinitatea nodului и Pod afinitate.

Dacă aveți noduri care sunt potrivite pentru operațiuni intensive de calcul, atunci pentru o eficiență maximă este mai bine să legați aplicațiile de nodurile corespunzătoare. Pentru a face acest lucru folosește nodeSelector cu o etichetă de nod.

Să presupunem că aveți două noduri: unul cu CPUType=HIGHFREQ și un număr mare de nuclee rapide, altul cu MemoryType=HIGHMEMORY mai multa memorie si performante mai rapide. Cel mai simplu mod este de a atribui implementarea unui nod HIGHFREQprin adăugarea la secțiune spec acest selector:

…
nodeSelector:
	CPUType: HIGHFREQ

O modalitate mai scumpă și mai specifică de a face acest lucru este utilizarea nodeAffinity în câmp affinity razdela spec. Există două opțiuni:

  • requiredDuringSchedulingIgnoredDuringExecution: setare grea (planificatorul va implementa poduri numai pe anumite noduri (și nicăieri altundeva));

  • preferredDuringSchedulingIgnoredDuringExecution: setare soft (programatorul va încerca să se implementeze în anumite noduri, iar dacă aceasta nu reușește, va încerca să se implementeze la următorul nod disponibil).

Puteți specifica o anumită sintaxă pentru gestionarea etichetelor nodurilor, cum ar fi In, NotIn, Exists, DoesNotExist, Gt sau Lt. Totuși, rețineți că metodele complexe din liste lungi de etichete vor încetini luarea deciziilor în situații critice. Cu alte cuvinte, păstrați-l simplu.

După cum am menționat mai sus, Kubernetes vă permite să setați afinitatea podurilor curente. Adică, vă puteți asigura că anumite poduri funcționează împreună cu alte poduri din aceeași zonă de disponibilitate (relevantă pentru nori) sau noduri.

В podAffinity domenii affinity razdela spec Sunt disponibile aceleași câmpuri ca și în cazul nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution и preferredDuringSchedulingIgnoredDuringExecution. Singura diferență este că matchExpressions va lega podurile la un nod care rulează deja un pod cu acea etichetă.

Kubernetes oferă și un teren podAntiAffinity, care, dimpotrivă, nu leagă podul de un nod cu pod-uri specifice.

Despre expresii nodeAffinity Același sfat poate fi dat: încercați să păstrați regulile simple și logice, nu încercați să supraîncărcați specificația podului cu un set complex de reguli. Este foarte ușor să creați o regulă care nu se va potrivi cu condițiile clusterului, creând încărcare inutilă asupra planificatorului și reducând performanța generală.

7. Vizualizări și toleranțe

Există o altă modalitate de a gestiona programatorul. Dacă aveți un cluster mare cu sute de noduri și mii de microservicii, atunci este foarte dificil să nu permiteți ca anumite pod-uri să fie găzduite pe anumite noduri.

Mecanismul viciilor – regulile de interzicere – ajută la acest lucru. De exemplu, în anumite scenarii puteți interzice anumitor noduri să ruleze pod-uri. Pentru a aplica colorarea unui anumit nod, trebuie să utilizați opțiunea taint în kubectl. Specificați cheia și valoarea și apoi taint like NoSchedule sau NoExecute:

$ kubectl taint nodes node10 node-role.kubernetes.io/ingress=true:NoSchedule

De asemenea, merită remarcat faptul că mecanismul de contaminare suportă trei efecte principale: NoSchedule, NoExecute и PreferNoSchedule.

  • NoSchedule înseamnă că deocamdată nu va exista nicio intrare corespunzătoare în specificația podului tolerations, nu va putea fi implementat pe nod (în acest exemplu node10).

  • PreferNoSchedule - versiune simplificată NoSchedule. În acest caz, programatorul va încerca să nu aloce pod-uri care nu au o intrare potrivită tolerations per nod, dar aceasta nu este o limitare grea. Dacă nu există resurse în cluster, podurile vor începe să fie implementate pe acest nod.

  • NoExecute - acest efect declanșează evacuarea imediată a păstăilor care nu au o intrare corespunzătoare tolerations.

Interesant este că acest comportament poate fi anulat folosind mecanismul de toleranțe. Acest lucru este convenabil atunci când există un nod „interzis” și trebuie doar să plasați servicii de infrastructură pe el. Cum să o facă? Permiteți doar acele păstăi pentru care există o toleranță adecvată.

Iată cum ar arăta specificația podului:

spec:
   tolerations:
     - key: "node-role.kubernetes.io/ingress"
        operator: "Equal"
        value: "true"
        effect: "NoSchedule"

Acest lucru nu înseamnă că următoarea redistribuire va cădea pe acest nod anume, acesta nu este mecanismul Node Affinity și nodeSelector. Dar, combinând mai multe caracteristici, puteți obține setări foarte flexibile de planificare.

8. Setați Prioritatea de implementare a podului

Doar pentru că aveți poduri alocate nodurilor nu înseamnă că toate podurile trebuie tratate cu aceeași prioritate. De exemplu, este posibil să doriți să implementați unele pod-uri înaintea altora.

Kubernetes oferă diferite moduri de a configura Pod Priority și Preemption. Decorul este format din mai multe părți: obiect PriorityClass și descrieri de câmpuri priorityClassName în specificația podului. Să ne uităm la un exemplu:

apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
  name: high-priority
value: 99999
globalDefault: false
description: "This priority class should be used for very important pods only"

Noi creăm PriorityClass, dați-i un nume, descriere și valoare. Cu cât mai sus value, cu cât prioritatea este mai mare. Valoarea poate fi orice număr întreg pe 32 de biți mai mic sau egal cu 1. Valorile mai mari sunt rezervate pentru podurile de sistem critice care, în general, nu pot fi preemptate. Deplasarea va avea loc numai dacă un pod cu prioritate înaltă nu are unde să se întoarcă, atunci unele dintre podurile de la un anumit nod vor fi evacuate. Dacă acest mecanism este prea rigid pentru tine, poți adăuga opțiunea preemptionPolicy: Never, iar apoi nu va exista nicio preempțiune, podul va sta primul în coadă și va aștepta ca programatorul să găsească resurse gratuite pentru acesta.

Apoi, creăm un pod în care indicăm numele priorityClassName:

apiVersion: v1
kind: Pod
metadata:
  name: static-web
  labels:
    role: myrole
 spec:
  containers:
    - name: web
      image: nginx
      ports:
        - name: web
          containerPort: 80
          protocol: TCP
  priorityClassName: high-priority
          

Puteți crea câte clase prioritare doriți, deși este recomandat să nu vă lăsați dus de asta (să zicem, limitați-vă la prioritate scăzută, medie și mare).

Astfel, dacă este necesar, puteți crește eficiența implementării serviciilor critice precum nginx-ingress-controller, coredns etc.

9. Optimizați clusterul ETCD

Nouă sfaturi de performanță Kubernetes

ETCD poate fi numit creierul întregului cluster. Este foarte important să mențineți funcționarea acestei baze de date la un nivel ridicat, deoarece viteza operațiunilor în Cube depinde de aceasta. O soluție destul de standard și, în același timp, bună ar fi păstrarea clusterului ETCD pe nodurile master pentru a avea o întârziere minimă pentru kube-apiserver. Dacă nu puteți face acest lucru, atunci plasați ETCD-ul cât mai aproape posibil, cu lățime de bandă bună între participanți. De asemenea, acordați atenție câte noduri din ETCD pot cădea fără a afecta clusterul

Nouă sfaturi de performanță Kubernetes

Rețineți că creșterea excesivă a numărului de membri într-un cluster poate crește toleranța la erori în detrimentul performanței, totul ar trebui să fie cu moderație.

Dacă vorbim despre configurarea serviciului, există câteva recomandări:

  1. Aveți hardware bun, în funcție de dimensiunea clusterului (puteți citi aici).

  2. Modificați câțiva parametri dacă ați răspândit un cluster între o pereche de DC-uri sau rețeaua și discurile dvs. lasă mult de dorit (puteți citi aici).

Concluzie

Acest articol descrie punctele pe care echipa noastră încearcă să le respecte. Aceasta nu este o descriere pas cu pas a acțiunilor, ci opțiuni care pot fi utile pentru optimizarea supraîncărcării clusterului. Este clar că fiecare cluster este unic în felul său, iar soluțiile de configurare pot varia foarte mult, așa că ar fi interesant să obțineți feedback despre modul în care vă monitorizați clusterul Kubernetes și cum îi îmbunătățiți performanța. Împărtășește-ți experiența în comentarii, va fi interesant de știut.

Sursa: www.habr.com