Kubernetes: miért olyan fontos a rendszererőforrás-kezelés beállítása?
Általános szabály, hogy mindig szükség van egy dedikált erőforráskészlet biztosítására egy alkalmazás számára a megfelelő és stabil működés érdekében. De mi van akkor, ha több alkalmazás fut ugyanazon a teljesítményen? Hogyan biztosítsuk mindegyiküket a minimálisan szükséges erőforrásokkal? Hogyan korlátozhatja az erőforrás-felhasználást? Hogyan lehet helyesen elosztani a terhelést a csomópontok között? Hogyan biztosítható a vízszintes méretezési mechanizmus működése, ha az alkalmazás terhelése növekszik?
Kezdje azzal, hogy milyen fő erőforrástípusok vannak a rendszerben - ez természetesen a processzoridő és a RAM. A k8s manifesztekben ezeket az erőforrástípusokat a következő mértékegységekben mérjük:
CPU - magokban
RAM - bájtban
Ezen túlmenően minden erőforráshoz kétféle követelmény állítható be - kéri и határértékek. Kérések – a csomópont ingyenes erőforrásaira vonatkozó minimális követelményeket írja le egy tároló (és a pod egészének) futtatásához, míg a korlátok szigorúan korlátozzák a tároló rendelkezésére álló erőforrásokat.
Fontos megérteni, hogy a jegyzéknek nem kell egyértelműen meghatároznia mindkét típust, de a viselkedés a következő lesz:
Ha egy erőforrásnak csak a korlátai vannak kifejezetten megadva, akkor az erre az erőforrásra vonatkozó kérések automatikusan a korlátokkal megegyező értéket vesznek fel (ezt a leírási entitások meghívásával ellenőrizheti). Azok. Valójában a tároló ugyanannyi erőforrásra lesz korlátozva, amelyre a futtatásához szüksége van.
Ha egy erőforráshoz csak kérések vannak kifejezetten megadva, akkor erre az erőforrásra nincs felső korlátozás beállítva - pl. a tárolót csak magának a csomópontnak az erőforrásai korlátozzák.
Az erőforrás-kezelést nem csak egy adott tároló szintjén, hanem névtér szinten is beállíthatja a következő entitások használatával:
LimitRange — leírja a korlátozási politikát a tartály/hüvely szintjén ns-ben, és szükséges a tartály/hüvely alapértelmezett korlátainak leírásához, valamint a nyilvánvalóan zsíros tartályok/hüvelyek létrehozásának megakadályozásához (vagy fordítva), számának korlátozásához és meghatározza a korlátok és kérések értékeinek lehetséges eltérését
Erőforráskvóták — általánosságban írja le a korlátozási szabályzatot az összes ns-ben lévő tárolóra, és általában az erőforrások környezetek közötti elhatárolására szolgál (hasznos, ha a környezetek nincsenek szigorúan elhatárolva csomóponti szinten)
Az alábbiakban példák láthatók az erőforráskorlátokat beállító jegyzékekre:
Azok. ebben az esetben egy konténer nginx-szel való futtatásához legalább 1 G szabad RAM-ra és 0.2 CPU-ra lesz szüksége a csomóponton, míg a konténer legfeljebb 0.2 CPU-t és a csomóponton elérhető összes RAM-ot fogyaszthat.
Azok. az összes kérelem konténer összege az alapértelmezett ns-ben nem haladhatja meg a 300 m-t a CPU és az 1G-t az OP esetében, és az összes korlát összege 700 m a CPU és a 2G az OP esetében.
Azok. az összes tároló alapértelmezett névterében a kérés 100 m-re lesz beállítva a CPU-nál és 1G-re az OP-nál, a korlát pedig - 1 CPU és 2G. Ugyanakkor a CPU (50m < x < 2) és a RAM (500M < x < 4G) kérés/korlátozás lehetséges értékeinek korlátja is be van állítva.
Azok. Az alapértelmezett ns-ben minden egyes pod esetében 4 vCPU és 1G lesz a korlát.
Most szeretném elmondani, milyen előnyökkel járhat számunkra ezeknek a korlátozásoknak a beállítása.
Terheléselosztó mechanizmus a csomópontok között
Mint tudod, a k8s komponens felelős a pod-ok csomópontok közötti elosztásáért, mint pl ütemező, amely egy meghatározott algoritmus szerint működik. Ez az algoritmus két szakaszon megy keresztül, amikor kiválasztja az optimális indítandó csomópontot:
szűrő
Körű
Azok. a leírt házirend szerint kezdetben olyan csomópontok kerülnek kiválasztásra, amelyeken lehetőség van egy készlet alapján pod indítására állítmányok (beleértve annak ellenőrzését, hogy a csomópont rendelkezik-e elegendő erőforrással a pod futtatásához – PodFitsResources), majd minden egyes csomópont esetében, a prioritások pontokat kapnak (beleértve, minél több szabad erőforrással rendelkezik egy csomópont, annál több pontot rendel hozzá - LeastResourceAllocation/LeastRequestedPriority/BalancedResourceAllocation), és a pod a legtöbb pontot elérő csomóponton indul (ha több csomópont teljesíti ezt a feltételt egyszerre, akkor véletlenszerűen kiválasztott) .
Ugyanakkor meg kell értenie, hogy az ütemezőt egy csomópont rendelkezésre álló erőforrásainak felmérésekor az etcd-ben tárolt adatok vezérlik - azaz. az ezen a csomóponton futó egyes pod kért/korlátozott erőforrásának mennyiségére, de nem a tényleges erőforrás-felhasználásra. Ez az információ a parancs kimenetéből nyerhető kubectl describe node $NODE, például:
Itt láthatjuk az összes, egy adott csomóponton futó pod-ot, valamint az egyes pod-ok által kért erőforrásokat. És így néznek ki az ütemező naplói, amikor a cronjob-cron-events-1573793820-xt6q9 pod elindul (ez az információ megjelenik az ütemező naplójában, amikor beállítja a 10. naplózási szintet az indítási parancs -v=10 argumentumában):
log
I1115 07:57:21.637791 1 scheduling_queue.go:908] About to try and schedule pod nxs-stage/cronjob-cron-events-1573793820-xt6q9
I1115 07:57:21.637804 1 scheduler.go:453] Attempting to schedule pod: nxs-stage/cronjob-cron-events-1573793820-xt6q9
I1115 07:57:21.638285 1 predicates.go:829] Schedule Pod nxs-stage/cronjob-cron-events-1573793820-xt6q9 on Node nxs-k8s-s5 is allowed, Node is running only 16 out of 110 Pods.
I1115 07:57:21.638300 1 predicates.go:829] Schedule Pod nxs-stage/cronjob-cron-events-1573793820-xt6q9 on Node nxs-k8s-s6 is allowed, Node is running only 20 out of 110 Pods.
I1115 07:57:21.638322 1 predicates.go:829] Schedule Pod nxs-stage/cronjob-cron-events-1573793820-xt6q9 on Node nxs-k8s-s3 is allowed, Node is running only 20 out of 110 Pods.
I1115 07:57:21.638322 1 predicates.go:829] Schedule Pod nxs-stage/cronjob-cron-events-1573793820-xt6q9 on Node nxs-k8s-s4 is allowed, Node is running only 17 out of 110 Pods.
I1115 07:57:21.638334 1 predicates.go:829] Schedule Pod nxs-stage/cronjob-cron-events-1573793820-xt6q9 on Node nxs-k8s-s10 is allowed, Node is running only 16 out of 110 Pods.
I1115 07:57:21.638365 1 predicates.go:829] Schedule Pod nxs-stage/cronjob-cron-events-1573793820-xt6q9 on Node nxs-k8s-s12 is allowed, Node is running only 9 out of 110 Pods.
I1115 07:57:21.638334 1 predicates.go:829] Schedule Pod nxs-stage/cronjob-cron-events-1573793820-xt6q9 on Node nxs-k8s-s11 is allowed, Node is running only 11 out of 110 Pods.
I1115 07:57:21.638385 1 predicates.go:829] Schedule Pod nxs-stage/cronjob-cron-events-1573793820-xt6q9 on Node nxs-k8s-s1 is allowed, Node is running only 19 out of 110 Pods.
I1115 07:57:21.638402 1 predicates.go:829] Schedule Pod nxs-stage/cronjob-cron-events-1573793820-xt6q9 on Node nxs-k8s-s2 is allowed, Node is running only 21 out of 110 Pods.
I1115 07:57:21.638383 1 predicates.go:829] Schedule Pod nxs-stage/cronjob-cron-events-1573793820-xt6q9 on Node nxs-k8s-s9 is allowed, Node is running only 16 out of 110 Pods.
I1115 07:57:21.638335 1 predicates.go:829] Schedule Pod nxs-stage/cronjob-cron-events-1573793820-xt6q9 on Node nxs-k8s-s8 is allowed, Node is running only 18 out of 110 Pods.
I1115 07:57:21.638408 1 predicates.go:829] Schedule Pod nxs-stage/cronjob-cron-events-1573793820-xt6q9 on Node nxs-k8s-s13 is allowed, Node is running only 8 out of 110 Pods.
I1115 07:57:21.638478 1 predicates.go:1369] Schedule Pod nxs-stage/cronjob-cron-events-1573793820-xt6q9 on Node nxs-k8s-s10 is allowed, existing pods anti-affinity terms satisfied.
I1115 07:57:21.638505 1 predicates.go:1369] Schedule Pod nxs-stage/cronjob-cron-events-1573793820-xt6q9 on Node nxs-k8s-s8 is allowed, existing pods anti-affinity terms satisfied.
I1115 07:57:21.638577 1 predicates.go:1369] Schedule Pod nxs-stage/cronjob-cron-events-1573793820-xt6q9 on Node nxs-k8s-s9 is allowed, existing pods anti-affinity terms satisfied.
I1115 07:57:21.638583 1 predicates.go:829] Schedule Pod nxs-stage/cronjob-cron-events-1573793820-xt6q9 on Node nxs-k8s-s7 is allowed, Node is running only 25 out of 110 Pods.
I1115 07:57:21.638932 1 resource_allocation.go:78] cronjob-cron-events-1573793820-xt6q9 -> nxs-k8s-s10: BalancedResourceAllocation, capacity 39900 millicores 66620178432 memory bytes, total request 2343 millicores 9640186880 memory bytes, score 9
I1115 07:57:21.638946 1 resource_allocation.go:78] cronjob-cron-events-1573793820-xt6q9 -> nxs-k8s-s10: LeastResourceAllocation, capacity 39900 millicores 66620178432 memory bytes, total request 2343 millicores 9640186880 memory bytes, score 8
I1115 07:57:21.638961 1 resource_allocation.go:78] cronjob-cron-events-1573793820-xt6q9 -> nxs-k8s-s9: BalancedResourceAllocation, capacity 39900 millicores 66620170240 memory bytes, total request 4107 millicores 11307422720 memory bytes, score 9
I1115 07:57:21.638971 1 resource_allocation.go:78] cronjob-cron-events-1573793820-xt6q9 -> nxs-k8s-s8: BalancedResourceAllocation, capacity 39900 millicores 66620178432 memory bytes, total request 5847 millicores 24333637120 memory bytes, score 7
I1115 07:57:21.638975 1 resource_allocation.go:78] cronjob-cron-events-1573793820-xt6q9 -> nxs-k8s-s9: LeastResourceAllocation, capacity 39900 millicores 66620170240 memory bytes, total request 4107 millicores 11307422720 memory bytes, score 8
I1115 07:57:21.638990 1 resource_allocation.go:78] cronjob-cron-events-1573793820-xt6q9 -> nxs-k8s-s8: LeastResourceAllocation, capacity 39900 millicores 66620178432 memory bytes, total request 5847 millicores 24333637120 memory bytes, score 7
I1115 07:57:21.639022 1 generic_scheduler.go:726] cronjob-cron-events-1573793820-xt6q9_nxs-stage -> nxs-k8s-s10: TaintTolerationPriority, Score: (10)
I1115 07:57:21.639030 1 generic_scheduler.go:726] cronjob-cron-events-1573793820-xt6q9_nxs-stage -> nxs-k8s-s8: TaintTolerationPriority, Score: (10)
I1115 07:57:21.639034 1 generic_scheduler.go:726] cronjob-cron-events-1573793820-xt6q9_nxs-stage -> nxs-k8s-s9: TaintTolerationPriority, Score: (10)
I1115 07:57:21.639041 1 generic_scheduler.go:726] cronjob-cron-events-1573793820-xt6q9_nxs-stage -> nxs-k8s-s10: NodeAffinityPriority, Score: (0)
I1115 07:57:21.639053 1 generic_scheduler.go:726] cronjob-cron-events-1573793820-xt6q9_nxs-stage -> nxs-k8s-s8: NodeAffinityPriority, Score: (0)
I1115 07:57:21.639059 1 generic_scheduler.go:726] cronjob-cron-events-1573793820-xt6q9_nxs-stage -> nxs-k8s-s9: NodeAffinityPriority, Score: (0)
I1115 07:57:21.639061 1 interpod_affinity.go:237] cronjob-cron-events-1573793820-xt6q9 -> nxs-k8s-s10: InterPodAffinityPriority, Score: (0)
I1115 07:57:21.639063 1 selector_spreading.go:146] cronjob-cron-events-1573793820-xt6q9 -> nxs-k8s-s10: SelectorSpreadPriority, Score: (10)
I1115 07:57:21.639073 1 interpod_affinity.go:237] cronjob-cron-events-1573793820-xt6q9 -> nxs-k8s-s8: InterPodAffinityPriority, Score: (0)
I1115 07:57:21.639077 1 selector_spreading.go:146] cronjob-cron-events-1573793820-xt6q9 -> nxs-k8s-s8: SelectorSpreadPriority, Score: (10)
I1115 07:57:21.639085 1 interpod_affinity.go:237] cronjob-cron-events-1573793820-xt6q9 -> nxs-k8s-s9: InterPodAffinityPriority, Score: (0)
I1115 07:57:21.639088 1 selector_spreading.go:146] cronjob-cron-events-1573793820-xt6q9 -> nxs-k8s-s9: SelectorSpreadPriority, Score: (10)
I1115 07:57:21.639103 1 generic_scheduler.go:726] cronjob-cron-events-1573793820-xt6q9_nxs-stage -> nxs-k8s-s10: SelectorSpreadPriority, Score: (10)
I1115 07:57:21.639109 1 generic_scheduler.go:726] cronjob-cron-events-1573793820-xt6q9_nxs-stage -> nxs-k8s-s8: SelectorSpreadPriority, Score: (10)
I1115 07:57:21.639114 1 generic_scheduler.go:726] cronjob-cron-events-1573793820-xt6q9_nxs-stage -> nxs-k8s-s9: SelectorSpreadPriority, Score: (10)
I1115 07:57:21.639127 1 generic_scheduler.go:781] Host nxs-k8s-s10 => Score 100037
I1115 07:57:21.639150 1 generic_scheduler.go:781] Host nxs-k8s-s8 => Score 100034
I1115 07:57:21.639154 1 generic_scheduler.go:781] Host nxs-k8s-s9 => Score 100037
I1115 07:57:21.639267 1 scheduler_binder.go:269] AssumePodVolumes for pod "nxs-stage/cronjob-cron-events-1573793820-xt6q9", node "nxs-k8s-s10"
I1115 07:57:21.639286 1 scheduler_binder.go:279] AssumePodVolumes for pod "nxs-stage/cronjob-cron-events-1573793820-xt6q9", node "nxs-k8s-s10": all PVCs bound and nothing to do
I1115 07:57:21.639333 1 factory.go:733] Attempting to bind cronjob-cron-events-1573793820-xt6q9 to nxs-k8s-s10
Itt látjuk, hogy kezdetben az ütemező szűr, és létrehoz egy listát 3 csomópontból, amelyeken elindítható (nxs-k8s-s8, nxs-k8s-s9, nxs-k8s-s10). Ezután több paraméter (beleértve a BalancedResourceAllocation, LeastResourceAllocation) alapján kiszámítja a pontszámokat minden egyes csomópontra, hogy meghatározza a legmegfelelőbb csomópontot. Végső soron a pod a legtöbb ponttal rendelkező csomóponton van ütemezve (itt egyszerre két csomópontnak ugyanannyi pontja van 100037, tehát egy véletlenszerű lesz kiválasztva - nxs-k8s-s10).
Teljesítmény: ha egy csomópont olyan podokat futtat, amelyekre nincsenek korlátozások beállítva, akkor a k8s esetén (erőforrás-felhasználás szempontjából) ez egyenértékű lesz azzal, mintha ezen a csomóponton egyáltalán nem lennének ilyen pod-ok. Ezért, ha feltételesen van egy torkos folyamattal rendelkező pod (például wowza), és nincsenek rá korlátozások beállítva, akkor olyan helyzet állhat elő, amikor ez a pod ténylegesen megette a csomópont összes erőforrását, de k8s esetén ez a csomópont terheletlennek minősül, és ugyanannyi pontot kap, amikor (pontosan a rendelkezésre álló erőforrásokat értékelő pontokban) olyan csomópontként rangsorolják, amely nem rendelkezik működő podokkal, ami végső soron a terhelés egyenetlen eloszlásához vezethet a csomópontok között.
Pod kilakoltatása
Mint tudják, minden podhoz hozzá van rendelve a 3 QoS osztály egyike:
garantált — akkor van hozzárendelve, ha a pod minden egyes tárolójához egy kérés és korlát van megadva a memóriához és a cpu-hoz, és ezeknek az értékeknek meg kell egyeznie
robbanékony — a podban legalább egy tárolónak van kérése és korlátja, kérés < limittel
legjobb erőfeszítés — ha a podban egyetlen tároló sem korlátozott erőforrással
Ugyanakkor, amikor egy csomópont erőforráshiányt tapasztal (lemez, memória), a kubelet elkezdi rangsorolni és kiüríteni a podokat egy adott algoritmus szerint, amely figyelembe veszi a pod prioritását és annak QoS osztályát. Például, ha a RAM-ról beszélünk, akkor a QoS osztály alapján a pontok a következő elv szerint járnak:
Azok. azonos prioritás mellett a kubelet először a legjobb QoS osztályú podokat üríti ki a csomópontból.
Teljesítmény: ha csökkenteni szeretné annak valószínűségét, hogy erőforráshiány esetén a kívánt pod kikerüljön a csomópontból, akkor a prioritás mellett gondoskodnia kell a kérés/limit beállításáról is.
Mechanizmus az alkalmazási podok vízszintes automatikus skálázásához (HPA)
Amikor a feladat a podok számának automatikus növelése és csökkentése az erőforrások felhasználásától függően (rendszer - CPU/RAM vagy felhasználó - rps), akkor egy ilyen k8s entitás, mint pl. HPA (Horizontal Pod Autoscaler). Ennek algoritmusa a következő:
Meghatározzák a megfigyelt erőforrás aktuális értékeit (currentMetricValue)
Meghatározzák az erőforrás kívánt értékeit (desiredMetricValue), amelyeket a rendszererőforrásokhoz kéréssel állítanak be
Meg van határozva a replikák aktuális száma (currentReplicas)
A következő képlet kiszámítja a replikák kívánt számát (kívánt replikák)
wishReplicas = [ currentReplicas * ( currentMetricValue / kívántMetricValue )]
Ebben az esetben a skálázás nem történik meg, ha az együttható (currentMetricValue / kívántMetricValue) közel van 1-hez (ebben az esetben magunk állíthatjuk be a megengedett hibát, alapértelmezés szerint 0.1).
Nézzük meg, hogyan működik a hpa az app-teszt alkalmazás példáján (leírása szerint Deployment), ahol a replikák számát a CPU-fogyasztástól függően módosítani kell:
Azok. azt látjuk, hogy az alkalmazáscsomag kezdetben két példányban indul el, amelyek mindegyike két nginx és nginx-exporter tárolót tartalmaz, amelyek mindegyikéhez egy meghatározott kéri CPU számára.
Azok. Létrehoztunk egy hpa-t, amely figyeli a Deployment app-tesztet, és beállítja a podok számát az alkalmazással a cpu indikátor alapján (azt várjuk, hogy a pod a kért CPU 30%-át fogja fogyasztani), a replikák száma pedig a 2-10.
Most nézzük meg a hpa működési mechanizmusát, ha az egyik kandallóra terhelést alkalmazunk:
# kubectl top pod
NAME CPU(cores) MEMORY(bytes)
app-test-78559f8f44-pgs58 101m 243Mi
app-test-78559f8f44-cj4jz 4m 240Mi
Összességében az alábbiakkal rendelkezünk:
A kívánt érték (desiredMetricValue) - a hpa beállítások szerint 30%
Aktuális érték (currentMetricValue) - a számításhoz a vezérlő-menedzser kiszámítja az erőforrás-felhasználás átlagos értékét %-ban, azaz. feltételesen a következőket teszi:
Megkapja a pod-metrikák abszolút értékeit a metrikaszervertől, pl. 101m és 4m
Kiszámítja az átlagos abszolút értéket, pl. (101m + 4m) / 2 = 53m
Lekéri a kívánt erőforrás-felhasználás abszolút értékét (ehhez az összes konténer igényét összegzik) 60m + 30m = 90m
Kiszámítja a CPU-fogyasztás átlagos százalékos arányát a kérelem podhoz viszonyítva, azaz. 53 m / 90 m * 100% = 59%
Most már minden megvan, ami ahhoz kell, hogy meghatározzuk, meg kell-e változtatni a replikák számát; ehhez kiszámítjuk az együtthatót:
ratio = 59% / 30% = 1.96
Azok. a replikák számát ~2-szeresére kell növelni, és értéke [2 * 1.96] = 4.
Következtetés: Amint látható, a mechanizmus működéséhez szükséges feltétel a kérések jelenléte a megfigyelt podban lévő összes konténerre vonatkozóan.
A csomópontok vízszintes automatikus skálázásának mechanizmusa (Cluster Autoscaler)
A terhelési túlfeszültségek során a rendszerre gyakorolt negatív hatások semlegesítésére nem elegendő egy konfigurált hpa. Például a hpa vezérlőkezelő beállításai szerint úgy dönt, hogy a replikák számát 2-szeresére kell növelni, de a csomópontoknak nincs szabad erőforrásuk ekkora számú pod futtatásához (azaz a csomópont nem tudja biztosítani a kért erőforrásokat a kérelmek sorba), és ezek a sorba rendezések függőben lévő állapotba kapcsolnak.
Ebben az esetben, ha a szolgáltató rendelkezik megfelelő IaaS/PaaS-szel (például GKE/GCE, AKS, EKS stb.), akkor egy olyan eszköz, mint pl. Node Autoscaler. Lehetővé teszi a fürtben lévő csomópontok maximális és minimális számának beállítását, és a csomópontok aktuális számának automatikus beállítását (a felhőszolgáltató API meghívásával egy csomópont megrendeléséhez/eltávolításához), ha erőforráshiány van a fürtben és a podokban. nem ütemezhetők (Függő állapotban vannak).
Következtetés: A csomópontok automatikus méretezése érdekében kéréseket kell beállítani a pod-tárolókban, hogy a k8s helyesen tudja felmérni a csomópontok terhelését, és ennek megfelelően jelenteni tudja, hogy a fürtben nincsenek erőforrások a következő pod indításához.
Következtetés
Megjegyzendő, hogy a tárolóerőforrás-korlátok beállítása nem feltétele az alkalmazás sikeres futtatásának, de a következő okok miatt mégis jobb ezt megtenni:
Az ütemező pontosabb működéséhez a k8s csomópontok közötti terheléselosztás szempontjából
A „hüvely kilakoltatási” esemény bekövetkezésének valószínűségének csökkentése érdekében
Az alkalmazásdobozok vízszintes automatikus skálázásához (HPA) a működéshez
Csomópontok vízszintes automatikus skálázásához (Cluster Autoscaling) felhőszolgáltatók számára