Перейти до вмісту

Модуль 2.5: Керування ресурсами

Складність: [СЕРЕДНЯ] — критично для робочих навантажень у продакшені

Час на проходження: 40-50 хвилин

Передумови: Модуль 2.1 (Поди), Модуль 2.2 (Деплойменти)


Що ви зможете зробити

Розділ «Що ви зможете зробити»

Після цього модуля ви зможете:

  • Налаштувати запити (requests) та ліміти (limits) ресурсів CPU й пам’яті, а потім пояснити, як ці значення впливають на планування та контроль під час виконання.
  • Діагностувати контейнери зі станом OOMKilled, тротлінг CPU, виселення через тиск на ресурси та поди у стані Pending, спричинені нестачею доступних для виділення ресурсів.
  • Впровадити керування ресурсами на рівні простору імен за допомогою стандартних значень LimitRange, обмежень ResourceQuota та перевірок, які доводять, що ці засоби контролю працюють.
  • Спроєктувати стратегію ресурсів, що збалансовує надійність застосунку, утилізацію кластера, толерантність до сплесків навантаження та можливості зміни розміру в Kubernetes 1.35+.

Чому цей модуль важливий

Розділ «Чому цей модуль важливий»

Гіпотетичний сценарій: команда запускає новий API у спільний кластер Kubernetes без запитів ресурсів і без квоти на рівні простору імен. Поди швидко стартують у тихому тестовому просторі імен, тож маніфести виглядають безпечними, але впродовж першого завантаженого ранку контейнери розростаються, доки вузол не входить у стан тиску на пам’ять. Інші робочі навантаження, які таки зарезервували пам’ять, тепер змагаються з подами класу BestEffort, kubelet починає виселяти кандидатів із нижчим пріоритетом, і інцидент здається випадковим, бо застосунок, який падає, — не єдиний застосунок, що припустився первісної помилки.

Керування ресурсами перетворює такого роду несподіванку на набір явних обіцянок. Запит (request) повідомляє планувальнику, скільки потужності потрібно поду, перш ніж його буде розміщено. Ліміт (limit) повідомляє середовищу виконання контейнерів, де примусово застосувати межу після того, як под уже запущено. LimitRange надає простору імен розумні стандартні значення та запобіжники, тоді як ResourceQuota обмежує загальний обсяг, який може спожити команда. Суть не в тому, щоб запам’ятати поля YAML; суть у тому, щоб спроєктувати контракти ресурсів, які роблять планування, збої та відповідальність передбачуваними.

Іспит CKA перевіряє це, бо та сама механіка з’являється під час сортування інцидентів у продакшені. Под у стані Pending може бути взагалі не зламаним; він може просто просити більше CPU чи пам’яті, ніж здатен надати будь-який вузол після врахування наявних запитів. Под, що перезапускається, може не мати винятку в застосунку; він може перетинати ліміт пам’яті cgroup і отримувати SIGKILL від ядра. Повільний под може бути не нездоровим; він може стикатися з тротлінгом CPU, бо низький ліміт не дає виконувати короткі сплески. У цьому модулі ви пов’яжете ці симптоми з об’єктами Kubernetes, які їх породили, а потім попрактикуєте команди, що доводять діагноз.

Аналогія з готельним номером усе ще корисна, якщо пам’ятати про її обмеження. Вузол схожий на готель, запити — це бронювання, а ліміти — правила заселення. Планувальник призначає номери на основі бронювань, а не здогадок про те, хто може з’явитися пізніше. Щойно гості прибувають, ліміти пам’яті стають суворими межами на кшталт протипожежних норм, тоді як ліміти CPU поводяться радше як правило тайм-шерингу, яке сповільнює галасливого гостя, а не виселяє його одразу.

Контракти ресурсів: запити, ліміти та одиниці

Розділ «Контракти ресурсів: запити, ліміти та одиниці»

Керування ресурсами в Kubernetes починається з поділу між наміром планування та примусовим контролем під час виконання. Запит — це обсяг CPU чи пам’яті, який Kubernetes використовує, вирішуючи, чи має вузол достатньо доступної для виділення потужності для поду. Ліміт — це максимальний обсяг, який контейнер може спожити після старту. Ці поля стоять поруч у маніфесті, але відповідають на різні питання, і плутанина між ними — джерело багатьох помилок на іспиті та збоїв у продакшені.

Найпростіший маніфест показує форму контракту. Цей под просить планувальник зарезервувати 100m CPU та 128Mi пам’яті, але дозволяє контейнеру розростатися до 500m CPU та 256Mi пам’яті під час виконання. Планувальник використовує для розміщення лише запит, тож малий запит може полегшити розміщення, але нереалістично малий запит також робить под вразливішим під час тиску на вузол, бо под позичає потужність, яку він не зарезервував.

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

apiVersion: v1
kind: Pod
metadata:
name: resource-demo
spec:
containers:
- name: app
image: nginx
resources:
requests: # Minimum guaranteed resources
memory: "128Mi"
cpu: "100m"
limits: # Maximum allowed resources
memory: "256Mi"
cpu: "500m"
АспектЗапити (Requests)Ліміти (Limits)
ПризначенняГарантія плануванняЖорстка межа
Коли застосовуєтьсяПланувальник вирішує розміщенняПримус середовища виконання контейнерів
НедовикористанняІнші поди можуть використати залишокНе застосовується
ПеревищенняНе застосовуєтьсяКонтейнер убито за пам’ять або затротлено за CPU

Діаграма нижче відображає найважливішу операційну відмінність. Пам’ять і CPU обидва мають запити та ліміти, але вони не збоять однаково, коли контейнер перетинає ліміт. Пам’ять не є стискуваною так само, як час CPU, тож контейнер, що перетинає свій ліміт пам’яті, може бути вбито з причиною OOMKilled. CPU можна нарізати в часі, тож контейнер, що перетинає свій ліміт CPU, тротлиться й продовжує працювати повільніше.

┌────────────────────────────────────────────────────────────────┐
│ Requests vs Limits │
│ │
│ Memory: 128Mi request, 256Mi limit │
│ │
│ 0 128Mi 256Mi Node Memory │
│ ├─────────┼──────────┼───────────────────────────────────► │
│ │ │ │ │
│ │ Reserved│ Can grow │ OOMKilled if exceeded │
│ │(guara- │ into this│ │
│ │ nteed) │ space │ │
│ │
│ CPU: 100m request, 500m limit │
│ │
│ 0 100m 500m Node CPU │
│ ├─────────┼──────────┼───────────────────────────────────► │
│ │ │ │ │
│ │ Reserved│ Can burst│ Throttled (not killed) │
│ │ │ up to │ │
│ │
└────────────────────────────────────────────────────────────────┘

Одиниці CPU легко прочитати неправильно під тиском іспиту. Kubernetes дозволяє записувати CPU у цілих ядрах або міліядрах, де 1000m означає одне ядро, а 100m — одну десяту ядра. Запит 100m не означає, що процес узагалі ніколи не зможе використати більше за одну десяту ядра, якщо ви також не встановите низький ліміт CPU; він означає, що планувальник резервує стільки потужності для розміщення, а середовище виконання може примусово застосувати ліміт, якщо той існує.

ЗначенняЩо означає
11 ядро CPU
1000m1 ядро CPU, записане в міліядрах
100m0,1 ядра CPU, або 100 міліядер
500m0,5 ядра CPU

Одиниці пам’яті мають окрему пастку, бо Kubernetes приймає і десяткові, і двійкові суфікси. Mi означає мебібайти, на основі степенів двійки, тоді як M означає мегабайти, на основі степенів десятки. Різниця достатньо мала, щоб не помітити її під час побіжного перегляду, і достатньо велика, щоб мати значення, коли ви задаєте жорсткі ліміти на багатьох подах. У маніфестах послідовно вживайте Mi та Gi, якщо у вас немає свідомої причини використовувати десяткові одиниці.

Запити та ліміти також взаємодіють із багатоконтейнерними подами. Kubernetes планує под як єдине ціле, тож додає запити основного контейнера, sidecar-ів та облік, пов’язаний з init-контейнерами, вирішуючи, чи под уміститься. Sidecar із відсутніми чи невідповідними ресурсами може змінити і тиск на планування, і класифікацію QoS. Під час реальних оглядів не зупиняйтеся після перевірки першого контейнера з ім’ям app; перевіряйте кожен контейнер у шаблоні поду, бо планувальник і kubelet оцінюють под у сукупності, а не вашу уявну модель основного процесу.

ЗначенняЩо означає
128Mi128 мебібайтів, або 128 разів по 1024 у квадраті байтів
1Gi1 гібібайт
256M256 мегабайтів, або 256 разів по 1000 у квадраті байтів

Зробіть паузу й передбачте: якщо под запитує 100m CPU та 128Mi пам’яті, але не має ліміту CPU і має ліміт пам’яті 256Mi, яке значення впливає на планування, яке значення впливає на поведінку OOMKilled, а який ресурс усе ще може розростатися над своїм запитом? Запишіть свою відповідь, перш ніж читати наступний розділ, бо ця відмінність — центр майже кожного шляху усунення несправностей у цьому модулі.

Планування, поди у стані Pending та тиск на вузол

Розділ «Планування, поди у стані Pending та тиск на вузол»

Планувальник не розміщує поди, спостерігаючи за поточним використанням CPU та пам’яті. Він підсумовує запити подів, уже призначених на вузол, порівнює цю суму з доступними для виділення ресурсами вузла й перевіряє, чи вміщаються запити нового поду. Доступні для виділення ресурси — це не те саме, що сира потужність, бо вузол резервує частину ресурсів для операційної системи, kubelet та іншої роботи на рівні вузла. Саме тому вузол із чотирма ядрами CPU може надавати подам менш як чотири ядра як доступні для виділення.

Terminal window
# Check node allocatable resources
kubectl describe node <node-name> | grep -A6 "Allocatable"
# Allocatable:
# cpu: 2
# memory: 4Gi
# pods: 110

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

Саме тому под у стані Pending часто є сигналом про потужність, а не вадою застосунку. Контейнер не завантажив образ, не запустив процес і не видав логи застосунку, бо планувальник не прив’язав його до вузла. Коли ви бачите Pending, прочитайте події, перш ніж змінювати образ чи перезапускати контролери. Повідомлення FailedScheduling, яке називає нестачу пам’яті, каже вам перевірити запити, доступну для виділення потужність вузла, квоти та обмеження розміщення; воно не каже вам налагоджувати бінарний файл застосунку.

┌────────────────────────────────────────────────────────────────┐
│ Scheduling Decision │
│ │
│ Node Capacity: 4Gi memory │
│ Already Requested: 3Gi │
│ Available: 1Gi │
│ │
│ Pod A requests 2Gi memory │
│ → Cannot schedule (2Gi > 1Gi available) │
│ → Pod stays Pending │
│ │
│ Pod B requests 500Mi memory │
│ → Can schedule (500Mi < 1Gi available) │
│ → Pod placed on node │
│ │
└────────────────────────────────────────────────────────────────┘

Ви можете створити цей режим збою напряму, попросивши великий обсяг пам’яті. Под є валідним YAML, а образ контейнера — звичайний, але планувальник не зможе прив’язати його, якщо жоден вузол не має достатньо залишкової доступної для виділення пам’яті після врахування запитів. На іспиті найшвидший шлях — зазвичай kubectl describe pod та розділ Events, бо повідомлення FailedScheduling каже вам, чи проблема в нестачі CPU, нестачі пам’яті, taint-ах, affinity чи іншому обмеженні розміщення.

Terminal window
# Create pod with huge request
cat << 'EOF' | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
name: big-pod
spec:
containers:
- name: nginx
image: nginx
resources:
requests:
memory: "100Gi"
EOF
# Check status
kubectl get pod big-pod
# NAME READY STATUS RESTARTS AGE
# big-pod 0/1 Pending 0 10s
# Check why
kubectl describe pod big-pod | grep -A5 "Events"
# Warning FailedScheduling Insufficient memory

Тиск на вузол відрізняється від збою планування, бо він трапляється після того, як поди вже запущені. Вузол може ввійти у стан MemoryPressure, DiskPressure чи PIDPressure, коли kubelet бачить, що локальні ресурси падають нижче налаштованих порогів. У цей момент kubelet може виселяти поди, і клас QoS плюс фактичне використання відносно запитів стає важливим. Планування захищало початкове розміщення; виселення захищає вузол, коли умови виконання змінилися.

Відмінність має значення, бо виправлення відбувається на іншому рівні. Якщо под не можна запланувати, ви або знижуєте його запит, або додаєте потужність, або змінюєте обмеження розміщення, або вивільняєте запитану потужність, переміщуючи інші робочі навантаження. Якщо вузол під тиском, ви перевіряєте, які запущені поди споживають незарезервовані ресурси, чи запити відображають реальність і чи перетинаються пороги виселення. Сприйняття обох симптомів як «у кластера закінчилася пам’ять» приховує корисні докази, які Kubernetes уже вам дає.

Terminal window
# Check node resource pressure
kubectl describe node <node-name> | grep -A10 "Conditions"
# MemoryPressure False KubeletHasSufficientMemory
# DiskPressure False KubeletHasNoDiskPressure
# PIDPressure False KubeletHasSufficientPID

Перш ніж запускати це в реальному кластері, який вивід ви очікуєте від поду у стані Pending, що запитав забагато пам’яті: причину статусу контейнера, подію планувальника чи рядок логу застосунку? Правильна відповідь формує порядок ваших команд. Якщо под так і не дістався вузла, логи контейнера — не перше місце, куди слід дивитися; подія планувальника — це доказ, який каже вам, що под так і не був допущений до виконання.

Примус під час виконання: тротлінг CPU та OOMKilled

Розділ «Примус під час виконання: тротлінг CPU та OOMKilled»

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

Terminal window
# Container trying to use 2 CPUs with 500m limit
# Gets throttled to 500m worth of CPU time

Тротлінг CPU підступний, бо середні значення можуть його приховати. kubectl top pod може показувати помірне середнє використання CPU, тоді як застосунок усе ще переживає стрибки затримки, особливо якщо процес сплесковий і тротлиться під час коротких вікон обробки запитів. Саме тому деякі продакшен-команди ретельно задають запити CPU, але уникають лімітів CPU для чутливих до затримки сервісів, тоді як інші тримають ліміти для сильнішої багатоорендної ізоляції. Правильна відповідь залежить від поведінки робочого навантаження та політики кластера, а не від універсального співвідношення.

Іспит не вимагає від вас налаштовувати лічильники ядра, але очікує, що ви знатимете патерн симптомів. Затротлений сервіс може виглядати здоровим з точки зору лічильника перезапусків, бо контейнер лишається живим. Проби готовності (readiness probes) можуть проходити між сплесками, проте користувачі бачать повільні відповіді під час пікового трафіку. Якщо контракт ресурсів має низький ліміт CPU, підвищення лише запиту не зніме обмеження під час виконання, тоді як підвищення лише ліміту не змінить резервувань для планування. Корисне виправлення залежить від того, що є справжньою проблемою — чесність розміщення чи запас під час виконання.

Ліміти пам’яті дають помітніший симптом. Коли процес усередині контейнера використовує більше пам’яті, ніж дозволяє його ліміт cgroup, ядро може його вбити, а Kubernetes повідомляє попередній стан контейнера як завершений із причиною OOMKilled. Код виходу 137 трапляється часто, бо він відповідає SIGKILL. Застосунок може не мати стек-трейсу, бо вбивство сталося поза нормальним шляхом обробки помилок застосунку.

Terminal window
# Check for OOMKilled
kubectl describe pod <pod-name> | grep -A5 "Last State"
# Last State: Terminated
# Reason: OOMKilled
# Exit Code: 137
# Check events
kubectl get events --field-selector reason=OOMKilling

Наступний под навмисно просить процес stress виділити більше пам’яті, ніж дозволяє його ліміт. Він корисний в одноразовому лабораторному просторі імен, бо доводить режим збою без потреби в зламаному застосунку. Не використовуйте такого роду робоче навантаження у спільному продакшен-просторі імен; це навчальний зонд, який має перетнути межу й бути вбитим.

Усунення несправностей пам’яті — це також місце, де запити та ліміти плутають найчастіше. Запит пам’яті не захищає контейнер від вбивства, коли той перетинає власний ліміт пам’яті. Ліміт пам’яті не гарантує, що под буде заплановано на вузол із достатньою зарезервованою потужністю для нормальної роботи. Вам потрібні обидві сторони контракту: запит має відображати те, що навантаженню потрібно для надійної роботи, а ліміт — максимум, який ви готові дозволити, перш ніж стримування стане безпечнішим за подальше зростання.

# Pod that will be OOMKilled
apiVersion: v1
kind: Pod
metadata:
name: memory-hog
spec:
containers:
- name: memory-hog
image: polinux/stress
command: ["stress"]
args: ["--vm", "1", "--vm-bytes", "200M", "--vm-hang", "1"]
resources:
limits:
memory: "100Mi" # Limit is less than 200M stress allocates

Гіпотетичний сценарій: вебконтейнер перезапускається кожні кілька хвилин, логи застосунку обриваються раптово, а контролер деплойменту далі створює замінні контейнери. Якщо kubectl describe pod показує Last State: Terminated із Reason: OOMKilled, перше виправлення — не перезапускати деплоймент знову. Вам потрібно вирішити, чи ліміт занизький для легітимного трафіку, чи запит занизький і спричиняє поганий пріоритет виселення, чи в застосунку є витік пам’яті, який зрештою перевищить будь-який розумний ліміт.

Класи якості обслуговування (QoS) та поведінка під час виселення

Розділ «Класи якості обслуговування (QoS) та поведінка під час виселення»

Kubernetes призначає кожному поду клас QoS на основі його конфігурації ресурсів. Цей клас не замінює класи пріоритету, витіснення (preemption) чи пороги виселення, але є важливим сигналом під час тиску на ресурси. Поди Guaranteed дали найсильнішу обіцянку, бо кожен контейнер має запити CPU та пам’яті, рівні лімітам. Поди BestEffort не дали жодної обіцянки щодо ресурсів. Поди Burstable стоять між цими випадками, бо мають щонайменше один запит чи ліміт, але не відповідають суворому правилу Guaranteed.

Клас QoSУмоваПріоритет виселення
Guaranteedrequests = limits для всіх контейнерівОстанній, найнижчий пріоритет виселення
BurstableЗадано щонайменше один запит чи лімітСередній
BestEffortЖодних запитів чи лімітівПерший, найвищий пріоритет виселення

Guaranteed суворіший, ніж очікує багато хто з тих, хто навчається. Кожен контейнер у поді повинен мати і запити, і ліміти CPU та пам’яті, причому запит має дорівнювати ліміту для кожного ресурсу. Якщо одна сторона відсутня або одне значення відрізняється, под не є Guaranteed. Ця суворість важлива, бо sidecar без відповідних ресурсів може змінити клас QoS усього поду.

Тому Guaranteed має бути свідомим вибором, а не звичкою форматування. Він дає поду найсильнішу обіцянку щодо ресурсів, але може знизити утилізацію кластера, бо кожна репліка резервує свій повний ліміт. Цей компроміс може бути доречним для компонента, який мусить пережити тиск, але марнотратним для навантаження, яке зазвичай використовує невелику базову лінію й лише зрідка дає сплеск. Питання проєктування полягає в тому, чи вартість виселення або затротленої поведінки під час сплеску вища за вартість резервування більшої потужності.

apiVersion: v1
kind: Pod
metadata:
name: guaranteed-pod
spec:
containers:
- name: app
image: nginx
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "128Mi" # Same as request
cpu: "100m" # Same as request
Terminal window
# Check QoS class
kubectl get pod guaranteed-pod -o jsonpath='{.status.qosClass}'
# Guaranteed

Burstable — звичайний клас для багатьох сервісів, бо команди часто хочуть реалістичний запит і деякий запас над ним. Цей компроміс покращує утилізацію, але також означає, що под позичає потужність, коли працює над своїм запитом. Під час тиску на пам’ять вузла под Burstable, що значно перевищує свій запит, є сильнішим кандидатом на виселення, ніж под Burstable, який усе ще нижчий за свій запит, бо перший под споживає більше, ніж зарезервував.

apiVersion: v1
kind: Pod
metadata:
name: burstable-pod
spec:
containers:
- name: app
image: nginx
resources:
requests:
memory: "128Mi"
limits:
memory: "256Mi" # Different from request

Поди BestEffort легко створити випадково, бо под без секції ресурсів отримує цей клас. Вони корисні для швидких експериментів в одноразовому просторі імен, але є поганими сусідами у спільних продакшен-кластерах. Планувальник може розміщувати їх без резервування CPU чи пам’яті, і під час тиску вони серед перших подів, яких kubelet розглядає для виселення.

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

apiVersion: v1
kind: Pod
metadata:
name: besteffort-pod
spec:
containers:
- name: app
image: nginx
# No resources section
Eviction Order (first to last):
1. BestEffort pods (no requests set - evicted first)
2. Burstable pods using more than their requests
3. Burstable pods using less than their requests
4. Guaranteed pods (last resort)

Зробіть паузу й передбачте: под має requests: {cpu: 100m, memory: 128Mi} та limits: {memory: 256Mi} без ліміту CPU. Він не є BestEffort, бо має налаштування ресурсів, і не є Guaranteed, бо CPU неповний, а запит пам’яті не дорівнює ліміту пам’яті. Це робить його Burstable, і якщо він спробує використати 300Mi пам’яті, ліміт пам’яті все одно може його вбити, навіть попри те, що клас QoS не є Guaranteed.

Керування простором імен за допомогою LimitRange та ResourceQuota

Розділ «Керування простором імен за допомогою LimitRange та ResourceQuota»

Налаштування окремих подів необхідні, але їх недостатньо в кластері, де кілька команд незалежно створюють робочі навантаження. Політика простору імен дає операторам платформи місце, де можна задати стандартні значення та стелі, не редагуючи кожен маніфест застосунку. LimitRange застосовує обмеження до окремих об’єктів під час допуску, тоді як ResourceQuota обмежує сукупне споживання у просторі імен. Разом вони запобігають і випадковим подам BestEffort, і захопленню ресурсів на рівні простору імен.

LimitRange може задати стандартні запити та ліміти, коли контейнер їх опускає. Він також може визначати мінімальні та максимальні значення, щоб под не міг запитати абсурдно крихітні чи величезні обсяги. Це особливо корисно, коли ResourceQuota вимагає запитів чи лімітів, бо LimitRange може автоматично постачати ці відсутні поля, перш ніж буде перевірено облік квоти. Об’єкт обмежений простором імен, тож кожна команда може мати політику, що відповідає профілю її навантаження.

Стандартні значення мають бути достатньо консервативними, щоб захистити простір імен, не вдаючи, що знають кожне навантаження. Зависокий стандартний запит марнує квоту й робить малі поди дорогими на вигляд. Занизький стандартний запит відтворює проблему перевиділення під виглядом офіційнішої політики. Багато команд починають із малих стандартних значень для звичайних вебсервісів, вимагають явних ресурсів для важчих навантажень і розглядають винятки через звичайні платформні процеси. Ключ у тому, що простір імен тепер має передбачувану базову лінію замість покладання на те, що кожен автор маніфесту згадає однакові поля.

apiVersion: v1
kind: LimitRange
metadata:
name: cpu-memory-limits
namespace: development
spec:
limits:
- type: Container
default: # Default limits if not specified
cpu: "500m"
memory: "256Mi"
defaultRequest: # Default requests if not specified
cpu: "100m"
memory: "128Mi"
min: # Minimum allowed
cpu: "50m"
memory: "64Mi"
max: # Maximum allowed
cpu: "1"
memory: "1Gi"
Terminal window
# Apply LimitRange to namespace
kubectl apply -f limitrange.yaml
# Now create pod without resources
kubectl run test --image=nginx -n development
# Check - default resources were applied!
kubectl get pod test -n development -o yaml | grep -A10 resources

LimitRange може застосовуватися не лише до окремих контейнерів. Політика для контейнера найпоширеніша для стандартних значень CPU та пам’яті, але обмеження на рівні поду можуть обмежувати суму по контейнерах, а обмеження PVC можуть керувати запитами на сховище. Важлива деталь для іспиту в тому, що LimitRange — це політика часу допуску. Вона змінює чи відхиляє нові об’єкти; вона не змінює безперервно розмір уже запущених подів після того, як їх допущено.

ТипДо чого застосовується
ContainerОкремі контейнери
PodСума всіх контейнерів у поді
PersistentVolumeClaimЗапити на сховище PVC

ResourceQuota — це сукупна сторона тієї самої історії керування. Замість казати, що один контейнер не може перевищити максимум, квота каже, що простір імен у цілому не може перевищити певну суму. Вона може обмежувати запитаний CPU, запитану пам’ять, ліміти, кількість подів, кількість PVC та інші лічильники об’єктів. Це дає кожній команді бюджет і не дає галасливому простору імен випадково спожити весь кластер.

Проєктування квоти має віддзеркалювати те, як ви хочете, щоб команди робили компроміси. Квота requests.cpu контролює зарезервований CPU й тому слід планування. Квота limits.memory контролює максимальний радіус ураження пам’яті, якщо кожен контейнер виросте до свого ліміту. Квота на кількість подів не дає простору імен створювати тисячі крихітних об’єктів, навіть коли кожен об’єкт має малі ресурси. Ці виміри відповідають на різні питання, тож корисна політика простору імен часто поєднує квоти на обчислення з квотами на кількість об’єктів замість покладання лише на одне жорстке значення.

apiVersion: v1
kind: ResourceQuota
metadata:
name: compute-quota
namespace: development
spec:
hard:
requests.cpu: "4" # Total CPU requests
requests.memory: "8Gi" # Total memory requests
limits.cpu: "8" # Total CPU limits
limits.memory: "16Gi" # Total memory limits
pods: "10" # Total number of pods
persistentvolumeclaims: "5" # Total PVCs
Terminal window
# View quota
kubectl get resourcequota -n development
# Detailed view
kubectl describe resourcequota compute-quota -n development
# Name: compute-quota
# Resource Used Hard
# -------- ---- ----
# limits.cpu 2 8
# limits.memory 4Gi 16Gi
# pods 5 10

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

Terminal window
# If quota exceeded
kubectl run new-pod --image=nginx -n development
# Error: exceeded quota: compute-quota, requested: pods=1, used: pods=10, limited: pods=10

Зупиніться й подумайте: ви створюєте ResourceQuota у просторі імен із pods: 10 та requests.cpu: 4. Розробник намагається створити под, не вказавши жодного запиту CPU. Под може бути відхилено, бо квоти на обчислення вимагають наявності відповідних запитів чи лімітів ресурсів, якщо тільки LimitRange не постачає стандартні значення під час допуску. Краще проєктування — поєднати квоту з LimitRange, щоб звичайні навантаження отримували розумні стандартні значення, а незвичайні навантаження мусили оголошувати свої потреби явно.

Правильний розмір, моніторинг та зміна розміру в Kubernetes 1.35

Розділ «Правильний розмір, моніторинг та зміна розміру в Kubernetes 1.35»

Вибір значень ресурсів — це проблема вимірювання, перш ніж стати проблемою YAML. Якщо ви задаєте запити нижче за нормальне використання, планувальник перевиділяє вузол, і под може стати поганим кандидатом на виселення під час тиску. Якщо ви задаєте запити значно вище за нормальне використання, планувальник пакує менше подів на вузол, і кластер виглядає повним, тоді як реальне використання низьке. Практична мета — зарезервувати достатньо для стабільної роботи, дозволити доречні сплески й не дати лімітам приховувати проблеми проєктування.

Хороший перший прохід — поспостерігати за навантаженням під репрезентативним тестом, а потім відокремити базову лінію від сплеску. Базова лінія — це рівень, який вам було б некомфортно втратити під час нормальної роботи, тож він належить близько до запиту. Сплеск — це тимчасовий запас, який може бути корисним, але не має визначати потужність планування, якщо тільки він не постійний. Пам’ять заслуговує особливої обережності, бо вона часто липка: процес, що виділив пам’ять, може не повертати її швидко, а занизький ліміт може створювати цикли перезапусків, що виглядають як нестабільність застосунку.

Terminal window
# 1. Profile your application
# Run locally or in test environment to measure actual usage
# 2. Set requests slightly above average usage
# Ensures pod gets scheduled
# 3. Set limits to handle bursts
# Allow headroom for spikes but protect the node

Співвідношення нижче — не універсальні правила; це відправні точки для розмови. Вебсервер із короткими сплесками може використовувати малий запит CPU та вищий ліміт CPU. База даних зазвичай потребує консервативнішої пам’яті, бо виселення чи поведінка OOMKilled дорого коштують, а відновлення зі станом може бути повільним. Кеш може більше дбати про розмір пам’яті, ніж про сплеск CPU. Завжди поєднуйте ці патерни з вимірами з тестового чи staging-середовища.

Тип застосункуЗапитЛімітСпіввідношення
Вебсервер100m CPU, 128Mi500m CPU, 512Mi1:5, 1:4
Фоновий обробник200m CPU, 256Mi1 CPU, 1Gi1:5, 1:4
База даних500m CPU, 1Gi2 CPU, 4Gi1:4, 1:4
Кеш100m CPU, 512Mi200m CPU, 1Gi1:2, 1:2

Ви можете задати ресурси напряму в маніфесті або використати kubectl set resources проти наявного навантаження. Для довговічних систем віддавайте перевагу декларативним маніфестам у системі контролю версій, бо контракт ресурсів є частиною проєктування навантаження. Для іспиту чи швидкого ремонту kubectl set resources корисний, бо він патчить контролер і дає контролеру перестворити поди з новим шаблоном.

Terminal window
# Create with resources using a manifest
cat << 'EOF' | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
containers:
- name: nginx
image: nginx
resources:
requests:
cpu: "100m"
memory: "128Mi"
limits:
cpu: "500m"
memory: "256Mi"
EOF
Terminal window
# Update existing deployment
kubectl set resources deployment/nginx \
-c nginx \
--requests="cpu=100m,memory=128Mi" \
--limits="cpu=500m,memory=256Mi"
# Check resource usage (requires metrics-server)
kubectl top pods
kubectl top nodes

Команди моніторингу допомагають порівняти задекларований намір зі спостережуваною поведінкою. kubectl describe node показує потужність, доступні для виділення ресурси та виділені запити й ліміти. kubectl top показує поточне використання, коли metrics-server встановлено й він справний. Жодна команда сама по собі не є достатньою. Задекларовані значення кажуть вам, у що вірить планувальник і система квот, тоді як метрики використання кажуть вам, чи навантаження насправді використовує контракт ресурсів, який ви йому дали.

Коли цифри розходяться, довіряйте кожному інструменту в тому, для повідомлення про що його призначено. kubectl top може сказати вам, що под наразі використовує набагато менше пам’яті, ніж його запит, що може вказувати на надмірне резервування. kubectl describe node може сказати вам, що запитана пам’ять уже достатньо висока, щоб блокувати нові розміщення, навіть якщо поточне використання зараз нижче. Вивід ResourceQuota може сказати вам, що бюджет простору імен вичерпано ще до появи будь-якої проблеми на рівні вузла. Хороша діагностика — це звичка вирівнювати ці погляди замість сприйняття однієї команди як авторитетної.

Terminal window
# Check node resource usage
kubectl top nodes
# NAME CPU(cores) CPU% MEMORY(bytes) MEMORY%
# node1 250m 12% 1200Mi 60%
# Check pod resource usage
kubectl top pods
kubectl top pods -n kube-system
kubectl top pod nginx --containers
Terminal window
# Node capacity and allocatable
kubectl describe node <node-name> | grep -A10 "Capacity"
kubectl describe node <node-name> | grep -A10 "Allocatable"
# Node resource usage summary
kubectl describe node <node-name> | grep -A10 "Allocated resources"

Kubernetes 1.35 робить зміну розміру ресурсів поду на місці (in-place) для CPU й пам’яті загальнодоступною через підресурс /resize. Це не означає, що кожна зміна розміру невидима для застосунку. Специфікація поду, resizePolicy контейнера, ресурс, що змінюється, та підтримка з боку середовища виконання вирішують, чи контейнер можна оновити на місці, чи його треба перезапустити. Використовуйте це як інструмент для правильного розміру та операційного ремонту, але пам’ятайте про навантаження, керовані контролерами, та володіння в GitOps.

Зміна розміру на місці особливо корисна для закриття розриву між спостереженням і виправленням. До появи цієї можливості вертикальні зміни зазвичай означали заміну подів, що змішувало виправлення ресурсів із поведінкою розгортання. Зі зміною розміру оператор іноді може скоригувати запущений под, поспостерігати, чи зникає тиск, а потім підвищити нові значення до шаблону навантаження-власника. Цей робочий процес усе ще дисциплінований: пряма зміна — це доказ і пом’якшення, тоді як оновлення шаблону чи маніфесту — це довговічна конфігурація.

Terminal window
# Check current resources
kubectl get pod nginx -o jsonpath='{.spec.containers[0].resources}'
# Resize CPU and memory without replacing the Pod object
kubectl patch pod nginx --subresource resize --patch '
{
"spec": {
"containers": [{
"name": "nginx",
"resources": {
"requests": {"cpu": "200m", "memory": "256Mi"},
"limits": {"cpu": "500m", "memory": "512Mi"}
}
}]
}
}'
# Verify whether the resize is pending or in progress
kubectl get pod nginx -o jsonpath='{.status.conditions[?(@.type=="PodResizePending")].status}'
kubectl get pod nginx -o jsonpath='{.status.conditions[?(@.type=="PodResizeInProgress")].status}'
# Empty output means the condition is not currently set.
# If either condition is True, the resize is still pending or in progress.
apiVersion: v1
kind: Pod
metadata:
name: resize-demo
spec:
containers:
- name: app
image: nginx
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 200m
memory: 256Mi
resizePolicy:
- resourceName: cpu
restartPolicy: NotRequired # CPU changes apply live
- resourceName: memory
restartPolicy: RestartContainer # Memory changes restart container

Який підхід ви б обрали тут і чому: запатчити один запущений под підресурсом /resize, запатчити Деплоймент-власник за допомогою kubectl set resources чи оновити маніфест у системі контролю версій і дати вашій системі доставки розгорнути його? Для некерованого налагоджувального поду пряма зміна розміру може бути доречною. Для Деплойменту довговічне виправлення зазвичай належить шаблону поду, бо наступне розгортання чи замінний под інакше дрейфуватиме назад до старого контракту.

Патерни та антипатерни

Розділ «Патерни та антипатерни»

Використовуйте патерн «спершу запит» для більшості сервісів без стану. Почніть із вимірювання усталеного CPU й пам’яті, задайте запити близько до нормального використання плюс помірний запас, а потім вирішіть, чи потрібні ліміти для ізоляції. Це працює, бо планувальник має для міркувань лише запити. Якщо ці значення реалістичні, кластер може ефективно пакувати навантаження, не вдаючи, що кожен сплеск постійний.

Патерн «спершу запит» також робить комунікацію під час інцидентів чіткішою. Коли сервіс не можна запланувати, ви можете вказати на задеклароване резервування й порівняти його з доступними для виділення ресурсами. Коли простір імен досягає квоти, ви можете показати, які навантаження споживають бюджет через свої запити. Без запитів кожна розмова стає суперечкою про поточне використання, а поточне використання змінюється щохвилини. Стабільні контракти ресурсів дають командам спільну мову для потужності, володіння та справедливості.

Використовуйте запобіжники простору імен для спільних кластерів. LimitRange має надавати стандартні значення та розумні значення min чи max, тоді як ResourceQuota має обмежувати бюджет простору імен. Цей патерн масштабується краще, ніж покладання на те, що кожен розробник пам’ятатиме поля ресурсів вручну, і він дає чіткіші помилки допуску, коли простір імен перевищує свій бюджет. Міркування щодо масштабування — це володіння політикою: платформні команди мають публікувати стандартні значення й навчати команди застосунків, як запитувати винятки.

Використовуйте QoS свідомо для критичних навантажень. Деякі компоненти заслуговують на QoS Guaranteed, бо виселення було б дорожчим за нижчу утилізацію. Інші навантаження краще зробити Burstable, бо невеликий додатковий запас покращує ефективність витрат. Патерн полягає в тому, щоб обирати клас як рішення про надійність, а не як випадковий результат того, які поля випадково опустили в маніфесті.

Уникайте конфігурації лише з лімітами. Вона виглядає захисною, бо контейнер має максимум, але дає планувальнику замало інформації, якщо стандартні значення допуску не додають запит. Навантаження тоді може приземлитися на вузол, не зарезервувавши потужність, яка йому зазвичай потрібна, і під тиском із ним можуть повестися гірше, ніж очікує команда. Краща альтернатива — задати явні запити, а потім обирати ліміти лише там, де поведінка під час збою прийнятна.

Уникайте копіювання співвідношень між типами навантажень без вимірювання. Інтенсивний за CPU пакетний обробник, чутливий до затримки API, JVM-сервіс та кеш у пам’яті не мають однакового патерну сплесків чи ризику пам’яті. Команди потрапляють у цю пастку, бо один приклад маніфесту легко стандартизувати. Краща альтернатива — публікувати початкові профілі, а потім вимагати від команд підтверджувати їх метриками зі свого навантаження.

Уникайте сприйняття kubectl top як інструмента планування потужності самого по собі. Він показує нещодавнє використання, а не резервування планувальника, використання квоти чи лічильники тротлінгу. Под із низьким середнім CPU усе одно може тротлитися під час коротких сплесків, а вузол із низькою поточною пам’яттю усе одно може не мати змоги запланувати под, бо запити вже заповнюють доступну для виділення потужність. Краща альтернатива — порівняти kubectl top, kubectl describe node, події подів та вивід квоти, перш ніж вирішувати, що змінювати.

Ще один антипатерн — використання політики ресурсів як покарання замість контракту. Якщо платформні команди задають крихітні стандартні значення, щоб змусити розробників просити більше, навантаження падають заплутаними способами, і розробники вчаться обходити політику. Якщо команди застосунків задають роздуті запити, щоб уникнути майбутніх розмов, кластер стає штучно повним. Краще керування ресурсами — це перемовини з доказами: стандартні значення обробляють звичайний випадок, метрики обґрунтовують винятки, а квоти роблять спільну межу явною.

Рамка прийняття рішень

Розділ «Рамка прийняття рішень»

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

Для проєктування простору імен вирішіть, чи проблема в тому, що бракує стандартних значень, чи в надмірному розмірі окремого поду, чи в надмірному сукупному споживанні. Брак стандартних значень указує на LimitRange із defaultRequest та default. Завеликі окремі поди вказують на значення max у LimitRange або політику огляду. Сукупне перевикористання вказує на ResourceQuota. Коли потрібні і стандартні значення, і сукупні стелі, спершу створіть LimitRange, щоб звичайні поди отримували поля ресурсів, перш ніж допуск квоти їх оцінить.

Для усунення несправностей читайте збої в порядку, у якому їх зустрічає Kubernetes. Помилки допуску трапляються перед плануванням, тож повідомлення ResourceQuota та LimitRange з’являються, коли API-сервер відхиляє об’єкт. Помилки планування трапляються після допуску, тож вони з’являються як події поду, поки под лишається у стані Pending. Примус під час виконання трапляється після того, як под прив’язано й контейнери стартували, тож симптоми OOMKilled, перезапусків та тротлінгу з’являються у статусі, подіях і метриках. Ця хронологія не дає вам налагоджувати неправильний рівень.

Для правильного розміру відокремте екстрений ремонт від довговічної конфігурації. У Kubernetes 1.35+ підресурс /resize може бути корисним, коли одному запущеному поду потрібне негайне коригування CPU чи пам’яті й політика це дозволяє. Зміна на рівні Деплойменту краща, коли кожна репліка має поділяти новий контракт. Оновлення маніфесту в системі контролю версій — це довговічна відповідь, коли зміна має пережити розгортання, огляди та майбутні узгодження.

Для надійності порівняйте вартість виселення з вартістю нижчої утилізації. QoS Guaranteed може бути доречним для критичних сервісів, суміжних із площиною управління, чи навантажень зі станом, де виселення має високий вплив. Burstable часто доречний для сервісів без стану, які виграють від ефективного пакування й можуть толерувати зрідка поведінку під тиском. BestEffort зазвичай має обмежуватися експериментами, одноразовими задачами чи просторами імен, де виселення прийнятне.

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

  • Зміна розміру поду на місці тепер GA: У Kubernetes 1.35 запити та ліміти CPU й пам’яті можна оновлювати для запущених Подів через підресурс /resize, а resizePolicy впливає на те, чи потрібен перезапуск контейнера.
  • Ліміти CPU та пам’яті збоять по-різному: Перевищення CPU тротлиться, тоді як перевищення пам’яті може завершити контейнер зі станом OOMKilled і зазвичай з’являється з кодом виходу 137.
  • Guaranteed QoS — це все або нічого: Кожен контейнер у поді потребує відповідних запитів і лімітів CPU та пам’яті; один неповний sidecar може зробити весь под Burstable.
  • Metrics-server живить kubectl top: Поширений конвеєр метрик кластера збирає нещодавнє використання ресурсів для команд на кшталт kubectl top, але планування все одно використовує задекларовані запити, а не поточне використання.
ПомилкаЧому вона трапляєтьсяЯк її виправити
Не задано запитівМаніфест працює в тихому тестовому просторі імен, тож команда забуває, що в планувальника немає резервування для використанняЗадайте реалістичні запити CPU та пам’яті або використайте LimitRange для застосування стандартних значень
Занизькі лімітиСкопійоване значення з прикладу стає продакшен-межею без профілювання навантаженняВиміряйте реальне використання, підніміть ліміт пам’яті, коли це легітимно, і виправляйте витоки, коли використання росте без меж
Запити скрізь дорівнюють лімітамКоманди женуться за Guaranteed QoS, не зважаючи на толерантність до сплесків чи утилізаціюРезервуйте Guaranteed QoS для навантажень, яким він потрібен, і використовуйте контракти Burstable для сервісів, що можуть безпечно давати сплеск
Використання M замість MiДесяткові та двійкові суфікси виглядають схоже під час швидкого переглядуСтандартизуйте Mi та Gi для маніфестів пам’яті, якщо десяткові одиниці не задумані навмисно
Квота без стандартних значень LimitRangeResourceQuota вимагає полів, які звичайні маніфести подів опускаютьСтворіть стандартні значення LimitRange перед квотами на обчислення або разом із ними, щоб допуск міг заповнити відсутні запити
Погляд лише на kubectl topНещодавнє використання читати легше, ніж резервування планувальника та подіїПорівняйте використання з describe node, подіями подів, виводом ResourceQuota та специфікаціями ресурсів
Патчинг одного поду, що належить ДеплойментуПрямі правки поду здаються швидкими під час інциденту, але шаблон контролера досі має старі ресурсиПатчте контролер чи маніфест у системі контролю версій для довговічних змін, а зміну розміру поду використовуйте лише коли прямий ремонт задуманий навмисно
Питання 1: Под у стані Pending після деплою. Образ існує, простір імен правильний, а `kubectl describe pod` показує FailedScheduling із нестачею пам'яті. Що слід перевірити й змінити першим?

Перевірте запит пам’яті поду й порівняйте його з доступними для виділення ресурсами вузла та вже запитаною пам’яттю. Планувальник використовує запити, а не поточне використання, тож под може лишатися у стані Pending, навіть коли вузол виглядає тихим у kubectl top. Якщо запит випадково завеликий, зменшіть його до виміряного значення; якщо запит легітимний, додайте потужність або перемістіть навантаження з нижчим пріоритетом. Зміна лише ліміту пам’яті не вирішить розміщення, якщо запит усе одно не вміщається.

Питання 2: Контейнер перезапускається з кодом виходу 137, без помилки застосунку, із `Last State: Terminated, Reason: OOMKilled`. Як ви діагностуєте, чи піднімати ліміт, чи виправляти застосунок?

Почніть із підтвердження ліміту пам’яті та перевірки спостережуваного зростання пам’яті за доступними метриками. Якщо використання неухильно зростає, не повертаючись до стабільного рівня, підозрюйте витік і ставтеся до вищого ліміту як до тимчасового стримування, а не остаточного виправлення. Якщо використання дає сплеск лише під час валідного трафіку, а потім стабілізується, ліміт може бути занизьким для нормальної роботи. Також перевірте запит, бо низький запит може зробити под вразливішим під час тиску на вузол, навіть якщо ліміт запобігає шкоді на рівні вузла.

Питання 3: Ваш API має запит CPU `100m`, ліміт CPU `200m`, і затримка стрибає під піковим трафіком, навіть попри те, що середній CPU у `kubectl top` виглядає низьким. Яка проблема ресурсів може пояснити цей симптом?

Под може тротлитися за CPU під час коротких сплесків, навіть попри те, що середнє за вікном метрик низьке. Ліміти CPU застосовуються протягом малих періодів планування, тож сплеск обробки запиту може досягати межі 200m і сповільнюватися, тоді як довше середнє все одно виглядає помірним. Ви можете перевірити це, піднявши чи прибравши ліміт CPU в контрольованому середовищі й порівнявши затримку. Запиту все одно слід задати реалістичний розмір, щоб планування лишалося чесним.

Питання 4: Простір імен має ResourceQuota для `requests.cpu` та `pods`, але розробники часто створюють поди без полів ресурсів і отримують помилки допуску. Який об'єкт слід додати й чому?

Додайте LimitRange, що постачає значення defaultRequest для CPU та пам’яті, а зазвичай і стандартні ліміти. Квоти на обчислення потребують відповідних полів запиту чи ліміту, щоб Kubernetes міг обліковувати використання простору імен під час допуску. LimitRange заповнює відсутні значення перед оцінкою квоти, що дає змогу допустити звичайні поди, водночас зараховуючи їх до бюджету простору імен. Вам також варто задати розумні значення min і max, якщо розмір окремого поду потребує запобіжників.

Питання 5: Під час тиску на пам'ять вузла ви бачите под Guaranteed, под Burstable, що використовує набагато більше за свій запит, та под BestEffort. Який под найімовірніше буде виселено першим і який урок проєктування з цього випливає?

Под BestEffort — перший імовірний кандидат на виселення, бо він не зробив жодного резервування CPU чи пам’яті. Якщо тиск триває, поди Burstable, що використовують більше за свої запити, стають сильнішими кандидатами перед подами Guaranteed. Урок проєктування в тому, що запити — не паперова формальність; вони впливають і на планування, і на те, як kubelet оцінює справедливість під тиском. Критичні навантаження не мають випадково працювати як BestEffort через те, що було опущено секцію ресурсів.

Питання 6: Ви змінюєте розмір запущеного поду в Kubernetes 1.35 за допомогою підресурсу `/resize`, і зміна спрацьовує. Чому це все одно може бути неправильним довгостроковим виправленням для Деплойменту?

Пряма зміна розміру поду змінює запущений об’єкт Pod, але Деплоймент створює поди зі свого шаблону. Якщо шаблон досі містить старі ресурси, майбутні замінні поди можуть повернутися до старих значень запиту й ліміту. Для довговічних навантажень, керованих контролерами, патчте Деплоймент або оновлюйте маніфест у системі контролю версій, що ним володіє. Зміна розміру на місці корисна для негайного ремонту та правильного розміру, але володіння й узгодження все одно мають значення.

Ця вправа використовує звичайні поди та політику простору імен, щоб пов’язати планування, QoS і поведінку допуску. Виконуйте її в одноразовому кластері чи лабораторному середовищі. Мета — не запам’ятати кожну команду, а передбачити результат перед кожною перевіркою, а потім порівняти це передбачення зі статусом, подіями та YAML об’єктів Kubernetes.

Завдання 1: Створіть под Burstable із явними запитами та лімітами

Розділ «Завдання 1: Створіть под Burstable із явними запитами та лімітами»

Створіть под, де запити й ліміти відрізняються, потім перевірте клас QoS. Зверніть увагу, що под не є Guaranteed, навіть попри те, що має і значення CPU, і значення пам’яті, бо значення запиту й ліміту не рівні.

Terminal window
cat << 'EOF' | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
name: resource-test
spec:
containers:
- name: nginx
image: nginx
resources:
requests:
cpu: "100m"
memory: "128Mi"
limits:
cpu: "200m"
memory: "256Mi"
EOF
kubectl get pod resource-test -o jsonpath='{.status.qosClass}'
# Burstable (because requests are not equal to limits)

Завдання 2: Створіть под Guaranteed

Розділ «Завдання 2: Створіть под Guaranteed»

Тепер створіть под, де запити CPU та пам’яті дорівнюють лімітам. Це суворий патерн, якого Kubernetes вимагає для QoS Guaranteed.

Terminal window
cat << 'EOF' | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
name: guaranteed
spec:
containers:
- name: nginx
image: nginx
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "128Mi"
cpu: "100m"
EOF
kubectl get pod guaranteed -o jsonpath='{.status.qosClass}'
# Guaranteed

Завдання 3: Створіть под BestEffort

Розділ «Завдання 3: Створіть под BestEffort»

Створіть под без налаштувань ресурсів і підтвердьте, що Kubernetes класифікує його як BestEffort. У спільному кластері це зазвичай сигнал про те, що бракує стандартних значень простору імен.

Terminal window
kubectl run besteffort --image=nginx
kubectl get pod besteffort -o jsonpath='{.status.qosClass}'
# BestEffort

Завдання 4: Застосуйте LimitRange і поспостерігайте за стандартними значеннями

Розділ «Завдання 4: Застосуйте LimitRange і поспостерігайте за стандартними значеннями»

Створіть простір імен, додайте LimitRange, а потім створіть под без явних ресурсів. Маніфест поду, який ви надіслали, не містив ресурсів, але допущений под має показати стандартні значення, надані політикою простору імен.

Terminal window
kubectl create namespace limits-test
cat << 'EOF' | kubectl apply -f -
apiVersion: v1
kind: LimitRange
metadata:
name: default-limits
namespace: limits-test
spec:
limits:
- type: Container
default:
cpu: "200m"
memory: "128Mi"
defaultRequest:
cpu: "100m"
memory: "64Mi"
EOF
# Create pod without resources
kubectl run test-defaults --image=nginx -n limits-test
# Check - defaults applied!
kubectl get pod test-defaults -n limits-test -o yaml | grep -A8 resources

Завдання 5: Перевірте допуск ResourceQuota

Розділ «Завдання 5: Перевірте допуск ResourceQuota»

Додайте квоту на кількість подів і створюйте поди, доки бюджет простору імен не вичерпається. test-defaults із Завдання 4 уже займає один із двох дозволених слотів подів, тож pod1 доводить простір імен до 2/2, і другий новий под відхиляється.

Terminal window
cat << 'EOF' | kubectl apply -f -
apiVersion: v1
kind: ResourceQuota
metadata:
name: pod-quota
namespace: limits-test
spec:
hard:
pods: "2"
EOF
# Create pods until quota exceeded
kubectl run pod1 --image=nginx -n limits-test
kubectl run pod2 --image=nginx -n limits-test # Should fail: test-defaults + pod1 already reach 2/2
kubectl describe resourcequota pod-quota -n limits-test

Завдання 6: Прибирання

Розділ «Завдання 6: Прибирання»

Видаліть окремі поди й тестовий простір імен, щоб лабораторія залишила кластер чистим для наступного модуля.

Terminal window
kubectl delete pod resource-test guaranteed besteffort
kubectl delete namespace limits-test
  • Налаштувати запити та ліміти CPU й пам’яті на поді, а потім перевірити допущену специфікацію ресурсів.
  • Діагностувати, чому под є Burstable, Guaranteed чи BestEffort, за його полями ресурсів.
  • Впровадити LimitRange, що постачає стандартні значення простору імен для подів без явних ресурсів.
  • Впровадити ResourceQuota й перевірити збій допуску, коли простір імен перевищує свій бюджет.
  • Спроєктувати потік усунення несправностей ресурсів, що перевіряє події, QoS, квоту, метрики використання та володіння контролером.

Додаткові тренувальні вправи

Розділ «Додаткові тренувальні вправи»

Вправи нижче зберігають командні патерни з оригінального тренувального набору. Сприймайте їх як вправи на час після того, як ви зрозуміли концепції, а не як заміну поясненням вище.

Terminal window
# Drill 1: Resource Creation
cat << 'EOF' | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
name: web
spec:
containers:
- name: nginx
image: nginx
resources:
requests:
cpu: "100m"
memory: "128Mi"
limits:
cpu: "500m"
memory: "512Mi"
EOF
kubectl get pod web -o jsonpath='{.spec.containers[0].resources}'
kubectl get pod web -o jsonpath='{.status.qosClass}'
kubectl delete pod web
Terminal window
# Drill 2: QoS Class Identification
cat << 'EOF' | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
name: qos-guaranteed
spec:
containers:
- name: app
image: nginx
resources:
requests:
cpu: "100m"
memory: "100Mi"
limits:
cpu: "100m"
memory: "100Mi"
EOF
cat << 'EOF' | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
name: qos-burstable
spec:
containers:
- name: app
image: nginx
resources:
requests:
cpu: "100m"
EOF
kubectl run qos-besteffort --image=nginx
for pod in qos-guaranteed qos-burstable qos-besteffort; do
echo "$pod: $(kubectl get pod $pod -o jsonpath='{.status.qosClass}')"
done
kubectl delete pod qos-guaranteed qos-burstable qos-besteffort
Terminal window
# Drill 3: LimitRange
kubectl create namespace lr-test
cat << 'EOF' | kubectl apply -f -
apiVersion: v1
kind: LimitRange
metadata:
name: mem-limit
namespace: lr-test
spec:
limits:
- type: Container
default:
memory: "256Mi"
defaultRequest:
memory: "128Mi"
min:
memory: "64Mi"
max:
memory: "1Gi"
EOF
kubectl run default-test --image=nginx -n lr-test
kubectl get pod default-test -n lr-test -o jsonpath='{.spec.containers[0].resources}'
cat << 'EOF' | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
name: too-big
namespace: lr-test
spec:
containers:
- name: app
image: nginx
resources:
limits:
memory: "2Gi"
EOF
kubectl delete namespace lr-test
Terminal window
# Drill 4: ResourceQuota
kubectl create namespace quota-test
cat << 'EOF' | kubectl apply -f -
apiVersion: v1
kind: ResourceQuota
metadata:
name: compute-quota
namespace: quota-test
spec:
hard:
requests.cpu: "1"
requests.memory: "1Gi"
limits.cpu: "2"
limits.memory: "2Gi"
pods: "3"
EOF
kubectl describe resourcequota compute-quota -n quota-test
cat << 'EOF' | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
name: pod1
namespace: quota-test
spec:
containers:
- name: nginx
image: nginx
resources:
requests:
cpu: "200m"
memory: "256Mi"
limits:
cpu: "400m"
memory: "512Mi"
---
apiVersion: v1
kind: Pod
metadata:
name: pod2
namespace: quota-test
spec:
containers:
- name: nginx
image: nginx
resources:
requests:
cpu: "200m"
memory: "256Mi"
limits:
cpu: "400m"
memory: "512Mi"
---
apiVersion: v1
kind: Pod
metadata:
name: pod3
namespace: quota-test
spec:
containers:
- name: nginx
image: nginx
resources:
requests:
cpu: "200m"
memory: "256Mi"
limits:
cpu: "400m"
memory: "512Mi"
EOF
cat << 'EOF' | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
name: pod4
namespace: quota-test
spec:
containers:
- name: nginx
image: nginx
resources:
requests:
cpu: "200m"
memory: "256Mi"
limits:
cpu: "400m"
memory: "512Mi"
EOF
kubectl describe resourcequota compute-quota -n quota-test
kubectl delete namespace quota-test
Terminal window
# Drill 5: Resource Troubleshooting
cat << 'EOF' | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
name: pending-pod
spec:
containers:
- name: nginx
image: nginx
resources:
requests:
cpu: "100"
memory: "100Gi"
EOF
kubectl get pod pending-pod
kubectl describe pod pending-pod | grep -A5 "Events"
kubectl delete pod pending-pod
cat << 'EOF' | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
name: pending-pod
spec:
containers:
- name: nginx
image: nginx
resources:
requests:
cpu: "100m"
memory: "128Mi"
EOF
kubectl get pod pending-pod
kubectl delete pod pending-pod
Terminal window
# Drill 6: Update Resources
kubectl create deployment resource-update --image=nginx --replicas=2
kubectl set resources deployment/resource-update \
--requests="cpu=100m,memory=128Mi" \
--limits="cpu=200m,memory=256Mi"
kubectl get pods -l app=resource-update -w &
sleep 10
kill %1 2>/dev/null
kubectl describe deployment resource-update | grep -A10 "Resources"
kubectl delete deployment resource-update
Розв'язок Drill 7: повне налаштування ресурсів простору імен
Terminal window
kubectl create namespace challenge
cat << 'EOF' | kubectl apply -f -
apiVersion: v1
kind: LimitRange
metadata:
name: limits
namespace: challenge
spec:
limits:
- type: Container
default:
cpu: "200m"
memory: "256Mi"
defaultRequest:
cpu: "100m"
memory: "128Mi"
max:
cpu: "1"
memory: "1Gi"
EOF
cat << 'EOF' | kubectl apply -f -
apiVersion: v1
kind: ResourceQuota
metadata:
name: quota
namespace: challenge
spec:
hard:
pods: "4"
requests.cpu: "2"
requests.memory: "4Gi"
EOF
kubectl create deployment app --image=nginx --replicas=2 -n challenge
kubectl get all -n challenge
kubectl describe resourcequota quota -n challenge
kubectl delete namespace challenge

Модуль 2.6: Планування — вибір вузлів, affinity, taint-и та toleration-и.