Що таке Pod в Kubernetes
Kubernetes ніколи не запускає окремі Docker-контейнери безпосередньо; він завжди загортає їх в абстракцію Pod.
🟢 Junior Level
30-секундна відповідь
Pod (Под) — це мінімальна атомарна одиниця розгортання та керування в Kubernetes. Kubernetes ніколи не запускає окремі Docker-контейнери безпосередньо; він завжди загортає їх в абстракцію Pod.
Pod являє собою логічний хост для одного або кількох тісно пов’язаних контейнерів, які:
- Поділяють один мережевий стек: у всіх контейнерів пода рівно одна спільна IP-адреса та єдиний простір портів (вони спілкуються один з одним через
localhost). - Поділяють дискові томи (Volumes): файли, створені одним контейнером, можуть бути миттєво прочитані іншим через спільну директорію.
- Мають спільний життєвий цикл: усі контейнери пода призначаються планувальником (
kube-scheduler) на один і той самий фізичний сервер (Node) та разом створюються і знищуються.
Наочна аналогія з життя
Pod — це стручок гороху (англ. pea pod):
- Горошини всередині стручка — це контейнери.
- Стручок може містити одну велику горошину (класичний Pod з одним Java-мікросервісом — 95% випадків в індустрії).
- Або стручок містить дві горошини (основний Java-застосунок + допоміжний контейнер-помічник, наприклад, збирач логів).
- Стручок неподільний: ви не можете покласти одну горошину в один холодильник, а другу — в інший. Pod цілком живе на одній ноді.
Базовий маніфест пода для Java 21
apiVersion: v1
kind: Pod
metadata:
name: payment-service-pod
labels:
app: payment-service
spec:
containers:
- name: payment-app
image: payment-service:2.0
ports:
- containerPort: 8080
resources:
requests:
memory: "512Mi"
cpu: "500m"
limits:
memory: "1Gi"
cpu: "1000m"
[!IMPORTANT] Поди ефемерні! У продакшені поди ніколи не створюються вручну. Ними керують високорівневі контролери: Deployment (для stateless-сервісів), StatefulSet (для БД) або DaemonSet.
🟡 Middle Level
Внутрішня будова: Контейнер pause (Інфраструктурний фундамент)
Як Kubernetes змушує кілька незалежних контейнерів ділити одну IP-адресу та єдиний мережевий стек?
- Під час створення кожного пода насамперед запускається прихований системний контейнер —
pause(образregistry.k8s.io/pause). - Контейнер
pauseініціалізує простори імен ядра Linux (Network, IPC, UTS). - Потім запускаються користувацькі контейнери (Java, Nginx, Fluentd), які підключаються до вже створених namespaces контейнера
pauseза допомогою прапорця--net=container:pause. - Навіть якщо ваш Java-контейнер перезапуститься через помилку OOMKilled, мережева адреса пода та відкриті з’єднання не втратяться, оскільки контейнер
pauseпродовжує безперервно працювати в пам’яті ядра.
Паттерни використання кількох контейнерів в одному Pod
+------------------------------------+
| K8S POD |
| |
| [ Main App: Java 21 (Port 8080) ] |
| ^ |
| | localhost |
| v |
| [ Sidecar: Envoy Proxy (Port 15001)]
| ^ |
| | Shared Volume |
| v (/var/log) |
| [ Sidecar: Fluent-Bit ] |
+------------------------------------+
│ IP: 10.244.1.45
▼
1. Sidecar (Причіп / Помічник)
Розширює функціональність основного контейнера без зміни його коду:
- Збір логів із локального тома (
emptyDir) та відправлення в Elastic/Kafka. - Проксі-агент Service Mesh (Envoy в Istio) для шифрування mTLS.
2. Init Containers (Контейнери ініціалізації)
Виконуються суворо послідовно ДО старту основних контейнерів:
- Перевірка доступності БД (
nc -z postgres 5432). - Завантаження сертифікатів або застосування міграцій схеми Liquibase/Flyway.
3. Ambassador та Adapter
- Ambassador: приховує зовнішню топологію (застосунок надсилає запити на
localhost:6379, а ambassador маршрутизує їх у складний шардований кластер Redis). - Adapter: нормалізує вивід застосунку до єдиного формату моніторингу Prometheus.
Фази життєвого циклу Pod (Pod Phase)
Pending: маніфест прийнятий API-сервером, але под ще не запущений (йде планування ноди або завантаження образівImagePullBackOff).Running: под прив’язаний до ноди, всі контейнери створені, і хоча б один працює або перебуває в процесі запуску/перезапуску.Succeeded: усі контейнери пода успішно завершили роботу з кодом0(характерно дляJob).Failed: усі контейнери завершилися, і хоча б один із них упав із ненульовим кодом помилки.Unknown: зв’язок із нодою втрачено, статус пода не може бути отриманий відkubelet.
🔴 Senior Level
Простори імен Linux: Ізоляція vs Поділ у Pod
Senior-інженер повинен точно розуміти, які простори імен Linux поділяються, а які ізольовані:
| Namespace Linux | За замовчуванням у Pod | Пояснення |
|---|---|---|
| Network (NET) | 🟢 Поділяється | Єдиний IP, спільні мережеві інтерфейси veth, обмін по localhost. |
| IPC | 🟢 Поділяється | Спільна пам’ять (POSIX shared memory /dev/shm), семафори. |
| UTS | 🟢 Поділяється | Спільне мережеве ім’я хоста (Hostname збігається з metadata.name). |
| PID | 🔴 Ізольований | Кожен контейнер має власне дерево процесів і свій PID 1. |
| Mount (MNT) | 🔴 Ізольований | У кожного контейнера свій rootfs, але томи (Volumes) монтуються спільно. |
Прапорець shareProcessNamespace: true
Якщо в специфікації пода встановити shareProcessNamespace: true:
- Дерево процесів стає єдиним для всіх контейнерів пода.
- Процес контейнера
pauseотримує PID 1 і бере на себе функцію Zombie Reaping. - Основний Java-процес і sidecar бачать процеси один одного через
ps aux. - Сценарій застосування: сайдкар-контейнер безпеки може надіслати системний сигнал
kill -HUPабо зняти дамп потоківjcmdз процесу Java, що працює в сусідньому контейнері.
Класи якості обслуговування (Quality of Service — QoS) та OOM-Score
Kubernetes автоматично призначає кожному поду один із трьох класів QoS, який безпосередньо визначає значення прапорця oom_score_adj в ядрі Linux:
[Гарантована живучість] [Перший кандидат на знищення]
Guaranteed Burstable BestEffort
(requests == limits) (requests < limits) (немає requests/limits)
oom_score_adj = -997 oom_score_adj = 2..999 oom_score_adj = 1000
Guaranteed:- Для всіх контейнерів пода задані
requestsіlimits, і вони суворо рівні за CPU та RAM. - Значення
oom_score_adj = -997. Ядро Linux уб’є цей под в останню чергу, тільки якщо фізична пам’ять сервера повністю вичерпана системними процесами.
- Для всіх контейнерів пода задані
Burstable:- Задані
requests, але вони менші заlimits(або задані лише для одного ресурсу). - Значення
oom_score_adjрозраховується динамічно в діапазоні[2, 999]пропорційно перевищенню ліміту.
- Задані
BestEffort:- Не задано жодного
requestsіlimits. - Значення
oom_score_adj = 1000. При найменшій нестачі пам’яті на ноді Linux OOM Killer миттєво знищує поди цього класу в першу чергу.
- Не задано жодного
Native Sidecar Containers (Kubernetes 1.28+ / 1.29 GA)
Історично сайдкари запускалися як звичайні паралельні контейнери, що викликало дві великі проблеми:
- Основний сервіс стартував раніше, ніж сайдкар безпеки (Envoy/Vault), і падав через відсутність мережі.
- У пакетних завданнях (
Job) задача завершувалася, але сайдкар збору логів продовжував працювати вічно, не даючи Job завершитися зі статусомCompleted.
Сучасний стандарт (JEP / KEP-753):
Сайдкари оголошуються в секції initContainers із властивістю restartPolicy: Always:
spec:
initContainers:
- name: vault-agent
image: vault:1.15
restartPolicy: Always # Kubernetes розуміє, що це Native Sidecar!
containers:
- name: java-app
image: payment-service:2.0
Kubelet дочікується запуску сайдкара і проходження його startupProbe, запускає основний застосунок, а при завершенні основного застосунку сам коректно зупиняє сайдкар!
4 Tricky Questions
1. Яку роль відіграє службовий контейнер pause всередині кожного Pod, і що станеться, якщо вбити процес pause на сервері вручну?
Відповідь:
Контейнер pause є «якорем» просторів імен Linux:
- Він утримує відкритими дескриптори спільних namespaces (мережевого стека, IPC, UTS), дозволяючи користувацьким контейнерам пода перезапускатися без зміни IP-адреси та розриву мережевих таблиць хоста.
- Якщо процес
pauseвбити вручну з консолі хоста черезkill -9:- Дескриптори namespaces закриваються ядром Linux.
- З погляду
kubelet, інфраструктурний фундамент пода зруйнований. - Kubelet фіксує відмову пода, примусово зупиняє всі інші користувацькі контейнери в цьому поді та перестворює весь Pod з нуля з виділенням нового контейнера
pauseі нової IP-адреси.
2. Чому два контейнери в одному Pod не можуть слухати один і той самий TCP-порт (наприклад, 8080), навіть якщо у них різні імена та різні образи?
Відповідь:
Оскільки всі контейнери всередині одного Pod поділяють один спільний мережевий простір імен (Network Namespace), вони працюють на єдиному мережевому стеку зі спільним інтерфейсом lo (localhost) та спільним віртуальним інтерфейсом eth0.
Спроба другого контейнера відкрити сокет на тому ж порту 8080 призведе до помилки ядра Linux java.net.BindException: Address already in use, так само, як якби ви спробували запустити два застосунки на порту 8080 на одному фізичному комп’ютері. Контейнери одного пода зобов’язані використовувати різні порти.
3. Як налаштувати Pod, щоб Java-застосунок гарантовано отримав клас QoS Guaranteed, і чому для Java в Production це найкращий вибір?
Відповідь:
Щоб отримати клас Guaranteed, необхідно:
- Вказати секції
requestsіlimitsдля всіх контейнерів пода (включно із сайдкарами). - Значення
requests.cpuмають бути суворо рівніlimits.cpu, аrequests.memoryсуворо рівніlimits.memory:resources: requests: { memory: "2Gi", cpu: "2000m" } limits: { memory: "2Gi", cpu: "2000m" }Чому це критично для Java: При класі
Guaranteedядро виставляєoom_score_adj = -997. У разі нестачі пам’яті ноди K8s спочатку вб’є подиBestEffortтаBurstable. Крім того, за умови збігу CPU requests і limits Kubernetes виділить поду виділені фізичні ядра CPU (CPU Pinning / Static CPU Manager Policy), виключаючи перемикання контексту між ядрами та забезпечуючи мінімальний джитер для JVM Garbage Collector.
4. У якому порядку завершуються контейнери під час видалення Pod (kubectl delete pod), і як виконується Graceful Shutdown?
Відповідь:
- Под переходить у статус
Terminating, його IP негайно видаляється зі списківEndpointsсервісів (трафік припиняє надходити). - Якщо у контейнера заданий хук
preStop, він викликається першим. - Kubelet надсилає сигнал
SIGTERMпроцесу з PID 1 усередині кожного контейнера. - Контейнери завершують активні транзакції (Spring Boot graceful shutdown).
- Запускається таймер зворотного відліку
terminationGracePeriodSeconds(за замовчуванням 30 секунд). - Якщо після закінчення таймера контейнери не зупинилися, kubelet надсилає нещадний сигнал
SIGKILL, примусово очищаючи пам’ять.
🎯 Шпаргалка для інтерв’ю
Головне про Pod
- Визначення: мінімальна атомарна одиниця планування в Kubernetes.
- Склад: один (зазвичай) або кілька контейнерів.
- Спільні ресурси: єдина IP-адреса, єдиний простір портів, спільні томи (
Volumes), IPC. - Контейнер
pause: утримує namespaces в ядрі Linux. - Паттерни: Sidecar (розширення/логи), Init Containers (передпускова підготовка).
- QoS класи: Guaranteed (-997), Burstable (2..999), BestEffort (1000).
- Ефемерність: поди не відновлюють самі себе; ними керує
Deployment.
Класифікація QoS
| Клас QoS | Умова в YAML | oom_score_adj | Пріоритет виселення (Eviction) |
| :— | :— | :—: | :— |
| Guaranteed | requests == limits (CPU & RAM) | -997 | 🟢 Останній (максимальний захист) |
| Burstable | requests < limits | 2 .. 999 | 🟡 Середній |
| BestEffort | Ліміти і реквести не задані | 1000 | 🔴 Перший кандидат на знищення |
Червоні прапорці на співбесіді (Чого говорити не можна)
- ❌ «У продакшені ми створюємо поди безпосередньо через маніфест kind: Pod» — у разі падіння ноди такий под не відновиться, потрібен
Deployment. - ❌ «Кожен контейнер усередині пода отримує свою власну IP-адресу» — весь под має рівно одну спільну IP.
- ❌ «Контейнери всередині одного пода можуть слухати один і той самий порт» — станеться конфлікт портів
Address already in use.