Як отримати доступ до ресурсів Kubernetes Pod

Як отримати доступ до ресурсів Kubernetes PodThe Reward by Tohad

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

Але пізніше програму потрібно розгорнути в продакшен кластері разом з іншими програмами. Для цього потрібно виділити ресурси для контейнера і переконатися, що їх достатньо для запуску та роботи програми, а в інших запущених програмах не виникне проблем.

Команда Kubernetes aaS від Mail.ru переклала статтю про ресурси контейнерів (CPU & MEM), запити та обмеження ресурсів. Ви дізнаєтесь, які переваги дають ці налаштування і що станеться, якщо їх не встановити.

Обчислювальні ресурси

У нас є два типи ресурсів з наступними одиницями:

  • Центральний процесор (CPU) - ядра;
  • Пам'ять (MEM) – байти.

Ресурси вказують кожному контейнеру. У наступному YAML-файлі Pod ви побачите розділ ресурсів, який містить запитані та граничні ресурси:

  • Запитані ресурси Pod = сума ресурсів усіх контейнерів;
  • Граничні ресурси Pod = сума граничних ресурсів всіх контейнерів.

apiVersion: v1
kind: Pod
metadata:
  name: backend-pod-name
  labels:
    application: backend
spec:
  containers:
    — name: main-container
      image: my-backend
      tag: v1
      ports:
      — containerPort: 8080
      resources:
        requests:
          cpu: 0.2 # REQUESTED CPU: 200m cores
          memory: "1Gi" # REQUESTED MEM: 1Gi
        limits:
          cpu: 1 # MAX CPU USAGE: 1 core
          memory: "1Gi" # MAX MEM USAGE:  1Gi
    — name: other-container
      image: other-app
      tag: v1
      ports:
      — containerPort: 8000
      resources:
        requests:
          cpu: "200m" # REQUESTED CPU: 200m cores
          memory: "0.5Gi" # REQUESTED MEM: 0.5Gi
        limits:
          cpu: 1 # MAX CPU USAGE: 1 core
          memory: "1Gi" # MAX MEM USAGE:  1Gi

Приклад запитаних та граничних ресурсів

Поле resources.requested із специфікації Pod — один із елементів, який використовують для пошуку потрібної ноди. На неї можна запланувати розгортання Pod. Як же шукають потрібну ноду?

Kubernetes складається з кількох компонентів, у тому числі містить головний вузол або майстер-ноду (Kubernetes Control Plane). У майстрі-ноді кілька процесів: kube-apiserver, kube-controller-manager та kube-scheduler.

Процес kube-scheduler відповідає за перегляд новостворених модулів та пошук можливих робочих вузлів, які відповідають усім запитам модулів, у тому числі за кількістю ресурсів, що запитуються. Список вузлів, знайдених kube-scheduler, ранжується. Pod планується на вузлі з найвищими балами.

Як отримати доступ до ресурсів Kubernetes PodКуди буде розміщено фіолетовий Pod?

На картинці видно, що kube-scheduler має запланувати новий фіолетовий Pod. Кластер Kubernetes містить два вузли: A та B. Як можна помітити, kube-scheduler не може запланувати Pod на вузол A – доступні (незапитані) ресурси не відповідають запитам фіолетового Pod. Так, запитаний фіолетовим Pod 1 Гб пам'яті не вміститься на вузлі А, оскільки доступний обсяг пам'яті – 0,5 Гб. Але у вузла достатньо ресурсів. У результаті kube-scheduler вирішує, що призначення фіолетового Pod - вузол B.

Тепер ми знаємо, як запрошені ресурси впливають на вибір ноди для запуску Pod. Але як впливають граничні ресурси?

Граничні ресурси – межа, яку CPU/MEM не може перетинати. Проте ресурс CPU гнучкий, тому контейнери, що досягли граничних значень CPU, не призведуть до завершення роботи Pod. Натомість запуститься тротлінг по CPU. Якщо ж буде досягнуто межі використання MEM, то контейнер буде зупинено через OOM-Killer і перезапущено, якщо це дозволено налаштуванням RestartPolicy.

Запитані та граничні ресурси в деталях

Як отримати доступ до ресурсів Kubernetes PodЗв'язок ресурсів між Docker та Kubernetes

Найкращий спосіб пояснити, як працюють запитані та граничні ресурси — уявити зв'язок між Kubernetes та Docker. На малюнку вище ви можете подивитися, як пов'язані поля Kubernetes та прапори запуску Docker.

Пам'ять: запит та обмеження

containers:
...
 resources:
   requests:
     memory: "0.5Gi"
   limits:
     memory: "1Gi"

Як згадувалося вище, пам'ять вимірюється у байтах. Грунтуючись на документації Kubernetes, ми можемо вказати пам'ять у вигляді числа. Зазвичай воно ціле, наприклад 2678 – тобто 2678 байт. Можна також використовувати суфікси G и Gi, головне пам'ятати, що вони не рівнозначні. Перший – десятковий, а другий – двійковий. Як приклад, згаданий у документації k8s: 128974848, 129e6, 129M, 123Mi - Вони практично еквівалентні.

Параметр Kubernetes limits.memory відповідає прапору --memory з Docker. У випадку з request.memory стрілка для Docker відсутня, оскільки Docker не використовує поле. Ви можете запитати, чи це потрібно взагалі? Так потрібно. Як я вже казав, поле має значення для Kubernetes. На основі інформації з нього kube-scheduler вирішує, на який вузол запланувати Pod.

Що буде, якщо встановити для запиту недостатньо пам'яті?

Якщо контейнер досягне межі запитаної пам'яті, то Pod поміщається в групу Pod, які зупиняються при нестачі пам'яті в ноді.

Що станеться, якщо встановити занадто мале граничне значення пам'яті?

Якщо контейнер перевищує граничне значення пам'яті, він буде завершено через OOM-Killed. І буде перезапущено, якщо це можливо на підставі RestartPolicy, де значення за замовчуванням — Always.

Що буде, якщо не вказати потрібну пам'ять?

Kubernetes візьме граничне значення та встановить його як значення за промовчанням.

Що може статися, якщо не вказати граничну пам'ять?

Контейнер не має обмежень, він може використовувати стільки пам'яті, скільки захоче. Якщо він почне використовувати всю доступну пам'ять ноди, його вб'є OOM. Потім контейнер буде перезапущено, якщо це можливо на основі RestartPolicy.

Що буде, якщо не вказати ліміт пам'яті?

Це найгірший сценарій: планувальник не знає, скільки ресурсів потрібно контейнеру, і це може спричинити серйозні проблеми на ноді. У цьому випадку добре мати обмеження за умовчанням в просторі імен (встановлювані LimitRange). Обмежень за замовчуванням немає — Pod не має обмежень, він може використовувати стільки пам'яті, скільки захоче.

Якщо запитувана пам'ять більше, ніж може запропонувати нода, Pod не буде запланований. Важливо пам'ятати, що Requests.memory - Не мінімальне значення. Це опис обсягу пам'яті, достатнього постійної роботи контейнера.

Зазвичай рекомендують встановлювати те саме значення для request.memory и limit.memory. Завдяки цьому Kubernetes не запланує Pod на вузлі, який має достатньо пам'яті для запуску Pod, але недостатньо для роботи. Майте на увазі: при плануванні Pod Kubernetes враховує тільки requests.memory, а limits.memory не враховує.

CPU: запит та обмеження

containers:
...
 resources:
   requests:
     cpu: 1
   limits:
     cpu: "1200m"

C CPU все трохи складніше. Повертаючись до картинки із взаємозв'язком між Kubernetes та Docker, можна помітити, що request.cpu відповідає --cpu-shares, тоді як limit.cpu відповідає прапору cpus в Docker.

CPU, який запитує Kubernetes, множиться на 1024 – пропорцію циклів CPU. Якщо ви хочете запросити 1 повне ядро, то маєте додати cpu: 1як показано вище.

Запит повного ядра (пропорція = 1024) не означає, що його контейнер отримає. Якщо у вашого хост-комп'ютера тільки одне ядро, і ви використовуєте більше одного контейнера, всі контейнери повинні спільно використовувати доступний CPU між собою. Як це відбувається? Давайте подивимося на картинку.

Як отримати доступ до ресурсів Kubernetes Pod
Запит CPU - система з одним ядром

Уявімо, що у вас є хост-система з одним ядром, на якій запущені контейнери. Мама (Kubernetes) спекла пиріг (CPU) і хоче розділити його між дітьми (контейнерами). Троє дітей хочуть за цілим пирогом (пропорція = 1024), ще одна дитина хоче половину пирога (512). Мама хоче бути справедливою та проводить нескладний розрахунок.

# Сколько пирогов хотят дети?
# 3 ребенка хотят по целому пирогу и еще один хочет половину пирога
cakesNumberKidsWant = (3 * 1) + (1 * 0.5) = 3.5
# Выражение получается так:
3 (ребенка/контейнера) * 1 (целый пирог/полное ядро) + 1 (ребенок/контейнер) * 0.5 (половина пирога/половина ядра)
# Сколько пирогов испечено?
availableCakesNumber = 1
# Сколько пирога (максимально) дети реально могут получить?
newMaxRequest = 1 / 3.5 =~ 28%

Виходячи з розрахунку, троє дітей отримають по 28% ядра, а не по цілому ядру. Четвертій дитині дістанеться 14% від повного ядра, а не половина. Але все буде інакше, якщо у вас мультиядерна система.

Як отримати доступ до ресурсів Kubernetes Pod
Запит CPU — мультиядерна (4) система

На зображенні вище видно, що троє дітей хочуть за цілим пирогом, а одне — половину. Оскільки мама спекла чотири пирога, кожен із її дітей отримає стільки, скільки захоче. У багатоядерній системі процесорні ресурси розподілені за всіма доступними ядрами процесора. Якщо контейнер обмежений менш ніж одним повним ядром CPU, то все одно може використовувати його на 100%.

Наведені вище розрахунки спрощені розуміння того, як CPU розподіляється між контейнерами. Звичайно, крім самих контейнерів, є інші процеси, які також використовують ресурси CPU. Коли процеси в одному контейнері простоюють, інші можуть використовувати його ресурс. CPU: "200m" відповідає CPU: 0,2що означає приблизно 20% одного ядра.

Тепер давайте поговоримо про limit.cpu. CPU, який обмежує Kubernetes, множиться на 100. Результат – кількість часу, який контейнер може використовувати кожні 100 мкс (cpu-period).

limit.cpu відповідає прапору Docker --cpus. Це нова комбінація старих --cpu-period и --cpu-quota. Встановлюючи його, ми вказуємо, скільки доступних ресурсів CPU контейнер може максимально використовувати доти, доки не почнеться тротлінг:

  • процесори - Комбінація cpu-period и cpu-quota. cpus = 1.5 еквівалентно установці cpu-period = 100000 и cpu-quota = 150000;
  • cpu-period - Період планувальника CPU CFSза замовчуванням 100 мікросекунд;
  • cpu-quota - кількість мікросекунд всередині cpu-period, Яким обмежений контейнер.

Що буде, якщо встановити недостатньо запитаного CPU?

Якщо контейнеру потрібно більше, ніж встановлено, він вкраде CPU в інших процесів.

Що станеться, якщо встановити недостатній ліміт CPU?

Оскільки ресурс CPU регульований, то ввімкнеться тротлінг.

Що станеться, якщо не вказати запит CPU?

Як і у випадку з пам'яттю, значення запиту дорівнює ліміту.

Що буде, якщо не вказати ліміт CPU?

Контейнер використовуватиме стільки CPU, скільки йому необхідно. Якщо в просторі імен визначено політику CPU за замовчуванням (LimitRange), цей ліміт використовують і для контейнера.

Що станеться, якщо не вказати ні запит, ні ліміт CPU?

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

Пам'ятайте: якщо ви запитаєте більше CPU, ніж можуть надати ноди, то Pod не буде заплановано. Requests.cpu - не мінімальне значення, а значення, достатнє для запуску Pod та роботи без збоїв. Якщо програма не виконує складних обчислень, найкращий варіант — встановити request.cpu <= 1 і запустити стільки реплік, скільки потрібно.

Ідеальна кількість запитаних ресурсів чи ліміту ресурсів

Ми дізналися про обмеження обчислювальних ресурсів. Тепер настав час відповісти на запитання: «Скільки ресурсів потрібно моєму Pod для роботи програми без проблем? Яка кількість ідеальна?».

На жаль, на ці питання немає однозначних відповідей. Якщо ви не знаєте, як працює ваш додаток, скільки CPU або пам'яті йому потрібно, найкращий варіант - дати програмі багато пам'яті та CPU, а потім запустити тести продуктивності.

Крім тестів продуктивності протягом тижня спостерігайте за поведінкою програми в моніторингу. Якщо з графіків випливає, що ваш додаток споживає менше ресурсів, ніж ви запитували, можна зменшити кількість запитаного CPU або пам'яті.

Як приклад подивіться цей дашборд Grafana. Він відображає різницю між запрошеними ресурсами чи лімітом ресурсів та поточним використанням ресурсів.

Висновок

Запит та обмеження ресурсів допомагають підтримувати працездатність кластера Kubernetes. Правильна конфігурація лімітів зводить до мінімуму витрати та постійно підтримує додатки у робочому стані.

Якщо коротко, то треба пам'ятати про кілька моментів:

  1. Затребувані ресурси - конфігурація, яка враховується під час запуску (коли Kubernetes планує розміщення програми). Навпаки, обмеження ресурсів важливе під час роботи — коли програма вже запущена на вузлі.
  2. Порівняно з пам'яттю, CPU - регульований ресурс. У разі нестачі CPU ваш Pod не завершить роботу, увімкнеться механізм тротлінгу.
  3. Затребувані ресурси та ліміт ресурсів – це не мінімальні та максимальні значення! Визначаючи запитані ресурси, ви гарантуєте, що програма працюватиме без проблем.
  4. Хороша практика - встановлювати запит пам'яті, що дорівнює ліміту пам'яті.
  5. Добре встановлювати запитаний CPU <=1, якщо програма не виконує складних обчислень.
  6. Якщо ви запитаєте більше ресурсів, ніж на ноді, то Pod ніколи не буде запланований на цю ноду.
  7. Використовуйте навантажувальне тестування та моніторинг, щоб визначити правильну кількість запитаних ресурсів/лімітів ресурсів.

Сподіваюся, ця стаття допоможе зрозуміти основну концепцію обмеження ресурсів. І ви зможете застосувати ці знання у своїй роботі.

Успіхів!

Що ще почитати:

  1. Спостережуваність SRE: простору імен та структура метрик.
  2. 90+ корисних інструментів для Kubernetes: розгортання, керування, моніторинг, безпека та не тільки.
  3. Наш канал Навколо Kubernetes у Телеграмі.

Джерело: habr.com

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