Короткий ответ: под в Kubernetes - минимальная развёртываемая единица вычислений, которую можно создавать и которой можно управлять. Под содержит один или несколько контейнеров с общими ресурсами хранения и сети и спецификацией запуска контейнеров. Контейнер внутри пода - отдельный процесс со своей файловой системой образа, но без собственного IP, без отдельного объекта в API и без возможности перезапуска в одиночку.
Практические следствия видны сразу. Все контейнеры пода делят одно сетевое пространство имён, поэтому обращаются друг к другу через localhost, используют единый IP-адрес и один общий набор портов. Перезапустить один контейнер, не затронув соседей, штатными средствами нельзя: kubelet и контроллеры работают с подом целиком.
Ниже разбираем, где проходит граница между двумя сущностями, почему Kubernetes выбрал единицей управления под и в каких ситуациях эта разница ломает отладку или архитектуру.
| Свойство | Контейнер | Под |
|---|---|---|
| Объект в API Kubernetes | Нет, отдельного kind не существует | Да, kind: Pod |
| IP-адрес | Общий, принадлежит поду | Один адрес из CIDR кластера |
| localhost | Разделяется с соседями по поду | Общий для всех контейнеров |
| Порты | Уникальны в пределах пода | Один набор портов на весь под |
| Планирование на узел | Не планируется | Единица планирования для kube-scheduler |
| Масштабирование | Не масштабируется | ReplicaSet, Deployment, StatefulSet |
| Жизненный цикл | Перезапускается kubelet по restartPolicy | Создаётся, удаляется и пересоздаётся целиком |
Что такое под и контейнер в Kubernetes: базовое определение
Контейнер - процесс, запущенный из образа в изолированных пространствах имён ядра Linux: PID, mount, network, UTS, IPC. Запустить такой процесс умеет containerd, CRI-O и любой другой runtime, совместимый с Container Runtime Interface.
Под - обёртка над одним или несколькими такими процессами, которой Kubernetes даёт общую сеть, общие тома и общий жизненный цикл. В декларативной модели вы описываете желаемое состояние пода, а контроллеры приводят к нему фактическое. Узел для пода выбирает kube-scheduler, kubelet на узле поднимает под, а не отдельные контейнеры по вашему запросу.
Близкая аналогия: под похож на небольшую виртуальную машину, а контейнеры в нём - на процессы внутри неё. Общий сетевой интерфейс, общая точка остановки, общая судьба.
Чаще всего в поде один контейнер, и тогда разница незаметна в работе. Второй частый случай - несколько контейнеров: sidecar (лог-агент, прокси Envoy, экспортёр метрик для Prometheus) и init-контейнеры, которые готовят данные до старта основного процесса. Отдельная категория - эфемерные контейнеры из kubectl debug, которые подключаются к поду для разовой диагностики.
Почему Kubernetes не управляет контейнерами напрямую
API-сервер Kubernetes хранит объекты: Pod, Deployment, Service, ConfigMap, Secret. Контейнер в этом списке отсутствует: у него нет имени в etcd, меток, владельца и квоты. Его нельзя описать отдельным манифестом и нельзя выбрать через kubectl get.
Правила, которые контролируют создание объектов, применяются к подам. Admission controllers - это код внутри API-сервера Kubernetes, который применяется к запросам на создание, удаление или изменение объектов; пример такого уровня контроля - блокировка подключения к поду через прокси API-сервера (Kubernetes, Admission Control).
Вывод для практики: контейнер - деталь внутреннего устройства пода, а единица планирования, размещения и контроля - под. Как устроен путь запроса от манифеста до запущенного контейнера, мы разбирали в статье про архитектуру Kubernetes и компоненты кластера.
Изоляция и общий сетевой стек: почему localhost и IP общие
Каждому поду Kubernetes выделяет собственное сетевое пространство имён (network namespace) и один IP-адрес для каждого семейства адресов. Все контейнеры пода входят в это пространство, поэтому у них общий интерфейс, общая таблица маршрутизации и общий набор портов. Внутри пода контейнеры делят IP-адрес и пространство портов и могут находить друг друга через localhost.
Пространство имён живёт дольше отдельных контейнеров: его держит инфраструктурный pause-контейнер. Pause-контейнер существует в каждом поде и служит контейнером-шаблоном, резервирующим namespaces, общие для всех контейнеров внутри пода; он запускается и переходит в спящий режим. Если основной контейнер упал и перезапустился, IP пода и сетевые настройки остаются теми же.
Три практических следствия общей изоляции:
- Контейнеры видят друг друга на localhost: приложение на порту 8080 доступно sidecar-прокси как localhost:8080.
- Занять один и тот же порт двумя контейнерами нельзя: второй упадёт с ошибкой address already in use.
- Для внешнего мира под выглядит одним адресом. Service балансирует трафик на IP пода, а не на IP контейнера, а kubectl port-forward пробрасывает порты пода целиком.
Проверить адресацию просто: kubectl get pod <имя-пода> -o wide показывает IP пода и узел. Точный набор адресов отдаёт запрос kubectl get pod <имя-пода> -o jsonpath='{.status.podIPs[*].ip}'.
Практические следствия общего localhost для отладки
Отладка приложения в поде с sidecar сводится к проверке изнутри пространства имён: kubectl exec -it <имя-пода> -c app -- curl -s localhost:8080/health. Тот же адрес отвечает и из контейнера-соседа, поэтому связку «приложение + прокси» можно проверить одной командой, не покидая под.
Ошибка, которая стоит часа простоя: два контейнера в поде слушают порт 8080. Второй не стартует, kubelet перезапускает его по кругу, под остаётся в статусе Running с одним падающим контейнером, а в логах видно bind: address already in use. Лечится разнесением портов и настройкой обмена по localhost на разных номерах.
Чек-лист сетевой отладки пода:
- Посмотрите IP пода: kubectl get pod <имя-пода> -o wide.
- Проверьте порты каждого контейнера: kubectl get pod <имя-пода> -o jsonpath='{.spec.containers[*].ports}'.
- Снимите логи каждого контейнера отдельно: kubectl logs <имя-пода> -c <контейнер>.
- Проверьте связку изнутри: curl localhost:<порт> через kubectl exec.
Монтирование томов: общая файловая система внутри пода
Тома описываются на уровне пода в spec.volumes, а подключаются к контейнерам через volumeMounts. Один том можно смонтировать сразу в несколько контейнеров, и тогда они работают с одними и теми же файлами.
emptyDir создаётся при запуске пода и удаляется вместе с ним. Это удобный канал обмена между контейнерами и место для временных файлов.
apiVersion: v1
kind: Pod
metadata:
name: web-with-logger
spec:
volumes:
- name: logs
emptyDir: {}
containers:
- name: web
image: nginx:1.27
volumeMounts:
- name: logs
mountPath: /var/log/nginx
- name: logger
image: busybox:1.36
command: ["sh", "-c", "tail -F /logs/access.log"]
volumeMounts:
- name: logs
mountPath: /logs
Проверка после применения: kubectl exec -it web-with-logger -c logger -- ls -l /logs. Если каталог пуст, сверьте, куда пишет nginx, и совпадают ли mountPath у обоих контейнеров.
Жизненный цикл тома привязан к поду: перезапуск контейнера внутри пода данные в emptyDir сохраняет, а пересоздание пода их удаляет. Для файлов, которые должны пережить под, нужен persistentVolumeClaim и том типа persistentVolume.
Типичные сценарии использования общих томов
- Sidecar для логов. Приложение пишет в /var/log, агент читает тот же каталог и отправляет записи в централизованное хранилище. Работает потому, что том описан в поде, а не в контейнере.
- Init-контейнер для конфигурации. Init-контейнер скачивает конфиги или шаблоны в том, основной контейнер стартует уже с готовыми файлами. Порядок гарантирует kubelet: init-контейнеры завершаются до запуска остальных.
- git-sync и nginx. Контейнер git-sync обновляет каталог со статикой по расписанию, nginx раздаёт файлы из того же тома. Пересоздание пода при этом не нужно, содержимое обновляется на ходу.
Для всех трёх сценариев общий том - способ передать файлы между контейнерами без сети и без внешнего хранилища.
Жизненный цикл пода и контейнера: почему нельзя перезапустить один контейнер
restartPolicy задаётся в спецификации пода и действует на все его контейнеры: Always (значение по умолчанию), OnFailure, Never. Kubernetes управляет сбоями контейнеров внутри подов с помощью restartPolicy, заданной в спецификации пода; пока под работает, kubelet способен перезапускать контейнеры для обработки некоторых сбоев. Такой перезапуск создаёт заменяющие контейнеры внутри того же пода и на том же узле, поэтому единицей владения и учёта остаётся под.
Команды kubectl restart container в Kubernetes нет. Перезапустить контейнер можно только косвенно: дать процессу упасть, провалить liveness-пробу или пересоздать под целиком. Ни один из этих путей не оставляет соседей нетронутыми.
Состояние пода описывают фазы: Pending, Running, Succeeded, Failed, Unknown. Фаза относится ко всему поду, а сведения об отдельных контейнерах лежат в status.containerStatuses: ready, restartCount, state (running, waiting, terminated) с причинами CrashLoopBackOff, ImagePullBackOff, OOMKilled.
Пересоздание пода остаётся основным способом применить новые параметры: kubectl delete pod <имя-пода>, после чего контроллер (Deployment, StatefulSet, DaemonSet) поднимает новый экземпляр вместе со всеми контейнерами. Если под не восстанавливается после падения, причины обычно в пробах и политиках: типичные случаи разобраны в статье про самовосстановление в Kubernetes.
Команды для диагностики состояния пода и контейнеров
- kubectl get pods -o wide: список подов, статусы, IP, узлы, число перезапусков.
- kubectl describe pod <имя-пода>: события, статусы контейнеров, условия, причины ожидания.
- kubectl logs <имя-пода> -c <контейнер>: логи конкретного контейнера; флаг --previous показывает логи предыдущего запуска упавшего контейнера.
- kubectl exec -it <имя-пода> -c <контейнер> -- /bin/sh: вход внутрь для проверки конфигов, портов и файлов.
- kubectl delete pod <имя-пода>: пересоздание пода целиком.
Чек-лист диагностики: статус пода, логи каждого контейнера по отдельности, события из describe, значения ready и restartCount, потребление CPU и памяти относительно requests и limits.
Почему Kubernetes управляет подами: архитектурный взгляд
API-сервер принимает декларативные запросы к объектам, а контроллеры в цикле согласования приводят фактическое состояние к описанному. Контейнер собственного объекта в этом контуре не имеет, поэтому декларативно управлять им нельзя. Как устроен цикл watch и reconcile, мы показывали в разборе архитектуры контроллеров Kubernetes.
Admission control проходит в две фазы: сначала mutating admission controllers, затем validating admission controllers; если любой контроллер на любой фазе отклоняет запрос, весь запрос отклоняется (документация по admission control). Такой порядок объясняет, почему политики вообще влияют на состав контейнеров в поде: они вмешиваются на уровне пода до записи объекта в etcd.
Роль admission controllers в управлении подами
Admission controllers срабатывают на запросы, которые создают, удаляют или изменяют объекты, и не могут блокировать запросы на чтение (get, watch, list), поскольку чтение обходит слой admission control (Kubernetes, Admission Control). Именно поэтому правила формулируют в терминах подов: обязательные метки, запрет privileged-режима, ограничения на hostPath, лимиты ресурсов, разрешённые реестры образов.
Сторонние политики, например OPA Gatekeeper и Kyverno, подключаются к API-серверу как validating webhooks и видят манифест пода целиком со всеми контейнерами, потому что контейнер не отдельный объект для проверки.
Итог: под - объект с именем, метками, квотой, владельцем (ownerReferences) и политиками. Контейнер - поле в спецификации этого объекта, единица выполнения, а не управления.
Практические последствия: когда разница критична при отладке и проектировании
Отладка приложения с sidecar. Логи разнесены по контейнерам: kubectl logs <имя-пода> -c app и kubectl logs <имя-пода> -c envoy показывают разные потоки. Ищите причину в том контейнере, который её породил, а не в поде целиком.
Два контейнера на одном порту. Под соберётся, но один контейнер будет падать с address already in use. Разносите порты и настраивайте обмен по localhost с разными номерами, а единый внешний порт отдавайте через Service.
Init-контейнер для данных. Подготовка конфигов и миграции схемы выполняются до старта основного контейнера, том доступен обоим. Учитывайте побочный эффект: зависший init-контейнер блокирует весь под.
Перезапуск пода вместо контейнера. Смонтированные как том ConfigMap обновляются автоматически: когда ConfigMap, используемый в томе, обновляется, спроецированные ключи в конечном итоге тоже обновляются, а kubelet проверяет свежесть смонтированного ConfigMap при каждой периодической синхронизации. ConfigMap, потребляемые как переменные окружения, автоматически не обновляются и требуют перезапуска пода, то есть перезапуска всех контейнеров.
Ресурсы на двух уровнях. requests и limits задаются для каждого контейнера, а планировщик суммирует requests по поду и ищет узел, где хватает места на все контейнеры сразу. Один прожорливый контейнер ухудшает размещение соседей и удлиняет ожидание в Pending.
Типичные ошибки новичков и как их избежать
- Искать kubectl restart container. Такой команды нет: перезапуск идёт через падение процесса, liveness-пробу или пересоздание пода, а kubectl rollout restart deployment/<имя> обновляет поды контролируемо.
- Ждать отдельный IP у каждого контейнера. Адрес один на под, и Service указывает на поды, а не на контейнеры.
- Смонтировать том в один контейнер и искать файлы в другом. Том описывается в поде, но подключается к каждому контейнеру отдельным volumeMounts.
- Настроить порты без учёта общего localhost. Конфликт портов внутри пода ломает контейнер, хотя сам под остаётся Running.
- Задавать политики и проверки на уровне контейнера. Проверяйте под целиком: контейнер не отдельный объект API.
Отдельный класс проблем связан с пробами, лимитами, RBAC и сетевыми политиками вокруг подов. Их разбор с командами для быстрой диагностики собран в статье про 10 частых ошибок при настройке Kubernetes.
Чек-лист и готовые команды для работы с подами и контейнерами
kubectl get pods -o wide kubectl describe pod <имя-пода> kubectl logs <имя-пода> -c <контейнер> kubectl logs <имя-пода> -c <контейнер> --previous kubectl exec -it <имя-пода> -c <контейнер> -- /bin/sh kubectl delete pod <имя-пода> kubectl rollout restart deployment/<имя>
Чек-лист перед деплоем и во время разбора инцидента:
- Статус пода и число перезапусков: kubectl get pods -o wide.
- Логи каждого контейнера по отдельности, включая предыдущий запуск.
- События и условия пода: kubectl describe pod <имя-пода>.
- Сеть: IP пода, порты, доступность сервиса по localhost из соседнего контейнера.
- Тома: совпадение mountPath и наличие файлов с обеих сторон.
- Ресурсы: requests, limits и фактическое потребление относительно них.
- Политики: restartPolicy, пробы, метки и требования admission-политик кластера.
Все команды выше входят в состав kubectl и не требуют дополнительных плагинов. Начните с kubectl get pods -o wide и kubectl describe pod <имя-пода>: первый покажет, где искать сбой, второй объяснит, почему контейнер внутри пода не поднимается. Помните главное правило: вы управляете подом, а контейнеры живут внутри него по общим правилам сети, тома и жизненного цикла.