Автомасштабування та керування ресурсами в Kubernetes (огляд та відео доповіді)

27 квітня на конференції Страйк-2019У рамках секції «DevOps» прозвучала доповідь «Автомасштабування та управління ресурсами в Kubernetes». У ньому розповідається про те, як за допомогою K8s забезпечити високу доступність додатків та гарантувати їх максимальну продуктивність.

Автомасштабування та керування ресурсами в Kubernetes (огляд та відео доповіді)

За традицією раді уявити відео з доповіддю (44 хвилини, набагато інформативніші за статтю) і основне вичавлення в текстовому вигляді. Поїхали!

Розберемо тему доповіді за словами та почнемо з кінця.

Кубернетес

Нехай у нас на хості є Docker-контейнери. Навіщо? Для забезпечення повторюваності та ізоляції, які у свою чергу дозволяють зробити просто і добре деплою, CI/CD. Таких машин із контейнерами у нас багато.

Що в цьому випадку дає Kubernetes?

  1. Ми перестаємо думати про ці машини і починаємо працювати з «хмарою», кластером із контейнерів або pod'ів (груп із контейнерів).
  2. Більше того, ми не думаємо навіть про окремі pod'и, а керуємо ще большими групами. Такі високорівневі примітиви дозволяють нам сказати, що є шаблон для запуску певного робочого навантаження, а ось потрібна кількість екземплярів для її запуску. Якщо ми згодом поміняємо шаблон, поміняються і всі екземпляри.
  3. За допомогою декларативного API ми замість виконання послідовності конкретних команд описуємо «пристрій світу» (YAML), який створюється Kubernetes'ом. І знову: при змінах опису змінюватиметься і його реальне відображення.

Управління ресурсами

центральний процесор

Нехай ми запускаємо на сервері nginx, php-fpm та mysql. У цих служб насправді буде ще більше працюючих процесів, кожен із яких потребує обчислювальних ресурсів:

Автомасштабування та керування ресурсами в Kubernetes (огляд та відео доповіді)
(числа на слайді - "папуги", абстрактна потреба кожного процесу в обчислювальних потужностях)

Щоб з цим можна було зручно працювати, логічно об'єднати процеси за групами (наприклад, усі процеси nginx в одну групу nginx). Простий і очевидний спосіб зробити це помістити кожну групу в контейнер:

Автомасштабування та керування ресурсами в Kubernetes (огляд та відео доповіді)

Щоб продовжити, необхідно згадати, що таке контейнер (в Linux). Їхня поява стала можливою завдяки трьом ключовим можливостям у ядрі, реалізованим уже досить давно: можливості, просторів імен и групи. А подальшому розвитку сприяли інші технології (включаючи зручні оболонки типу Docker):

Автомасштабування та керування ресурсами в Kubernetes (огляд та відео доповіді)

У контексті доповіді нас цікавить лише групитому що саме контрольні групи — та частина функціональних можливостей контейнерів (Docker'а тощо), що реалізує управління ресурсами. Процеси, об'єднані в групи, як ми того хотіли, це і є контрольні групи.

Повернемося до потреб у CPU у цих процесів, а тепер уже – у груп процесів:

Автомасштабування та керування ресурсами в Kubernetes (огляд та відео доповіді)
(повторюся, що всі числа - абстрактне вираження потреби в ресурсах)

При цьому у самого CPU є кінцевий ресурс (У прикладі це 1000), якого всім може бракувати (сума потреб всіх груп - 150 850 460 = 1460). Що відбуватиметься у такому разі?

Ядро починає роздавати ресурси і робить це чесно, видаючи однакову кількість ресурсів кожній групі. Але в першому випадку їх більше потрібного (333> 150), тому надлишок (333-150 = 183) залишається в резерві, який також розподіляється між двома іншими контейнерами:

Автомасштабування та керування ресурсами в Kubernetes (огляд та відео доповіді)

У результаті: першому контейнеру вистачило ресурсів, другому сильно не вистачило, третьому трохи не вистачило. Такий результат дій «чесного» планувальника в Linux - CFS. Його роботу можна регулювати за допомогою призначення ваги кожному із контейнерів. Наприклад, так:

Автомасштабування та керування ресурсами в Kubernetes (огляд та відео доповіді)

Подивимося на випадок нестачі ресурсів другого контейнера (php-fpm). Усі ресурси контейнера розподіляються між процесами порівну. В результаті, master-процес працює добре, а всі worker'и гальмують, отримавши менше половини від потрібного:

Автомасштабування та керування ресурсами в Kubernetes (огляд та відео доповіді)

Так працює планувальник CFS. Ваги, які ми призначаємо контейнерам, надалі будемо називати request'ами. Чому саме так – див. далі.

Погляньмо на всю ситуацію з іншого боку. Як відомо, всі дороги ведуть до Риму, а у випадку комп'ютера — до CPU. CPU один, завдань багато - потрібний світлофор. Найпростіший спосіб управління ресурсами - «світлофорний»: видали одному процесу фіксований час доступу до CPU, потім наступного і т.п.

Автомасштабування та керування ресурсами в Kubernetes (огляд та відео доповіді)

Цей підхід називається жорстким квотуванням (Hard limiting). Запам'ятаємо його просто як ліміти. Однак, якщо роздати всім контейнерами ліміти, виникає проблема: mysql їхав дорогою і в якийсь момент його потреба в CPU закінчилася, але всі інші процеси змушені чекати, поки CPU простоює.

Автомасштабування та керування ресурсами в Kubernetes (огляд та відео доповіді)

Повернемося до ядра Linux та його взаємодії з CPU - загальна картина виходить наступною:

Автомасштабування та керування ресурсами в Kubernetes (огляд та відео доповіді)

У cgroup є два налаштування — по суті, це дві прості «крутилки», що дозволяють визначати:

  1. вага для контейнера (request'и) - це акцій;
  2. відсоток від загального часу CPU для роботи над завданнями контейнера (ліміти) – це частка.

У чому міряти CPU?

Є різні шляхи:

  1. Що таке папуги, ніхто не знає - потрібно щоразу домовлятися.
  2. Відсотки Зрозуміліше, але відносні: 50% від сервера з 4 ядрами і з 20 ядрами - різні речі.
  3. Можна використати вже згадані ваги, які знає Linux, але вони також відносні.
  4. Найадекватніший варіант — міряти обчислювальні ресурси секундах. Тобто. в секундах процесорного часу стосовно секунд реального часу: видали 1 секунду процесорного часу в 1 реальну секунду - це одне ядро ​​CPU цілком.

Щоб стало говорити ще простіше, вимірювати стали прямо в ядрах, маючи на увазі під ними той самий час CPU щодо реального. Оскільки Linux розуміє ваги, а не такий процесорний час/ядра, знадобився механізм перекладу з одного до іншого.

Розглянемо простий приклад із сервером з 3 ядрами CPU, де трьома pod'ам будуть обрані такі ваги (500, 1000 і 1500), які легко конвертуються у відповідні частини виділених ним ядер (0,5, 1 і 1,5).

Автомасштабування та керування ресурсами в Kubernetes (огляд та відео доповіді)

Якщо взяти другий сервер, де ядер буде вдвічі більше (6), і розмістити там ті самі pod'и, розподіл ядер легко вважати простим множенням на 2 (1, 2 та 3 відповідно). Але важливий момент відбувається тоді, коли на цьому сервері з'явиться четвертий pod, вага якого нехай для зручності буде 3000. Він забирає собі частину ресурсів CPU (половину ядер), а в інших pod'ів вони перераховуються (зменшаться вдвічі):

Автомасштабування та керування ресурсами в Kubernetes (огляд та відео доповіді)

Kubernetes та ресурси CPU

У Kubernetes ресурси CPU прийнято вимірювати в міліядрах, тобто. як базова вага береться 0,001 ядра. (Те саме в термінології Linux/cgroups називають CPU share, хоча, якщо говорити точніше, то 1000 міліядер = 1024 CPU shares.) K8s стежить за тим, щоб не розміщувати на сервері більше pod'ів, ніж є ресурсів CPU для суми ваги всіх pod'ів.

Як це відбувається? При додаванні сервера в кластер Kubernetes повідомляється, скільки у нього є ядер CPU. А при створенні нового pod'а планувальник Kubernetes знає, скільки ядер знадобиться цьому pod'у. Таким чином, pod буде визначено на сервер, де ядер достатньо.

Що ж станеться, якщо НЕ вказано request (тобто у pod'а не визначено кількість потрібних йому ядер)? Давайте розберемося, як Kubernetes взагалі рахує ресурси.

У pod'а можна вказати і request'и (планувальник CFS), і ліміти (пам'ятаєте світлофор?):

  • Якщо вони вказані рівні, то pod'у призначається QoS-клас гарантований. Така кількість завжди доступних йому ядер гарантується.
  • Якщо request менше ліміту - QoS-клас burstable. Тобто. ми очікуємо, що pod, наприклад, завжди використовує 1 ядро, проте це значення не є для нього обмеженням: іноді ПОД може використовувати і більше (коли на сервері є вільні ресурси для цього).
  • Є ще QoS-клас найкращих зусиль - До нього відносяться ті самі pod'и, для яких не вказано request. Ресурси їм видаються в останню чергу.

Пам'ять

З пам'яттю ситуація подібна, але трохи інша — все-таки природа цих ресурсів різна. Загалом аналогія така:

Автомасштабування та керування ресурсами в Kubernetes (огляд та відео доповіді)

Давайте подивимося, як у пам'яті реалізуються request'и. Нехай pod'и живуть на сервері, змінюючи споживану пам'ять, поки один з них не стане таким великим, що пам'ять закінчиться. У цьому випадку з'являється OOM killer і вбиває найбільший процес:

Автомасштабування та керування ресурсами в Kubernetes (огляд та відео доповіді)

Нас це не завжди влаштовує, тому є можливість регулювати, які процеси для нас є важливими і не повинні вбиватися. Для цього використовується параметр oom_score_adj.

Повернемось до QoS-класів CPU і проведемо аналогію зі значеннями oom_score_adj, що визначають для pod'ів пріоритети споживання пам'яті:

  • Найнижче значення oom_score_adj у pod'а -998 - означає, що такий pod повинен вбиватися в останню чергу, це гарантований.
  • Найвище – 1000 – це найкращих зусиль, такі pod'и вбиваються раніше за всіх.
  • Для розрахунку інших значень (burstable) є формула, суть якої зводиться до того що чим більше pod запросив ресурсів, тим менше шансів, що його уб'ють.

Автомасштабування та керування ресурсами в Kubernetes (огляд та відео доповіді)

Друга «крутилка» limit_in_bytes - Для лімітів. З нею все простіше: ми просто призначаємо максимальну кількість пам'яті, що видається, і тут (на відміну від CPU) немає питання, в чому її (пам'ять) вимірювати.

Разом

Кожному pod'у у Kubernetes задаються requests и limits — обидва параметри для CPU та пам'яті:

  1. на підставі requests працює планувальник Kubernetes, який розподіляє pod'и по серверах;
  2. на підставі всіх параметрів визначається QoS-клас pod'а;
  3. на підставі CPU requests розраховуються відносні ваги;
  4. на підставі CPU requests налаштовується CFS-планувальник;
  5. на підставі memory requests налаштовується OOM killer;
  6. на підставі CPU limits налаштовується світлофор;
  7. на підставі memory limits налаштовується ліміт на cgroup'у.

Автомасштабування та керування ресурсами в Kubernetes (огляд та відео доповіді)

Загалом ця картинка відповідає на всі питання, як відбувається основна частина управління ресурсами Kubernetes.

Автомасштабування

K8s cluster-autoscaler

Уявімо, що весь кластер вже зайнятий і має бути створений новий pod. Поки під не може з'явитися, він висить у статусі В очікуванні. Щоб він все-таки з'явився, ми можемо підключити новий сервер до кластера або поставити cluster-autoscaler, який зробить це за нас: замовить віртуальну машину у хмарного провайдера (запитом по API) і підключить її до кластера, після чого pod буде додано .

Автомасштабування та керування ресурсами в Kubernetes (огляд та відео доповіді)

Це і є автомасштабування кластера Kubernetes, яке чудово (на наш досвід) працює. Однак, як і скрізь, тут не без нюансів.

Поки ми збільшували розміри кластера, все було добре, але що відбувається, коли кластер став звільнятися? Проблема в тому, що мігрувати pod'и (для звільнення хостів) дуже технічно складно та дорого за ресурсами. У Kubernetes працює зовсім інший підхід.

Розглянемо кластер із трьох серверів, у якому є Deployment. У нього 3 pod'ів: зараз це по 6 на кожний сервер. Ми з якоїсь причини захотіли вимкнути один із серверів. Для цього скористаємося командою kubectl drain, яка:

  • заборонить надсилати нові pod'и на цей сервер;
  • видалить існуючі pod'и на сервері.

Оскільки Kubernetes стежить за підтримкою числа pod'ів (6), він просто перестворить їх на інших вузлах, але не на тому, що відключається, оскільки він вже позначений як недоступний для розміщення нових pod'ів. Це основна механіка для Kubernetes.

Автомасштабування та керування ресурсами в Kubernetes (огляд та відео доповіді)

Однак і тут є нюанс. В аналогічній ситуації для StatefulSet (замість Deployment) дії будуть іншими. Тепер у нас уже stateful-додаток - наприклад, три pod'а з MongoDB, у одного з яких виникла якась проблема (дані зіпсувалися або інша помилка, що не дозволяє коректно запуститися pod'у). І ми знову вирішуємо вимкнути один сервер. Що станеться?

Автомасштабування та керування ресурсами в Kubernetes (огляд та відео доповіді)

MongoDB міг би померти, оскільки йому потрібний кворум: для кластера з трьох інсталяцій хоча б дві повинні функціонувати. Однак цього не відбувається - завдяки PodDisruptionBudget. Цей параметр визначити мінімально необхідну кількість працюючих pod'ів. Знаючи, що один із pod'ів з MongoDB вже не працює, і побачивши, що для MongoDB у PodDisruptionBudget встановлено minAvailable: 2, Kubernetes не дасть видалити під.

Підсумок: для того, щоб коректно працювало переміщення (а насправді - перестворення) pod'ів при звільненні кластера, необхідно настроювати PodDisruptionBudget.

Горизонтальне масштабування

Розглянемо іншу ситуацію. Є програма, запущена як Deployment в Kubernetes. На його pod'и (наприклад, їх три) приходить користувальницький трафік, а ми в них заміряємо якийсь показник (скажімо, навантаження на CPU). Коли навантаження зростає, ми фіксуємо це за графіком і збільшуємо кількість pod'ів для розподілу запитів.

Сьогодні в Kubernetes це не потрібно робити вручну: налаштовується автоматичне збільшення/зменшення кількості pod'ів залежно від значень показників навантаження, що заміряються.

Автомасштабування та керування ресурсами в Kubernetes (огляд та відео доповіді)

Головні питання тут у тому, що саме вимірювати и як інтерпретувати отримані значення (для ухвалення рішення про зміну числа pod'ів). Вимірювати можна дуже багато:

Автомасштабування та керування ресурсами в Kubernetes (огляд та відео доповіді)

Як це робити технічно — збирати метрики тощо. — я докладно розповідав у доповіді про Моніторинг та Kubernetes. А основна порада для вибору оптимальних параметрів експериментуйте!

є метод USE (Utilization Saturation and Errors), сенс якого у наступному. На підставі чого є сенс масштабувати, наприклад, php-fpm? На підставі того, що worker'и закінчуються, це утилізація. А якщо worker'и закінчилися і нові підключення не приймаються, то це вже насичення. Обидва ці параметри необхідно вимірювати, а залежно від значень і масштабування.

Замість висновку

Доповідь має продовження: про вертикальне масштабування і про те, як правильно підбирати ресурси. Про це я розповім у майбутніх роликах на нашому YouTube - Підписуйтесь, щоб не пропустити!

Відео та слайди

Відео з виступу (44 хвилини):

Презентація доповіді:

PS

Інші доповіді про Kubernetes у нашому блозі:

Джерело: habr.com

Додати коментар або відгук