CPU ograničenja i agresivno prigušivanje u Kubernetesu

Bilješka. prev.: Ova uzbudljiva povijest Omia — europskog agregatora putovanja — vodi čitatelje od osnovne teorije do fascinantnih praktičnih zamršenosti konfiguracije Kubernetesa. Poznavanje takvih slučajeva pomaže ne samo proširiti vaše horizonte, već i spriječiti ne-trivijalne probleme.

CPU ograničenja i agresivno prigušivanje u Kubernetesu

Jeste li ikada imali aplikaciju koja je zapela na mjestu, prestala odgovarati na zdravstvene provjere i niste mogli shvatiti zašto? Jedno moguće objašnjenje povezano je s ograničenjima kvote CPU resursa. O tome ćemo govoriti u ovom članku.

TL; DR:
Toplo preporučujemo da onemogućite CPU ograničenja u Kubernetesu (ili onemogućite CFS kvote u Kubeletu) ako koristite verziju Linux kernela s greškom CFS kvote. U srži postoji ozbiljno i dobro poznat bug koji dovodi do pretjeranog prigušivanja i kašnjenja
.

U Omiju cjelokupnom infrastrukturom upravlja Kubernetes. Sva naša radna opterećenja s statusom i bez statusa pokreću se isključivo na Kubernetesu (koristimo Google Kubernetes Engine). U posljednjih šest mjeseci počeli smo promatrati nasumična usporavanja. Aplikacije se zamrzavaju ili prestaju reagirati na provjere zdravlja, gube vezu s mrežom itd. Ovakvo ponašanje nas je dugo zbunjivalo i konačno smo odlučili ozbiljno shvatiti problem.

Sažetak članka:

  • Nekoliko riječi o kontejnerima i Kubernetesu;
  • Kako se implementiraju CPU zahtjevi i ograničenja;
  • Kako CPU ograničenje funkcionira u višejezgrenim okruženjima;
  • Kako pratiti prigušivanje CPU-a;
  • Rješenje problema i nijanse.

Nekoliko riječi o kontejnerima i Kubernetesu

Kubernetes je u biti moderan standard u svijetu infrastrukture. Njegov glavni zadatak je orkestracija kontejnera.

spremnici

U prošlosti smo morali stvarati artefakte poput Java JAR-ova/WAR-ova, Python jaja ili izvršnih datoteka za izvođenje na poslužiteljima. No, da bi funkcionirali, bilo je potrebno dodatno raditi: instalirati runtime okruženje (Java/Python), postaviti potrebne datoteke na prava mjesta, osigurati kompatibilnost s određenom verzijom operativnog sustava itd. Drugim riječima, posebna se pažnja morala posvetiti upravljanju konfiguracijom (što je često bio izvor sukoba između programera i administratora sustava).

Kontejneri su promijenili sve. Sada je artefakt slika spremnika. Može se predstaviti kao neka vrsta proširene izvršne datoteke koja sadrži ne samo program, već i punopravno okruženje za izvršavanje (Java/Python/...), kao i potrebne datoteke/pakete, unaprijed instalirane i spremne za trčanje. Kontejneri se mogu postaviti i pokrenuti na različitim poslužiteljima bez dodatnih koraka.

Osim toga, kontejneri rade u svom vlastitom sandbox okruženju. Imaju vlastiti virtualni mrežni adapter, vlastiti datotečni sustav s ograničenim pristupom, vlastitu hijerarhiju procesa, vlastita ograničenja CPU-a i memorije itd. Sve je to implementirano zahvaljujući posebnom podsustavu Linux kernela – imenskim prostorima.

Kubernetes

Kao što je ranije rečeno, Kubernetes je kontejnerski orkestrator. To funkcionira ovako: date mu skup strojeva, a zatim kažete: "Hej, Kubernetes, pokrenimo deset instanci mog spremnika s 2 procesora i 3 GB memorije svaki, i nastavimo ih raditi!" Kubernetes će se pobrinuti za ostalo. Pronaći će slobodan kapacitet, pokrenuti spremnike i ponovno ih pokrenuti ako je potrebno, pokrenuti ažuriranje pri promjeni verzija itd. U biti, Kubernetes vam omogućuje da apstrahirate hardversku komponentu i čini široku paletu sustava prikladnim za implementaciju i pokretanje aplikacija.

CPU ograničenja i agresivno prigušivanje u Kubernetesu
Kubernetes iz ugla laika

Što su zahtjevi i ograničenja u Kubernetesu

U redu, pokrili smo kontejnere i Kubernetes. Također znamo da se više spremnika može nalaziti na istom stroju.

Može se povući analogija sa zajedničkim stanom. Prostran prostor (strojevi/agregati) uzima se i iznajmljuje više zakupaca (kontejnera). Kubernetes djeluje kao posrednik nekretnina. Postavlja se pitanje, kako zadržati stanare od međusobnih sukoba? Što ako netko od njih, recimo, odluči posuditi kupaonicu na pola dana?

Ovdje zahtjevi i ograničenja stupaju na scenu. CPU Zatražite potrebna isključivo za potrebe planiranja. Ovo je nešto poput "liste želja" spremnika, a koristi se za odabir najprikladnijeg čvora. U isto vrijeme CPU Ograničiti može se usporediti s ugovorom o najmu - čim odaberemo jedinicu za kontejner, Ne možete prelaziti utvrđene granice. I tu nastaje problem...

Kako se zahtjevi i ograničenja implementiraju u Kubernetes

Kubernetes koristi mehanizam za prigušivanje (preskakanje ciklusa sata) ugrađen u kernel za implementaciju CPU ograničenja. Ako aplikacija premaši ograničenje, ograničenje je omogućeno (tj. prima manje CPU ciklusa). Zahtjevi i ograničenja za memoriju organizirani su drugačije, pa ih je lakše otkriti. Da biste to učinili, samo provjerite zadnji status ponovnog pokretanja modula: je li "OOMkilled". Prigušivanje CPU-a nije tako jednostavno, budući da K8s metriku čini dostupnom samo korištenjem, a ne cgroupom.

CPU zahtjev

CPU ograničenja i agresivno prigušivanje u Kubernetesu
Kako se CPU zahtjev implementira

Radi jednostavnosti, pogledajmo proces na primjeru stroja s 4-jezgrenim CPU-om.

K8s koristi mehanizam kontrolne grupe (cgroups) za kontrolu raspodjele resursa (memorija i procesor). Za njega je dostupan hijerarhijski model: dijete nasljeđuje ograničenja roditeljske grupe. Pojedinosti o distribuciji pohranjene su u virtualnom datotečnom sustavu (/sys/fs/cgroup). U slučaju procesora to je /sys/fs/cgroup/cpu,cpuacct/*.

K8s koristi datoteku cpu.share za dodjelu resursa procesora. U našem slučaju, root cgroup dobiva 4096 dionica CPU resursa - 100% raspoložive snage procesora (1 jezgra = 1024; ovo je fiksna vrijednost). Korijenska grupa raspodjeljuje resurse proporcionalno ovisno o udjelima potomaka registriranih u cpu.share, a oni, pak, čine isto sa svojim potomcima itd. Na tipičnom Kubernetes čvoru, root cgroup ima tri djece: system.slice, user.slice и kubepods. Prve dvije podskupine koriste se za raspodjelu resursa između kritičnih opterećenja sustava i korisničkih programa izvan K8s. Zadnji - kubepods — stvorio Kubernetes za raspodjelu resursa između mahuna.

Gornji dijagram pokazuje da su prva i druga podskupina primile svaka 1024 dionica, s dodijeljenom podskupinom kuberpod 4096 dionice Kako je to moguće: na kraju krajeva, root grupa ima pristup samo 4096 udjela, a zbroj udjela njenih potomaka znatno premašuje ovaj broj (6144)? Poanta je u tome da vrijednost ima logičan smisao, pa je Linux planer (CFS) koristi za proporcionalnu dodjelu CPU resursa. U našem slučaju primaju prve dvije skupine 680 stvarnih dionica (16,6% od 4096), a ostatak dobiva kubepod 2736 dionice U slučaju zastoja, prve dvije grupe neće koristiti dodijeljene resurse.

Srećom, planer ima mehanizam za izbjegavanje trošenja neiskorištenih CPU resursa. On prenosi "neaktivan" kapacitet u globalni skup, iz kojeg se distribuira grupama kojima je potrebna dodatna snaga procesora (prijenos se događa u serijama kako bi se izbjegli gubici zaokruživanja). Slična se metoda primjenjuje na sve potomke potomaka.

Ovaj mehanizam osigurava pravednu raspodjelu snage procesora i osigurava da niti jedan proces ne "krade" resurse od drugih.

CPU ograničenje

Unatoč činjenici da konfiguracije ograničenja i zahtjeva u K8s izgledaju slično, njihova implementacija je radikalno drugačija: ovo najviše zavaravajuće a najmanje dokumentirani dio.

K8s uključuje CFS mehanizam kvota implementirati ograničenja. Njihove postavke navedene su u datotekama cfs_period_us и cfs_quota_us u direktoriju cgroup (datoteka se također nalazi tamo cpu.share).

Za razliku od cpu.share, kvota se temelji na vremenski period, a ne na dostupnoj snazi ​​procesora. cfs_period_us specificira trajanje perioda (epohe) - uvijek je 100000 μs (100 ms). Postoji opcija za promjenu ove vrijednosti u K8s, ali je za sada dostupna samo u alfa verziji. Planer koristi epohu za ponovno pokretanje korištenih kvota. Druga datoteka cfs_quota_us, navodi dostupno vrijeme (kvota) u svakoj epohi. Imajte na umu da je također navedeno u mikrosekundama. Kvota može premašiti duljinu epohe; drugim riječima, može biti veći od 100 ms.

Pogledajmo dva scenarija na strojevima sa 16 jezgri (najčešći tip računala koje imamo u Omiju):

CPU ograničenja i agresivno prigušivanje u Kubernetesu
Scenarij 1: 2 niti i ograničenje od 200 ms. Nema prigušivanja

CPU ograničenja i agresivno prigušivanje u Kubernetesu
Scenarij 2: 10 niti i ograničenje od 200 ms. Prigušivanje počinje nakon 20 ms, pristup resursima procesora nastavlja se nakon dodatnih 80 ms

Recimo da ste postavili CPU ograničenje na 2 jezgre; Kubernetes će ovu vrijednost prevesti na 200 ms. To znači da spremnik može koristiti najviše 200 ms CPU vremena bez prigušenja.

I tu počinje zabava. Kao što je gore spomenuto, dostupna kvota je 200 ms. Ako radite paralelno deset niti na 12-jezgrenom stroju (pogledajte ilustraciju za scenarij 2), dok su svi ostali moduli u stanju mirovanja, kvota će se potrošiti za samo 20 ms (budući da je 10 * 20 ms = 200 ms), a sve niti ovog modula će visjeti » (gas) sljedećih 80 ms. Već spomenuti programer bug, zbog čega dolazi do prevelikog prigušivanja i kontejner ne može ispuniti ni postojeću kvotu.

Kako procijeniti prigušivanje u kapsulama?

Samo se prijavite na pod i izvršite cat /sys/fs/cgroup/cpu/cpu.stat.

  • nr_periods — ukupan broj razdoblja raspoređivača;
  • nr_throttled — broj prigušenih razdoblja u sastavu nr_periods;
  • throttled_time — kumulativno vrijeme prigušenja u nanosekundama.

CPU ograničenja i agresivno prigušivanje u Kubernetesu

Što se zapravo događa?

Kao rezultat toga, dobivamo visok trottling u svim aplikacijama. Ponekad je unutra jedan i pol puta jače od proračunatog!

To dovodi do raznih grešaka - neuspjeha provjere spremnosti, zamrzavanja spremnika, prekida mrežne veze, isteka vremena unutar servisnih poziva. To u konačnici rezultira povećanom latencijom i većom stopom pogrešaka.

Odluka i posljedice

Ovdje je sve jednostavno. Napustili smo CPU ograničenja i počeli ažurirati jezgru OS-a u klasterima na najnoviju verziju, u kojoj je pogreška ispravljena. Broj grešaka (HTTP 5xx) u našim uslugama odmah je značajno pao:

HTTP 5xx pogreške

CPU ograničenja i agresivno prigušivanje u Kubernetesu
HTTP 5xx pogreške za jednu kritičnu uslugu

Vrijeme odziva p95

CPU ograničenja i agresivno prigušivanje u Kubernetesu
Kašnjenje zahtjeva za kritičnu uslugu, 95. percentil

Operativni troškovi

CPU ograničenja i agresivno prigušivanje u Kubernetesu
Broj utrošenih sati instance

Što je ulov?

Kao što je navedeno na početku članka:

Može se povući analogija sa zajedničkim stanom... Kubernetes djeluje kao trgovac nekretninama. Ali kako zaštititi stanare od međusobnih sukoba? Što ako netko od njih, recimo, odluči posuditi kupaonicu na pola dana?

Evo u čemu je kvaka. Jedan nepažljivi spremnik može pojesti sve raspoložive CPU resurse na stroju. Ako imate pametni skup aplikacija (na primjer, JVM, Go, Node VM su ispravno konfigurirani), to nije problem: možete dugo raditi u takvim uvjetima. Ali ako su aplikacije loše optimizirane ili uopće nisu optimizirane (FROM java:latest), situacija bi mogla izmaći kontroli. U Omiu imamo automatizirane bazne Docker datoteke s odgovarajućim zadanim postavkama za glavni jezični stog, tako da ovaj problem nije postojao.

Preporučujemo praćenje metrike KORISTITE (korištenje, zasićenost i pogreške), kašnjenja API-ja i stope pogrešaka. Osigurajte da rezultati ispunjavaju očekivanja.

reference

Ovo je naša priča. Sljedeći materijali uvelike su pomogli da se shvati što se događa:

Izvješća o greškama Kubernetesa:

Jeste li se u svojoj praksi susreli sa sličnim problemima ili imate iskustva u vezi s prigušivanjem u kontejnerskim proizvodnim okruženjima? Podijelite svoju priču u komentarima!

PS od prevoditelja

Pročitajte i na našem blogu:

Izvor: www.habr.com

Dodajte komentar