Что такое 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 (для сервисов без состояния), 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.