Ограничения на процесора и агресивно дроселиране в Kubernetes

Забележка. превод: Тази предупредителна история на Omio, европейският агрегатор за пътувания, отвежда читателите от основната теория до очарователните практически тънкости на конфигурацията на Kubernetes. Запознаването с такива случаи помага не само за разширяване на кръгозора, но и за предотвратяване на нетривиални проблеми.

Ограничения на процесора и агресивно дроселиране в Kubernetes

Случвало ли ви се е да се сблъскате с факта, че приложението „заседна“ на място, спря да отговаря на заявки за здравни проверки и не можете да разберете причината за това поведение? Едно възможно обяснение е свързано с ограничението на квотата за ресурсите на процесора. Ще бъде обсъдено в тази статия.

TL; DR:
Силно препоръчваме да деактивирате ограниченията на процесора в Kubernetes (или да деактивирате квотите на CFS в Kubelet), ако използвате версия на ядрото на Linux с грешка в квотата на CFS. В основата е на разположение сериозни и всеизвестен грешка, която води до прекомерно ограничаване и закъснения
.

В Омио цялата инфраструктура се управлява от Kubernetes. Всички наши работни натоварвания със състояние и без състояние работят изключително на Kubernetes (ние използваме Google Kubernetes Engine). През последните шест месеца започнахме да наблюдаваме случайни забавяния. Приложенията замръзват или спират да отговарят на проверки на здравето, губят мрежова връзка и т.н. Дълго време това поведение ни объркваше и накрая решихме да се заемем сериозно с проблема.

Резюме на статията:

  • Няколко думи за контейнерите и Kubernetes;
  • Как се изпълняват заявките и ограниченията на процесора;
  • Как работи CPU ограничението в многоядрени среди;
  • Как да проследим CPU throttling;
  • Решаване на проблеми и подробности.

Няколко думи за контейнерите и Kubernetes

Kubernetes по същество е модерният стандарт в света на инфраструктурата. Основната му задача е оркестрацията на контейнера.

Контейнери

В миналото трябваше да създаваме артефакти като Java JAR/WAR, Python Eggs или изпълними файлове, които да работят на сървъри. Въпреки това, за да работят, трябваше да свършите допълнителна работа: да инсталирате средата за изпълнение (Java/Python), да поставите необходимите файлове на правилните места, да осигурите съвместимост с определена версия на операционната система и т.н. С други думи, трябваше да обърнете голямо внимание на управлението на конфигурацията (което често предизвикваше спорове между разработчиците и системните администратори).

Контейнерите промениха всичко. Сега изображението на контейнера е артефактът. Може да бъде представен като вид разширен изпълним файл, съдържащ не само програмата, но и пълна среда за изпълнение (Java / Python / ...), както и необходимите файлове / пакети, предварително инсталирани и готови за изпълнение. Контейнерите могат да се разгръщат и изпълняват на различни сървъри без никакви допълнителни стъпки.

В допълнение, контейнерите работят в собствена пясъчна среда. Те имат собствен виртуален мрежов адаптер, собствена файлова система с ограничен достъп, собствена йерархия на процесите, собствени ограничения върху процесора и паметта и т.н. Всичко това се реализира благодарение на специална подсистема на ядрото на Linux - пространства от имена (namespaces).

Kubernetes

Както споменахме по-рано, Kubernetes е оркестратор на контейнери. Работи по следния начин: давате му набор от машини и след това казвате: „Хей, Kubernetes, стартирай десет екземпляра на моя контейнер с 2 процесора и 3 GB памет всеки и ги поддържай работещи!“. Kubernetes ще се погрижи за останалото. Той ще намери свободни капацитети, ще стартира контейнери и ще ги рестартира, ако е необходимо, ще пусне актуализация при смяна на версии и т.н. По същество Kubernetes ви позволява да се абстрахирате от хардуера и прави цялото разнообразие от системи подходящи за внедряване и стартиране на приложения.

Ограничения на процесора и агресивно дроселиране в Kubernetes
Kubernetes от гледна точка на обикновен лаик

Какво представляват заявките и ограниченията в Kubernetes

Добре, разбрахме контейнерите и Kubernetes. Знаем също, че множество контейнери могат да се намират на една и съща машина.

Можете да направите аналогия с общински апартамент. Просторно помещение (автомобили/възли) се взема и отдава под наем на няколко наематели (контейнери). Kubernetes действа като брокер. Възниква въпросът как да предпазим наемателите от конфликти помежду си? Ами ако някой от тях, да речем, реши да вземе банята за половин ден?

Тук влизат в действие заявките и ограниченията. процесор Поискайте необходими само за целите на планирането. Това е нещо като "списък с желания" на контейнера и се използва за избор на най-подходящия възел. В същото време процесорът Граница може да се сравни с договор за наем - веднага щом изберем възел за контейнера, това не мога надхвърлят границите. И тук идва проблемът...

Как заявките и ограниченията се прилагат в Kubernetes

Kubernetes използва механизма за дроселиране (прескачане на часовника), вграден в ядрото, за прилагане на ограничения на процесора. Ако приложението надхвърли ограничението, дроселирането е активирано (т.е. то получава по-малко процесорни цикли). Заявките и ограниченията на паметта са организирани по различен начин, така че да се забелязват по-лесно. За да направите това, достатъчно е да проверите състоянието на последното рестартиране на модула: дали е „OOMKilled“. Дроселирането на процесора не е толкова лесно, тъй като K8s прави достъпни показатели само според употребата, а не чрез cgroups.

Заявка за процесор

Ограничения на процесора и агресивно дроселиране в Kubernetes
Как се изпълнява заявката на процесора

За по-голяма простота, нека разгледаме процеса на примера на машина с 4-ядрен процесор.

K8s използва механизма cgroups за управление на разпределението на ресурси (памет и процесор). За него е наличен йерархичен модел: детето наследява границите на родителската група. Подробностите за разпространението се съхраняват във виртуалната файлова система (/sys/fs/cgroup). В случай на процесор това /sys/fs/cgroup/cpu,cpuacct/*.

K8s използва файл cpu.share за разпределяне на ресурсите на процесора. В нашия случай основната cgroup получава 4096 споделяния на CPU ресурси - 100% от наличната мощност на процесора (1 ядро ​​= 1024; това е фиксирана стойност). Основната група разпределя ресурсите пропорционално в зависимост от дяловете на наследниците, посочени в cpu.share, а те от своя страна правят същото с техните потомци и т.н. В типичен Kubernetes хост, основната cgroup има три деца: system.slice, user.slice и kubepods. Първите две подгрупи се използват за разпределяне на ресурси между критични системни натоварвания и потребителски програми извън K8s. последно - kubepods - създаден от Kubernetes за разпределяне на ресурси между подс.

Диаграмата по-горе показва, че първата и втората подгрупа са получили 1024 споделя, докато подгрупата kuberpod е разпределена 4096 акции. Как е възможно това: в крайна сметка основната група има достъп само до 4096 дялове, а сборът от дяловете на нейните потомци значително надвишава този брой (6144)? Въпросът е, че стойността има логическо значение, така че Linux Scheduler (CFS) я използва, за да разпредели пропорционално ресурсите на процесора. В нашия случай първите две групи получават 680 реални акции (16,6% от 4096) и kubepod получава останалите 2736 акции. В случай на прекъсване, първите две групи няма да използват разпределените ресурси.

За щастие планировчикът има механизъм за избягване на загуба на неизползвани ресурси на процесора. Той прехвърля „неактивен“ капацитет към глобалния пул, от който се разпределя към групи, които се нуждаят от допълнителна мощност на процесора (прехвърлянето се извършва на партиди, за да се избегнат загуби от закръгляване). Същият метод се прилага за всички потомци на потомци.

Този механизъм осигурява справедливо разпределение на мощността на процесора и гарантира, че никой процес не "краде" ресурси от други.

Ограничение на процесора

Въпреки че конфигурациите на ограниченията и заявките в K8s изглеждат подобни, тяхното изпълнение е фундаментално различно: най-подвеждащо и най-малко документираната част.

K8s цикли Квотен механизъм на CFS за налагане на ограничения. Техните настройки са посочени във файловете cfs_period_us и cfs_quota_us в директорията cgroup (има и файл cpu.share).

За разлика от cpu.share, квотата се базира на период от време, а не на наличната мощност на процесора. cfs_period_us определя продължителността на периода (епохата) - винаги е 100000 100 µs (8 ms). В KXNUMXs има опция за промяна на тази стойност, но засега тя е достъпна само в алфа версия. Планировчикът използва епохата, за да рестартира използваните квоти. втори файл, cfs_quota_us, указва наличното време (квота) във всяка епоха. Обърнете внимание, че също е посочено в микросекунди. Квотата може да надвишава дължината на една епоха; с други думи, може да е по-голямо от 100 ms.

Нека да разгледаме два сценария на 16-ядрени машини (най-често срещаният тип машини, които имаме в Omio):

Ограничения на процесора и агресивно дроселиране в Kubernetes
Сценарий 1: 2 нишки и ограничение от 200ms. Без дроселиране

Ограничения на процесора и агресивно дроселиране в Kubernetes
Сценарий 2: 10 нишки и ограничение от 200ms. Дроселирането започва след 20 ms, достъпът до ресурсите на процесора се възобновява след още 80 ms

Да приемем, че сте задали ограничението на процесора на 2 ядки; Kubernetes ще преведе тази стойност в 200ms. Това означава, че контейнерът може да използва максимум 200 ms процесорно време без дроселиране.

И тук започва забавлението. Както бе споменато по-горе, наличната квота е 200 ms. Ако имате паралелна работа десет нишки на 12-ядрена машина (вижте илюстрацията за сценарий 2), докато всички останали подове са неактивни, квотата ще бъде изчерпана само за 20ms (защото 10 * 20ms = 200ms) и всички нишки на този pod ще висят » (дросел) за следващите 80 ms. Ситуацията се утежнява от вече споменатото грешка в планировчика, поради което се получава прекомерно дроселиране и контейнерът дори не може да изработи съществуващата квота.

Как да оценим дроселирането в контейнерите?

Просто влезте в групата и направете cat /sys/fs/cgroup/cpu/cpu.stat.

  • nr_periods е общият брой периоди на планировчика;
  • nr_throttled — броят на дроселираните периоди в състава nr_periods;
  • throttled_time е кумулативното дроселирано време в наносекунди.

Ограничения на процесора и агресивно дроселиране в Kubernetes

Какво всъщност се случва?

В резултат на това получаваме високо дроселиране във всички приложения. Понякога той е вътре един път и половина по-силен от очакваното!

Това води до различни грешки - неуспешни проверки на готовността, замръзване на контейнера, прекъсване на мрежовата връзка, изчакване в сервизните повиквания. В крайна сметка това се изразява в повишена латентност и увеличен процент грешки.

Решение и последствия

Тук всичко е просто. Изоставихме ограниченията на процесора и започнахме да актуализираме ядрото на ОС в клъстери до най-новата версия, в която грешката беше коригирана. Броят на грешките (HTTP 5xx) в нашите услуги веднага спадна значително:

HTTP 5xx грешки

Ограничения на процесора и агресивно дроселиране в Kubernetes
HTTP 5xx грешки за една критична услуга

p95 време за реакция

Ограничения на процесора и агресивно дроселиране в Kubernetes
Забавяне на критична заявка за услуга, 95-ти процентил

Оперативни разходи

Ограничения на процесора и агресивно дроселиране в Kubernetes
Брой прекарани часове за екземпляри

Каква е уловката?

Както е посочено в началото на статията:

Можете да направите аналогия с общински апартамент ... Kubernetes действа като брокер. Но как да предпазим наемателите от конфликти помежду си? Ами ако някой от тях, да речем, реши да вземе банята за половин ден?

Тук е уловката. Един невнимателен контейнер може да погълне всички налични ресурси на процесора на машината. Ако имате интелигентен стек от приложения (напр. JVM, Go, Node VM правилно конфигуриран), това не е проблем: можете да работите в такива условия дълго време. Но ако приложенията са лошо оптимизирани или изобщо не са оптимизирани (FROM java:latest), ситуацията може да излезе извън контрол. В Omio имаме автоматизирани базови Docker файлове с адекватни настройки по подразбиране за стека на основния език, така че този проблем не съществуваше.

Препоръчваме да наблюдавате показателите ИЗПОЛЗВАЙТЕ (използване, насищане и грешки), закъснения на API и проценти на грешки. Уверете се, че резултатите отговарят на вашите очаквания.

Позоваването

Това е нашата история. Следните материали значително помогнаха да се разбере какво се случва:

Доклади за грешки в Kubernetes:

Срещали ли сте подобни проблеми във вашата практика или имате опит, свързан с дроселиране в контейнерни производствени среди? Споделете своята история в коментарите!

PS от преводача

Прочетете също в нашия блог:

Източник: www.habr.com

Добавяне на нов коментар