🐳 Розділ 14 · Питання #10

Що таке Node в Kubernetes

Якщо Control Plane — це «мозок» та адміністрація кластера, то ноди — це його «робочі руки». Кластер об'єднує обчислювальні ресурси десятків таких нод (процесори, оперативну пам'...


🟢 Junior Level

30-секундна відповідь

Node (Вузол) — це окремий фізичний сервер або віртуальна машина в кластері Kubernetes, на якій фактично запускаються та виконуються поди із застосунками.

Якщо Control Plane — це «мозок» та адміністрація кластера, то ноди — це його «робочі руки». Кластер об’єднує обчислювальні ресурси десятків таких нод (процесори, оперативну пам’ять, диски) в єдиний спільний пул.


Наочна аналогія з життя

Уявіть міжнародний вантажний порт:

  • Control Plane (Диспетчерська): приймає замовлення, розподіляє вантажі по кораблях, стежить за розкладом.
  • Node (Вантажний корабель): володіє фізичною вантажопідйомністю (CPU та RAM).
  • Pod (Контейнери з вантажем): розміщуються на палубі корабля.
  • Капітан корабля (kubelet): стежить за кріпленням вантажів, перевіряє цілісність і безперервно доповідає в диспетчерську про вільне місце на палубі.

3 обов’язкові компоненти на кожному Worker Node

  1. kubelet — головний агент вузла. Приймає від kube-apiserver завдання на запуск подів, спілкується з середовищем виконання контейнерів, виконує перевірки працездатності (Health Checks) та відправляє сигнали життєздатності вузла (Node Heartbeats).
  2. kube-proxy — мережевий агент. Налаштовує правила фільтрації пакетів і маршрутизації (iptables або IPVS) у ядрі операційної системи хоста, відповідаючи за балансування трафіку сервісів.
  3. Container Runtime (CRI) — низькорівневе середовище виконання контейнерів. Починаючи з Kubernetes 1.24 (після повного видалення dockershim), стандартом індустрії є containerd та CRI-O.

🟡 Middle Level

Бюджет ресурсів ноди: Capacity vs Allocatable

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

\[\mathbf{Allocatable} = \mathbf{Capacity} - \mathbf{kube\text{-}reserved} - \mathbf{system\text{-}reserved} - \mathbf{eviction\text{-}threshold}\]
+--------------------------------------------------------------------------+
|                        Node Capacity (Наприклад: 32 GiB RAM)              |
|                                                                          |
|  [ kube-reserved ]   -> Пам'ять для процесів K8s (kubelet, containerd)   |
|  [ system-reserved ] -> Пам'ять для ядра ОС та системних демонів (sshd)   |
|  [ hard-eviction ]   -> Захисний буфер безпеки (поріг виселення)         |
| ──────────────────────────────────────────────────────────────────────── |
|  [ Allocatable ]     -> ТІЛЬКИ ЦЯ ЧАСТИНА доступна для планування подів! |
+--------------------------------------------------------------------------+

[!IMPORTANT] Планувальник kube-scheduler оперує виключно значенням Allocatable. Якщо сума requests уже запущених подів вичерпала Allocatable, планувальник ніколи не призначить новий под на цю ноду, навіть якщо фізично пам’ять вільна.


Статуси та умови ноди (Node Conditions)

Kubelet безперервно повідомляє стан здоров’я ноди через прапорці в об’єкті Node:

Condition Нормальне значення Опис
Ready True Нода здорова, kubelet активний і готовий приймати поди.
MemoryPressure False Якщо True — вільна пам’ять впала нижче порогу, планування блокується.
DiskPressure False Якщо True — диск переповнений (бракує місця під образи або логи).
PIDPressure False Якщо True — вичерпано ліміт доступних ідентифікаторів процесів Linux PID.
NetworkUnavailable False Якщо True — маршрутизацію CNI-мережі для ноди не налаштовано.

Обслуговування нод: Cordon і Drain

Перед проведенням технічних робіт на фізичному сервері (оновлення ядра Linux, заміна RAM):

  1. kubectl cordon <node> (Очеплення):
    • Позначає ноду як SchedulingDisabled.
    • Нові поди на ноду не плануються, але вже запущені продовжують спокійно працювати.
  2. kubectl drain <node> --ignore-daemonsets --delete-emptydir-data (Опустошення):
    • Блокує планування та дбайливо виселяє (evict) усі працюючі поди на інші здорові ноди кластера через механізм Graceful Shutdown.
  3. kubectl uncordon <node> (Зняття очеплення):
    • Повертає сервер у стрій після завершення робіт та перезавантаження.

🔴 Senior Level

Керування розміщенням: Taints, Tolerations та Node Affinity

Senior-інженер налаштовує гнучкий розподіл навантажень між спеціалізованими серверами (наприклад, вузли з GPU або вузли під бази даних).

   [ Звичайний Pod ]                    [ GPU Pod з Toleration ]
           │                                        │
           ▼                                        ▼
    ❌ Відштовхується!                      🟢 Дозволено розміщення!
           │                                        │
           └─────────────► [ Node з Taint ] ◄───────┘
                           gpu=true:NoSchedule

1. Taints (Мітки відштовхування на ноді)

Задаються на вузлі: kubectl taint nodes worker-gpu dedicated=gpu:NoSchedule.

  • NoSchedule: нові поди без відповідної толерантності не будуть заплановані на вузол.
  • PreferNoSchedule: планувальник спробує уникнути розміщення, але за дефіциту ресурсів посадить под сюди.
  • NoExecute: поди без толерантності негайно виселяються з вузла, навіть якщо вони вже працювали на ньому!

2. Tolerations (Толерантність у маніфесті Pod)

Дозволяє поду ігнорувати мітку відштовхування:

tolerations:
- key: "dedicated"
  operator: "Equal"
  value: "gpu"
  effect: "NoSchedule"

3. Node Affinity (Притягнення пода до ноди)

  • requiredDuringSchedulingIgnoredDuringExecution: жорстке правило (hard rule). Якщо ноди з міткою topology.kubernetes.io/zone=eu-west-1a немає, под залишиться в статусі Pending.
  • preferredDuringSchedulingIgnoredDuringExecution: м’яке правило (soft rule) з виставленням вагових коефіцієнтів пріоритету.

Архітектура Node Leases (Оптимізація etcd)

Історично (до K8s 1.14) кожен kubelet кожні 10 секунд відправляв повне оновлення об’єкта NodeStatus в kube-apiserver.

  • У кластері на 2000 нод це викликало величезне навантаження на etcd та мережу (розмір об’єкта Node з десятками образів та адрес — десятки кілобайт).
  • Сучасна архітектура: розділення статусу:
    • Важкий об’єкт Node оновлюється рідко (тільки за зміни умов, наприклад раз на хвилину).
    • Швидкий heartbeat передається через надлегковажний об’єкт Lease у просторі імен kube-node-lease кожні 10 секунд. Якщо оренда Lease не продовжується понад 40 секунд, Controller Manager переводить ноду в статус NotReady.

Kubelet Eviction Manager (Витіснення подів при дефіциті пам’яті)

Коли на ноді спрацьовує жорсткий поріг evictionHard: memory.available < 100Mi:

  1. Kubelet не чекає реакції Control Plane, а ухвалює рішення локально прямо на ноді.
  2. Kubelet ранжує поди за споживанням ресурсів відносно їхніх requests та класу якості обслуговування:
    • Спочатку виселяються поди BestEffort, що споживають найбільше пам’яті.
    • Потім виселяються поди Burstable, які перевищили свій requests.memory.
    • Поди Guaranteed виселяються в останню чергу, тільки якщо на ноді більше немає інших подів.

4 Tricky Questions

1. Чому команда kubectl drain <node> за замовчуванням завершується помилкою, якщо на ноді є поди з томами emptyDir або поди без контролера (Bare Pods)?

Відповідь: Команда drain захищає користувача від випадкової безповоротної втрати даних:

  1. Томи emptyDir: дані в emptyDir існують виключно на диску конкретної ноди. Видалення пода знищить ці дані назавжди. Тому drain вимагає явного підтвердження прапорцем --delete-emptydir-data.
  2. Поди без контролера (Bare Pods): якщо под створено безпосередньо (kind: Pod), а не через Deployment або StatefulSet, то при його видаленні його нікому перестворити на іншій ноді. Він просто зникне. Для їх видалення потрібен прапорець --force.
  3. DaemonSet: поди DaemonSet зобов’язані працювати на кожній ноді. Команда drain не може їх виселити на сусідню ноду (там уже запущено власний екземпляр), тому вимагає прапорця --ignore-daemonsets.

2. Що станеться з працюючими подами на ноді, якщо процес kubelet на цьому сервері аварійно завершиться (впаде або зависне)?

Відповідь:

  1. Процеси контейнерів продовжать працювати! Запущені контейнери керуються середовищем виконання containerd, яке працює як незалежний демон. Трафік через kube-proxy продовжить надходити до подів.
  2. Нода перестане оновлювати свій об’єкт Lease в kube-apiserver. Через 40 секунд Controller Manager надасть ноді стан Ready=Unknown.
  3. Запуститься таймер pod-eviction-timeout (за замовчуванням 5 хвилин).
  4. Якщо kubelet не відновиться за 5 хвилин, контролер почне створювати замісні копії подів на інших нодах кластера.
  5. Однак вихідні поди на зламаній ноді продовжать виконуватися доти, доки kubelet не оживе і не отримає команду на їх видалення.

3. У чому принципова різниця між ефектами Taints NoSchedule та NoExecute?

Відповідь:

  • NoSchedule: впливає тільки на майбутні поди в момент роботи планувальника kube-scheduler. Поди, які вже були запущені на цій ноді до додавання taint, продовжать спокійно працювати і не будуть зачеплені.
  • NoExecute: впливає і на нові, і на вже працюючі поди! Якщо на ноду повісити taint з ефектом NoExecute, планувальник миттєво ініціює видалення (eviction) усіх подів на цій ноді, у яких немає явного toleration для цієї мітки (або виселить їх після закінчення таймера tolerationSeconds).

4. Чим технологія автомасштабування вузлів Karpenter (від AWS) архітектурно перевершує класичний Kubernetes Cluster Autoscaler?

Відповідь:

  • Cluster Autoscaler (Класичний підхід): прив’язаний до груп автомасштабування хмари (AWS Auto Scaling Groups — ASG). При появі Pending подів він збільшує розмір визначеної групи вузлів фіксованого типу (наприклад, додає ще один m5.large). Процес займає 3–5 хвилин, а вузли часто витрачаються неефективно.
  • Karpenter: працює безпосередньо з API хмарного провайдера в обхід ASG. Він аналізує сукупні вимоги всіх Pending подів (CPU, GPU, архітектура ARM/x86, зони доступності) і за 30–45 секунд замовляє рівно один ідеально відповідний сервер (Right-Sized Instance або Spot-інстанс), заощаджуючи до 50% бюджету на хмару.

🎯 Шпаргалка для інтерв’ю

Головне про Node

  • Суть: робочий сервер кластера (фізичний або ВМ).
  • 3 обов’язкові демони: kubelet (агент пода), kube-proxy (L4-мережа), containerd / CRI-O (рантайм).
  • Ресурси: $\text{Allocatable} = \text{Capacity} - \text{Зарезервовано під K8s і ОС}$. Планування йде тільки за Allocatable!
  • Сервісні команди: cordon (заборона нових), drain (плавне виселення), uncordon (повернення в стрій).
  • Taints / Tolerations: механізм відштовхування невідповідних подів (NoSchedule, NoExecute).
  • Node Affinity: притягнення подів до серверів за мітками (SSD, GPU, Availability Zone).

Порівняння ефектів Taints

| Ефект Taint | Нові поди | Вже працюючі поди | | :— | :— | :— | | NoSchedule | ❌ Не пускає | 🟢 Не чіпає (продовжують працювати) | | PreferNoSchedule | 🟡 Намагається не пускати (м’яке правило)| 🟢 Не чіпає | | NoExecute | ❌ Не пускає | 🔴 Негайно виселяє з ноди! |

Червоні прапорці на співбесіді (Чого говорити не можна)

  • ❌ «Node і Pod — це одне й те саме» — Node це сервер (хост), Pod це контейнерна група, що працює на сервері.
  • ❌ «Уся пам’ять сервера доступна подам» — частина пам’яті жорстко зарезервована під kubelet, containerd та ОС (Allocatable < Capacity).
  • ❌ «Падіння kubelet відразу вбиває запущені контейнери» — контейнери продовжують працювати в containerd, але втрачається керування нодою.

Пов’язані питання