CPU-grÀnser och aggressiv throttling i Kubernetes

Obs. övers.: Den hĂ€r lĂ€rorika historien frĂ„n Omio – den europeiska resesamlaren – för andra lĂ€sare frĂ„n grundlĂ€ggande teori till spĂ€nnande praktiska detaljer i konfigureringen av Kubernetes. Att bekanta sig med sĂ„dana fall hjĂ€lper inte bara till att bredda perspektiven, utan Ă€ven att förebygga komplexa problem.

CPU-grÀnser och aggressiv throttling i Kubernetes

Har du nÄgonsin stött pÄ att en applikation "stannade upp", slutade svara pÄ statuskontroller (health check) och du inte kunde förstÄ orsaken till detta beteende? En av de möjliga förklaringarna Àr relaterad till kvotgrÀnser för CPU-resurser. Det Àr vad denna artikel handlar om.

TL;DR:
Vi rekommenderar starkt att avstÄ frÄn CPU-grÀnser i Kubernetes (eller stÀnga av CFS-kvoter i Kubelet) om en version av Linux-kÀrnan med CFS-kvotsfel anvÀnds. I kÀrnan har finns en allvarlig och vÀlkÀnd bugg som leder till överdriven throttling och fördröjningar.
.

I Omio hanteras all infrastruktur av Kubernetes.Alla vÄra stateful- och stateless-laster körs uteslutande pÄ Kubernetes (vi anvÀnder Google Kubernetes Engine). Under de senaste sex mÄnaderna har vi observerat slumpmÀssiga fördröjningar. Applikationer fryser eller slutar svara pÄ health checks, tappar nÀtverksanslutning och sÄ vidare. Ett sÄdant beteende har lÀnge stÀllt oss inför svÄrigheter, och till slut bestÀmde vi oss för att ta itu med problemet pÄ allvar.

Sammanfattning av artikeln:

  • NĂ„gra ord om containrar och Kubernetes;
  • Hur CPU-begĂ€rningar och grĂ€nser Ă€r implementerade;
  • Hur CPU-grĂ€nser fungerar i miljöer med flera kĂ€rnor;
  • Hur man övervakar CPU-throttling;
  • Lösning av problemet och dess nyanser.

NÄgra ord om containrar och Kubernetes

Kubernetes Àr i grunden den moderna standarden inom infrastrukturvÀrlden. D dess huvudsakliga uppgift Àr orkestrering av containrar.

Containrar

Tidigare var vi tvungna att skapa artefakter som Java JAR/WAR-filer, Python Egg-filer eller exekverbara filer för senare körning pÄ servrar. Men för att fÄ dem att fungera var vi tvungna att göra extra arbete: installera körmiljön (Java/Python), placera nödvÀndiga filer pÄ rÀtt stÀllen, sÀkerstÀlla kompatibilitet med en specifik version av operativsystemet, etc. Med andra ord var vi tvungna att noggrant hantera konfigurationerna (vilket ofta orsakade konflikter mellan utvecklare och systemadministratörer).

Containrar har förÀndrat allt. Nu Àr artefakten en containerbild. Den kan ses som en utvidgad körbar fil som innehÄller inte bara programmet utan ocksÄ en fullstÀndig körmiljö (Java/Python/...), samt nödvÀndiga filer/paket som Àr förinstallerade och redo att anvÀndas. Containrar kan distribueras och köras pÄ olika servrar utan nÄgra ytterligare ÄtgÀrder.

Dessutom arbetar containrar i sin egen sandbox-miljö. De har sin egen virtuella nĂ€tverksadapter, ett filsystem med begrĂ€nsad tillgĂ„ng, sin egen processhierarki, samt begrĂ€nsningar för CPU och minne etc. Allt detta realiseras tack vare en sĂ€rskild del av Linux-kĂ€rnan — namespaces.

Kubernetes

Som nÀmnts tidigare Àr Kubernetes en containerorkestrator. Den fungerar sÄ hÀr: du ger den en pool av maskiner och sÀger: "Hej, Kubernetes, kör tio instanser av min container med 2 processorer och 3 GB minne vardera, och hÄll dem i drift!" Kubernetes tar hand om resten. Den hittar lediga resurser, startar containrarna och kommer att starta om dem vid behov, uppdatera vid versionförÀndringar osv. I grunden lÄter Kubernetes dig abstrahera frÄn hÄrdvarukomponenter och gör alla olika system lÀmpliga för distribution och drift av applikationer.

CPU-grÀnser och aggressiv throttling i Kubernetes
Kubernetes ur en enkel persons perspektiv

Vad Àr request:er och limit:er i Kubernetes

Okej, vi har förstÄtt containrar och Kubernetes. Vi vet ocksÄ att flera containrar kan finnas pÄ en och samma maskin.

Vi kan dra en parallell med en delad lÀgenhet. Man tar ett rymligt rum (maskiner/noder) och hyr ut det till flera hyresgÀster (containrar). Kubernetes fungerar som mÀklare. FrÄgan uppstÄr, hur kan vi förhindra att hyresgÀsterna kommer i konflikt med varandra? Vad hÀnder om en av dem, sÀg, bestÀmmer sig för att ta över badrummet i ett halvt dygn?

HĂ€r kommer request:er och limit:er in i bilden. CPU BegĂ€ran behövs uteslutande för planering. Det Ă€r nĂ„got av en "önskelista" för containern, och anvĂ€nds för att vĂ€lja den mest lĂ€mpliga noden. Samtidigt kan CPU BegrĂ€nsa jĂ€mföras med hyresavtalet — sĂ„ snart vi hittar en nod för containern, kan den inte överskrida de angivna grĂ€nserna. Och hĂ€r uppstĂ„r problemet


Hur requests och limits implementeras i Kubernetes

Kubernetes anvÀnder en inbyggd kÀrnfunktion för throttling (taktbegrÀnsning) för att genomföra CPU-limits. Om en applikation överskrider grÀnsen aktiveras throttling (d.v.s. den fÄr fÀrre CPU-taktscykler). Requests och limits för minne organiseras pÄ ett annat sÀtt, vilket gör dem lÀttare att upptÀcka. Det rÀcker att kontrollera den senaste omstartstatusen för podden: om den inte Àr "OOMKilled". Med CPU-throttling Àr det inte sÄ enkelt, eftersom K8s endast gör anvÀndningsmetrik tillgÀnglig, inte per cgroups.

CPU-begÀran

CPU-grÀnser och aggressiv throttling i Kubernetes
Hur CPU-begÀran implementeras

För enkelhetens skull ska vi betrakta processen utifrÄn en maskin med 4 kÀrnor.

K8s anvÀnder mekanismen cgroups (kontrollgrupper) för att hantera resursfördelning (minne och CPU). För detta finns en hierarkisk modell: barn Àrver limits frÄn förÀldragruppen. Detaljer om fördelningen lagras i det virtuella filsystemet (/sys/fs/cgroup). NÀr det gÀller CPU Àr detta /sys/fs/cgroup/cpu,cpuacct/*.

K8s anvĂ€nder filen cpu.share för att fördela CPU-resurser. I vĂ„rt fall fĂ„r rotkontrollgruppen 4096 andelar av CPU-resurser — 100% av den tillgĂ€ngliga CPU-kapaciteten (1 kĂ€rna = 1024; detta Ă€r ett fast vĂ€rde). Rotgruppen fördelar resurser proportionellt beroende pĂ„ andelarna av dess barn, som specificeras i cpu.share, och de i sin tur gör likadant med sina barn, och sĂ„ vidare. I en typisk Kubernetes-nod har rotkontrollgruppen tre barn: system.slice, user.slice och kubepods. De tvĂ„ första undergrupperna anvĂ€nds för att fördela resurser mellan kritiska systemlaster och anvĂ€ndarprogram utanför K8s. Den sista — kubepods — skapas av Kubernetes för att fördela resurser mellan poddar.

I bilden ovan kan man se att den första och andra undergruppen fick 1024 andelar, medan undergruppen kubepod tilldelades 4096 andelar. Hur Àr detta möjligt: rotgruppen har ju bara tillgÄng till 4096 andelar, medan summan av dess barnandelar betydligt överstiger detta nummer (6144)? Anledningen Àr att vÀrdet har logisk betydelse, sÄ Linux-schemalÀggaren (CFS) anvÀnder det för proportional resursfördelning av CPU. I vÄrt fall fÄr de första tvÄ grupperna 680 verkliga andelar (16,6% av 4096), medan kubepod erhÄller de ÄterstÄende 2736 andelarna. Vid stillestÄnd kommer de första tvÄ grupperna inte att anvÀnda sina tilldelade resurser.

Lyckligtvis finns det en mekanism i schemalÀggaren som förhindrar förlust av oanvÀnda CPU-resurser. Den överför "idle" kapacitet till en global pool, frÄn vilken de fördelas till grupper som behöver extra processorkraft (överföringen sker i partier för att undvika förluster vid avrundning). En liknande metod tillÀmpas Àven pÄ alla efterkommande noder.

Denna mekanism sÀkerstÀller rÀttvis fördelning av processorkraft och ser till att ingen process "stjÀl" resurser frÄn andra.

CPU Limit

Trots att konfigurationerna för limit och begÀran i K8s ser likadana ut, skiljer sig deras implementering avsevÀrt: det Àr den mest missledande och minst dokumenterade delen.

K8s anvÀnder CFS kvotmekanismen för att genomföra begrÀnsningar. Deras instÀllningar anges i filerna cfs_period_us och cfs_quota_us i cgroup-katalogen (dÀr ligger ocksÄ filen cpu.share).

Till skillnad frĂ„n cpu.share, Ă€r kvoten baserad pĂ„ tidsperioden, inte pĂ„ tillgĂ€nglig processorkraft. cfs_period_us anger varaktigheten för perioden (epoken) — detta Ă€r alltid 100000 ”s (100 ms). I K8s finns det möjlighet att Ă€ndra detta vĂ€rde, men det Ă€r för nĂ€rvarande endast tillgĂ€ngligt i alpha-versionen. SchemalĂ€ggaren anvĂ€nder epoken för att Ă„terstarta anvĂ€nda kvoter. Den andra filen, cfs_quota_us, anger den tillgĂ€ngliga tiden (kvoten) i varje epok. Observera att den ocksĂ„ anges i mikrosekunder. Kvoten kan överstiga varaktigheten av epoken; med andra ord kan den vara större Ă€n 100 ms.

LÄt oss titta pÄ tvÄ scenarier pÄ 16-kÀrniga maskiner (den vanligaste typen av datorer hos oss pÄ Omio):

CPU-grÀnser och aggressiv throttling i Kubernetes
Scenario 1: 2 trÄdar och en grÀns pÄ 200 ms. Ingen throttling.

CPU-grÀnser och aggressiv throttling i Kubernetes
Scenario 2: 10 trÄdar och en grÀns pÄ 200 ms. Throttling börjar efter 20 ms, tillgÄngen till processorkraft Äterupptas ytterligare efter 80 ms.

Anta att du har stÀllt in CPU-limit pÄ 2 kÀrnor; Kubernetes översÀtter detta vÀrde till 200 ms. Det betyder att containern kan anvÀnda högst 200 ms processor-tid utan throttling.

Och hÀr börjar det intressanta. Som nÀmnts ovan Àr den tillgÀngliga kvoten 200 ms. Om du har tio trÄdar pÄ en 12-kÀrnig maskin (se illustrationen för scenario 2), medan alla andra poddar Àr inaktiva, kommer kvoten att ta slut efter endast 20 ms (eftersom 10 * 20 ms = 200 ms), och alla trÄdar i denna podd kommer att "frysas" (throttle) (hastighetsbegrÀnsning) ökar situationen som redan nÀmnts schemalÀggarbjugg, som orsakar överdriven throttle och containern kan inte ens nÄ den existerande kvoten.

Hur mÀter man throttling i poddar?

Logga bara in i podden och kör cat /sys/fs/cgroup/cpu/cpu.stat.

  • nr_periods — det totala antalet schemalĂ€ggningsperioder;
  • nr_throttled — antalet throttled-periodes i nr_periods;
  • throttled_time — den totala throttled-tiden i nanosekunder.

CPU-grÀnser och aggressiv throttling i Kubernetes

Vad hÀnder egentligen?

Resultatet Àr hög throttling i alla applikationer. Ibland Àr det 1,5 gÄnger starkare Àn berÀknat!

Detta leder till olika fel — misslyckanden i beredskapskontroller (readiness), frysta containrar, nĂ€tverksavbrott, timeouts inom tjĂ€nsteanrop. I slutĂ€ndan yttrar detta sig i ökad fördröjning och fler fel.

Lösning och konsekvenser

HÀr Àr det enkelt. Vi tog bort CPU-grÀnserna och uppdaterade operativsystemkÀrnan i vÄra kluster till den senaste versionen dÀr felet var fixat. Antalet fel (HTTP 5xx) i vÄra tjÀnster minskade genast avsevÀrt:

HTTP 5xx-fel

CPU-grÀnser och aggressiv throttling i Kubernetes
HTTP 5xx-fel frÄn en kritiskt viktig tjÀnst

p95 svarstid

CPU-grÀnser och aggressiv throttling i Kubernetes
Fördröjning av förfrÄgningar frÄn en kritiskt viktig tjÀnst, 95:e percentilen

Driftskostnader

CPU-grÀnser och aggressiv throttling i Kubernetes
Antalet förbrukade instans-timmar

Vad Àr fÀllan?

Som nÀmnts i början av artikeln:

Man kan dra en parallell med en kollektivlĂ€genhet
 Kubernetes fungerar som hyresvĂ€rd. Men hur hĂ„ller man hyresgĂ€sterna frĂ„n konflikter sinsemellan? Vad hĂ€nder om en av dem, sĂ€g, beslutar att ta över badrummet i ett halvt dygn?

Det Àr hÀr fÀllan ligger. En oansvarig container kan ta över alla tillgÀngliga processorr resurser pÄ maskinen. Om du har en bra applikationsstack (t.ex. korrekt konfigurerade JVM, Go, Node VM), dÄ Àr det inga problem: man kan arbeta under sÄdana förhÄllanden under en lÀngre tid. Men om applikationerna Àr dÄligt optimerade eller helt ooptimerade (FROM java:latest), kan situationen gÄ ur kontroll. Vi pÄ Omio har automatiserade grundlÀggande Dockerfiles med rimliga standardinstÀllningar för storen av grundsprÄk, sÄ vi har inte stött pÄ sÄdana problem.

Vi rekommenderar att övervaka metrik ANVÄND (anvĂ€ndning, mĂ€ttnad och fel), API-fördröjningar och felfrekvens. Se till att resultaten motsvarar förvĂ€ntningarna.

LĂ€nkar

SÄ hÀr Àr vÄr historia. Följande material har kraftigt bidragit till att förstÄ vad som hÀnder:

Felrapporter för Kubernetes:

Har du stött pÄ liknande problem i din erfarenhet eller har du erfarenhet relaterad till throttling i containeriserade produktionsmiljöer? Dela med dig av din historia i kommentarsfÀltet!

P.S. frÄn översÀttaren

LÀs Àven i vÄr blogg:

KĂ€lla: habr.com

Köp pĂ„litlig webbhotell med DDoS-skydd, VPS VDS-servrar đŸ”„ Köp pĂ„litlig webbhotell med DDoS-skydd, VPS VDS-servrar | ProHoster