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

Модуль 2.6: Планування (Scheduling)

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

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

Передумови: Модуль 2.1 (Поди), Модуль 2.5 (Керування ресурсами)


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

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

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

  • Спроєктувати правила розміщення на нодах за допомогою nodeSelector, спорідненості до нод (node affinity), спорідненості Подів (pod affinity) та антиспорідненості Подів (pod anti-affinity), пояснюючи при цьому компроміси планування.
  • Впровадити taint’и та толерантності так, щоб виділені, спеціалізовані або тимчасово захищені ноди приймали лише ті Поди, які мають там працювати.
  • Оцінити обмеження топологічного розподілу (topology spread constraints) для високої доступності між зонами та нодами без створення Подів, що без потреби лишаються у стані Pending.
  • Діагностувати Поди у стані Pending, читаючи події планувальника та зіставляючи їх із мітками, taint’ами, ресурсами, спорідненістю та обмеженнями топології.
  • Порівняти жорсткі та м’які правила планування так, щоб відповіді на іспиті та робочі маніфести обирали найменш крихкий контроль, який усе ж задовольняє вимогу.

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

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

Гіпотетичний сценарій: команда платформи додає три нові робочі ноди до кластера Kubernetes 1.35, маркує їх для навантажень зберігання даних і очікує, що розгортання бази даних потрапить саме туди. Поди лишаються у стані Pending, навіть попри те, що kubectl top nodes показує незавантажені CPU та пам’ять. Справжня проблема — не в нестачі ресурсів у широкому розумінні: Подові потрібна одна мітка, він віддає перевагу іншій мітці, йому бракує толерантності до taint’у ноди, і він використовує антиспорідненість, яку неможливо задовольнити за поточної розкладки нод. Людина, яка перевіряє лише використання ресурсів, гнатиметься за хибною проблемою, тоді як людина, яка розуміє планування, може прочитати подію й одразу перейти до обмеження, яке не спрацювало.

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

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

Аналогія з розсадкою на весіллі все ще корисна, якщо пам’ятати про її межі. nodeSelector — це як призначити гостя за столи з точно визначеною позначкою, наприклад «VIP», і нікуди більше. Спорідненість до нод — це багатша перевага щодо розсадки: гості можуть вимагати будь-який стіл біля сцени або лише віддавати перевагу одному боку зали, не наполягаючи на ньому. Taint’и — це таблички «зарезервовано» на столах, а толерантності — це бейджі, які дозволяють певним гостям там сидіти. Антиспорідненість Подів — це правило, яке тримає двох людей нарізно за різними столами, тоді як обмеження топологічного розподілу — це правило плану розсадки, яке тримає кожну секцію зали рівномірно заповненою, без переповнення в одному кутку.

Мітки нод, nodeSelector і найменше корисне обмеження

Розділ «Мітки нод, nodeSelector і найменше корисне обмеження»

Найпростіше правило планування — це збіг за міткою ноди. Ноди є об’єктами Kubernetes API, і мітки на нодах працюють як мітки на Подах: це метадані типу «ключ-значення», які можуть вибирати інші компоненти. Поле nodeSelector Пода — це точний збіг за логікою AND з цими мітками нод. Якщо Под каже disk: ssd, планувальник вилучає кожну ноду, яка не має disk=ssd, із набору придатних, перш ніж оцінювати решту нод. Це робить nodeSelector легким для читання, легким для тестування й легким для надмірного використання.

Використовуйте nodeSelector, коли вимога бінарна та стабільна. Операційна система, архітектура та невелика кількість класів апаратного забезпечення — гарні приклади, бо мітки описують реальні властивості нод, і маніфест зазвичай не має причин виражати альтернативи. Якщо навантаження не може працювати на нодах Windows, kubernetes.io/os: linux зрозуміліший за складніше правило спорідненості. Якщо тестове навантаження має потрапити на одну марковану навчальну ноду під час іспитової лабораторної, селектор швидкий і виправданий.

Компроміс полягає в тому, що nodeSelector не може виразити «одне з цих значень», «віддавати перевагу цьому, але дозволяти запасний варіант» чи «уникати цієї мітки, якщо можливо». Він також поєднує кожен ключ логікою AND, тож Под, що вимагає disk=ssd і zone=us-west-1a, потребує ноди, яка має обидві мітки одночасно. Ця точність корисна, коли вона вам справді потрібна, але може бути крихкою, коли інвентар кластера змінюється. Перш ніж додавати селектор, запитайте себе, чи має Под зазнати невдачі за відсутності мітки, чи безпечнішою була б перевага.

apiVersion: v1
kind: Pod
metadata:
name: ssd-pod
spec:
nodeSelector:
disk: ssd # Only schedule on nodes with this label
containers:
- name: nginx
image: nginx

Керування мітками нод часто є найшвидшим способом довести, чи правило планування є коректним. На іспиті CKA ви, ймовірно, маркуватимете ноду, створюватимете Под, а потім перевірятимете розміщення за допомогою kubectl get pod -o wide. У продакшені зміни міток потребують більшого регулювання, бо вони змінюють набір придатних нод для кожного Пода, який вибирає ці мітки. Помилки в ключі, як-от disks=ssd замість disk=ssd, достатньо, щоб цілком справний Под лишився у стані Pending.

Terminal window
# List node labels
kubectl get nodes --show-labels
# Label a node
kubectl label node worker-1 disk=ssd
# Remove a label
kubectl label node worker-1 disk-
# Overwrite a label
kubectl label node worker-1 disk=hdd --overwrite

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

МіткаОпис
kubernetes.io/hostnameІм’я хоста ноди
kubernetes.io/osОпераційна система (linux, windows)
kubernetes.io/archАрхітектура (amd64, arm64)
topology.kubernetes.io/zoneХмарна зона доступності
topology.kubernetes.io/regionХмарний регіон
node.kubernetes.io/instance-typeТип інстансу (хмара)
# Example: Schedule only on Linux nodes
spec:
nodeSelector:
kubernetes.io/os: linux

Зупиніться й передбачте: якщо Под використовує nodeSelector із kubernetes.io/os: linux та disk: ssd водночас, що станеться, коли кластер має ноди Linux і ноди SSD, але жодна окрема нода не має обох міток? Планувальник не об’єднує часткові збіги між нодами. Він фільтрує ноди, що задовольняють кожен ключ селектора, не знаходить жодної й лишає Под у стані Pending із подією FailedScheduling, яка вказує на невідповідність селектора нод.

Саме така поведінка є причиною, чому селектори мають описувати обов’язкові факти, а не розпливчасті переваги. Якщо розміщення на SSD покращує продуктивність, але звичайні диски прийнятні під час дефіциту потужностей, жорсткий селектор надто суворий. Якщо Linux обов’язковий, бо образ контейнера чи навантаження не може працювати на Windows, селектор доречний. Мислення в стилі CKA полягає в тому, щоб перетворити речення завдання на контракт планування: «має працювати на» означає жорстку фільтрацію, тоді як «має віддавати перевагу» означає оцінювання.

Спорідненість до нод: обов’язкові правила, бажані правила та оператори

Розділ «Спорідненість до нод: обов’язкові правила, бажані правила та оператори»

Спорідненість до нод (node affinity) — це наступний крок після nodeSelector. Вона все ще розміщує Поди на основі міток нод, але дає вам багатші вирази, кілька значень на ключ і м’які переваги. Найважливіший шаблон іменування — це requiredDuringSchedulingIgnoredDuringExecution проти preferredDuringSchedulingIgnoredDuringExecution. Перша фраза каже, чи є правило обов’язковим, чи перевагою під час планування; друга фраза каже, що зміни міток після розміщення автоматично не виселяють і не перепланують Под.

Цей суфікс «ignored during execution» (ігнорується під час виконання) запобігає поширеному непорозумінню. Якщо Под потрапляє на ноду, бо нода має disk=ssd, а хтось пізніше прибирає цю мітку, Под продовжує працювати. Kubernetes не виконує безперервно повторно кожне рішення про спорідненість до нод для вже прив’язаних Подів. Якщо вам потрібне виселення під час виконання на основі станів ноди чи адміністративної дії, ви перебуваєте на території taint’ів, толерантностей, спорожнення (drain) чи контролерів, а не спорідненості до нод.

ТипПоведінка
requiredDuringSchedulingIgnoredDuringExecutionЖорстка вимога (як nodeSelector)
preferredDuringSchedulingIgnoredDuringExecutionМ’яка перевага

Обов’язкова спорідненість до нод корисна, коли вам потрібно більше виразності, ніж дає nodeSelector, але ви все ж хочете жорсткий фільтр. Класичний приклад — «диск має бути SSD або NVMe». nodeSelector не може виразити два прийнятні значення для одного ключа, але спорідненість може використати оператор In із кількома значеннями. Планувальник трактує терми як альтернативи, а вирази в межах одного терму як скомбіновані вимоги, тож уважно читайте відступи, перш ніж довіряти правилу.

apiVersion: v1
kind: Pod
metadata:
name: affinity-required
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: disk
operator: In
values:
- ssd
- nvme
containers:
- name: nginx
image: nginx

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

apiVersion: v1
kind: Pod
metadata:
name: affinity-preferred
spec:
affinity:
nodeAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 80 # Higher weight = stronger preference
preference:
matchExpressions:
- key: disk
operator: In
values:
- ssd
- weight: 20
preference:
matchExpressions:
- key: zone
operator: In
values:
- us-west-1a
containers:
- name: nginx
image: nginx

У цьому прикладі zone — це користувацька мітка ноди, яку ви застосовуєте в лабораторній (наприклад, kubectl label node ... zone=us-west-1a). У хмарних кластерах краще використовуйте відомий ключ topology.kubernetes.io/zone — ноди мають надавати мітки зон, щоб будь-який із цих ключів робив внесок в оцінювання бажаної спорідненості.

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

Оператори спорідненості до нод роблять правило достатньо виразним для реальних контрактів планування. In і NotIn порівнюють значення мітки з набором. Exists і DoesNotExist дбають лише про те, чи присутній ключ. Gt і Lt виконують цілочислові порівняння, що може бути корисним для числових міток, але рідше трапляється в іспитових завданнях. Планувальник оцінює мітки на ноді, а не дані всередині образу контейнера чи застосунку.

ОператорЗначення
InЗначення мітки є в наборі
NotInЗначення мітки не є в наборі
ExistsМітка існує (будь-яке значення)
DoesNotExistМітки не існує
GtБільше ніж (цілочислове порівняння)
LtМенше ніж (цілочислове порівняння)
# Example: Node must have "gpu" label with any value
matchExpressions:
- key: gpu
operator: Exists
# Example: Node must NOT be in zone us-east-1c
matchExpressions:
- key: topology.kubernetes.io/zone
operator: NotIn
values:
- us-east-1c

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

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

Спорідненість і антиспорідненість Подів: розміщення навантажень відносно інших Подів

Розділ «Спорідненість і антиспорідненість Подів: розміщення навантажень відносно інших Подів»

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

Правила міжподової спорідненості використовують labelSelector для ідентифікації Подів-сусідів і topologyKey для визначення того, що означає «поряд» чи «нарізно». З topologyKey: kubernetes.io/hostname доменом є нода. З topology.kubernetes.io/zone доменом є зона. Планувальник дивиться на наявні Поди, що відповідають селектору, знаходить їхні ноди, читає топологічні мітки цих нод, а потім вирішує, чи дозволена нода-кандидат.

apiVersion: v1
kind: Pod
metadata:
name: web-pod
spec:
affinity:
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels:
app: cache
topologyKey: kubernetes.io/hostname # Same node
containers:
- name: web
image: nginx

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

Антиспорідненість Подів — поширений шаблон високої доступності, особливо з Деплойментами та StatefulSet’ами. Наведене нижче правило каже, що Под не повинен плануватися на ноду, яка вже має Под із міткою app=web. У разі використання з обов’язковою семантикою воно забезпечує один відповідний Под на топологічний домен. Це може бути саме правильним для трьох реплік на трьох нодах і саме неправильним для чотирьох реплік на трьох нодах.

apiVersion: v1
kind: Pod
metadata:
name: web-pod
labels:
app: web
spec:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels:
app: web
topologyKey: kubernetes.io/hostname
containers:
- name: web
image: nginx

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

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

topologyKeyЗначення
kubernetes.io/hostnameТа сама нода
topology.kubernetes.io/zoneТа сама зона доступності
topology.kubernetes.io/regionТой самий регіон
┌────────────────────────────────────────────────────────────────┐
│ Anti-Affinity with Different topologyKeys │
│ │
│ topologyKey: kubernetes.io/hostname │
│ -> Pods spread across nodes (one per node) │
│ │
│ Node1: [web-1] Node2: [web-2] Node3: [web-3] │
│ │
│ topologyKey: topology.kubernetes.io/zone │
│ -> Pods spread across zones (one per zone) │
│ │
│ Zone-A Zone-B Zone-C │
│ [web-1] [web-2] [web-3] │
│ Node1,Node2 Node3,Node4 Node5,Node6 │
│ │
└────────────────────────────────────────────────────────────────┘

Для завдань CKA найшвидша діагностика — перекласти топологічний ключ простою мовою. kubernetes.io/hostname означає «та сама або інша нода». topology.kubernetes.io/zone означає «та сама або інша зона». Якщо питання просить репліки між нодами, ймовірним ключем є ім’я хоста. Якщо воно просить зони доступності, використовуйте мітку зони й переконайтеся, що кластер справді має цю мітку на нодах у лабораторній.

Антиспорідненість Подів — це не те саме, що обмеження топологічного розподілу. Антиспорідненість відповідає на питання, чи має домен-кандидат уже відповідний Под, і може забезпечити сувору відсутність. Обмеження топологічного розподілу рахують, скільки відповідних Подів існує в кожному домені, та обмежують перекіс (skew) між доменами. Якщо ви хочете шість реплік між трьома зонами, антиспорідненість за зоною може заблокувати четвертий Под, тоді як топологічний розподіл може дозволити дві на зону з перекосом один або нуль залежно від поточного розподілу.

Taint’и та толерантності: відштовхування, дозвіл і виселення

Розділ «Taint’и та толерантності: відштовхування, дозвіл і виселення»

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

┌────────────────────────────────────────────────────────────────┐
│ Taints and Tolerations │
│ │
│ Node with taint: gpu=true:NoSchedule │
│ ┌─────────────────────────────────────────────┐ │
│ │ │ │
│ │ Regular Pod: blocked from node │ │
│ │ │ │
│ │ Pod with matching allowed on node │ │
│ │ toleration: │ │
│ │ │ │
│ └─────────────────────────────────────────────┘ │
│ │
└────────────────────────────────────────────────────────────────┘

Ефект taint’у контролює, наскільки сильним є відштовхування. NoSchedule запобігає плануванню нових Подів, якщо вони не толерують taint, тоді як наявні Поди лишаються. PreferNoSchedule просить планувальник уникати ноди, але дозволяє розміщення за потреби. NoExecute сильніший, бо він також впливає на наявні Поди; Поди без відповідної толерантності можуть бути виселені з ноди. Це робить NoExecute корисним для деякої поведінки, що залежить від стану ноди, і небезпечним як випадковий інструмент обслуговування.

ЕфектПоведінка
NoScheduleПоди не плануватимуться (наявні Поди лишаються)
PreferNoScheduleМ’яка версія — уникати, але дозволяти за потреби
NoExecuteВиселити наявні Поди, запобігти новому плануванню

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

Terminal window
# Add taint to node
kubectl taint nodes worker-1 gpu=true:NoSchedule
# View taints
kubectl describe node worker-1 | grep Taints
# Remove taint (note the minus sign)
kubectl taint nodes worker-1 gpu=true:NoSchedule-
# Multiple taints
kubectl taint nodes worker-1 dedicated=ml:NoSchedule
kubectl taint nodes worker-1 gpu=nvidia:NoSchedule

Синтаксис толерантності віддзеркалює taint, але семантика все ж є дозволом, а не приваблюванням. Под із толерантністю до gpu=true:NoSchedule може працювати на ноді з GPU-taint’ом, але він також може працювати на ноді без taint’у, якщо інше правило його не обмежує. Це часте джерело несподіванок. Якщо вимога каже «лише GPU-Поди мають працювати на GPU-нодах», додайте taint до GPU-нод і толерантності до GPU-Подів. Якщо вона каже «GPU-Поди мають працювати на GPU-нодах», також додайте спорідненість до нод за GPU-міткою.

apiVersion: v1
kind: Pod
metadata:
name: gpu-pod
spec:
tolerations:
- key: "gpu"
operator: "Equal"
value: "true"
effect: "NoSchedule"
containers:
- name: cuda-app
image: nvidia/cuda

Оператори толерантностей простіші за оператори спорідненості. Equal вимагає, щоб ключ, значення та ефект збігалися з taint’ом. Exists може збігатися з будь-яким значенням для заданого ключа, а толерантність лише з operator: Exists може толерувати всі taint’и. Ця форма зі знаком підстановки потужна й має бути рідкісною для застосункових Подів, бо вона обходить намір ізоляції taint’ів у межах кластера.

ОператорЗначення
EqualКлюч і значення мають збігатися
ExistsКлюч існує (збігається будь-яке значення)
# Match specific value
tolerations:
- key: "gpu"
operator: "Equal"
value: "nvidia"
effect: "NoSchedule"
# Match any value for key
tolerations:
- key: "gpu"
operator: "Exists"
effect: "NoSchedule"
# Tolerate all taints (wildcard)
tolerations:
- operator: "Exists"

Кілька поширених шаблонів кластера навмисно використовують taint’и. Ноди площини управління часто отримують taint, щоб звичайні застосункові Поди не конкурували з компонентами площини управління. GPU-ноди отримують taint, щоб не-GPU-Поди не марнували спеціалізоване апаратне забезпечення. Пакетні чи spot-ноди можуть отримати taint, щоб там працювали лише навантаження, спроєктовані для переривання. Точні ключі є рішенням локальної політики, але механіка планування однакова.

СценарійПриклад taint’у
GPU-нодиgpu=true:NoSchedule
Виділені нодиdedicated=team-a:NoSchedule
Ноди площини управлінняnode-role.kubernetes.io/control-plane:NoSchedule
Ноди, що спорожнюютьсяnode.kubernetes.io/unschedulable:NoSchedule

Коли ви усуваєте несправності з taint’ами, читайте подію планувальника буквально. Повідомлення на кшталт node(s) had untolerated taint означає, що додавання CPU не допоможе, зміна образу не допоможе й перезапуск Пода не допоможе. Або Подові потрібна відповідна толерантність, або taint потрібно прибрати, або навантаження не повинне намагатися використати цю ноду. Правильне виправлення залежить від політики, а не лише від синтаксису.

Обмеження топологічного розподілу: рівномірний розподіл без жорсткості «один на домен»

Розділ «Обмеження топологічного розподілу: рівномірний розподіл без жорсткості «один на домен»»

Обмеження топологічного розподілу були спроєктовані для поширеного випадку, коли ви хочете збалансований розподіл між доменами збою, а не сувору унікальність «один на домен». Планувальник рахує відповідні Поди в кожному топологічному домені та оцінює, чи перевищило б розміщення нового Пода значення maxSkew. З maxSkew: 1 між зонами Kubernetes намагається тримати різницю між найбільш і найменш заповненими придатними зонами не більшою за одиницю. Це часто кращий вираз високої доступності, ніж обов’язкова антиспорідненість за зоною.

apiVersion: v1
kind: Pod
metadata:
name: spread-pod
labels:
app: web
spec:
topologySpreadConstraints:
- maxSkew: 1 # Max difference between zones
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: DoNotSchedule # Hard requirement
labelSelector:
matchLabels:
app: web
containers:
- name: nginx
image: nginx

Чотири основні поля варто запам’ятати, бо вони безпосередньо відображають поведінку планувальника. labelSelector каже Kubernetes, які Поди рахувати. topologyKey визначає домени, як-от ноди чи зони. maxSkew визначає максимально допустимий дисбаланс. whenUnsatisfiable вирішує, чи є правило жорстким фільтром з DoNotSchedule, чи м’якою перевагою оцінювання з ScheduleAnyway. Помилка в будь-якому з цих полів змінює значення всього обмеження.

ПараметрОпис
maxSkewМаксимально допустима різниця в кількості Подів між доменами
topologyKeyКлюч мітки, що визначає домени (зона, нода тощо)
whenUnsatisfiableDoNotSchedule (жорстко) або ScheduleAnyway (м’яко)
labelSelectorЯкі Поди рахувати для розподілу

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

┌────────────────────────────────────────────────────────────────┐
│ Topology Spread (maxSkew: 1) │
│ │
│ Zone A Zone B Zone C │
│ [pod][pod] [pod] [pod] │
│ Count: 2 Count: 1 Count: 1 │
│ │
│ Max difference = 2-1 = 1 <= maxSkew │
│ │
│ New pod arrives - where can it go? │
│ Zone A: 3 pods -> difference 3-1=2 > maxSkew │
│ Zone B: 2 pods -> difference 2-1=1 <= maxSkew │
│ Zone C: 2 pods -> difference 2-1=1 <= maxSkew │
│ │
└────────────────────────────────────────────────────────────────┘

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

Зупиніться й передбачте: у вас є Деплоймент із трьома репліками з обов’язковою антиспорідненістю Подів за kubernetes.io/hostname, але кластер має лише дві ноди. Третя репліка не може сплануватися, бо кожна придатна нода вже має відповідний Под. Якщо справжня мета — «розподілити якомога рівномірніше», топологічний розподіл із м’яким або ретельно обраним жорстким правилом виражає цю мету точніше, ніж обов’язкова антиспорідненість.

Є також вимір вартості. Розподіл між зонами покращує відмовостійкість, але деякі хмарні середовища стягують плату за міжзонне передавання даних або роблять міжзонний трафік повільнішим за внутрішньозонний. Це не означає, що вам слід уникати розподілу між зонами; це означає, що вам слід обирати його для застосунків без стану, реплікованих сервісів і навантажень, спроєктованих для збою зони, водночас будучи обачнішими з «балакучими» системами зберігання. Планування — це завжди надійність, вартість і продуктивність, узгоджені в YAML.

Потік ухвалення рішень планувальником і усунення несправностей Подів у стані Pending

Розділ «Потік ухвалення рішень планувальником і усунення несправностей Подів у стані Pending»

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

┌────────────────────────────────────────────────────────────────┐
│ Scheduling Decision Flow │
│ │
│ Pod Created │
│ │ │
│ ▼ │
│ Filter Nodes │
│ ├── nodeSelector matches? │
│ ├── Node affinity required matches? │
│ ├── Taints tolerated? │
│ ├── Resources available? │
│ ├── Pod anti-affinity satisfied? │
│ └── Topology spread constraints ok? │
│ │ │
│ ▼ │
│ Score Remaining Nodes │
│ ├── Node affinity preferred │
│ ├── Pod affinity preferred │
│ └── Resource optimization │
│ │ │
│ ▼ │
│ Select Highest Scoring Node │
│ │ │
│ ▼ │
│ Bind Pod to Node │
│ │
└────────────────────────────────────────────────────────────────┘

Найкраща перша команда — це зазвичай kubectl describe pod <name>, бо розділ Events містить повідомлення FailedScheduling, згенеровані планувальником. Ці повідомлення часто стискають кілька незалежних причин в один рядок, як-от недостатньо CPU на деяких нодах і нетолерований taint на іншій. Трактуйте кожну причину як окремий результат фільтра. Не припускайте, що є одна корінна причина, доки не дізнаєтеся, чи задовольняє якась нода всі вимоги одночасно.

СимптомІмовірна причинаКоманда для налагодження
Pending (без подій)Жодна нода не відповідає обмеженнямkubectl describe pod
Pending (Insufficient)Нестача ресурсівПеревірити ресурси ноди
Pending (Taints)Немає толерантності до taint’уПеревірити taint’и ноди, толерантності Пода
Pending (Affinity)Жодна нода не відповідає правилам спорідненостіСпростити/прибрати спорідненість
Terminal window
# Check pod events
kubectl describe pod <pod-name> | grep -A10 Events
# Check node labels
kubectl get nodes --show-labels
# Check node taints
kubectl describe node <node> | grep Taints
# Check node resources
kubectl describe node <node> | grep -A10 "Allocated resources"
# Verify where pods landed
kubectl get pods -o wide

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

Сценарій вправи: Под каже, що вимагає tier=frontend, віддає перевагу zone=us-east-1a і толерує frontend=true:NoSchedule, але потрапляє за межі us-east-1a. Це не автоматично баг. Правило зони є бажаним, тож його можна проігнорувати, якщо інша нода набрала вищий бал або якщо жодна придатна нода в цій зоні не проходить жорсткі фільтри. Щоб примусити зону, зробіть це правилом обов’язкової спорідненості до нод, але прийміть, що Под тоді може лишитися у стані Pending, коли в зоні бракує потужностей.

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

Шаблони та антишаблони

Розділ «Шаблони та антишаблони»

Шаблон: використовуйте мітки для стабільних можливостей нод і taint’и для політики доступу до нод. Мітка на кшталт disk=ssd каже планувальнику, чим є нода. Taint на кшталт dedicated=ml:NoSchedule каже планувальнику, кому дозволено її використовувати. Поєднання обох дає вам повний контракт для спеціальних нод: звичайні Поди відштовхуються, а схвалені Поди й толерують taint, і вимагають відповідну мітку можливості.

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

Шаблон: використовуйте обмеження топологічного розподілу для розподілу реплік між зонами чи нодами, коли вам потрібен баланс, а не унікальність. Обов’язкова антиспорідненість чудова для «ніколи дві репліки в одному домені», але вона стає крихкою, коли кількість реплік перевищує кількість доменів. Топологічний розподіл дозволяє планувальнику міркувати про перекіс, що відповідає тому, як експлуатують більшість багатореплічних сервісів. Для шести реплік між трьома зонами збалансоване розміщення по дві на зону зазвичай є бажаним результатом.

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

Антишаблон: додавання taint’ів до виділених нод без додавання позитивного правила розміщення до виділених Подів. Толерантність дозволяє Подові використати ноду, але вона не заважає Подові використовувати ноди без taint’ів. Краща альтернатива — taint плюс толерантність плюс спорідненість до нод або nodeSelector. Це поєднання відштовхує всіх інших і приваблює призначене навантаження.

Антишаблон: написання обов’язкової антиспорідненості для кожного навантаження високої доступності. Команди потрапляють у цю пастку, бо «обов’язково» звучить безпечніше за «бажано», але сувора унікальність може блокувати масштабування й розгортання. Якщо справжня мета — рівномірний розподіл, використовуйте обмеження топологічного розподілу. Якщо справжня мета — якнайкраще розділення, використовуйте бажану антиспорідненість і відстежуйте розміщення.

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

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

Структура ухвалення рішень

Розділ «Структура ухвалення рішень»

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

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

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

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

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

Остаточне рішення — чи має правило відмовляти закрито (fail closed), чи деградувати плавно. Відмовляйте закрито, коли неправильне розміщення зламало б навантаження, відкрило б захищену ноду чи скасувало б гарантію надійності. Деградуйте плавно, коли правило про вартість, перевагу, затримку чи розділення за найкращих зусиль. Це формулювання корисне в оглядах, бо воно змушує команду сказати, якій шкоді правило запобігає, а не просто обрати найобмеженіше доступне поле.

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

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

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

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

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

  • Толерантність — це не призначення. Вона дозволяє Подові потрапити на ноду з taint’ом, але не вимагає цієї ноди. Дизайни виділених нод зазвичай потребують taint’у, відповідної толерантності та позитивного правила вибору ноди.

  • Обмеження топологічного розподілу стали стабільними в Kubernetes 1.19 й лишаються основним інструментом планування Kubernetes 1.35. Вони часто краще підходять, ніж обов’язкова антиспорідненість, для багатореплічних сервісів, яким потрібне збалансоване розміщення, а не сувора унікальність.

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

ПомилкаЧому це стаєтьсяЯк це виправити
Друкарська помилка в nodeSelectorМаніфест виглядає правильним на перший погляд, але жодна нода не має точного ключа й значення.Перевірте за допомогою kubectl get nodes --show-labels, потім виправте ключ чи промаркуйте призначену ноду.
Відсутня толерантністьНода має taint NoSchedule, а Под не має відповідного дозволу, щоб пройти цей фільтр.Додайте толерантність, що збігається з ключем, значенням і ефектом, або приберіть taint, якщо нода має бути загального призначення.
Трактування толерантності як приваблюванняПод толерує taint виділеної ноди, але все одно потрапляє на ноду без taint’у.Поєднайте толерантність зі спорідненістю до нод чи nodeSelector для мітки виділеної ноди.
Неправильний topologyKeyПравило розподіляє за іменем хоста, коли наміром була зона, чи розподіляє за зоною, коли кластеру бракує міток зон.Зіставте ключ із доменом збою й підтвердьте, що кожна нода-кандидат має мітку.
NoExecute замість NoScheduleОператор хотів заблокувати нові розміщення, але випадково запустив поведінку виселення.Використовуйте NoSchedule для контролю розміщення, а спорожнення чи NoExecute — лише коли виселення є наміром.
Антиспорідненість надто сувораОбов’язкове розділення вимагає більше топологічних доменів, ніж кластер насправді має.Використовуйте бажану антиспорідненість чи обмеження топологічного розподілу з відповідним maxSkew.
Ігнорування запитів ресурсівКоманда зосереджується на мітках і taint’ах, тоді як подія також повідомляє про недостатньо CPU чи пам’яті.Прочитайте повну подію FailedScheduling й порівняйте запити Пода з придатною для виділення потужністю ноди.
Питання 1: Вашій команді потрібно, щоб Поди працювали на SSD-нодах, але також приймали NVMe-ноди. Молодший інженер використав `nodeSelector: {disk: ssd}`, що виключає NVMe-ноди. Яке правило вам слід написати й чому?

Використайте спорідненість до нод requiredDuringSchedulingIgnoredDuringExecution з operator: In і значеннями ssd та nvme. Це жорстка вимога, бо навантаження має використовувати один із цих класів дисків, але йому потрібна логіка OR, яку nodeSelector не може виразити для одного ключа. Бажане правило дозволило б ноди не-SSD і не-NVMe, що порушує вимогу. Толерантність не допомогла б, бо taint’и контролюють дозвіл на ноди, а не вибір міток дисків.

Питання 2: Под толерує `gpu=nvidia:NoSchedule`. Кластер має одну ноду з цим taint'ом, одну ноду з `dedicated=ml-team:NoSchedule` і одну ноду без taint'у. Де Под може сплануватися й яке додаткове правило змусило б його використовувати лише GPU-ноди?

Под може сплануватися на ноді з GPU-taint’ом, бо він має відповідну толерантність, і він також може сплануватися на ноді без taint’у, бо там толерантність не потрібна. Він не може сплануватися на ноді dedicated=ml-team, бо цей taint інший і лишається нетолерованим. Щоб змусити Под використовувати лише GPU-ноди, додайте спорідненість до нод чи nodeSelector для GPU-мітки, як-от gpu=nvidia. Толерантність надає дозвіл, тоді як правило мітки створює приваблювання чи жорстку вимогу.

Питання 3: Ви розгортаєте шість веб-реплік між трьома зонами з обов'язковою антиспорідненістю Подів за `topology.kubernetes.io/zone`, і пізніші репліки лишаються у стані Pending. Що пішло не так і що слід використати натомість?

Обов’язкова антиспорідненість за зоною каже, що жодні два відповідні Поди не можуть ділити зону, тож вона може спланувати щонайбільше три репліки між трьома зонами. Четверта й пізніші репліки не мають легальної зони, навіть якщо кожна зона має вільні CPU та пам’ять. Обмеження топологічного розподілу краще підходять, бо вони балансують кількість між зонами замість того, щоб забезпечувати унікальність «один на зону». З maxSkew: 1 шість реплік можуть осісти в рівномірну форму по дві на зону, коли є потужності.

Питання 4: Під час завдання CKA `kubectl describe pod` показує `0/3 nodes are available: 2 Insufficient cpu, 1 node(s) had untolerated taint`. Які дві окремі проблеми й як вирішити, яке виправлення доречне?

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

Питання 5: Деплоймент має бажану спорідненість до нод для `zone=us-east-1a`, але Под потрапляє в іншу зону. Колега каже, що Kubernetes проігнорував маніфест. Як ви поясните цю поведінку?

Бажана спорідненість до нод впливає на оцінювання, а не на придатність, тож планувальнику дозволено прив’язати Под до іншої придатної ноди. Це може статися, коли жодна нода в us-east-1a не проходить жорсткі фільтри, або коли інші фактори оцінювання переважають перевагу. Якщо зона обов’язкова для коректності, змініть правило на обов’язкову спорідненість до нод і прийміть, що Под може лишитися у стані Pending, коли в цій зоні бракує потужностей. Якщо зона є лише перевагою щодо вартості чи затримки, поточна поведінка очікувана.

Питання 6: Клієнт кешу використовує обов'язкову спорідненість Подів, щоб працювати на тій самій ноді, що й `app=cache`, але Деплоймент кешу тимчасово масштабовано до нуля. Що станеться й який дизайн був би менш крихким?

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

Питання 7: Вам потрібно захистити GPU-ноди від звичайних навантажень і також гарантувати, що GPU-навантаження потраплять туди. Яке поєднання механізмів контролю планування вам слід обрати?

Додайте taint до GPU-нод, додайте відповідні толерантності до схвалених GPU-Подів і додайте спорідненість до нод чи nodeSelector для GPU-мітки ноди. Taint відштовхує звичайні Поди, яким бракує дозволу. Толерантність дозволяє схваленим Подам пройти фільтр taint’у. Спорідненість до нод чи селектор змушує ці схвалені Поди вимагати GPU-ноди замість того, щоб лише мати дозвіл їх використовувати. Використання лише толерантності неповне, бо Поди все ще могли б працювати на нодах загального призначення без taint’у.

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

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

  1. Промаркуйте ноду й використайте nodeSelector:
Terminal window
# Get a node name
NODE=$(kubectl get nodes -o jsonpath='{.items[0].metadata.name}')
# Label the node
kubectl label node $NODE disk=ssd
# Create pod with nodeSelector
cat << EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
name: ssd-pod
spec:
nodeSelector:
disk: ssd
containers:
- name: nginx
image: nginx
EOF
# Verify placement
kubectl get pod ssd-pod -o wide
# Cleanup
kubectl delete pod ssd-pod
kubectl label node $NODE disk-

Частина B: Taint’и та толерантності

Розділ «Частина B: Taint’и та толерантності»

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

  1. Додайте taint і створіть Под із толерантністю:
Terminal window
# Taint the node
kubectl taint nodes $NODE dedicated=special:NoSchedule
# Try to create pod without toleration
kubectl run no-toleration --image=nginx
# Check - should be Pending or on different node
kubectl get pod no-toleration -o wide
# Create pod with toleration
cat << EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
name: with-toleration
spec:
tolerations:
- key: "dedicated"
operator: "Equal"
value: "special"
effect: "NoSchedule"
containers:
- name: nginx
image: nginx
EOF
# Verify placement
kubectl get pod with-toleration -o wide
# Cleanup
kubectl delete pod no-toleration with-toleration
kubectl taint nodes $NODE dedicated-

Частина C: Антиспорідненість Подів

Розділ «Частина C: Антиспорідненість Подів»

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

  1. Розподіліть Поди між нодами:
Terminal window
cat << EOF | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
name: spread-deploy
spec:
replicas: 3
selector:
matchLabels:
app: spread
template:
metadata:
labels:
app: spread
spec:
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchLabels:
app: spread
topologyKey: kubernetes.io/hostname
containers:
- name: nginx
image: nginx
EOF
# Check pod distribution
kubectl get pods -l app=spread -o wide
# Cleanup
kubectl delete deployment spread-deploy
  • Спроєктувати правило розміщення на основі nodeSelector і перевірити обрану ноду.
  • Впровадити taint і відповідну толерантність, потім чисто прибрати обидва.
  • Порівняти дозволене розміщення з обов’язковим розміщенням, пояснивши, чому толерантність сама по собі не є приваблюванням.
  • Оцінити поведінку антиспорідненості Подів, перевіривши, чи репліки розподіляються між нодами.
  • Оцінити обмеження топологічного розподілу, перевіривши розміщення реплік між маркованими зонами.
  • Діагностувати щонайменше один Под у стані Pending з подій планувальника, а не вгадуючи з самого лише маніфесту.

Тренувальні вправи

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

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

Вправа 1: nodeSelector (Ціль: 3 хвилини)

Розділ «Вправа 1: nodeSelector (Ціль: 3 хвилини)»
Terminal window
NODE=$(kubectl get nodes -o jsonpath='{.items[0].metadata.name}')
# Label node
kubectl label node $NODE env=production
# Create pod with nodeSelector (YAML matches exam-style manifests)
cat << 'EOF' | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
name: selector-test
spec:
nodeSelector:
env: production
containers:
- name: nginx
image: nginx
EOF
# Verify
kubectl get pod selector-test -o wide
# Cleanup
kubectl delete pod selector-test
kubectl label node $NODE env-

Вправа 2: Taint’и (Ціль: 5 хвилин)

Розділ «Вправа 2: Taint’и (Ціль: 5 хвилин)»
Terminal window
NODE=$(kubectl get nodes -o jsonpath='{.items[0].metadata.name}')
# Add taint
kubectl taint nodes $NODE app=critical:NoSchedule
# View taint
kubectl describe node $NODE | grep Taints
# Pod without toleration - will be Pending or elsewhere
kubectl run no-tol --image=nginx
kubectl get pod no-tol -o wide
# Pod with toleration
cat << 'EOF' | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
name: with-tol
spec:
tolerations:
- key: "app"
operator: "Equal"
value: "critical"
effect: "NoSchedule"
containers:
- name: nginx
image: nginx
EOF
kubectl get pod with-tol -o wide
# Cleanup
kubectl delete pod no-tol with-tol
kubectl taint nodes $NODE app-

Вправа 3: Спорідненість до нод (Ціль: 5 хвилин)

Розділ «Вправа 3: Спорідненість до нод (Ціль: 5 хвилин)»
Terminal window
NODE=$(kubectl get nodes -o jsonpath='{.items[0].metadata.name}')
kubectl label node $NODE size=large
cat << 'EOF' | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
name: affinity-test
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: size
operator: In
values:
- large
- xlarge
containers:
- name: nginx
image: nginx
EOF
kubectl get pod affinity-test -o wide
# Cleanup
kubectl delete pod affinity-test
kubectl label node $NODE size-

Вправа 4: Антиспорідненість Подів (Ціль: 5 хвилин)

Розділ «Вправа 4: Антиспорідненість Подів (Ціль: 5 хвилин)»
Terminal window
cat << 'EOF' | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
name: anti-affinity
spec:
replicas: 3
selector:
matchLabels:
app: anti-test
template:
metadata:
labels:
app: anti-test
spec:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels:
app: anti-test
topologyKey: kubernetes.io/hostname
containers:
- name: nginx
image: nginx
EOF
# Check distribution (each pod on different node)
kubectl get pods -l app=anti-test -o wide
# Cleanup
kubectl delete deployment anti-affinity

Вправа 5: Усунення несправностей — Под у стані Pending (Ціль: 5 хвилин)

Розділ «Вправа 5: Усунення несправностей — Под у стані Pending (Ціль: 5 хвилин)»
Terminal window
NODE=$(kubectl get nodes -o jsonpath='{.items[0].metadata.name}')
# Create impossible scenario
kubectl taint nodes $NODE impossible=true:NoSchedule
cat << 'EOF' | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
name: pending-pod
spec:
nodeSelector:
nonexistent: label
containers:
- name: nginx
image: nginx
EOF
# Diagnose
kubectl get pod pending-pod
kubectl describe pod pending-pod | grep -A10 Events
# YOUR TASK: Why is it Pending? Fix it.
# Cleanup
kubectl delete pod pending-pod
kubectl taint nodes $NODE impossible-
Рішення

Под у стані Pending з двох причин. nodeSelector вимагає nonexistent=label, якої не має жодна нода, доки ви її не додасте. Нода також має taint, який Под не толерує. Виправте сценарій, додавши відсутню мітку до призначеної ноди й додавши відповідну толерантність, або прибравши штучний taint і селектор, якщо навантаження має бути загального призначення.

Вправа 6: Виклик — Складне планування

Розділ «Вправа 6: Виклик — Складне планування»

Створіть Под, який має працювати на нодах із міткою tier=frontend, віддає перевагу нодам із міткою zone=us-east-1a і толерує taint frontend=true:NoSchedule. Цей виклик поєднує жорстку вимогу до ноди, м’яку перевагу розміщення та дозвіл taint’у. Після того як він спланується, поясніть, яка частина маніфесту була б відповідальною, якби Под був у стані Pending.

Terminal window
# YOUR TASK: Create this pod
Рішення
Terminal window
NODE=$(kubectl get nodes -o jsonpath='{.items[0].metadata.name}')
kubectl label node $NODE tier=frontend zone=us-east-1a
kubectl taint nodes $NODE frontend=true:NoSchedule
cat << 'EOF' | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
name: complex-schedule
spec:
tolerations:
- key: "frontend"
operator: "Equal"
value: "true"
effect: "NoSchedule"
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: tier
operator: In
values:
- frontend
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
preference:
matchExpressions:
- key: zone
operator: In
values:
- us-east-1a
containers:
- name: nginx
image: nginx
EOF
kubectl get pod complex-schedule -o wide
# Cleanup
kubectl delete pod complex-schedule
kubectl label node $NODE tier- zone-
kubectl taint nodes $NODE frontend-

Вправа 7: Топологічний розподіл (Ціль: 5 хвилин)

Розділ «Вправа 7: Топологічний розподіл (Ціль: 5 хвилин)»

Застосуйте обмеження топологічного розподілу з теоретичного розділу й перевірте, що репліки app: web розподіляються між маркованими зонами. Ця вправа найкраще працює в лабораторній з кількома нодами (minikube з --nodes 3, kind або хмарний кластер); на кластері з однією нодою Поди все ще плануються, але перекіс зон не має значення.

Terminal window
# Label nodes with zone topology (adjust node count to your lab)
NODE1=$(kubectl get nodes -o jsonpath='{.items[0].metadata.name}')
NODE2=$(kubectl get nodes -o jsonpath='{.items[1].metadata.name}')
kubectl label node $NODE1 topology.kubernetes.io/zone=zone-a --overwrite
kubectl label node $NODE2 topology.kubernetes.io/zone=zone-b --overwrite
# Deploy replicas with topology spread (same labels/key as the theory example)
cat << 'EOF' | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-spread
spec:
replicas: 4
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
spec:
topologySpreadConstraints:
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: ScheduleAnyway
labelSelector:
matchLabels:
app: web
containers:
- name: nginx
image: nginx
EOF
# Verify spread — check NODE column and zone labels on those nodes
kubectl get pods -l app=web -o wide
kubectl get nodes -L topology.kubernetes.io/zone
# Expected: when two zones have eligible nodes, no zone should hold more
# than one extra replica compared to the least-populated zone (maxSkew: 1)
# Cleanup
kubectl delete deployment web-spread
kubectl label node $NODE1 topology.kubernetes.io/zone-
kubectl label node $NODE2 topology.kubernetes.io/zone-

Перевірка засвоєння

Розділ «Перевірка засвоєння»

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

У цьому прикладі zone — це користувацька мітка ноди, яку ви застосовуєте в лабораторній (наприклад, kubectl label node ... zone=us-west-1a). У хмарних кластерах краще використовуйте відомий ключ topology.kubernetes.io/zone — ноди мають надавати мітки зон, щоб будь-який із цих ключів робив внесок в оцінювання бажаної спорідненості.

Модуль 2.7: ConfigMaps та Secrets — Керування конфігурацією застосунків.