Поды и контейнеры в Kubernetes: ключевые отличия и практические последствия | AdminWiki

Поды и контейнеры в Kubernetes: ключевые отличия и практические последствия

03 октября 2026 11 мин. чтения

Короткий ответ: под в 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 на разных номерах.

Чек-лист сетевой отладки пода:

  1. Посмотрите IP пода: kubectl get pod <имя-пода> -o wide.
  2. Проверьте порты каждого контейнера: kubectl get pod <имя-пода> -o jsonpath='{.spec.containers[*].ports}'.
  3. Снимите логи каждого контейнера отдельно: kubectl logs <имя-пода> -c <контейнер>.
  4. Проверьте связку изнутри: 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/<имя>

Чек-лист перед деплоем и во время разбора инцидента:

  1. Статус пода и число перезапусков: kubectl get pods -o wide.
  2. Логи каждого контейнера по отдельности, включая предыдущий запуск.
  3. События и условия пода: kubectl describe pod <имя-пода>.
  4. Сеть: IP пода, порты, доступность сервиса по localhost из соседнего контейнера.
  5. Тома: совпадение mountPath и наличие файлов с обеих сторон.
  6. Ресурсы: requests, limits и фактическое потребление относительно них.
  7. Политики: restartPolicy, пробы, метки и требования admission-политик кластера.

Все команды выше входят в состав kubectl и не требуют дополнительных плагинов. Начните с kubectl get pods -o wide и kubectl describe pod <имя-пода>: первый покажет, где искать сбой, второй объяснит, почему контейнер внутри пода не поднимается. Помните главное правило: вы управляете подом, а контейнеры живут внутри него по общим правилам сети, тома и жизненного цикла.

Поделиться: