Модуль 1.3: Pods — атомарна одиниця
Модуль 1.3: Pods — атомарна одиниця
Розділ «Модуль 1.3: Pods — атомарна одиниця»Складність: [СЕРЕДНЯ] Час на проходження: 60-75 хвилин Пререквізити: Модуль 1.2 (Основи kubectl)
Що ви зможете зробити
Розділ «Що ви зможете зробити»- Діагностувати складні помилки ініціалізації та виконання Pod (такі як
CrashLoopBackOff,ImagePullBackOff,CreateContainerConfigError,CreateContainerErrorтаOOMKilled), використовуючи вбудовані інструменти інспекції Kubernetes та глибоке розуміння кодів завершення Linux. - Проектувати просунуті архітектури багатоконтейнерних Pod, впроваджуючи патерни sidecar, ambassador, adapter та init container для вирішення реальних завдань логування, проксіювання та початкового налаштування без модифікації коду застарілих застосунків.
- Оцінювати вплив запитів (requests) та лімітів (limits) ресурсів на алгоритми планування Pod, стабільність вузлів (nodes) та механізми Completely Fair Scheduler (CFS) ядра Linux і Out Of Memory (OOM) killer.
- Класифікувати Pods за відповідними класами Quality of Service (QoS) (Guaranteed, Burstable, BestEffort) та передбачати, як Kubelet буде пріоритезувати їх під час дефіциту ресурсів вузла та подій витіснення (eviction).
- Порівнювати імперативне створення через командний рядок та декларативне керування через YAML маніфести, обґрунтовуючи перевагу індустрії до декларативних робочих процесів у GitOps-орієнтованих CI/CD конвеєрах.
- Впроваджувати спільне сховище (через
emptyDirтаhostPath) та стратегії мережевої взаємодії всередині Pod з низькою затримкою між кількома пов’язаними контейнерами, що знаходяться в одних і тих самих мережевих та IPC просторах імен (namespaces). - Налаштовувати, оптимізувати та оцінювати Readiness, Liveness та Startup проби (probes) для керування переходами станів Pod, запобігання передчасному завершенню застарілих застосунків, що повільно запускаються, та гарантування маршрутизації трафіку в продакшені без простоїв (zero-downtime).
- Формулювати безпечні архітектури Pod, використовуючи
securityContextдля впровадження принципів найменших привілеїв, відмови від можливостей (capabilities) ядра Linux, вимоги файлових систем тільки для читання та пом’якшення потенційних вразливостей виходу з контейнера.
Чому це важливо
Розділ «Чому це важливо»Наприкінці 2021 року платформа електронної комерції — назвемо її «GlobalTradeX» — намагалася модернізувати свою інфраструктуру, перенісши монолітну систему керування запасами в Kubernetes напередодні сезону розпродажів до Чорної п’ятниці. Прагнучи здійснити міграцію за принципом «lift-and-shift» з мінімальним рефакторингом коду, команда невірно витлумачила фундаментальну концепцію Kubernetes: вони ставилися до Kubernetes Pods як до традиційних віртуальних машин.
Замість того, щоб розділити свою архітектуру на окремі мікросервіси, вони створили єдину специфікацію Pod, що містила сім різних контейнерів застосунків. Всередині цього Pod жили веб-фронтенд на Next.js, бекенд-API на Java Spring Boot, кеш Redis в оперативній пам’яті, фоновий воркер Celery, агент логування Fluentd, експортер метрик Prometheus і процес cron-job. Вони вважали, що діють ефективно, тримаючи все разом, як це було на їхніх застарілих фізичних серверах.
Коли почався сплеск трафіку у Чорну п’ятницю, контейнер Java бекенд-API почав споживати багато ресурсів CPU для обробки транзакцій бази даних. Оскільки всі сім компонентів ПЗ були заблоковані всередині однієї абстракції Pod, Kubernetes не міг масштабувати Java API незалежно від інших компонентів.
Щоб впоратися з навантаженням на CPU, Horizontal Pod Autoscaler (HPA) був змушений дублювати весь Pod як єдине ціле. Він почав запускати непотрібні копії кешу Redis, фронтенд-сервера та фонових воркерів лише для того, щоб отримати більше потужності для Java API. Це швидке масштабування миттєво вичерпало фізичну пам’ять кластера.
Протягом години робочі вузли EC2 почали виходити з ладу через системне вичерпання ресурсів і паніку Out Of Memory. Система обліку товарів «лягла». З’єднання з зовнішньою базою даних були вичерпані дубльованими фоновими воркерами. Компанія втратила приблизно 2,4 мільйона доларів за чотири години простою, а інженерна команда провела вихідні, вручну відкочуючи розгортання до своєї застарілої інфраструктури віртуальних машин.
Цей архітектурний збій виник через нерозуміння того, чим є Pod, а чим — ні. Pod — це не віртуальна машина. Це не звалище для розрізнених компонентів застосунку. Pod — це найменша, атомарна, неподільна одиниця виконання та планування в екосистемі Kubernetes.
Опанувавши Pods, ви отримаєте можливість прогнозовано планувати робочі навантаження, ізолювати збої та проектувати мікросервіси, які масштабуються саме тоді і так, як це потрібно. Ви дізнаєтеся, як судово-медично налагоджувати збої, коли контейнер відмовляється запускатися, як пов’язувати допоміжні процеси без шкоди для модульності застосунку та як розмовляти декларативною мовою YAML, якої очікує керуюча площина (control plane) Kubernetes. Ви перейдете від сприйняття Kubernetes як «чорної скриньки» до розуміння його як оркестратора примітивів Linux.
Коротка історія: від chroot до Pods
Розділ «Коротка історія: від chroot до Pods»Перш ніж ми зануримося в механізми Linux, які живлять Kubernetes Pod, ми маємо встановити історичний контекст, який зумовив його винахід. Еволюція Pod не відбулася у вакуумі; вона стала результатом десятиліть спроб і помилок в ізольованих обчисленнях.
У 1979 році концепція ізоляції процесів народилася з впровадженням системного виклику chroot у Unix версії 7. chroot дозволяв системному адміністратору назавжди змінити видимий кореневий каталог для конкретного запущеного процесу та його нащадків. Це було революційно, оскільки процес більше не міг фізично переміщатися по файловій системі для доступу до інших каталогів, що забезпечувало елементарну форму ізоляції.
Перенесемося у 2000 рік: поява FreeBSD Jails довела концепцію chroot до логічного максимуму. Jails ізолювали не лише файлову систему, але й мережевий стек та вигляд процесів, створюючи «віртуальні машини», які використовували одне ядро. За цим послідували Solaris Zones у 2004 році, які додали в ці середовища розширене керування ресурсами.
У 2008 році спільнота Linux інтегрувала Linux Control Groups (cgroups) в основну гілку ядра. Розроблені інженерами Google, cgroups забезпечили облік, необхідний для обмеження того, скільки ресурсів CPU, пам’яті, дискового вводу-виводу та пропускної здатності мережі може споживати певна група процесів. У поєднанні з Linux Namespaces (просторами імен) були готові фундаментальні будівельні блоки для сучасних контейнерів Linux.
У 2013 році Docker демократизував цю технологію Linux. Docker взяв складні в налаштуванні низькорівневі примітиви та обгорнув їх у елегантний інтерфейс командного рядка (CLI) та формат пакування образів. Раптом будь-який розробник зміг створити ізольований контейнер Linux за лічені секунди.
Однак, коли компанії почали масштабувати Docker у виробничих центрах обробки даних, виникла нова складна проблема: оркестрація. Керування десятками тисяч окремих контейнерів Docker, розкиданих по фізичних серверах, було логістичним кошмаром. Google, використовуючи контейнеризовані навантаження всередині компанії протягом десятиліття, знала, що керування поодинокими контейнерами є структурно недосконалим.
Інженери Google розуміли, що в розподілених системах застосунки майже ніколи не існують в ідеальній ізоляції. Веб-серверу потрібен пересилач логів. Кешу потрібен збирач метрик. Проксі-серверу потрібен рівень шифрування. Якщо ви оркеструєте окремі контейнери, ви змушуєте ці компоненти спілкуватися через маршрутизовану публічну мережу, що створює затримки та ризики для безпеки.
Щоб вирішити це, коли Google відкрила код Kubernetes у 2014 році, вони представили своє найблискучіше архітектурне рішення: Pod. Вони відмовилися від ідеї планування «сирих» контейнерів. Замість цього вони винайшли Pod — логічну пісочницю, розроблену для розміщення згуртованої групи взаємозалежних контейнерів, які мають працювати на одній хост-машині, спільно використовуючи ту саму IP-адресу та локальний дисковий том.
Що таке Pod насправді (Глибоке занурення в примітиви Linux)
Розділ «Що таке Pod насправді (Глибоке занурення в примітиви Linux)»Коли ви вперше дізнаєтеся про контейнеризацію, вам кажуть, що контейнер — це ізольований процес, що працює на спільній хост-машині Linux. Він має власну виділену файлову систему, ізольований мережевий стек і обмежений доступ до базового CPU та пам’яті. Тож чому Kubernetes не керує контейнерами безпосередньо? Чому інженери створили цей рівень абстракції під назвою «Pod»?
Щоб зрозуміти це рішення, ми маємо поглянути на те, як ядро Linux досягає ізоляції контейнерів. «Контейнер» не є фізичною конструкцією; це ілюзія, створена комбінацією Linux Kernel Namespaces та Control Groups (cgroups).
Namespaces (Простори імен) обмежують те, що конкретний процес може бачити. Вони створюють ілюзію, що процес володіє операційною системою одноосібно.
- PID (Process ID) Namespace: Гарантує, що процеси всередині одного контейнера не можуть бачити процеси в іншому контейнері або надсилати їм сигнали. Для контейнеризованого застосунку його основний процес завжди виглядає як Process ID (PID) 1.
- Network Namespace: Надає ізольований мережевий стек, включаючи власні віртуальні мережеві інтерфейси, унікальну IP-адресу, таблиці маршрутизації та повний простір портів (0-65535).
- Mount Namespace: Дає контейнеру його власну ізольовану ієрархію файлової системи, окрему від кореневого каталогу
/хост-машини. - UTS (UNIX Time-Sharing) Namespace: Дозволяє ізольованому контейнеру мати власне незалежне ім’я хоста (hostname) та доменне ім’я.
- IPC (Inter-Process Communication) Namespace: Ізолює спільні сегменти пам’яті та механізми міжпроцесної взаємодії, запобігаючи перегляду пам’яті між контейнерами.
- User Namespace: Дозволяє процесу мати привілеї root всередині контейнера, залишаючись при цьому непривілейованим користувачем у хост-системі.
Control Groups (cgroups) обмежують те, що процес може використовувати. Вони діють як бухгалтери ядра Linux.
- Вони гарантують, що контейнер не зможе спожити більше відведеного йому кванта часу CPU або фізичної пам’яті RAM. Якщо контейнер перевищує свій ліміт memory cgroup, ядро завершує його роботу, щоб захистити хост.
Якби Kubernetes керував лише окремими контейнерами, співпраця між процесами з низькою затримкою була б складною, неефективною та небезпечною. Уявіть основний контейнер застосунку, якому потрібен допоміжний процес для стиснення його лог-файлів, або проксі-контейнер для шифрування вихідного мережевого трафіку.
Якби це були окремі контейнери, керовані Kubernetes безпосередньо, вони мали б різні IP-адреси, ізольовані файлові системи та не мали б високоефективного способу спілкування через локальну міжпроцесну взаємодію (IPC) або спільну пам’ять без проходження через фізичний мережевий стек.
Pod вирішує цю архітектурну проблему, створюючи спільне середовище. Pod — це логічна оболонка, яка обгортає один або кілька фізичних контейнерів. Контейнери всередині одного Pod спільно використовують певні простори імен, тримаючи інші ізольованими.
+----------------------------------------------------+| Pod Sandbox || || Спільні простори імен: Network, IPC, UTS || Спільні ресурси: IP-адреса, томи || || +----------------+ +----------------+ || | контейнер pause| | Контейнер заст.| || | (Тримає Net/IPC)|<------>| (Mount/PID NS) | || +----------------+ +----------------+ || ^ ^ || | | || +----------------+ +----------------+ || | Контейнер Sidecar| | Спільний том | || | (Mount/PID NS) |<------>| (emptyDir/PVC) | || +----------------+ +----------------+ |+----------------------------------------------------+Зокрема, контейнери всередині одного Pod спільно використовують:
- Network Namespace: Вони мають ту саму IP-адресу та простір портів. Вони можуть спілкуватися один з одним за допомогою інтерфейсу зворотного зв’язку
localhost. - IPC Namespace: Вони можуть використовувати SystemV IPC або черги повідомлень POSIX для прямого спілкування у спільній пам’яті, минаючи мережевий стек TCP/IP.
- UTS Namespace: Вони мають те саме внутрішнє ім’я хоста.
- Томи зберігання: Вони можуть монтувати ті самі фізичні або віртуальні томи для одночасного читання та запису спільних файлів.
Однак вони не ділять автоматично PID namespace або Mount namespace. Кожен контейнер зберігає власну ізольовану файлову систему зі специфічними бінарними файлами застосунку, запобігаючи «пеклу залежностей».
Секретний контейнер «pause»: неоспіваний герой Kubernetes
Розділ «Секретний контейнер «pause»: неоспіваний герой Kubernetes»Щоб утримувати ці спільні простори імен Linux разом, не покладаючись на самі контейнери застосунків, Kubernetes робить дещо хитре. Коли планувальник (Scheduler) призначає Pod робочому вузлу, Kubelet вузла дає вказівку Container Runtime Interface (CRI) створити пісочницю Pod. Він не просто запускає ваш контейнер застосунку.
Замість цього, першою дією середовища виконання є запуск крихітного, невидимого контейнера, відомого як контейнер pause (або «інфраструктурний контейнер»).
Єдине завдання контейнера pause — запросити та отримати простори імен network, IPC та UTS у ядра Linux, а потім «заснути», виконавши системний виклик pause(). Ваші реальні контейнери застосунків потім запускаються і приєднуються до вже існуючих просторів імен контейнера pause.
Уявіть, якби у вашому Pod був лише один контейнер застосунку, відповідальний за утримання мережевого простору імен. Якби цей застосунок завершився зі збоєм, простір імен Linux був би знищений, IP-адреса вивільнена, а при перезапуску застосунку йому було б призначено зовсім нову IP-адресу. Ця постійна нестабільність зруйнувала б мережу кластера.
Оскільки контейнер pause буквально нічого не робить, крім сну, він ніколи не «падає». Він тримає мережевий простір імен відкритим нескінченно довго, діючи як якір. Це означає, що якщо ваш контейнер застосунку впаде і перезапуститься сотню разів, IP-адреса Pod ніколи не зміниться.
Зупиніться та подумайте: Якщо контейнер А в багатоконтейнерному Pod запускає веб-сервер на порту 8080, а контейнер Б у тому ж Pod намагається запуститися і зайняти порт 8080 для метрик, що станеться і чому? (Оскільки обидва контейнери використовують спільний мережевий простір імен, інтерфейс loopback та IP-адресу, надану контейнером
pause, контейнер Б впаде з помилкоюAddress already in use. У багатоконтейнерному Pod номери портів мають бути унікальними для всіх контейнерів, так само як вони мають бути унікальними на одному ноутбуці розробника.)
PLEG (Pod Lifecycle Event Generator) та цикл синхронізації Kubelet
Розділ «PLEG (Pod Lifecycle Event Generator) та цикл синхронізації Kubelet»Щоб зрозуміти, як працюють Pods, ми маємо поглянути на демон Kubelet, що працює на кожному робочому вузлі. Kubelet — це менеджер скінченних автоматів. Його завдання — порівнювати «бажаний стан» Pods, призначених його вузлу, з реальною дійсністю контейнерів, запущених через локальне середовище CRI.
Якщо API Server очікує Pod з двома запущеними контейнерами, але CRI повідомляє, що працює лише один, бо інший впав, Kubelet втручається, щоб повернути фізичну реальність до бажаного стану.
Раніше Kubelet активно опитував (polling) базовий демон Docker щосекунди, запитуючи: «Чи працює контейнер X?». У кластері з тисячами Pods на вузол такий обсяг опитувань знищував продуктивність CPU вузла.
Для вирішення цієї проблеми масштабованості архітектори Kubernetes впровадили Pod Lifecycle Event Generator (PLEG).
PLEG — це вбудована підсистема у бінарному файлі Kubelet. Замість того, щоб опитувати кожен контейнер безпосередньо, PLEG використовує низькорівневі функції ядра Linux для підписки на асинхронний потік системних подій, що надходять від середовища CRI.
Коли контейнер падає, CRI надсилає подію з низькою затримкою безпосередньо до PLEG. PLEG реєструє зміну стану, перетворює подію на Pod Lifecycle Event і додає її до основної черги циклу синхронізації Kubelet. Kubelet «прокидається», зчитує подію, оцінює restartPolicy для Pod і наказує CRI перебудувати та перезапустити процес контейнера.
Ця подієво-орієнтована архітектура гарантує, що Kubelet реагує на збої Pod за мілісекунди без накладних витрат на опитування. Розуміння PLEG є критично важливим для адміністраторів. Якщо PLEG відчуває дефіцит ресурсів CPU або перевантажений штормом подій падіння контейнерів, він реєструватиме помилки PLEG is not healthy. Коли PLEG виходить з ладу, Kubelet «сліпне»; він перестає повідомляти про статуси Pod назад до керуючої площини, що спричиняє збої вузлів.
Чому Pods взагалі існують? Просунуті архітектурні патерни
Розділ «Чому Pods взагалі існують? Просунуті архітектурні патерни»Якщо статистична телеметрія показує, що понад 90% часу Pod містить лише один контейнер застосунку, навіщо вимагати цю абстракцію? Відповідь криється в решті 10% критичних випадків, де пов’язані процеси архітектурно необхідні для вирішення складних проблем розподілених систем.
1. Патерн Sidecar (Доповнення без модифікації)
Розділ «1. Патерн Sidecar (Доповнення без модифікації)»Патерн Sidecar використовується дуже широко. Уявіть, що вам доручено модернізувати застарілий застосунок, написаний на C++. Він жорстко прописує запис своїх операційних логів у локальний файл за шляхом /var/log/legacy-app/audit-trail.log. Однак ваш стек спостережуваності Kubernetes використовує Fluent-bit для збору логів виключно зі стандартного виводу (stdout) і їх маршрутизації до централізованого кластера Datadog.
Замість болісного переписування вихідного коду C++, ви можете розгорнути контейнер Sidecar поруч із ним у тій самій пісочниці Pod.
Основний контейнер запускає застарілий застосунок C++ і пише свої логи у спільну точку монтування тому emptyDir. Контейнер Sidecar монтує той самий том і запускає простий скрипт оболонки: tail -f /shared-logs/audit-trail.log. Оскільки Sidecar виводить результати команди tail безпосередньо у свій власний ізольований потік stdout, демон логування кластера може ідеально зафіксувати застарілі логи.
2. Патерн Ambassador (Мережеве проксіювання)
Розділ «2. Патерн Ambassador (Мережеве проксіювання)»Патерн Ambassador використовує допоміжний контейнер для маршрутизації та захисту мережевого трафіку до і від основного контейнера застосунку. Уявіть, що вашому основному застосунку потрібно підключитися до зовнішньої бази даних, і з’єднання вимагає взаємного обміну сертифікатами TLS (mTLS) та пулінгу з’єднань.
Замість того, щоб вбудовувати цю складну мережеву логіку безпосередньо в код застосунку, ви розгортаєте контейнер Ambassador (наприклад, проксі-сервер Envoy) всередині Pod.
Ваш застосунок робить звичайний текстовий незашифрований локальний HTTP-запит безпосередньо до localhost:5432. Контейнер Ambassador, що активно слухає цей порт loopback, перехоплює трафік, шифрує його на льоту за допомогою mTLS, керує пулом з’єднань і безпечно пересилає корисне навантаження до зовнішньої бази даних. Цей патерн є основою, на якій побудовані сучасні сервісні сітки (Service Meshes).
3. Патерн Adapter (Нормалізація даних)
Розділ «3. Патерн Adapter (Нормалізація даних)»Патерн Adapter використовується для стандартизації та нормалізації різноманітних виводів кількох застосунків. Припустимо, ви керуєте десятьма різними мікросервісами. Всі вони видають метрики продуктивності, але у несумісних форматах (JSON, XML та текст). Ви хочете моніторити їх усі за допомогою сучасного стека Prometheus, який очікує стандартний текстовий формат.
Замість переписування коду в масштабах усієї компанії, ви розгортаєте контейнер Adapter у Pod кожного застосунку. Контейнер Adapter опитує ендпоінт метрик основного застосунку через localhost, транслює формат на льоту і відкриває новий ендпоінт на новому порту, який Prometheus може легко зчитувати.
4. Init Containers (Суворе початкове налаштування)
Розділ «4. Init Containers (Суворе початкове налаштування)»Іноді застосунку потрібна підготовка середовища, перш ніж він зможе безпечно завантажитися. Можливо, потрібно виконати міграцію схеми бази даних або почекати, поки зовнішній API підтвердить свою доступність.
Init Containers — це спеціалізовані контейнери, які запускаються перед тим, як основним контейнерам застосунку буде дозволено стартувати. Вони виконуються послідовно. Init Container 1 має успішно завершитися (код завершення 0), перш ніж почнеться Init Container 2. Тільки після завершення всіх Init-контейнерів Kubelet дозволить запуск основних контейнерів застосунку.
Якщо Init-контейнер виходить з ладу, Kubernetes буде неодноразово перезапускати Pod до успішного завершення Init-контейнера. Це забезпечує надійний механізм затримки запуску застосунку до виконання всіх залежностей. Init-контейнери також можуть містити потужні інструменти (такі як aws-cli або бінарні файли для міграції БД), які ви не хочете пакувати в образ основного контейнера застосунку.
5. Ephemeral Containers (Налагодження у живому продакшені)
Розділ «5. Ephemeral Containers (Налагодження у живому продакшені)»Потужною функцією в Kubernetes є Ефемерний контейнер (Ephemeral Container). Раніше, якщо Pod у продакшені «падав», а образ був зібраний без оболонки (shell) з міркувань безпеки (наприклад, образи distroless), ви не могли використовувати kubectl exec для його налагодження.
Ефемерні контейнери вирішують це. Вони дозволяють адміністратору підключити тимчасовий контейнер до Pod, який вже запущений. Ви можете підключити образ для налагодження (наприклад, busybox або netshoot) до працюючого distroless Pod. Оскільки ефемерний контейнер приєднується до існуючих мережевих та IPC просторів імен Pod, ви можете запустити tcpdump всередині ефемерного контейнера, щоб прослуховувати живий мережевий трафік вашого застосунку в реальному часі.
Мережа в Pod: CNI та розподіл IP
Розділ «Мережа в Pod: CNI та розподіл IP»На відміну від Docker, запущеного на вашому ноутбуці, де мережа є відносно простою, Kubernetes працює в розподілених кластерах фізичних серверів. Як Pod отримує унікальну IP-адресу, яка може спілкуватися з будь-яким іншим Pod на будь-якому іншому вузлі?
Ця магія забезпечується за допомогою Container Network Interface (CNI). Сам Kubernetes не займається розподілом IP-адрес або фізичною маршрутизацією. Замість цього Kubernetes суворо визначає програмний інтерфейс (специфікацію CNI) і покладається на сторонніх постачальників плагінів (таких як Calico, Cilium, Flannel або AWS VPC CNI) для виконання маніпуляцій з мережею.
+-----------+ +------------+ +---------------+| Планувальник| -----> | Kubelet | -----> | CRI Runtime || (Призначає)| | (На вузлі) | | (pause cont.) |+-----------+ +------------+ +---------------+ | v +------------+ | CNI Plugin | (напр., Calico, VPC CNI) +------------+ | 1. Виділяє IP з IPAM | 2. Створює пару veth | 3. Налаштовує iptables/маршрути v +-------------------+ | Pod Net Namespace | (IP призначено eth0) +-------------------+Коли планувальник призначає ваш Pod робочому вузлу, відбувається наступна послідовність:
- Kubelet наказує локальному середовищу CRI створити пісочницю Pod через контейнер
pause. - Перш ніж Kubelet дозволить запуск будь-яких контейнерів застосунку, він фізично викликає налаштований бінарний файл CNI, що знаходиться на вузлі.
- Плагін CNI оцінює свою конфігурацію та безпечно виділяє унікальну IP-адресу з CIDR-блоку кластера, призначеного для цього вузла.
- Плагін CNI виконує мережеві команди Linux для динамічного створення пари віртуальних інтерфейсів ethernet (пара
veth). Він вставляє один кінець париvethвсередину мережевого простору імен Pod (призначаючи йому запитану IP-адресу якeth0) і прив’язує протилежний кінець до мережевого мосту хост-машини. - Плагін CNI програмує правила
iptablesвузла або карти eBPF, щоб гарантувати, що мережеві пакети, призначені для цієї IP-адреси, маршрутизуються з фізичної мережевої карти безпосередньо в інтерфейс Pod. - CNI успішно повертає IP-адресу очікуваному Kubelet. Kubelet записує цю IP в об’єкт API Status для Pod і дозволяє CRI розпочати завантаження основних контейнерів застосунку.
Зупиніться та подумайте: Якщо бінарний файл плагіна CNI відсутній або неправильно налаштований на робочому вузлі, що станеться, коли планувальник призначить новий Pod цьому вузлу? Чи запустяться контейнери? (Kubelet не зможе налаштувати мережевий простір імен, оскільки не зможе виконати плагін CNI для виділення IP-адреси або налаштування маршрутизації. Pod залишиться в стані
ContainerCreatingабоNetworkPluginNotReady, і контейнерам застосунку не буде дозволено завантажитися, доки мережа не буде встановлена.)
Просунуте планування Pod: Taints, Tolerations та Affinity
Розділ «Просунуте планування Pod: Taints, Tolerations та Affinity»Планувальник (Scheduler) Kubernetes — це потужний алгоритмічний двигун. За замовчуванням він намагається рівномірно розподілити ваші Pods по кластеру для максимального використання ресурсів. Однак часто вам потрібен точний контроль над тим, де розміщуються ваші атомарні одиниці. У вас можуть бути спеціалізовані вузли з дорогими GPU, NVMe-дисками або вузли, призначені для зон комплаєнсу.
1. Taints вузлів та Tolerations для Pod (Відштовхування)
Розділ «1. Taints вузлів та Tolerations для Pod (Відштовхування)»Уявіть, що у вас є робочі вузли з GPU, призначені виключно для машинного навчання. За замовчуванням планувальник може випадковим чином призначити звичайні веб-сервери безпосередньо на ці вузли, марнуючи обчислювальні ресурси.
Щоб вирішити це, ви застосовуєте Taint (заплямування) до об’єкта Node. Taint відштовхує Pods. Якщо ви застосуєте taint accelerator=nvidia-gpu:NoSchedule до вузла, планувальник відмовиться розміщувати будь-який стандартний Pod на цьому хості.
Щоб дозволити Pod для машинного навчання заплануватися на цей вузол, ви налаштовуєте Toleration (допуск) всередині YAML цього Pod. Toleration математично заявляє: «Я допускаю taint accelerator=nvidia-gpu». Коли планувальник оцінює вузол, він розпізнає валідний Toleration і дозволяє Pod приземлитися.
2. Node Affinity (Притягування)
Розділ «2. Node Affinity (Притягування)»У той час як Taints відштовхують навантаження, Node Affinity дозволяє вам притягувати Pods до конкретних вузлів на основі логічних виразів. Це архітектурне вдосконалення порівняно з елементарним параметром nodeSelector.
Node Affinity має два основні режими роботи:
requiredDuringSchedulingIgnoredDuringExecution: Це жорстке правило. Якщо ви вимагаєте, щоб Pod ОБОВ’ЯЗКОВО був запланований на вузол з міткоюzone=us-east-1a, і жоден вузол з цією міткою не має достатньо ресурсів CPU, Pod назавжди залишиться в станіPending.preferredDuringSchedulingIgnoredDuringExecution: Це гнучка, «м’яка» перевага. Ви можете вказати планувальнику: «Я волію, щоб цей Pod потрапив на вузол зdisk-type=nvme, але якщо таких немає, сміливо плануй його на звичайні вузли».
3. Pod Affinity та Anti-Affinity (Розподіл для високої доступності)
Розділ «3. Pod Affinity та Anti-Affinity (Розподіл для високої доступності)»Вершиною контролю планування є Pod Affinity та Pod Anti-Affinity. Замість розрахунку розміщення на основі міток вузлів, ці правила розраховують розміщення суворо на основі міток інших Pods, які вже працюють на вузлі.
- Pod Anti-Affinity (Розподіл): Це критично важливо для екстремальної високої доступності. Якщо ви розгортаєте три репліки свого веб-застосунку
payment-gateway, ви не хочете, щоб планувальник розмістив усі три Pods на одному фізичному робочому вузлі. НалаштувавшиpodAntiAffinityпроти міткиapp=payment-gatewayз ключем топологіїkubernetes.io/hostname, ви змушуєте планувальник розподілити три Pods по різних фізичних серверах. - Pod Affinity (Спільне розміщення): І навпаки, якщо у вас є веб-застосунок, який робить десять тисяч мікрозапитів на секунду до Pod кешування, ви хочете, щоб ці два окремі Pods були розміщені на одному фізичному вузлі. Налаштувавши
podAffinity, ви гарантуєте, що вони динамічно приземляться разом, безпечно спілкуючись через локальний інтерфейс loopback.
| Функція | Ціль | Дія | Випадок використання |
|---|---|---|---|
| Taints & Tolerations | Вузли (Taint), Pods (Tolerate) | Відштовхує Pods від вузлів | Виділення вузлів під специфічні навантаження (напр., GPU) |
| Node Affinity | Pods | Притягує Pods до конкретних вузлів | Гарантія запуску Pod у певних зонах або на специфічному залізі |
| Pod Affinity | Pods | Притягує Pods до інших Pods | Спільне розміщення тісно пов’язаних сервісів для зменшення затримок |
| Pod Anti-Affinity | Pods | Відштовхує Pods від інших Pods | Розподіл реплік по хостах/зонах для високої доступності |
Безпека Pod: загартування атомарної одиниці
Розділ «Безпека Pod: загартування атомарної одиниці»Потужність контейнеризації нерозривно пов’язана з ризиками безпеки. Оскільки контейнери спільно використовують базове ядро Linux хоста, серйозна вразливість може дозволити зловмиснику, який зламав Pod веб-застосунку, вийти за межі контейнера, отримати root-доступ до фізичного робочого вузла і почати діяти далі для компрометації всього кластера.
Щоб запобігти цьому, Kubernetes надає Security Context (контекст безпеки) — набір глибоко вбудованих конфігурацій ядра Linux, що застосовуються безпосередньо до специфікації YAML Pod або контейнера.
Загартований Security Context включає:
runAsNonRoot: true: Це налаштування забороняє середовищу виконання запускати процес застосунку від імені користувачаroot(UID 0). Якщо точка входу (entrypoint) Dockerfile помилково намагається запуститися від root, Kubelet відмовиться запускати контейнер.readOnlyRootFilesystem: true: Критичний контроль безпеки. Він примусово монтує рівень файлової системи контейнера як «тільки для читання». Якщо зловмисник успішно використає вразливість віддаленого виконання коду (RCE), він не зможе завантажити шкідливий файл або змінити бінарні скрипти. (Примітка: Ви повинні продумано змонтувати томemptyDirспеціально в/tmp, якщо вашому застосунку потрібен простір для тимчасових файлів).allowPrivilegeEscalation: false: Це налаштування змінює бітno_new_privs. Воно гарантує, що жоден дочірній процес, створений контейнером, не зможе отримати вищі привілеї, ніж його батьківський процес, роблячи шкідливіsetuidбінарні файли марними.capabilities: drop: ["ALL"]: За замовчуванням контейнери Linux зберігають підмножину привілеїв root, згрупованих у «можливості» (capabilities), як-отCAP_NET_RAW. Загартований застосунок у продакшені рідко потребує цих можливостей. Явно відмовившись від усіх можливостей, ви суттєво зменшуєте поверхню потенційної атаки.
Зупиніться та подумайте: Ви налаштували
runAsNonRoot: trueу маніфесті Pod. Однак Dockerfile, використаний для створення образу, закінчується командоюUSER root. Що станеться, коли Kubernetes спробує запустити цей Pod? (Kubelet перевірить метадані образу і побачить, що процес має намір запуститися від UID 0 (root). Оскільки специфікація Pod явно забороняє це черезrunAsNonRoot: true, Kubelet відмовиться запускати контейнер, видавши помилкуCreateContainerConfigErrorі не дозволивши Pod запуститися.)
Сховище в Pods: томи та постійність
Розділ «Сховище в Pods: томи та постійність»Pods за своєю природою ефемерні, що означає, що будь-який файл, записаний безпосередньо у кореневу файлову систему контейнера, буде втрачений у момент падіння контейнера або витіснення (eviction) Pod. Для збереження стану або спільного використання даних між контейнерами в одному Pod Kubernetes вводить концепцію Томів (Volumes).
Том — це, по суті, каталог, доступний для контейнерів у Pod. Тип носія, що стоїть за цим каталогом, залежить виключно від конкретного налаштованого типу тому (Volume Type).
1. emptyDir (Ефемерний простір для тимчасових файлів)
Розділ «1. emptyDir (Ефемерний простір для тимчасових файлів)»Том emptyDir створюється в той самий момент, коли Pod призначається вузлу. Спочатку він порожній. Всі контейнери всередині Pod можуть однаково читати та писати в ті самі файли в томі emptyDir. Коли Pod видаляється з вузла, дані в emptyDir назавжди стираються. Це ідеально підходить для тимчасового простору або спільного використання логів із sidecar.
2. hostPath (Небезпечний доступ до вузла)
Розділ «2. hostPath (Небезпечний доступ до вузла)»Том hostPath примусово монтує файл або каталог з файлової системи базового фізичного хост-вузла у ваш Pod. Це небезпечно. Якщо ви змонтуєте /var/run/docker.sock у Pod, цей Pod отримає повний root-контроль над усім вузлом. hostPath рідко використовується у безпечних продакшн-середовищах, за винятком високопривілейованих системних демонів, яким потрібен прямий доступ до логів на рівні вузла.
3. PersistentVolumes (Постійність даних)
Розділ «3. PersistentVolumes (Постійність даних)»Для справжньої стійкості даних (наприклад, файлу бази даних PostgreSQL) ви маєте використовувати PersistentVolumeClaims (PVCs). PVC — це явний запит на постійне сховище. Коли Pod використовує PVC, Kubernetes виділяє зовнішній блок-пристрій зберігання (наприклад, том AWS EBS) і приєднує його до робочого вузла. Навіть якщо Pod буде знищено, фізичний хмарний диск залишиться недоторканим. Коли Pod буде переплановано на зовсім інший вузол, Kubernetes безшовно приєднає його до нового вузла, зберігши дані назавжди.
Життєвий цикл Pod, скінченний автомат та проби
Розділ «Життєвий цикл Pod, скінченний автомат та проби»Pod — це за своєю суттю ефемерна сутність. Він народжується, живе, виконує свій обов’язок і вмирає. Коли Pod помер, він завершений; він ніколи не воскресає. Якщо на робочому вузлі стається паніка ядра (kernel panic), Pods, що працювали на цьому конкретному вузлі, втрачаються назавжди. Контролери Kubernetes (такі як Deployments) мають втрутитися, щоб створити абсолютно нові Pods на заміну мертвим.
Щоб по-справжньому зрозуміти, що Pod робить зараз на системному рівні, ви маєте знати його чіткі фази життєвого циклу:
- Pending: Специфікація Pod була офіційно прийнята API-сервером Kubernetes і записана в базу даних
etcd. Однак один або кілька його контейнерів ще не були створені середовищем виконання. Ця фаза включає час очікування на виконання алгоритмів bin-packing планувальником та час завантаження образів контейнерів. Якщо Pod «застряг» у Pending на години, це проблема обмежень планування або помилка авторизації при завантаженні образу. - Running: Pod був прив’язаний до конкретного вузла. Контейнер
pauseзакріпив мережеву пісочницю. Всі контейнери були успішно ініціалізовані. Принаймні один контейнер все ще активно працює або перебуває в процесі запуску. - Succeeded: Всі контейнери в Pod успішно завершили роботу (повернувши код завершення Linux рівно 0), і політика перезапуску вказує, що вони не будуть перезапущені. Ця фаза характерна для разових завдань, cron-завдань або процесів пакетної обробки.
- Failed: Всі контейнери в Pod завершили роботу, і принаймні один контейнер завершився зі збоєм (повернувши код завершення, відмінний від нуля, або був примусово завершений OOM killer).
- Unknown: Справжній стан Pod не вдалося надійно отримати керуючій площині Kubernetes. Це майже завжди пов’язано зі збоєм мережевого зв’язку між API-сервером та конкретним демоном Kubelet на вузлі.
Стан Pod та проби: справжня міра готовності
Розділ «Стан Pod та проби: справжня міра готовності»Хоча високорівнева «Фаза» (Phase) надає загальний огляд існування Pod, Kubernetes також суворо відстежує дуже специфічні Стани (Conditions), які дають глибоке уявлення про готовність Pod обслуговувати живий трафік:
PodScheduled: Чи був Pod призначений робочому вузлу планувальником?Initialized: Чи успішно завершили всі послідовні Init-контейнери свої завдання з кодом 0?ContainersReady: Чи повністю завантажилися всі основні контейнери застосунків?Ready: Чи офіційно готовий Pod бути доданим до балансувальника навантаження та обслуговувати HTTP-запити?
Цілком можливо, що Pod перебуває у фазі Running, але має стан Ready, встановлений у False. Це трапляється, якщо застосунок всередині контейнера активно працює як процес Linux, але йому потрібно 60 секунд, щоб завантажити свій контекст Spring Boot та встановити пули з’єднань.
Для керування цим процесом Kubernetes використовує Проби (Probes) — активні перевірки стану, що систематично виконуються Kubelet проти ваших контейнерів:
Pod стартує | v+-------------------+| Startup Probe | ---> (Провал) ---> Перезапуск контейнера+-------------------+ | (Успіх) v+-------------------+ +-------------------+| Liveness Probe | ---> | Readiness Probe | ---> (Провал) ---> Вилучення з балансувальника| (Працює постійно) | | (Працює постійно) |+-------------------+ +-------------------+ | (Провал) | (Успіх) v vПерезапуск контейнера Додавання до балансувальника- Liveness Probes: «Чи не заблокований застосунок і чи не “завис” він?». Kubelet перевіряє, чи живий застосунок. Якщо Liveness-проба неодноразово провалюється, Kubelet перезапускає процес контейнера, намагаючись очистити блокування та відновити сервіс.
- Readiness Probes: «Чи повністю готовий застосунок приймати трафік користувачів?». Kubelet перевіряє, чи закінчив застосунок завантаження. Якщо Readiness-проба провалюється, контейнер не перезапускається. Замість цього стан
Readyдля Pod встановлюється уFalse, а IP-адреса Pod видаляється з усіх сервісів Kubernetes та хмарних балансувальників. Трафік відводиться до здорових Pods, доки проба знову не стане успішною. - Startup Probes: «Чи закінчив застосунок, що повільно завантажується, свою початкову послідовність старту?». Ця проба вимикає всі перевірки Liveness та Readiness, доки сама не стане успішною. Це запобігає передчасному вбивству Kubelet-ом застарілого застосунку, бо його Liveness-проба спрацювала швидше, ніж він встиг завантажитися.
Ці проби налаштовуються за допомогою специфічних параметрів:
initialDelaySeconds: Скільки секунд чекати після старту контейнера перед запуском першої проби.periodSeconds: Як часто виконувати пробу.failureThreshold: Скільки разів поспіль проба має провалитися, перш ніж Kubernetes вживе заходів.successThreshold: Скільки разів поспіль провалена проба має бути успішною, щоб знову вважатися здоровою.
Анатомія специфікації Pod: головний маніфест
Розділ «Анатомія специфікації Pod: головний маніфест»У Kubernetes ви точно визначаєте, як має виглядати архітектура кластера, за допомогою структурованих маніфестів YAML. Маніфест Pod має чотири критичні розділи верхнього рівня: apiVersion, kind, metadata та spec.
Розглянемо специфікацію Pod корпоративного рівня для продакшену:
apiVersion: v1kind: Podmetadata: name: financial-processor-pod namespace: payments-prod labels: app: payment-gateway environment: production tier: backend security-zone: pci-dss annotations: prometheus.io/scrape: "true" prometheus.io/port: "8443"spec: restartPolicy: Always serviceAccountName: processor-vault-accessor priorityClassName: mission-critical-high nodeSelector: disk-type: nvme-ssd compliance: pci-dss-certified tolerations: - key: "dedicated" operator: "Equal" value: "payments" effect: "NoSchedule" affinity: podAntiAffinity: requiredDuringSchedulingIgnoredDuringExecution: - labelSelector: matchExpressions: - key: app operator: In values: - payment-gateway topologyKey: "kubernetes.io/hostname" volumes: - name: tmp-scratch-data emptyDir: sizeLimit: 1Gi initContainers: - name: vault-bootstrap image: hashicorp/vault-agent:1.12 command: ["/bin/sh", "-c", "vault pull-secrets > /shared/secrets.env"] volumeMounts: - name: tmp-scratch-data mountPath: /shared containers: - name: main-processor image: registry.example.com/payment-processor:v2.4.1 imagePullPolicy: IfNotPresent command: ["/app/start-processor.sh"] ports: - containerPort: 8443 name: https-metrics protocol: TCP securityContext: runAsUser: 1000 runAsGroup: 3000 runAsNonRoot: true readOnlyRootFilesystem: true allowPrivilegeEscalation: false capabilities: drop: - ALL volumeMounts: - name: tmp-scratch-data mountPath: /var/scratch livenessProbe: httpGet: path: /health/live port: 8443 initialDelaySeconds: 30 periodSeconds: 15 failureThreshold: 3 readinessProbe: httpGet: path: /health/ready port: 8443 initialDelaySeconds: 15 periodSeconds: 10 successThreshold: 2 resources: requests: cpu: "500m" memory: "256Mi" limits: cpu: "1000m" memory: "512Mi"Розбір складної майстер-специфікації
Розділ «Розбір складної майстер-специфікації»- Metadata:
nameунікально ідентифікує Pod у йогоnamespace. Міткиlabelsє критично важливими для архітектури Kubernetes. Це пари ключ-значення, що використовуються для організації та вибору ресурсів. Без точних міток сервіси Kubernetes не мали б математичного способу динамічно вибирати, на які Pods вони маршрутизують трафік.annotationsсхожі на мітки, але використовуються для метаданих, що не ідентифікують об’єкт і часто зчитуються зовнішніми інструментами інфраструктури. - nodeSelector та Tolerations: Це вказівки для планувальника.
nodeSelectorвимагає розміщення Pod тільки на вузлах, що мають точно відповідні мітки. Блокtolerationsдозволяє цьому Pod ігнорувати Taint (заплямування) вузла. - affinity: Цей блок
podAntiAffinityзмушує планувальник гарантувати, що жодні два Podspayment-gatewayніколи не будуть заплановані на одній фізичній хост-машині. Це гарантує високу доступність. - serviceAccountName: Явно надає Pod специфічну криптографічну ідентичність, дозволяючи йому динамічно автентифікуватися в API Kubernetes або у великих хмарних провайдерів без жорсткого кодування статичних облікових даних у образі контейнера.
- securityContext: Цей блок загартовує контейнер на рівні ядра Linux. Він змушує застосунок працювати як непривілейований користувач, забороняє процесу роботу від імені
root, відмовляється від УСІХ можливостей ядра Linux і монтує всю файлову систему контейнера як «Тільки для читання», гарантуючи, що зловмисники не зможуть завантажити шкідливі файли. - Volumes: Ми визначаємо том
emptyDirз суворим лімітом розміру 1 ГіБ. Це тимчасовий каталог, що динамічно створюється при призначенні Pod вузлу. Він існує лише доти, доки Pod перебуває на цьому вузлі, і безпечно ділиться між усіма контейнерами. - Resource Requests та Limits: Це найважливіший розділ для стабільності кластера та запобігання каскадним збоям вузлів.
Важливість запитів (Requests) та лімітів (Limits)
Розділ «Важливість запитів (Requests) та лімітів (Limits)»Коли ви просите Kubernetes запланувати Pod, алгоритм bin-packing Планувальника (Scheduler) має математично довести, чи достатньо на вузлі фізичного простору для його розміщення.
- Запити (Requests) (Гарантія): Це саме те, що контейнер гарантовано отримає від кластера. Якщо контейнер запитує
256Miпам’яті, планувальник розмістить цей Pod виключно на вузлі, який має принаймні256Miнерозподіленої пам’яті. Це використовується виключно для розрахунків планування. - Ліміти (Limits) (Жорстка стеля): Це максимальний поріг, який контейнеру дозволено використовувати через cgroups ядра Linux.
- CPU Limits: Якщо контейнер намагається спожити більше CPU, ніж його ліміт, механізм Completely Fair Scheduler (CFS) ядра активно дроселює (уповільнює) застосунок. Контейнер не впаде, але відчує значні затримки.
- Memory Limits: Якщо контейнер намагається виділити більше фізичної пам’яті, ніж його ліміт, ядро хоста негайно завершує основний процес застосунку через OOM (Out Of Memory) Killer, що призводить до падіння контейнера та коду виходу
137.
Чи має Pod ліміти/запити ДЛЯ ОБОХ CPU та пам'яті? | +-- ТАК --> Чи дорівнюють Запити Лімітам? | | | +-- ТАК --> [ Guaranteed ] | | | +-- НІ --> [ Burstable ] | +-- НІ ---> Чи має він БУДЬ-ЯКІ запити чи ліміти? | +-- ТАК --> [ Burstable ] | +-- НІ --> [ BestEffort ]На основі того, як ви налаштуєте ці два значення, Kubernetes класифікує ваш Pod за одним із трьох класів Quality of Service (QoS):
- Guaranteed: Pod встановлює і запити, і ліміти для CPU та пам’яті, і вони точно рівні між собою. Це Pods найвищого пріоритету. Вони будуть вбиті останніми, якщо на вузлі закінчиться пам’ять.
- Burstable: Pod має визначені запити, які суворо менші за його ліміти. Йому гарантовано базовий рівень, але він може «вистрілити» (burst) до свого ліміту, якщо вузол має вільні ресурси. Якщо на вузлі закінчиться пам’ять, Pods класу Burstable, що перевищують свої запити, вбиваються перед Guaranteed Pods.
- BestEffort: Pod має нульові запити та нульові ліміти. Йому дозволено використовувати будь-які вільні ресурси, але при найменшому тиску на пам’ять вузла Pods класу BestEffort завершуються негайно для захисту системи.
Спробуйте самі: Погляньте на наступну специфікацію: CPU Request: 250m, CPU Limit: 500m, Memory Request: 512Mi, Memory Limit: 512Mi. Який клас QoS призначить Kubernetes цьому Pod? (Kubernetes призначить клас QoS
Burstable. Хоча запит і ліміт пам’яті рівні, запит CPU суворо менший за ліміт CPU. Щоб Pod отримав класGuaranteed, і для CPU, і для пам’яті запити мають бути ідеально рівними лімітам.)
Імперативне проти декларативного керування
Розділ «Імперативне проти декларативного керування»Існує два чітких, філософськи протилежних способи надати вказівку Kubernetes щодо керування Pod.
Імперативне керування передбачає введення команд безпосередньо у ваш термінал, явно вказуючи API Kubernetes, яку конкретну дію виконати прямо зараз.
kubectl run my-nginx-test --image=nginx:alpine --port=80 --labels=env=dev
Цей підхід швидкий і чудово підходить для генерації шаблонів YAML через «сухий запуск» (dry-run). Однак це антипатерн для продакшн-середовищ. Якщо молодший інженер випадково видалить Pod, не залишиться жодного історичного запису про те, як він був створений. Це не підлягає контролю версій, аудиту або повторенню у сценарії аварійного відновлення.
Декларативне керування передбачає написання детального файлу YAML, що визначає точний стан, якого ви бажаєте, і прохання до Kubernetes автономно привести фізичну реальність у відповідність до вашого файлу.
kubectl apply -f pod-manifest.yaml
Це загальноприйнятий галузевий стандарт. Файл YAML надійно фіксується у репозиторії Git (практика, відома як GitOps). Будь-які зміни в інфраструктурі мають проходити через формальний запит на злиття (pull request) та рецензування коду колегами. Якщо весь дата-центр згорить, ви просто повторно застосуєте репозиторій файлів YAML до нового кластера, і ваша архітектура буде відновлена автономно.
Історія з фронту: Ефемерний привид у машині
Розділ «Історія з фронту: Ефемерний привид у машині»В одному великому міжнародному інформаційному агентстві під час надзвичайної події інженер з надійності сайтів (SRE) вручну оновив образ контейнера критичного Pod кешування за допомогою швидкої імперативної команди (kubectl set image pod/cache-pod cache=redis:6.2-alpine). Він миттєво виправив баг, відновив роботу сервісу і пішов додому на вихідні.
Однак у неділю вранці апаратний збій вузла спричинив автоматичне витіснення імперативного Pod кешування та його примусове перепланування на здоровий вузол. Оскільки імперативна зміна інженера була повністю ефемерною і ніколи не була збережена в декларативних маніфестах YAML, кластер автоматично взяв старе, бажне визначення образу безпосередньо з Git-репозиторію, щоб відтворити новий екземпляр Pod. Застосунок знову зламався, весь веб-сайт ліг, а платформа засвоїла суворий урок: Якщо це не визначено в Git, цього просто не існує. Ніколи не використовуйте імперативні команди для зміни стану в продакшені.
Діагностика збоїв Pod: глибока детективна робота
Розділ «Діагностика збоїв Pod: глибока детективна робота»Коли Pod не запускається, постійно падає або таємниче зникає, Kubernetes надає безліч доказів. Ваше завдання — інтерпретувати їх за допомогою інструментарію kubectl.
1. Стан Pending (Помилки планування та завантаження)
Розділ «1. Стан Pending (Помилки планування та завантаження)»Якщо ви застосовуєте маніфест Pod, і він ніяк не переходить у фазу Running, найперша команда, яку ви маєте виконати:
kubectl describe pod <pod-name>
Не дивіться на вивід YAML; прокрутіть прямо до низу до розділу Events. Це хронологічний лог того, що намагалися зробити Control Plane та Планувальник.
- Insufficient Resources: Якщо подія каже
0/50 nodes are available: 50 Insufficient memory, ваш Pod математично запитав більше гарантованої пам’яті, ніж будь-який вузол у вашому кластері може надати. Ви маєте знизити запити CPU/пам’яті, видалити інші Pods, щоб звільнити місце, або додати більші вузли в кластер. - Taint / Affinity Mismatch: Якщо написано
0/50 nodes are available: 50 node(s) had taint {dedicated: database}, that the pod didn't tolerate, планувальник відмовляється розміщувати ваш веб-Pod на вузлі, зарезервованому виключно для баз даних. - ImagePullBackOff / ErrImagePull: Якщо подія каже
Failed to pull image "my-company/backend:v99": rpc error: code = NotFound, Kubelet не може завантажити образ. Kubernetes відчадушно намагається завантажити образ і експоненціально відступає (back-off) перед наступною спробою.- Виправлення: Ретельно перевірте назву образу на друкарські помилки, переконайтеся, що тег образу існує у віддаленому реєстрі, і перевірте, чи має кластер правильні секрети автентифікації (
imagePullSecrets) для доступу до приватних реєстрів.
- Виправлення: Ретельно перевірте назву образу на друкарські помилки, переконайтеся, що тег образу існує у віддаленому реєстрі, і перевірте, чи має кластер правильні секрети автентифікації (
2. CreateContainerConfigError та CreateContainerError
Розділ «2. CreateContainerConfigError та CreateContainerError»Іноді образ завантажується успішно, але контейнер відмовляється починати виконання. Якщо ви бачите ці помилки в Events, ви попросили Kubelet зробити щось логічно неможливе.
- Першопричина: Ви визначили змінну середовища, яка посилається на Kubernetes Secret або ConfigMap, якого буквально не існує в просторі імен. Kubelet відмовляється запускати контейнер, бо не може виконати контракт конфігурації.
- Виправлення: Переконайтеся, що ваші ConfigMaps та Secrets створені перед розгортанням Pod, який від них залежить.
3. CrashLoopBackOff
Розділ «3. CrashLoopBackOff»Це чи не найпоширеніша помилка для початківців у Kubernetes. Pod досягає фази Running, що означає, що планування, мережа та завантаження образу пройшли ідеально. Однак процес застосунку всередині контейнера «падає» (завершується з ненульовим кодом Linux, таким як 1, 2 або 255).
Kubelet перезапускає процес контейнера. Він знову миттєво падає. Kubelet знову перезапускає його. Щоб вузол не витрачав 100% циклів CPU на перезапуск зламаного застосунку, Kubernetes вводить експоненціальну затримку (backoff). Він чекає 10 секунд, потім 20, потім 40, аж до суворого ліміту рівно у 5 хвилин між перезапусками. Цей цикл відомий як CrashLoopBackOff.
Як систематично діагностувати: НЕ використовуйте тут describe. Контейнер насправді успішно запустився, тож події планування в нормі. Вам потрібно побачити, що саме процес застосунку вивів у стандартний вивід консолі за мілісекунди до смерті.
kubectl logs <pod-name> --previous (прапор --previous показує логи останнього екземпляра контейнера, що впав).
Якщо в логах написано FATAL: Unable to connect to database - Connection Refused або SyntaxError: Unexpected token, ви знайшли першопричину. Це баг у коді застосунку, відсутня змінна середовища або мережева помилка, а не помилка планування Kubernetes.
4. Тихий вбивця: OOMKilled (Код виходу 137)
Розділ «4. Тихий вбивця: OOMKilled (Код виходу 137)»Якщо погано написаний застосунок з часом «підтікає» пам’яттю, він врешті-решт досягне жорсткого ліміту пам’яті, визначеного у специфікації resources.limits.memory. Коли цей поріг перейдено, ядро Linux агресивно завершує процес-порушник, щоб захистити стабільність решти вузла.
Якщо застосунок Pod несподівано перезапускається без жодного повідомлення про помилку в логах, негайно перевірте системний код виходу.
kubectl describe pod <pod-name>
Погляньте уважно на блок Last State конкретного контейнера. Якщо ви бачите Reason: OOMKilled та Exit Code: 137, ваш застосунок повністю вичерпав дозволену пам’ять і був «асимільований» ядром.
Виправлення: Ви маєте виправити архітектурний витік пам’яті у вихідному коді застосунку або відредагувати YAML Pod, щоб суттєво збільшити ліміт фізичної пам’яті (наприклад, з 512Mi до 1024Mi).
5. Інтерактивне налагодження за допомогою exec та port-forward
Розділ «5. Інтерактивне налагодження за допомогою exec та port-forward»Іноді простого читання статичних логів недостатньо для вирішення складної періодичної проблеми з мережею. Вам потрібно фізично потрапити всередину ізольованої мережі та файлової системи контейнера, щоб побачити те, що бачить він.
kubectl exec -it <pod-name> -- /bin/sh
Ця команда переносить вас безпосередньо в інтерактивну оболонку всередині живого запущеного процесу контейнера. Звідси ви можете виконати nslookup для перевірки DNS-резолвінгу всередині кластера, ping віддалених баз даних для перевірки маршрутизації або прочитати локальні конфігураційні файли, фізично змонтовані на диск контейнера.
Крім того, якщо вам потрібно протестувати веб-застосунок, запущений у Pod, але ви ще не налаштували складну публічну маршрутизацію (Services, Ingresses), ви можете прокинути трафік безпосередньо з вашого ноутбука в мережевий простір імен Pod через зашифроване з’єднання з API-сервером:
kubectl port-forward pod/<pod-name> 8080:80
Тепер відкриття http://localhost:8080 у вашому локальному браузері направить HTTP-запит через зашифрований тунель Kubernetes API безпосередньо в порт 80 вашого глибоко ізольованого Pod.
Чи знали ви?
Розділ «Чи знали ви?»- Фундаментальна концепція та термінологія «Pod» була натхненна біологічним терміном для згуртованої групи китів (зграї), що ідеально пасує до іконічного логотипа Docker з китом та ширшої морської тематики Kubernetes.
- Образ контейнера
pause, який тихо утримує життєво важливий мережевий простір імен відкритим для кожного Pod у вашому глобальному кластері, неймовірно малий та оптимізований — скомпільований суворо на низькорівневій мові С, результуючий бінарний файл зазвичай менший за 700 кілобайт. - У Kubernetes 1.28+ нативна підтримка справжніх «Sidecar Containers» нарешті була введена як вбудована функція, що фундаментально змінило спосіб налаштування Init-контейнерів. Тепер їм можна офіційно наказати працювати нескінченно довго поруч з основними навантаженнями, не блокуючи послідовність запуску, що вирішує давній архітектурний біль сервісних сіток.
- Якщо процес контейнера несподівано завершується з кодом
137, це математично означає, що він отримав фатальний сигналSIGKILL(signal 9) від хост-системи Linux. У контексті Kubernetes це гарантовано вказує на те, що процес був різко припинений механізмом OOM Killer вузла.
Типові помилки
Розділ «Типові помилки»| Помилка | Чому це трапляється | Як виправити |
|---|---|---|
| Ставлення до Pods як до віртуальних машин | Нерозуміння спільного життєвого циклу. Розміщення фронтенду, бекенду та БД в одному Pod. | Розділяйте незалежні застосунки на окремі одноконтейнерні Pods, якщо вони не пов’язані фізично і не ділять диск. |
| Розгортання «голих» некерованих Pods у продакшені | Хибна віра в те, що Pods стійкі самі по собі. Вони ефемерні і не воскреснуть при падінні вузла. | Завжди обгортайте Pods у високорівневі контролери, такі як Deployments, DaemonSets або StatefulSets. |
| Ігнорування Resource Requests та Limits | Лінь, поспіх або відсутність профілювання продуктивності застосунку. | Завжди суворо визначайте ліміти пам’яті та CPU. Без них один витік пам’яті в одному Pod може повалити весь вузол. |
Нерозуміння localhost у багатоконтейнерних Pods | Забуття того, що всі контейнери в Pod ділять той самий мережевий стек, таблицю маршрутизації та IP. | Використовуйте різні порти для кожного окремого контейнера всередині Pod, щоб уникнути помилок Address already in use. |
Використання тегів latest для образів Docker | Зручність розробника, яка небезпечно потрапляє у продакшн-маніфести. | Завжди фіксуйте образи за допомогою конкретних SHA або незмінних тегів версій (напр., v2.1.4), щоб запобігти сюрпризам. |
Використання exec для ручного виправлення проблем | Ставлення до ефемерних контейнерів як до серверів. Ручні зміни втрачаються миттєво при перезапуску Pod. | Pods суворо імутабельні. Виправляйте конфігурацію в Git або Dockerfile, збирайте новий образ і деплойте декларативно. |
| Зловживання Init-контейнерами для постійних завдань | Нерозуміння того, що Init-контейнери мають повністю завершитися (код 0) перед стартом основного застосунку. | Використовуйте фонові sidecar-контейнери для постійних завдань; Init-контейнери — тільки для скінченної підготовки перед стартом. |
| Ігнорування Liveness та Readiness проб | Припущення, що фреймворк сам впорається зі станом здоров’я, або просто забудькуватість. | Неконтрольовані Pods отримають трафік раніше, ніж завантажаться, що спричинить помилки 502 для користувачів. |
Контрольні запитання
Розділ «Контрольні запитання»1. Сценарій: У вас є застарілий застосунок, який жорстко прописує критичні логи аудиту в локальний файл `/var/log/app.log`. Стандарт моніторингу вашої організації вимагає, щоб усі логи передавалися у стандартний вивід (`stdout`) для збору через Fluentd. Як ви маєте спроектувати Pod, щоб виконати цю вимогу, не переписуючи жодного рядка коду застосунку на C++?
Відповідь: Впровадити архітектурний патерн Sidecar. Розгорнути багатоконтейнерний Pod, де основний застосунок пише логи у спільний том emptyDir. Потім розгорнути другий контейнер-sidecar у тому ж Pod, який монтує цей спільний том і виконує команду tail -f /var/log/app.log. Оскільки sidecar зчитує файл і виводить дані у свій потік stdout, демон логування кластера зможе їх безперешкодно зібрати.
2. Сценарій: Ваш новий Pod мікросервісу застряг у стані `Pending` на понад 15 хвилин. Ви запускаєте `kubectl describe pod my-microservice` і бачите подію: `0/50 nodes are available: 50 Insufficient cpu`. Яка першопричина і які два архітектурні варіанти вирішення у вас є?
Відповідь: Першопричина полягає в тому, що запитаний обсяг CPU для Pod вищий за доступну нерозподілену потужність CPU на будь-якому окремому робочому вузлі кластера. Планувальник не може знайти вузол, щоб його розмістити. Варіанти: знизити значення requests для CPU в YAML, якщо застосунку насправді не потрібно стільки ресурсів, або додати більший робочий вузол (або активувати автоскейлінг), щоб забезпечити достатню фізичну потужність.
3. Сценарій: Ви проектуєте Pod, якому потрібно завантажити модель машинного навчання розміром 500 МБ з S3-бакета перед тим, як основний застосунок на Python почне обслуговувати трафік. Яку функцію Kubernetes ви маєте впровадити, щоб гарантувати завантаження файлу до старту веб-сервера?
Відповідь: Ви маєте використовувати Init Container для цього суворого завдання підготовки. Визначте Init-контейнер з інструментом aws-cli і змонтуйте том emptyDir, спільний з основним застосунком. Init-контейнер виконає команду завантаження з S3 у спільний том і завершиться з кодом 0. Kubernetes гарантує, що основний контейнер не запуститься, поки Init-контейнер успішно не фінішує.
4. Сценарій: Розробник скаржиться, що його застосунок на Node.js випадково перезапускається під навантаженням. Ви інспектуєте Pod і бачите кількість перезапусків 14, причому останній стан вказує `Reason: OOMKilled` та `Exit Code: 137`. Яка підсистема ядра завершила контейнер і яке виправлення потрібне?
Відповідь: Контейнер завершив OOM Killer ядра Linux. Це сталося через те, що процес Node.js спробував виділити більше оперативної пам’яті, ніж дозволено жорстким лімітом cgroup контейнера. Для остаточного виправлення потрібно відредагувати декларативний маніфест YAML і збільшити значення resources.limits.memory. Також варто перевірити застосунок на наявність витоків пам’яті.
5. Сценарій: Два окремі контейнери визначені в одному Pod. Контейнер А запускає веб-сервер Nginx на порту 80. Контейнер Б запускає експортер метрик Prometheus, якому потрібно знімати дані зі сторінки статусу Контейнера А. Яке ім'я хоста та порт має використовувати Контейнер Б у своєму HTTP-запиті до Контейнера А?
Відповідь: Контейнер Б має робити HTTP-запит безпосередньо до http://localhost:80. Це можливо, тому що всі контейнери в одному Pod ділять спільний мережевий простір імен, наданий контейнером pause. Вони можуть спілкуватися через loopback-інтерфейс так само, як процеси на одному фізичному сервері.
6. Сценарій: Ви помітили, що Pod у продакшені застряг у `CrashLoopBackOff`. Ви запускаєте `kubectl logs my-failing-pod`, але вивід термінала абсолютно порожній. Чому логи можуть бути порожніми і як визначити причину збою запуску?
Відповідь: Логи можуть бути порожніми, якщо застосунок впав ще до того, як встиг ініціалізуватися і щось записати у стандартний вивід. Це часто трапляється, якщо в образі відсутній виконуваний файл точки входу або через помилку прав доступу. Потрібно використати kubectl describe pod my-failing-pod і глибоко вивчити розділи State та Events — вони покажуть сирий код виходу або помилки середовища виконання.
7. Сценарій: Ви використали імперативну команду `kubectl run test-pod --image=nginx` для перевірки мережі. Через десять хвилин вузол, на якому був запланований Pod, зазнає апаратного збою і втрачає живлення. Що станеться з вашим `test-pod`?
Відповідь: Ваш test-pod буде назавжди втрачений і не відновиться самостійно. Оскільки Pod був створений імперативно як «голий» некерований ресурс, він не контролюється високорівневим автономним контролером (як Deployment). Керуюча площина врешті помітить, що вузол мертвий, і позначить стан Pod як Terminating, але не перестворить його на іншому вузлі.
8. Сценарій: Аудит безпеки виявив, що зловмисник вийшов за межі контейнера застосунку, оскільки той працював від імені користувача root. Яку конфігурацію YAML ви маєте додати в маніфест Pod, щоб запобігти цьому?
Відповідь: Потрібно додати блок securityContext у специфікацію Pod або контейнера. Зокрема, додайте runAsNonRoot: true, щоб вимагати виконання від непривілейованого користувача. Крім того, налаштування readOnlyRootFilesystem: true примусово завадить зловмиснику завантажувати шкідливі файли безпосередньо на диск контейнера.
Практична вправа: виклик із налагодження багатоконтейнерного Pod
Розділ «Практична вправа: виклик із налагодження багатоконтейнерного Pod»У цій вправі ви створите багатоконтейнерний Pod, фізично перевірите його спільні простори імен, навмисно введете фатальну помилку конфігурації та використаєте діагностичні команди для виявлення причини збою.
Завдання 1: Декларативне створення багатоконтейнерного Pod
Напишіть декларативний маніфест YAML з назвою multi-pod.yaml, який створює один Pod із двома контейнерами, що взаємодіють.
- Назва Pod:
web-logger. - Контейнер 1: Назва
nginx-server, образnginx:alpine, монтує спільний томhtml-dirза шляхом/usr/share/nginx/html. - Контейнер 2: Назва
content-writer, образbusybox. Монтує той самий томhtml-dirза шляхом/data. Його команда має бути циклом shell, що пише поточну дату в/data/index.htmlкожні 5 секунд. (Підказка для масиву command:["/bin/sh", "-c", "while true; do date > /data/index.html; sleep 5; done"]) - Спільний том має бути типу
emptyDir: {}.
Рішення:
apiVersion: v1kind: Podmetadata: name: web-loggerspec: volumes: - name: html-dir emptyDir: {} containers: - name: nginx-server image: nginx:alpine volumeMounts: - name: html-dir mountPath: /usr/share/nginx/html - name: content-writer image: busybox command: ["/bin/sh", "-c", "while true; do date > /data/index.html; sleep 5; done"] volumeMounts: - name: html-dir mountPath: /dataЗавдання 2: Застосування та перевірка архітектури
Застосуйте маніфест у своєму кластері. Переконайтеся, що Pod переходить через фазу Pending у стан Running і що обидва контейнери готові. Використовуйте port-forwarding для перегляду згенерованої сторінки у вашому браузері.
Рішення:
# Застосуйте маніфестkubectl apply -f multi-pod.yaml
# Слідкуйте за статусом, поки не побачите 2/2 Readykubectl get pods -w
# Встановіть тунель port-forwardkubectl port-forward pod/web-logger 8080:80
# В іншому терміналі перевірте через curlcurl http://localhost:8080# Ви маєте побачити дату, що оновлюється кожні 5 секунд!Завдання 3: Інтерактивне дослідження просторів імен
Використовуйте kubectl exec, щоб зайти в оболонку контейнера nginx-server. Встановіть утиліту curl і зробіть запит до localhost:80. Чому це працює між межами контейнерів?
Рішення:
# Зайдіть в контейнерkubectl exec -it web-logger -c nginx-server -- /bin/sh
# Встановіть curlapk add --no-cache curl
# Запит до локального інтерфейсуcurl http://localhost:80
# ВийдітьexitЧому це працює: Хоча ми зайшли у файлову систему nginx-server, він слухає на порту 80 спільного мережевого простору імен всього Pod. Інтерфейс localhost спільний для всіх контейнерів у Pod завдяки архітектурі контейнера pause.
Завдання 4: Навмисне провокування події OOMKilled
Створіть файл oom-pod.yaml. Визначте Pod з образом polinux/stress для генерації навантаження. Дайте йому ліміт пам’яті 50Mi. Встановіть команду: ["stress", "--vm", "1", "--vm-bytes", "150M", "--vm-hang", "1"].
Рішення:
apiVersion: v1kind: Podmetadata: name: memory-hogspec: containers: - name: stress-test image: polinux/stress command: ["stress", "--vm", "1", "--vm-bytes", "150M", "--vm-hang", "1"] resources: limits: memory: "50Mi"# Застосуйте "приречений" Podkubectl apply -f oom-pod.yaml
# Спостерігайте за результатомkubectl get pods -wВи побачите стан OOMKilled. Процес хотів 150 МБ RAM, але Kubernetes встановив жорсткий ліміт cgroup у 50 МБ. Ядро миттєво завершило процес.
Завдання 5: Судово-медична діагностика смерті
Використовуйте kubectl describe, щоб довести, чому Pod memory-hog помер. Знайдіть код виходу та причину.
Рішення:
kubectl describe pod memory-hogУ розділі Containers:, блок Last State:, ви побачите Reason: OOMKilled та Exit Code: 137. Це “незаперечний доказ”, який ви шукаєте під час аварій у продакшені.
Завдання 6: Системне очищення
Видаліть створені Pods.
Рішення:
kubectl delete pod web-logger memory-hogНаступний модуль
Розділ «Наступний модуль»Тепер, коли ви досконало розумієте атомарну одиницю виконання в екосистемі Kubernetes, ви можете запитати: «Якщо Pods ефемерні і вмирають разом із вузлами, як мені забезпечити стабільну роботу корпоративного застосунку? Як масштабуватися з 1 до 1000 Pods без написання тисячі окремих YAML?».
Відповідь криється у високорівневих автономних циклах керування. У Модулі 1.4: Deployments ми глибоко вивчимо об’єкт Deployment — алгоритмічний двигун, який невтомно стежить за вашими Pods, воскрешає їх та забезпечує оновлення всього парку застосунків без простоїв.