Из чего состоит под в Kubernetes: pause-контейнер, прикладные контейнеры и общие ресурсы | AdminWiki

Из чего состоит под в Kubernetes: pause-контейнер, прикладные контейнеры и общие ресурсы

03 октября 2026 13 мин. чтения
Содержание статьи

Под в Kubernetes состоит из двух уровней: инфраструктурного pause-контейнера, который создаёт и удерживает общие пространства имён, и одного или нескольких прикладных контейнеров, которые к этим пространствам присоединяются. У всех контейнеров пода один сетевой интерфейс и один IP-адрес, они обращаются друг к другу через localhost, делят IPC-шину и монтируют одни и те же тома.

Отсюда следствие, которое снимает большую часть путаницы: в kubectl get pods вы видите поды, а не контейнеры. Внутри пода контейнеров почти всегда больше, чем один, и как минимум один из них - pause-контейнер, который вы не описывали в манифесте.

Ниже разбираем по частям: роль pause-контейнера, поведение прикладных и init-контейнеров, общие namespace и тома, критерии для sidecar и команды проверки на своём кластере.

Что такое под в Kubernetes и почему это не просто контейнер

Под - минимальная единица развёртывания и планирования в Kubernetes. kube-scheduler выбирает для пода одну ноду, и все контейнеры пода гарантированно оказываются на ней: разнести их по разным машинам нельзя. Внутри ноды kubelet через CRI просит container runtime создать sandbox, а затем контейнеры. Полный путь от kubectl apply до запуска процесса разобран в статье про архитектуру Kubernetes и путь пода от манифеста до запуска.

Контейнер с точки зрения ядра Linux - это процесс в изолированных пространствах имён, ограниченный cgroup. Docker и containerd запускают такие процессы из образов, а Kubernetes добавляет ещё один уровень абстракции: под как обёртку над группой процессов с частично общими пространствами имён. Один под может содержать один контейнер (типовой случай для stateless-сервиса) или несколько: прикладные, init и sidecar.

Под vs контейнер: в чём разница на практике

Разница проявляется в трёх местах: сеть, адресация и команды. У двух контейнеров одного пода один и тот же IP и одна таблица маршрутизации, поэтому связь между ними идёт через localhost, а Service для этого не нужен. У контейнеров разных подов адреса разные, и для связи понадобится Service, прямой podIP или DNS-имя.

Второй момент: kubectl get pods не показывает контейнеры. Чтобы получить их список, запросите spec пода:

kubectl get pod my-app-7d9c8f4b6f-2xk9p -o jsonpath='{.spec.containers[*].name}'

Вывод вида app nginx-exporter означает два прикладных контейнера в одном поде. Флаг -o wide добавляет podIP и ноду, а -o yaml показывает полный манифест с томами, портами и ресурсами.

Pause-контейнер: инфраструктурный контейнер пода

pause-контейнер создаётся первым, ещё до прикладных. Прикладной логики в нём нет: его процесс ничего не обрабатывает и не слушает порты. Задача другая - создать общие пространства имён и удерживать их, пока в поде работает хотя бы один контейнер. pause-контейнер существует в каждом поде и служит контейнером-шаблоном, от которого все новые контейнеры в поде наследуют namespaces; он резервирует namespaces, общие для всех контейнеров внутри пода (разбор роли pause-контейнера).

Образ у pause-контейнера минималистичный: в поставке Kubernetes это registry.k8s.io/pause, и в документации containerd рекомендуется использовать registry.k8s.io/pause:3.10 как CRI sandbox-образ. Конкретные имена образов и версии sandbox зависят от версии kubelet и container runtime, поэтому проверяйте их на своей ноде командами из раздела про crictl, а не по памяти.

Как pause-контейнер удерживает network namespace

container runtime создаёт sandbox: новый сетевой namespace, виртуальный интерфейс, IP-адрес из podCIDR ноды, таблицы маршрутизации. В этом namespace живёт pause-контейнер. При создании пода процесс pause вызывает интерфейс CRI, чтобы создать окружение «песочницу» и настроить базовую среду выполнения, например сеть; как только процесс запущен, в его namespace можно добавлять другие процессы, формируя под с общими namespace (разбор роли pause-контейнера). Прикладные контейнеры стартуют с указанием присоединиться к готовому namespace, поэтому видят тот же интерфейс, тот же IP и тот же loopback.

Пример: в поде nginx слушает 80, а sidecar-прокси слушает 15001. Прокси обращается к бэкенду по 127.0.0.1:80, nginx отвечает. Ни Service, ни DNS в этой схеме не участвуют: трафик не покидает под.

Проверка - посмотреть интерфейсы изнутри прикладных контейнеров:

kubectl exec my-app-7d9c8f4b6f-2xk9p -c app -- ip addr
kubectl exec my-app-7d9c8f4b6f-2xk9p -c nginx-exporter -- ip addr

Список интерфейсов и IPv4-адрес совпадут. Если утилиты ip в образе нет, добавьте временный контейнер в под:

kubectl debug -it my-app-7d9c8f4b6f-2xk9p --image=nicolaka/netshoot --target=app

Контейнеры внутри одного пода и так используют общий network namespace, а флаг --target нужен, чтобы видеть ещё и процессы конкретного контейнера. Как устроены маршруты внутри пода и на ноде, разбираем отдельно в материале про таблицы маршрутов между подами и нодами.

Почему pause-контейнер не виден в kubectl get pods

pause-контейнер не описан в spec пода: его создаёт kubelet, а не вы в манифесте. Поэтому ни kubectl get pods, ни kubectl describe pod его не покажут, и в status.containerStatuses его тоже не будет. Чтобы найти pause-контейнер, зайдите на ноду по SSH и выполните:

crictl ps --name pause
crictl pods

Первая команда покажет все sandbox-контейнеры на ноде, вторая - поды, которым они соответствуют. Диагностическая ценность высокая: если прикладные контейнеры не получают IP или под висит в ContainerCreating, причина часто в sandbox, а не в приложении.

Прикладные контейнеры в поде: сколько их может быть и зачем

Прикладной контейнер выполняет работу: обслуживает HTTP, забирает сообщения из очереди, проксирует трафик, отдаёт метрики. Количество таких контейнеров в поде не ограничено единицей, но ограничено ресурсами ноды и логикой планирования.

Что общего и что разного: сеть, IPC и UTS у контейнеров общие, файловые системы раздельные. Каждый контейнер стартует из своего образа, и файл, записанный в /tmp одного контейнера, другому не виден. Обмен файлами идёт только через тома, смонтированные в оба контейнера.

Классический пример связки без sidecar: nginx и php-fpm в одном поде. php-fpm слушает 127.0.0.1:9000, nginx проксирует к нему по тому же адресу, статика монтируется в оба контейнера общим томом.

kubectl get pod my-app-7d9c8f4b6f-2xk9p -o jsonpath='{.spec.containers[*].name}'

Init-контейнеры: подготовка перед запуском прикладных

Init-контейнеры запускаются до прикладных, выполняются строго последовательно и должны завершиться с кодом 0. Пока последний init не завершится, прикладные контейнеры не стартуют, а под остаётся в фазе Pending с причиной вида Init:0/2. Типовые задачи: прогнать миграции, выкачать конфиг из внешнего источника, выставить права на том.

apiVersion: v1
kind: Pod
metadata:
  name: app-with-init
spec:
  initContainers:
  - name: fetch-config
    image: alpine/git:2.45.2
    command: ["git", "clone", "https://git.example.com/app/config.git", "/config"]
    volumeMounts:
    - name: config
      mountPath: /config
  containers:
  - name: app
    image: example/app:1.4.2
    volumeMounts:
    - name: config
      mountPath: /etc/app
  volumes:
  - name: config
    emptyDir: {}

Посмотреть список init-контейнеров можно отдельным запросом:

kubectl get pod app-with-init -o jsonpath='{.spec.initContainers[*].name}'

Init-контейнер, созданный с restartPolicy: Always, запускается и остаётся работающим в течение всей жизни пода: Kubernetes реализует sidecar-контейнеры как особый случай init-контейнеров, и такие контейнеры продолжают работать после старта пода (Sidecar Containers). Эта функциональность впервые стала доступна в релизе Kubernetes v1.28 и является стабильной начиная с версии v1.33.

Общие ресурсы контейнеров в поде: network namespace, IPC и тома

Набор общих ресурсов у контейнеров пода невелик, но именно он определяет всю механику взаимодействия.

РесурсСтатус в подеЧто это даёт
network namespaceОбщий, создаёт pause-контейнерОдин IP, один loopback, связь через localhost
IPC namespaceОбщий по умолчаниюShared memory, семафоры, очереди сообщений между контейнерами
UTS namespaceОбщийВсе контейнеры видят одно hostname пода
PID namespaceРаздельный по умолчаниюПроцессы соседнего контейнера не видны; включается через shareProcessNamespace: true
ТомаМонтируются в указанные контейнерыОсновной штатный способ обмена файлами внутри пода

Network namespace: один IP на весь под

Адрес пода берётся из podCIDR ноды и живёт до удаления пода. Своего IP-стека на каждый контейнер нет, поэтому два контейнера не могут слушать один и тот же порт на одном IP. Связка nginx:80 и экспортера, который тоже хочет 80, не запустится: конфликт портов. Разносите сервисы по разным портам или по разным подам.

Проверить адрес пода и общее hostname:

kubectl get pod my-app-7d9c8f4b6f-2xk9p -o jsonpath='{.status.podIP}'
kubectl exec my-app-7d9c8f4b6f-2xk9p -c app -- hostname

hostname у всех контейнеров пода одинаковый - это следствие общего UTS namespace. Для доступа к поду снаружи используют Service: он получает ClusterIP, по которому Kubernetes распределяет трафик между подами. Каждый ClusterIP уникален в кластере, а попытка занять уже выделенный адрес завершается ошибкой; динамическое выделение идёт из верхней части диапазона, и только когда она исчерпана - из нижней (Allocation des ClusterIP des Services). Место пода среди остальных объектов API разобрано в обзоре ключевых объектов Kubernetes.

IPC namespace: общая шина для взаимодействия

Контейнеры одного пода видят одни и те же объекты межпроцессного взаимодействия: POSIX shared memory, семафоры, очереди сообщений. Это сценарий для приложений, которые передают данные через /dev/shm без сети и без файлов.

Уточнение, где часто путаются: shareProcessNamespace управляет PID namespace, к IPC он отношения не имеет. Общий IPC внутри пода - поведение по умолчанию, отдельного флага для его отключения в PodSpec нет. Вариант с hostIPC выносит контейнеры в IPC-пространство ноды и снижает изоляцию.

kubectl exec my-app-7d9c8f4b6f-2xk9p -c app -- ipcs -m

Утилита ipcs есть не во всех образах, поэтому для проверки берите образ с пакетом util-linux или временный контейнер через kubectl debug.

Общие тома: emptyDir и другие способы обмена файлами

Том описывается один раз на уровне пода, а монтируется в те контейнеры, где для него указан volumeMounts. Жизненный цикл тома связан с подом, а не с контейнером: временные (ephemeral) тома имеют жизненный цикл, привязанный к конкретному поду, и когда под перестаёт существовать, Kubernetes уничтожает ephemeral-тома, но не уничтожает persistent-тома (Volumes). Поэтому в emptyDir удобно держать данные между перезапусками контейнера.

Типы томов различаются источником данных и сроком жизни:

  • emptyDir - каталог на диске ноды (или в памяти при medium: Memory), удаляется вместе с подом;
  • configMap и secret - файлы из объектов API, при стандартных настройках монтируются только для чтения;
  • hostPath - путь с ноды, годится для системных задач и требует аккуратности с точки зрения безопасности;
  • persistentVolumeClaim - том, который переживает под; подключается через StorageClass и CSI-драйвер.

Для emptyDir можно указать medium: Memory - тогда Kubernetes монтирует tmpfs (RAM-backed); если размер не задан, тома в памяти ограничиваются объёмом allocatable-памяти узла (Volumes).

kubectl get pod my-app-7d9c8f4b6f-2xk9p -o jsonpath='{.spec.volumes[*].name}'

Sidecar-паттерн: когда несколько контейнеров в поде оправданы

Sidecar - вспомогательный контейнер, который живёт рядом с основным и решает задачу из смежной области: доставка логов, проксирование с mTLS, съём метрик, синхронизация конфигурации. Родственные роли называют ambassador (прокси к внешнему сервису) и adapter (приведение формата метрик или логов к нужному виду).

Критерий, по которому видно, что sidecar уместен: задача требует общего network namespace или общего тома и должна решаться вместе с основным контейнером. Если оба условия не выполняются, под - не то место.

Пример sidecar для логирования

Основной контейнер пишет логи в файл, sidecar читает тот же каталог и отправляет их в централизованное хранилище. Приложение при этом ничего не знает о логировании.

apiVersion: v1
kind: Pod
metadata:
  name: web-with-logger
spec:
  containers:
  - name: web
    image: nginx:1.27
    volumeMounts:
    - name: logs
      mountPath: /var/log/nginx
  - name: logger
    image: fluent/fluent-bit:3.1
    volumeMounts:
    - name: logs
      mountPath: /var/log/nginx
  volumes:
  - name: logs
    emptyDir: {}

Логи sidecar вытаскиваются отдельно от логов приложения:

kubectl logs web-with-logger -c logger --tail=50
kubectl logs web-with-logger -c web --tail=50

Такой же каркас используется для sidecar-прокси на Envoy с mTLS, для nginx-prometheus-exporter и для git-sync, который обновляет конфиг в общем томе. В каждом случае общий ресурс - network namespace или том.

Когда sidecar лишнее усложнение

Есть сценарии, где второй контейнер в поде создаёт больше проблем, чем решает:

  • задача закрывается на уровне ноды: сбор системных метрик, ротация логов, сетевые политики через DaemonSet;
  • компонент нужно масштабировать и обновлять независимо от приложения;
  • контейнеры не взаимодействуют и не делят ресурсы: их логичнее разнести по разным подам и связать через Service;
  • sidecar удваивает потребление памяти на каждый экземпляр пода, а выгода не сформулирована;
  • один из контейнеров просто ждёт или спит, пока работает второй, - это антипаттерн.

Проверить, не раздулся ли под, помогает тот же jsonpath по контейнерам:

kubectl get pod web-with-logger -o jsonpath='{range .spec.containers[*]}{.name}{"\t"}{.resources}{"\n"}{end}'

Как проверить состав пода и его контейнеров: команды для практики

Набор команд ниже закрывает большинство вопросов: какие контейнеры в поде, какой у них статус, какие тома и общий ли у них сетевой стек. Примеры даны для пространства имён по умолчанию; добавьте -n , если под в другом.

Просмотр контейнеров через kubectl

# прикладные и init-контейнеры
kubectl get pod my-app-7d9c8f4b6f-2xk9p -o jsonpath='{.spec.containers[*].name}'
kubectl get pod my-app-7d9c8f4b6f-2xk9p -o jsonpath='{.spec.initContainers[*].name}'

# фактически запущенные контейнеры
kubectl get pod my-app-7d9c8f4b6f-2xk9p -o jsonpath='{.status.containerStatuses[*].name}'

# все поды с их контейнерами
kubectl get pods -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.spec.containers[*].name}{"\n"}{end}'

# тома и IP пода
kubectl get pod my-app-7d9c8f4b6f-2xk9p -o jsonpath='{.spec.volumes[*].name}'
kubectl get pod my-app-7d9c8f4b6f-2xk9p -o jsonpath='{.status.podIP}'

Разница между spec и status принципиальна: spec показывает, что вы попросили, status - что реально запущено. Если в spec два контейнера, а в status один, второй не стартовал: смотрите kubectl describe pod и блок Events.

kubectl describe pod my-app-7d9c8f4b6f-2xk9p

В блоке Containers видны образы, порты, запросы и лимиты, точки монтирования; в Events - причины проблем с планированием, образами и томами.

Просмотр pause-контейнера через crictl

pause-контейнер доступен только со стороны ноды, через CRI. Если у вас есть SSH-доступ к worker-узлу:

sudo crictl ps --name pause
sudo crictl pods
sudo crictl ps -a | head

Флаг -a показывает и остановленные контейнеры, что полезно, когда sandbox перезапускается. Убедиться, что найденный контейнер действительно инфраструктурный, можно по образу:

sudo crictl inspect  | grep -i image

Связка sandbox и пода видна через crictl pods: у каждого пода свой sandbox, а имена совпадают с именами подов в kubectl. Для доступа к узлу без SSH используйте kubectl debug node/ с образом, где есть nsenter и доступ к сокету CRI. Если ноды разворачивались вручную, пригодится материал про установку кластера на kubeadm и containerd.

Частые ошибки и вопросы при работе с подами

Короткий список того, что ломается чаще всего:

  • два контейнера слушают один порт на общем IP - второй не стартует;
  • ожидание сетевой изоляции между контейнерами пода;
  • запись файла в файловую систему контейнера и попытка прочитать его из соседнего контейнера без общего тома;
  • sidecar, который не обменивается данными с основным контейнером и не делит с ним ресурсы;
  • расчёт на то, что init-контейнер работает в фоне, тогда как он обязан завершиться до старта прикладных.

Отдельно про безопасность: PodSecurityPolicy устарел как механизм управления безопасностью подов, и в документации приведено сопоставление старых политик с Pod Security Standards (PodSecurityPolicy в Pod Security Standards). Планируйте ограничения подов через актуальные стандарты, а не через PSP.

Почему контейнеры в поде не изолированы по сети

Это проектное решение, а не недоработка. Общий network namespace делает связь между контейнерами пода локальной: один процесс слушает 127.0.0.1:8080, второй обращается туда напрямую, без Service, DNS и лишнего сетевого хопа. Плата за удобство - отсутствие сетевой изоляции между контейнерами: если она нужна, разносите их по разным подам и ограничивайте трафик NetworkPolicy.

Как ограничить ресурсы для контейнеров в поде

requests и limits задаются для каждого контейнера отдельно, а scheduler суммирует requests всех контейнеров пода, включая init. Если сумма не помещается ни на одной ноде, под останется в Pending. Kubernetes предоставляет штатные механизмы описания, запроса, выделения и ограничения ресурсов, которые использует рабочая нагрузка (Resource management).

  containers:
  - name: app
    image: example/app:1.4.2
    resources:
      requests:
        cpu: 100m
        memory: 128Mi
      limits:
        cpu: 500m
        memory: 256Mi
  - name: logger
    image: fluent/fluent-bit:3.1
    resources:
      requests:
        cpu: 50m
        memory: 64Mi
      limits:
        cpu: 200m
        memory: 128Mi

Посмотреть фактические запросы и лимиты по контейнерам:

kubectl get pod my-app-7d9c8f4b6f-2xk9p -o jsonpath='{range .spec.containers[*]}{.name}{"\t"}{.resources}{"\n"}{end}'

Если контейнер регулярно упирается в memory limit, его сбрасывает OOMKiller, а под получает статус OOMKilled. Как измерить реальное потребление и настроить cgroups без запаса «на глаз», разобрано в статье про производительность и эффективность контейнеров.

Перед деплоем прогоняйте короткий чек-лист: список контейнеров в spec совпадает с ожидаемым, у каждого контейнера заданы requests и limits, общие тома смонтированы во все контейнеры, которым они нужны, ни один контейнер не слушает занятый порт, а вспомогательные контейнеры действительно делят с основным сеть или том. Пяти проверок достаточно, чтобы не ловить ContainerCreating, конфликты портов и OOMKilled после выката.

Поделиться: