Limite CPU și throttling agresiv în Kubernetes

Notă. transl.: Această istorie revelatoare a Omio — un agregator european de călătorii — îi duce pe cititori de la teoria de bază la complexitățile practice fascinante ale configurației Kubernetes. Familiarizarea cu astfel de cazuri ajută nu numai la lărgirea orizontului, ci și la prevenirea problemelor non-triviale.

Limite CPU și throttling agresiv în Kubernetes

Ați avut vreodată o aplicație blocată, nu mai răspunde la controalele de sănătate și nu ați putut să vă dați seama de ce? O posibilă explicație este legată de limitele cotei de resurse CPU. Despre asta vom vorbi în acest articol.

TL; DR:
Vă recomandăm insistent să dezactivați limitele CPU în Kubernetes (sau să dezactivați cotele CFS în Kubelet) dacă utilizați o versiune a nucleului Linux cu o eroare a cotei CFS. În miez este disponibil serios si bine cunoscute o eroare care duce la accelerare excesivă și întârzieri
.

În Omio întreaga infrastructură este gestionată de Kubernetes. Toate sarcinile noastre de lucru cu stare și fără stat rulează exclusiv pe Kubernetes (folosim Google Kubernetes Engine). În ultimele șase luni, am început să observăm încetiniri aleatorii. Aplicațiile îngheață sau nu mai răspund la verificările de sănătate, pierd conexiunea la rețea etc. Acest comportament ne-a nedumerit multă vreme și, în cele din urmă, am decis să luăm problema în serios.

Rezumatul articolului:

  • Câteva cuvinte despre containere și Kubernetes;
  • Cum sunt implementate cererile și limitele CPU;
  • Cum funcționează limita CPU în medii multi-core;
  • Cum să urmăriți accelerarea procesorului;
  • Rezolvarea problemei și nuanțe.

Câteva cuvinte despre containere și Kubernetes

Kubernetes este în esență standardul modern în lumea infrastructurii. Sarcina sa principală este orchestrarea containerelor.

containere

În trecut, a trebuit să creăm artefacte precum JAR-uri/războaie Java, Python Eggs sau executabile pentru a rula pe servere. Totuși, pentru a le face să funcționeze, a trebuit să se facă lucrări suplimentare: instalarea mediului de rulare (Java/Python), plasarea fișierelor necesare în locurile potrivite, asigurarea compatibilității cu o anumită versiune a sistemului de operare etc. Cu alte cuvinte, a trebuit să se acorde o atenție deosebită gestionării configurației (care a fost adesea o sursă de dispută între dezvoltatori și administratorii de sistem).

Containerele au schimbat totul. Acum artefactul este o imagine de container. Poate fi reprezentat ca un fel de fișier executabil extins care conține nu doar programul, ci și un mediu de execuție cu drepturi depline (Java/Python/...), precum și fișierele/pachetele necesare, preinstalate și gata de alerga. Containerele pot fi implementate și rulate pe diferite servere fără pași suplimentari.

În plus, containerele funcționează în propriul mediu sandbox. Au propriul adaptor de rețea virtuală, propriul sistem de fișiere cu acces limitat, propria ierarhie a proceselor, propriile limitări ale CPU și memoriei etc. Toate acestea sunt implementate datorită unui subsistem special al nucleului Linux - spații de nume.

Kubernetes

După cum am menționat mai devreme, Kubernetes este un orchestrator de containere. Funcționează astfel: îi oferiți un grup de mașini și apoi spuneți: „Hei, Kubernetes, haideți să lansăm zece instanțe ale containerului meu cu 2 procesoare și 3 GB de memorie fiecare și să le menținem în funcțiune!” Kubernetes se va ocupa de restul. Va găsi capacitate liberă, va lansa containere și le va reporni dacă este necesar, va lansa o actualizare la schimbarea versiunilor etc. În esență, Kubernetes vă permite să abstrageți componenta hardware și face o mare varietate de sisteme adecvate pentru implementarea și rularea aplicațiilor.

Limite CPU și throttling agresiv în Kubernetes
Kubernetes din punctul de vedere al profanului

Care sunt cererile și limitele în Kubernetes

Bine, am acoperit containerele și Kubernetes. De asemenea, știm că mai multe containere pot locui pe aceeași mașină.

Se poate face o analogie cu un apartament comun. Un spatiu spatios (utilaje/unitati) este luat si inchiriat mai multor chiriasi (containere). Kubernetes acționează ca agent imobiliar. Apare întrebarea cum să feriți chiriașii de conflicte între ei? Ce se întâmplă dacă unul dintre ei, să zicem, decide să împrumute baia pentru jumătate de zi?

Aici intervin cererile și limitele. CPU Cerere necesare exclusiv în scopuri de planificare. Aceasta este ceva ca o „listă de dorințe” a containerului și este folosită pentru a selecta cel mai potrivit nod. În același timp, procesorul Limita poate fi comparat cu un contract de închiriere - de îndată ce selectăm o unitate pentru container, acesta nu poti depășește limitele stabilite. Și aici apare problema...

Cum sunt implementate cererile și limitele în Kubernetes

Kubernetes folosește un mecanism de limitare (sărirea ciclurilor de ceas) încorporat în nucleu pentru a implementa limitele CPU. Dacă o aplicație depășește limita, limitarea este activată (adică primește mai puține cicluri CPU). Solicitările și limitele pentru memorie sunt organizate diferit, astfel încât sunt mai ușor de detectat. Pentru a face acest lucru, trebuie doar să verificați ultima stare de repornire a podului: dacă este „OOMKilled”. Reglarea procesorului nu este atât de simplă, deoarece K8s face disponibile valori doar în funcție de utilizare, nu de cgroups.

Solicitare CPU

Limite CPU și throttling agresiv în Kubernetes
Cum este implementată cererea CPU

Pentru simplitate, să ne uităm la procesul folosind o mașină cu un procesor cu 4 nuclee ca exemplu.

K8s folosește un mecanism de grup de control (cgroups) pentru a controla alocarea resurselor (memorie și procesor). Pentru aceasta este disponibil un model ierarhic: copilul moștenește limitele grupului părinte. Detaliile de distribuție sunt stocate într-un sistem de fișiere virtual (/sys/fs/cgroup). În cazul unui procesor acesta este /sys/fs/cgroup/cpu,cpuacct/*.

K8s folosește fișierul cpu.share pentru a aloca resursele procesorului. În cazul nostru, rădăcina cgroup primește 4096 de părți ale resurselor CPU - 100% din puterea procesorului disponibilă (1 nucleu = 1024; aceasta este o valoare fixă). Grupul rădăcină distribuie resursele proporțional în funcție de cotele descendenților înregistrați în cpu.share, iar ei, la rândul lor, fac la fel cu descendenții lor etc. Pe un nod Kubernetes tipic, rădăcina cgroup are trei copii: system.slice, user.slice и kubepods. Primele două subgrupuri sunt folosite pentru a distribui resursele între încărcările critice ale sistemului și programele utilizator din afara K8-urilor. Ultimul - kubepods — creat de Kubernetes pentru a distribui resursele între poduri.

Diagrama de mai sus arată că primul și al doilea subgrup au primit fiecare 1024 acțiuni, cu subgrupul kuberpod alocat 4096 acțiuni Cum este posibil acest lucru: la urma urmei, grupul rădăcină are acces numai la 4096 acțiunile, iar suma acțiunilor descendenților ei depășește semnificativ acest număr (6144)? Ideea este că valoarea are sens logic, așa că programatorul Linux (CFS) o folosește pentru a aloca proporțional resursele CPU. În cazul nostru, primele două grupe primesc 680 acțiuni reale (16,6% din 4096), iar kubepod primește restul 2736 acțiuni În caz de nefuncționare, primele două grupuri nu vor folosi resursele alocate.

Din fericire, planificatorul are un mecanism pentru a evita irosirea resurselor CPU neutilizate. Transferă capacitatea „inactivă” către un pool global, din care este distribuită grupurilor care au nevoie de putere suplimentară de procesor (transferul are loc în loturi pentru a evita pierderile de rotunjire). O metodă similară se aplică tuturor descendenților descendenților.

Acest mecanism asigură o distribuție echitabilă a puterii procesorului și asigură că niciun proces nu „fură” resurse de la alții.

Limita CPU

În ciuda faptului că configurațiile de limite și solicitări în K8-uri arată similar, implementarea lor este radical diferită: aceasta cel mai înșelător și partea mai puțin documentată.

K8-urile se cuplează Mecanismul cotelor CFS a implementa limite. Setările lor sunt specificate în fișiere cfs_period_us и cfs_quota_us în directorul cgroup (fișierul se află și acolo cpu.share).

Spre deosebire de cpu.share, cota se bazează pe perioada de timp, și nu pe puterea procesorului disponibilă. cfs_period_us precizează durata perioadei (epocii) - este întotdeauna 100000 μs (100 ms). Există o opțiune de modificare a acestei valori în K8s, dar este disponibilă doar în alfa deocamdată. Programatorul folosește epoca pentru a reporni cotele utilizate. Al doilea dosar cfs_quota_us, precizează timpul disponibil (cota) în fiecare epocă. Rețineți că este specificat și în microsecunde. Cota poate depăși lungimea epocii; cu alte cuvinte, poate fi mai mare de 100 ms.

Să ne uităm la două scenarii pe mașini cu 16 nuclee (cel mai comun tip de computer pe care îl avem la Omio):

Limite CPU și throttling agresiv în Kubernetes
Scenariul 1: 2 fire și o limită de 200 ms. Fără accelerare

Limite CPU și throttling agresiv în Kubernetes
Scenariul 2: 10 fire și limită de 200 ms. Throttlingul începe după 20 ms, accesul la resursele procesorului este reluat după încă 80 ms

Să presupunem că setați limita CPU la 2 sâmburi; Kubernetes va traduce această valoare la 200 ms. Aceasta înseamnă că containerul poate folosi maximum 200 ms de timp CPU fără limitare.

Și de aici începe distracția. După cum sa menționat mai sus, cota disponibilă este de 200 ms. Dacă lucrați în paralel zece fire pe o mașină cu 12 nuclee (vezi ilustrația pentru scenariul 2), în timp ce toate celelalte poduri sunt inactive, cota va fi epuizată în doar 20 ms (deoarece 10 * 20 ms = 200 ms) și toate firele acestui pod se vor bloca » (regulator) pentru următoarele 80 ms. Cele deja menționate eroare de planificare, din cauza căreia are loc o limitare excesivă și containerul nici măcar nu poate îndeplini cota existentă.

Cum se evaluează accelerarea în poduri?

Doar conectați-vă la pod și executați cat /sys/fs/cgroup/cpu/cpu.stat.

  • nr_periods — numărul total de perioade de planificare;
  • nr_throttled — numărul de perioade accelerate din compoziție nr_periods;
  • throttled_time — timp de accelerare cumulat în nanosecunde.

Limite CPU și throttling agresiv în Kubernetes

Ce se întâmplă cu adevărat?

Ca rezultat, obținem o accelerare ridicată în toate aplicațiile. Uneori e înăuntru o dată și jumătate mai puternic decât calculat!

Acest lucru duce la diverse erori - eșecuri de verificare a pregătirii, înghețarea containerului, întreruperile conexiunii la rețea, timeout-uri în apelurile de service. Acest lucru are ca rezultat în cele din urmă o latență crescută și rate de eroare mai mari.

Decizie și consecințe

Totul este simplu aici. Am abandonat limitele CPU și am început să actualizăm nucleul sistemului de operare în clustere la cea mai recentă versiune, în care eroarea a fost remediată. Numărul de erori (HTTP 5xx) din serviciile noastre a scăzut imediat semnificativ:

Erori HTTP 5xx

Limite CPU și throttling agresiv în Kubernetes
Erori HTTP 5xx pentru un serviciu critic

Timp de răspuns p95

Limite CPU și throttling agresiv în Kubernetes
Latența solicitării serviciului critic, percentila 95

Costuri de operare

Limite CPU și throttling agresiv în Kubernetes
Numărul de ore de instanță petrecute

Care este captura?

După cum se spune la începutul articolului:

Se poate face o analogie cu un apartament comun... Kubernetes acționează ca agent imobiliar. Dar cum să îi feri pe chiriași de conflicte între ei? Ce se întâmplă dacă unul dintre ei, să zicem, decide să împrumute baia pentru jumătate de zi?

Iată prinderea. Un container neglijent poate consuma toate resursele CPU disponibile pe o mașină. Dacă aveți o stivă inteligentă de aplicații (de exemplu, JVM, Go, Node VM sunt configurate corect), atunci aceasta nu este o problemă: puteți lucra în astfel de condiții mult timp. Dar dacă aplicațiile sunt slab optimizate sau deloc optimizate (FROM java:latest), situația poate scăpa de sub control. La Omio avem Dockerfile de bază automatizate cu setări implicite adecvate pentru stiva majoră de limbi, așa că această problemă nu a existat.

Vă recomandăm să monitorizați valorile UTILIZAȚI (utilizare, saturație și erori), întârzieri API și rate de eroare. Asigurați-vă că rezultatele corespund așteptărilor.

referințe

Aceasta este povestea noastră. Următoarele materiale au ajutat foarte mult la înțelegerea a ceea ce se întâmplă:

Rapoarte de eroare Kubernetes:

Ați întâmpinat probleme similare în practica dumneavoastră sau aveți experiență legată de throttling în medii de producție containerizate? Împărtășește-ți povestea în comentarii!

PS de la traducator

Citește și pe blogul nostru:

Sursa: www.habr.com

Adauga un comentariu