Kufijtë e CPU dhe ngadalësimi agresiv në Kubernetes

Shën. përk.: kjo histori e mësimdhënëse e Omio - një agregator udhëtimi evropian - udhëheq lexuesit nga teoria bazë në hollësitë praktike emocionuese të konfigurimit të Kubernetes. Njohja me raste të tilla ndihmon jo vetëm në zgjerimin e horizontit, por gjithashtu në parandalimin e problemeve jo trivial.

Kufijtë e CPU dhe ngadalësimi agresiv në Kubernetes

A keni hasur ndonjëherë në një situatë ku aplikacioni ka ngecur, nuk përgjigjej më në kërkesat e kontrollit të shëndetit (health checks) dhe nuk keni mundur të kuptoni shkakun e kësaj sjelljeje? Një nga shpjegimet e mundshme lidhet me kufirin e kuotave për burimet CPU. Ky do të jetë subjekti i këtij artikulli.

TL;DR:
Ne rekomandojmë me forcë të heqim dorë nga kufizimet e CPU në Kubernetes (ose të çaktivizojmë kuotat CFS në Kubelet), nëse po përdoret një version i bërthamës Linux me gabimin CFS-Quota. Në bërthamë ka ka një gabim serioz dhe të njohur mirë i cili çon në mbingopje dhe vonesa.
.

Në Omio e gjithë infrastruktura menaxhohet nga Kubernetes.Të gjitha ngarkesat tona stateful dhe stateless funksionojnë ekskluzivisht në Kubernetes (ne përdorim Google Kubernetes Engine). Gjatë gjashtë muajve të fundit kemi vënë re ngecja të rastit. Aplikacionet ngecin ose nuk përgjigjen më në kontrollin e shëndetit, humbin lidhjen me rrjetin etj. Një sjellje e tillë na ka lënë në një bllokim për një kohë të gjatë, dhe përfundimisht vendosëm të merremi me problemin me seriozitet.

Përmbledhje e artikullit:

  • Disa fjalë për kontejnerët dhe Kubernetes;
  • Si janë implementuar kërkesat dhe kufizimet e CPU;
  • Si funksionon kufizimi i CPU në mjediset me shumë bërthama;
  • Si të monitoroni mbingopjen e CPU;
  • Zgjidhja e problemit dhe nuancat.

Disa fjalë për kontejnerët dhe Kubernetes

Kubernetes, në thelb, është standardi modern në botën e infrastrukturës. Detyra e tij kryesore është orkestrimi i kontejnerëve.

Kontejnerët

Në të shkuarën, na ka ndodhur të krijojmë artefakte si Java JAR'/WAR', Python Egg' ose skedarë ekzekutues për t'i ekzekutuar më pas në servera. Megjithatë, për t'i bërë ata të funksionojnë, duhej të bënim punë shtesë: të instaloja mjedisin e ekzekutimit (Java/Python), të vendosja skedarët e nevojshëm në vendet e duhura, të siguroja përputhshmërinë me versionin e caktuar të sistemit operativ etj. Me fjalë të tjera, na duhej t'i kushtonim vëmendje të madhe menaxhimit të konfigurimeve (çka shpesh shkaktonte mosmarrëveshje midis zhvilluesve dhe administratorëve të sistemeve).

Kontejnerët ndryshuan gjithçka. Tani, artefakti është një imazhi kontejneri. Eshte si një skedar ekzekutues i zgjeruar, që përmban jo vetëm programin, por edhe një mjedis të plotë ekzekutimi (Java/Python/…), si dhe skedarët/paketat e nevojshme, të parainstaluara dhe gati për t'u ekzekutuar. Kontejnerët mund të vendosen dhe ekzekutohen në servera të ndryshëm pa ndonjë veprim të shtuar.

Për më tepër, kontejnerët funksionojnë në një ambient të vetë-substancuar. Ata kanë një adapter të tyren virtual, një sistem skedarësh me qasje të kufizuar, një hierarki procesesh, kufizime për CPU dhe memorie, etj. Të gjitha këto realizohen falë një nën-sistemi të veçantë të bërthamës Linux - namespaces.

Kubernetes

Siç u tha më parë, Kubernetes është orkestratori i kontejnerëve. Funksionon në këtë mënyrë: ju ofroni një grup makinash, dhe pastaj i thoni: «Hej, Kubernetes, fillo dhjetë instanca të kontejnerit tim me 2 procesorë dhe 3 GB memorie për secilin, dhe mbaji ato në gjendje pune!». Kubernetes do të kujdeset për gjithçka tjetër. Ai do të gjejë kapacitetet e lira, do të nisë kontejnerët dhe do t'i rinisë ato kur të jetë e nevojshme, do të nxjerrë një përditësim kur të ketë ndryshime versioni, etj. Në thelb, Kubernetes lejon që të shkëputeni nga komponentët harduerikë dhe e bën gjithë larmishmërinë e sistemeve të përshtatshme për vendosjen dhe funksionimin e aplikacioneve.

Kufijtë e CPU dhe ngadalësimi agresiv në Kubernetes
Kubernetes nga perspektiva e një qytetari të zakonshëm

Çfarë janë kërkesat dhe kufizimet në Kubernetes

Mirë, tani e kuptuam kontejnerët dhe Kubernetes. Gjithashtu, ne e dimë se disa kontejnerë mund të jenë në një makinë.

Mund të bëhet një analogji me një apartament të zakonshëm. Merrni një hapësirë të gjerë (makina/nodet) dhe e jepni me qira disa banorëve (kontejnerëve). Kubernetes vepron si agjent ndihmës. Lind pyetja, si të mbash të kaluarit ndarë nga njëri-tjetri? Çfarë nëse njëri prej tyre, për shembull, vendos të marrë banjën për gjysmë dite?

Këtu hyjnë në lojë kërkesat dhe kufizimet. CPU Kërkesa nevojitet vetëm për planifikim. Është diçka si «lista e dëshirave» e kontejnerit, dhe përdoret për të përzgjedhur nodin më të përshtatshëm. Ndërsa CPU Kufizim mund të krahasohet me kontratën e qirasë - sa herë që ne përzgjedhim një nod për kontejnerin, ai nuk do të mund të kalojë kufijtë e vendosur.

Si implementohen kërkesat dhe kufizimet në Kubernetes

Kubernetes përdor një mekanizëm të integruar në bërthamë për ngadalësimin e CPU për të realizuar kufizimet e CPU. Nëse aplikacioni kalon limitin, aktivizohet ngadalësimi (dmth. ai merr më pak skena CPU). Kërkesat dhe kufizimet për kujtesën janë të organizuara ndryshe, prandaj është më e lehtë t'i zbulojmë ato. Mjafton të kontrollojmë statusin e fundit të ribashkimit të pod-it: a është ai "OOMKilled". Me ngadalësimin e CPU, gjërat nuk janë aq të thjeshta, pasi K8s ofron vetëm metrika për përdorimin, e jo për cgroups.

Kërkesa CPU

Kufijtë e CPU dhe ngadalësimi agresiv në Kubernetes
Si është realizuar kërkesa CPU

Për thjeshtësi, le ta shqyrtojmë procesin në shembullin e një makine me CPU katër bërthamor.

K8s përdor mekanizmin e grupeve kontrolluese (cgroups) për të menaxhuar shpërndarjen e burimeve (kujtesë dhe CPU). Për të, është e disponueshme një model hierarkik: një fëmijë trashëgon kufizimet e grupit prind. Detajet e shpërndarjes ruhen në sistemin virtual të skedarëve (/sys/fs/cgroup). Në rastin e CPU, kjo është /sys/fs/cgroup/cpu,cpuacct/*.

K8s përdor skedarin cpu.share për shpërndarjen e burimeve të CPU. Në rastin tonë, grupi kontrollues rrënjësor merr 4096 aksione të burimeve CPU - 100% e fuqisë së disponueshme të CPU (1 bërthamë = 1024; kjo është një vlerë fikse). Grupi rrënjësor shpërndan burimet proporcionalisht në varësi të aksioneve të fëmijëve të shkruara në cpu.share, dhe ata, nga ana e tyre, veprojnë në mënyrë të ngjashme me fëmijët e tyre, etj. Në një nod tipik Kubernetes, grupi kontrollues rrënjësor ka tri fëmijë: system.slice, user.slice dhe kubepods. Dy grupet e para përdoren për shpërndarjen e burimeve midis ngarkesave kritike të sistemit dhe programeve të përdoruesve jashtë K8s. Të fundit - kubepods — krijohet nga Kubernetes për të shpërndarë burimet midis pod-ëve.

Në diagramin e mësipërm shihet se grupet e para dhe të dyta morën nga 1024 aksione, ndersa grupit kubepod i ishte ndarë 4096 aksioneve. Si është e mundur që grupit rrënjësor i janë dhënë vetëm 4096 aksione, kur gjithsej aksionet e fëmijëve të tij e kalojnë këtë numër (6144)? Çështja është se vlera ka një kuptim logjik, prandaj planifikuesi Linux (CFS) e përdor atë për shpërndarjen proporcionale të burimeve të CPU. Në rastin tonë, dy grupet e para marrin nga 680 aksionet reale (16,6% nga 4096), dhe kubepod merr të mbeturat 2736 aksioneve. Në rastin e papunësisë, grupet e para dy nuk do të përdorin burimet e ndara.

Fatmirësisht, në planifikues ka një mekanizëm që parandalon humbjen e burimeve të papërdorura të CPU. Ai kalon fuqitë "në papunësi" në një pool global, nga i cili ata shpërndahen në grupe që kanë nevojë për fuqira shtesë të CPU (transferimi ndodh në grupe për të shmangur humbjet nga rrethimi). Një metodë e ngjashme aplikohet gjithashtu për të gjithë pasardhësit e pasardhësve.

Ky mekanizëm siguron shpërndarjen e drejtë të fuqive të CPU dhe kontrollon që asnjë proces "të mos vjedhë" burime nga të tjerët.

Kufizimi CPU

Megjithëse konfigurimet e kufizimeve dhe kërkesave në K8s duken të ngjashme, implementimi i tyre është thelbësisht i ndryshëm: kjo është në të vërtetë më mashtruese dhe më pak e dokumentuar pjesë.

K8s angazhon mekanizmin e kuotave CFS për implementimin e kufizimeve. Konfigurimet e tyre caktohen në skedarët cfs_period_us dhe cfs_quota_us në drejtorinë e cgroup (po aty ndodhet skedari) cpu.share).

Ndryshe nga cpu.share, kuota bazohet në periudhën e kohës, e cila nuk është fuqia e disponueshme të CPU. cfs_period_us caktuar zgjasinë e periudhës (epokës) - gjithmonë 100000 µs (100 ms). Në K8s ka mundësi për të ndryshuar këtë vlerë, megjithatë ajo është aktualisht në versionin alfa. Planifikuesi përdor epokën për rinovimin e kuotave të shfrytëzuara. Skedari i dytë, cfs_quota_us, përcakton kohën e disponueshme (kuotën) në çdo epokë. Vini re se ajo gjithashtu tregohet në mikrosekonda. Kuota mund të kalojë gjatësi e epokës; në fjalë të tjera, ajo mund të jetë më e madhe se 100 ms.

Le të shqyrtojmë dy skenarë në makinat me 16 bërthama (tipi më i zakonshëm i kompjuterëve në Omio):

Kufijtë e CPU dhe ngadalësimi agresiv në Kubernetes
Skenari 1: 2 rrjedha dhe kufizimi prej 200 ms. Pa ngadalësim

Kufijtë e CPU dhe ngadalësimi agresiv në Kubernetes
Skenari 2: 10 rrjedha dhe kufizimi prej 200 ms. Ngadalësimi fillon pas 20 ms, aksesimi i burimeve të CPU rinovohet pas 80 ms

Supozoni se keni vendosur kufizimin e CPU në 2 bërthama; Kubernetes do ta përkthejë këtë vlerë në 200 ms. Kjo do të thotë se kontenieri mund të përdorë maksimumi 200 ms kohë CPU pa ngadalësim.

Dhe këtu fillon e gjitha. Siç u tha më sipër, kuota e disponueshme është 200 ms. Nëse keni dhjetë rrjedha që punojnë paralelisht në një makinë me 12 bërthama (shih ilustrimin e skenarit 2), derisa të gjitha pod-ët e tjerë të jenë në papunësi, kuota do të shterojë brenda vetëm 20 ms (sepse 10 * 20 ms = 200 ms), dhe të gjitha rrjedhat e këtij pod-i do të "ngadalësohen" (throttle) (throttle) për 80 ms të ardhshme. Kjo e përkeqëson situatën e përmendur tashmë bugu i planifikuesit, për shkak të cilit ndodh tepër shpërthim dhe kontejneri nuk mund të prodhojë as kuotën e tij ekzistuese.

Si të vlerësojmë zhvlerësimin në pod’ë?

Thjesht hyni në pod dhe ekzekutoni cat /sys/fs/cgroup/cpu/cpu.stat.

  • nr_periods — numri i përgjithshëm i periudhave të planifikuesit;
  • nr_throttled — numri i periudhave me zhvlerësim në përbërje të nr_periods;
  • throttled_time — koha e përgjithshme e zhvlerësimit në nanosekonda.

Kufijtë e CPU dhe ngadalësimi agresiv në Kubernetes

Çfarë ndodh në të vërtetë?

Në përfundim, ne marrim një zhvlerësim të lartë në të gjitha aplikacionet. Ndonjëherë është deri në një herë e gjysmë më e fortë se sa llogaritet!

Kjo çon në gabime të ndryshme — dështime të kontrolleve të gatishmërisë (readiness), ngecje të kontejnerëve, prishje të lidhjeve rrjet, kohëzgjatje në thirrjet e shërbimeve. Në fund, kjo rezulton në rritjen e vonesës dhe një rritje të numrit të gabimeve.

Zgjidhja dhe pasojat

Këtu gjithçka është e thjeshtë. Ne heqëm kufizimet e CPU dhe punuam për përditësimin e bërthamës së OS në klasterët në versionin më të fundit, ku bug-u ishte rregulluar. Numri i gabimeve (HTTP 5xx) në shërbimet tona ra menjëherë ndjeshëm:

Gabimet HTTP 5xx

Kufijtë e CPU dhe ngadalësimi agresiv në Kubernetes
Gabimet HTTP 5xx në një shërbim jashtëzakonisht të rëndësishëm

Koha e përgjigjes p95

Kufijtë e CPU dhe ngadalësimi agresiv në Kubernetes
Vonesa e kërkesave të shërbimit jashtëzakonisht të rëndësishëm, percentile 95

Shpenzimet operative

Kufijtë e CPU dhe ngadalësimi agresiv në Kubernetes
Numri i orëve të shpenzuara

Cila është kurth?

Siç u tha në fillim të artikullit:

Mund të bëhet një analogji me një apartament të përbashkët… Kubernetes funksionon si një agjent pasurus. Por si t'i mbash qiramarrësit të mos bien në konflikt me njëri-tjetrin? Çfarë ndodh nëse njëri prej tyre, le të themi, vendos të zërë dushin për gjysmë dite?

Këtu është kurthi. Një kontejner i keq administruar mund të konsumojë të gjitha burimet e disponueshme të procesorit në makinë. Nëse keni një stek gjithnjë të mençur (për shembull, JVM, Go, Node VM të konfiguruara siç duhet), atëherë kjo nuk është problem: mund të punoni në këto kushte për një kohë të gjatë. Por nëse aplikacionet janë optimizuar keq ose aspak (FROM java:latest), situata mund të dalë jashtë kontrollit. Ne në Omio kemi Dockerfiles bazë të automatizuara me konfigurime të arsyeshme të parazgjedhura për stekët kryesorë të gjuhëve, prandaj një problem i tillë nuk ka ekzistuar.

Ne rekomandojmë të monitoroni metrikat USE (përdorimi, ngopja dhe gabimet), vonesat e API dhe shkallën e shfaqjes së gabimeve. Sigurohuni që rezultatet të përputhen me pritshmëritë.

Linke

Kjo është historia jonë. Materialet në vijim ndihmuan shumë për të kuptuar se çfarë po ndodh:

Raportet për gabimet e Kubernetes:

A keni hasur në probleme të ngjashme në praktikën tuaj ose keni përvojë që lidhet me zhvlerësimin në mjediset e prodhimit të containerizuar? Shpërndani historinë tuaj në komente!

P.S. nga përkthyesi

Lexoni gjithashtu në blogun tonë:

Burimi: habr.com

Bleni hostim të besueshëm për faqe me mbrojtje nga DDoS, serverë VPS VDS 🔥 Bleni hostim të besueshëm për faqe me mbrojtje nga DDoS, serverë VPS VDS | ProHoster