ProHoster > Блог > администрация > Kubernetes: защо е толкова важно да настроите управление на системните ресурси?
Kubernetes: защо е толкова важно да настроите управление на системните ресурси?
По правило винаги има нужда да се предостави специален пул от ресурси на приложението за неговата правилна и стабилна работа. Но какво ще стане, ако няколко приложения работят на едно и също захранване? Как да осигурим на всеки от тях минимално необходимите ресурси? Как можете да ограничите потреблението на ресурси? Как правилно да разпределите натоварването между възлите? Как да се гарантира, че механизмът за хоризонтално мащабиране работи, ако натоварването на приложението се увеличи?
Трябва да започнете с това какви основни видове ресурси съществуват в системата - това, разбира се, е процесорно време и RAM. В манифестите на k8s тези типове ресурси се измерват в следните единици:
CPU - в ядра
RAM - в байтове
Освен това за всеки ресурс е възможно да зададете два вида изисквания - искания и граници. Заявки - описва минималните изисквания за свободни ресурси на възел за изпълнение на контейнер (и под като цяло), докато ограниченията задават твърдо ограничение на ресурсите, налични за контейнера.
Важно е да се разбере, че манифестът не трябва изрично да дефинира и двата типа, но поведението ще бъде както следва:
Ако само ограниченията на даден ресурс са изрично посочени, тогава заявките за този ресурс автоматично приемат стойност, равна на ограниченията (можете да проверите това, като извикате обекти за описание). Тези. всъщност контейнерът ще бъде ограничен до същото количество ресурси, което изисква да работи.
Ако за даден ресурс са изрично посочени само заявки, тогава не се задават горни ограничения за този ресурс - т.е. контейнерът е ограничен само от ресурсите на самия възел.
Също така е възможно да се конфигурира управление на ресурси не само на ниво конкретен контейнер, но и на ниво пространство от имена, като се използват следните обекти:
LimitRange — описва политиката за ограничаване на ниво контейнер/шушулка в ns и е необходима, за да се опишат ограниченията по подразбиране за контейнера/шушулката, както и да се предотврати създаването на очевидно дебели контейнери/шушулки (или обратното), да се ограничи броят им и определяне на възможната разлика в стойностите в лимити и заявки
Квоти за ресурси — описва политиката за ограничаване като цяло за всички контейнери в ns и се използва, като правило, за разграничаване на ресурсите между среди (полезно, когато средите не са строго разграничени на ниво възел)
Следват примери за манифести, които задават ограничения на ресурсите:
Тези. в този случай, за да стартирате контейнер с nginx, ще ви трябва поне 1G свободна RAM и 0.2 CPU на възела, докато най-много контейнерът може да консумира 0.2 CPU и цялата налична RAM на възела.
Тези. сумата от всички контейнери за заявки в ns по подразбиране не може да надвишава 300m за процесора и 1G за OP, а сборът от всички ограничения е 700m за CPU и 2G за OP.
Тези. в пространството на имената по подразбиране за всички контейнери, заявката ще бъде зададена на 100m за CPU и 1G за OP, ограничение - 1 CPU и 2G. В същото време се задава и ограничение за възможните стойности в заявка/лимит за CPU (50m < x < 2) и RAM (500M < x < 4G).
Тези. за всеки pod в ns по подразбиране ще има ограничение от 4 vCPU и 1G.
Сега бих искал да ви кажа какви предимства може да ни даде поставянето на тези ограничения.
Механизъм за балансиране на натоварването между възлите
Както знаете, компонентът k8s е отговорен за разпределението на pods между възли, като напр Scheduler, който работи по определен алгоритъм. Този алгоритъм преминава през два етапа при избора на оптималния възел за стартиране:
филтриране
Обхват
Тези. съгласно описаната политика, първоначално се избират възли, на които е възможно да се стартира pod въз основа на набор предикати (включително проверка дали възелът има достатъчно ресурси, за да стартира pod - PodFitsResources), и след това за всеки от тези възли, според приоритети се присъждат точки (включително колкото повече свободни ресурси има даден възел, толкова повече точки му се присвояват - LeastResourceAllocation/LeastRequestedPriority/BalancedResourceAllocation) и подът се стартира на възела с най-много точки (ако няколко възела отговарят на това условие наведнъж, тогава избран е случаен) .
В същото време трябва да разберете, че планировчикът, когато оценява наличните ресурси на възел, се ръководи от данните, които се съхраняват в etcd - т.е. за количеството заявен/ограничен ресурс на всеки pod, работещ на този възел, но не и за действителното потребление на ресурси. Тази информация може да бъде получена от изхода на командата kubectl describe node $NODE, например:
Тук виждаме всички подове, работещи на конкретен възел, както и ресурсите, които всеки под изисква. И ето как изглеждат регистрационните файлове на планировчика, когато се стартира подът cronjob-cron-events-1573793820-xt6q9 (тази информация ще се появи в регистрационния файл на планировчика, когато зададете 10-то ниво на регистриране в аргументите на командата за стартиране -v=10):
дневник
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
Тук виждаме, че първоначално планировчикът филтрира и генерира списък от 3 възела, на които може да бъде стартиран (nxs-k8s-s8, nxs-k8s-s9, nxs-k8s-s10). След това изчислява резултати въз основа на няколко параметъра (включително BalancedResourceAllocation, LeastResourceAllocation) за всеки от тези възли, за да определи най-подходящия възел. В крайна сметка подът се планира на възела с най-голям брой точки (тук два възела наведнъж имат еднакъв брой точки 100037, така че се избира случаен - nxs-k8s-s10).
Продукция: ако възел изпълнява подове, за които не са зададени ограничения, тогава за k8s (от гледна точка на потреблението на ресурси) това ще бъде еквивалентно на това, сякаш изобщо няма такива подове на този възел. Следователно, ако условно имате под с лаком процес (например wowza) и за него не са зададени ограничения, тогава може да възникне ситуация, когато този под всъщност изяде всички ресурси на възела, но за k8s този възел се счита за ненатоварен и ще му бъдат присъдени същия брой точки при класиране (именно в точки, оценяващи наличните ресурси) като възел, който няма работещи подове, което в крайна сметка може да доведе до неравномерно разпределение на натоварването между възлите.
Изгонване на под
Както знаете, на всеки под е присвоен един от 3 класа QoS:
гарантирано — се присвоява, когато за всеки контейнер в pod са посочени заявка и лимит за памет и процесор и тези стойности трябва да съвпадат
разрушаващ се — поне един контейнер в групата има заявка и ограничение, с искане < ограничение
най-добри усилия — когато нито един контейнер в групата не е с ограничен ресурс
В същото време, когато даден възел изпита липса на ресурси (диск, памет), kubelet започва да класира и изгонва pods според специфичен алгоритъм, който взема предвид приоритета на pod и неговия QoS клас. Например, ако говорим за RAM, тогава въз основа на класа QoS точките се присъждат съгласно следния принцип:
Тези. със същия приоритет, kubelet първо ще изгони подове с най-добрия QoS клас от възела.
Продукция: ако искате да намалите вероятността желаният под да бъде изгонен от възела в случай на липса на ресурси в него, тогава заедно с приоритета трябва да се погрижите и за задаването на заявката/лимита за него.
Механизъм за хоризонтално автоматично мащабиране на пакети приложения (HPA)
Когато задачата е автоматично да се увеличава и намалява броят на подовете в зависимост от използването на ресурси (система - CPU/RAM или потребител - rps), такъв k8s обект като HPA (Horizontal Pod Autoscaler). Алгоритъмът на което е както следва:
Определят се текущите показания на наблюдавания ресурс (currentMetricValue)
Определят се желаните стойности за ресурса (desiredMetricValue), които за системните ресурси се задават с помощта на заявка
Определя се текущият брой реплики (currentReplicas)
В този случай няма да настъпи мащабиране, когато коефициентът (currentMetricValue / desireMetricValue) е близо до 1 (в този случай можем сами да зададем допустимата грешка; по подразбиране тя е 0.1).
Нека да разгледаме как работи hpa, използвайки примера на приложението за тестване на приложения (описано като разполагане), където е необходимо да промените броя на репликите в зависимост от потреблението на процесора:
Тези. виждаме, че подът на приложението първоначално се стартира в два екземпляра, всеки от които съдържа два контейнера nginx и nginx-exporter, за всеки от които посочен искания за CPU.
Тези. Създадохме hpa, който ще наблюдава теста на приложението за внедряване и ще коригира броя на подовете с приложението въз основа на индикатора за процесора (очакваме, че подът трябва да консумира 30% от CPU, който иска), като броят на репликите е в диапазон от 2-10.
Сега нека да разгледаме механизма на работа на hpa, ако приложим товар към едно от огнищата:
# kubectl top pod
NAME CPU(cores) MEMORY(bytes)
app-test-78559f8f44-pgs58 101m 243Mi
app-test-78559f8f44-cj4jz 4m 240Mi
Общо имаме следното:
Желаната стойност (desiredMetricValue) - според настройките на hpa имаме 30%
Текуща стойност (currentMetricValue) - за изчисление контролерът-мениджър изчислява средната стойност на потреблението на ресурс в %, т.е. условно прави следното:
Получава абсолютни стойности на показателите на pod от сървъра на показателите, т.е. 101м и 4м
Получава абсолютната стойност за желаното потребление на ресурси (за това заявките на всички контейнери се сумират) 60m + 30m = 90m
Изчислява средния процент на потребление на процесора по отношение на заявката, т.е. 53m / 90m * 100% = 59%
Сега имаме всичко необходимо, за да определим дали трябва да променим броя на репликите; за да направим това, изчисляваме коефициента:
ratio = 59% / 30% = 1.96
Тези. броят на репликите трябва да се увеличи ~2 пъти и да възлиза на [2 * 1.96] = 4.
Заключение: Както можете да видите, за да работи този механизъм, необходимо условие е наличието на заявки за всички контейнери в наблюдавания pod.
Механизъм за хоризонтално автоматично мащабиране на възли (Cluster Autoscaler)
За да се неутрализира отрицателното въздействие върху системата по време на скокове на натоварване, не е достатъчно да имате конфигуриран hpa. Например, според настройките в мениджъра на контролера на hpa, той решава, че броят на репликите трябва да се увеличи 2 пъти, но възлите нямат свободни ресурси, за да стартират такъв брой подове (т.е. възелът не може да осигури поискани ресурси към групата заявки) и тези групи превключват в състояние на чакане.
В този случай, ако доставчикът има съответен IaaS/PaaS (например GKE/GCE, AKS, EKS и т.н.), инструмент като Възел Autoscaler. Позволява ви да зададете максималния и минималния брой възли в клъстера и автоматично да коригирате текущия брой възли (чрез извикване на API на доставчика на облак, за да поръчате/премахнете възел), когато има липса на ресурси в клъстера и подовете не могат да бъдат планирани (са в състояние на изчакване).
Заключение: За да можете автоматично да мащабирате възлите, е необходимо да зададете заявки в контейнерите на pod, така че k8s да може правилно да оцени натоварването на възлите и съответно да докладва, че няма ресурси в клъстера за стартиране на следващия pod.
Заключение
Трябва да се отбележи, че задаването на ограничения на ресурсите на контейнера не е изискване за успешното изпълнение на приложението, но все пак е по-добре да го направите поради следните причини:
За по-точна работа на планировчика по отношение на балансирането на натоварването между k8s възлите
За да се намали вероятността от възникване на събитие „изгонване на капсула“.
За работа с хоризонтално автоматично мащабиране на пакети приложения (HPA).
За хоризонтално автоматично мащабиране на възли (Cluster Autoscaling) за облачни доставчици