Learning Platform
Глоссарий Troubleshooting
Урок 05.01 · 22 мин
Средний
Podnamespacespause containerCNIPodIPDNS

Анатомия Pod

Pod — наименьшая deployable unit в Kubernetes. Это не контейнер, и это важно: Pod — это группа из одного или нескольких контейнеров, которые делят между собой набор Linux-ресурсов и всегда запускаются на одной node. Когда вы говорите «запустить приложение в K8s», на физическом уровне вы говорите «создать Pod на каком-то узле, внутри которого container runtime поднимет контейнеры». Если контейнеров в Pod несколько — они стартуют и убиваются вместе, видят друг друга на localhost и могут общаться через shared memory. Pod нельзя переместить между узлами: Pod либо живёт на узле A, либо удаляется и пересоздаётся на узле B как новый объект с новым IP.


Почему Pod, а не контейнер

Истоки идеи Pod идут от Google Borg. На практике почти всегда у вас один контейнер на Pod, и тогда Pod выглядит как «обёртка над контейнером». Но иногда нужны tightly coupled processes: процессы, которые обязаны жить на одной машине, иметь общий network namespace, общий доступ к диску и общий жизненный цикл. Примеры из реальности:

  • Sidecar — рядом с основным приложением крутится прокси (Envoy в service mesh), который перехватывает сетевой трафик. Им нужен общий network namespace, чтобы прокси видел localhost:8080 приложения.
  • Init контейнеры — отдельные контейнеры, которые запускаются ДО основных, делают какую-то подготовку (миграции БД, генерация конфига, дождаться доступности зависимости) и завершаются.
  • Adapter / Ambassador — конвертация форматов логов, прокси к внешнему сервису через shared loopback.

Pod как абстракция позволяет планировать (schedule) такую группу процессов атомарно: scheduler выбирает один узел для всей группы, kubelet запускает их вместе, livenessProbe убивает их вместе.

NOTE

Pod — это не виртуальная машина и не «маленький сервер». Это группа контейнеров, разделяющих набор Linux namespaces и volumes. Никакого собственного OS, никакого systemd, никакого SSH внутри Pod — только процессы контейнеров.


Linux namespaces и capabilities: как ядро делит root-полномочия

Shared Linux namespaces

Технически Pod в Linux реализован как набор контейнеров, у которых частично shared namespaces. Linux namespace — это механизм изоляции (PID, network, mount, IPC, UTS, user, cgroup), Docker и containerd используют их, чтобы один контейнер не видел процессы и сеть другого. В Pod часть этих namespace специально делается общей:

Namespaces внутри одного Pod
network namespaceОбщий на весь Pod. Все контейнеры видят один сетевой стек: один IP, общий список портов, общий loopback. Поэтому два контейнера в одном Pod не могут слушать один и тот же порт — у них общий 0.0.0.0.
IPC namespaceОбщий по умолчанию. Контейнеры могут использовать System V IPC (shared memory, semaphores) и POSIX message queues друг с другом. Редко нужно в современных приложениях, но иногда — для legacy.
UTS namespaceОбщий: одно hostname на весь Pod. Поэтому spec.hostname задаётся на уровне Pod, а не контейнера.
PID namespaceПо умолчанию каждый контейнер имеет свой PID namespace — процессы одного контейнера не видят процессов другого через ps. Можно переключить: spec.shareProcessNamespace: true делает PID общим (полезно для sidecar, которому нужно увидеть процесс приложения).
mount namespaceИзолирован: каждый контейнер видит свой rootfs из своего образа. Но volumes из spec.volumes монтируются в указанные mount points нескольких контейнеров — это и есть способ обмена файлами внутри Pod.
user / cgroupuser namespace обычно не используется в K8s (хотя есть поддержка). cgroup namespace изолирован — каждый контейнер видит свои cgroup limits для CPU и памяти.

Ключевой момент: общий network namespace означает, что все контейнеры в Pod видят друг друга на 127.0.0.1. Если контейнер app слушает :8080, а контейнер sidecar хочет до него достучаться — он просто делает curl http://localhost:8080. Никакого Service, никакого DNS — обычный loopback. Это и есть главная техническая причина существования Pod.

apiVersion: v1
kind: Pod
metadata:
  name: app-with-sidecar
spec:
  shareProcessNamespace: true   # опционально: общий PID namespace
  containers:
  - name: app
    image: myapp:1.0
    ports:
    - containerPort: 8080
  - name: sidecar
    image: envoyproxy/envoy:v1.30
    # этот контейнер видит app на localhost:8080

pause container: невидимый держатель namespaces

Когда вы делаете crictl ps на узле, вы увидите больше контейнеров, чем у вас в Pod-spec. На каждый Pod kubelet поднимает дополнительный pause container (он же infra container, sandbox container). Этот контейнер:

  • запускается первым в Pod;
  • держит все shared namespaces (network, IPC, UTS);
  • ничего не делает — буквально вызывает pause() в C и спит навечно;
  • весит ~700 KB (registry.k8s.io/pause).

Зачем он нужен. Когда обычный контейнер падает и kubelet его перезапускает, он должен сохранить network namespace — иначе Pod получит новый IP при каждом restart. Pause container держит namespace «живым», пока другие контейнеры пересоздаются вокруг него. Когда pause умирает — умирает весь Pod, namespace освобождается, и kubelet говорит CNI plugin отпустить IP.

TIP

Pause container — это причина, по которой kubectl get pods показывает Pod как Running даже если ваш app container в CrashLoopBackOff. Pod считается «живым», пока жив pause. Реальный статус приложения смотрите в status.containerStatuses.


PodIP и CNI

У каждого Pod — собственный IP-адрес, выделенный из cluster pod CIDR (например, 10.244.0.0/16). Этот IP не привязан к IP узла. Pod-ы на разных узлах могут общаться напрямую по IP без NAT — это базовое требование Kubernetes networking model.

Кто выдаёт IP: CNI plugin (Container Network Interface). Это бинарник, который kubelet вызывает в момент создания Pod. Популярные CNI: Calico, Cilium, Flannel, Weave Net, AWS VPC CNI, Azure CNI. Они отличаются по тому, как реализуют сеть (overlay через VXLAN, BGP, eBPF), но интерфейс к kubelet — стандартный.

Создание Pod: цепочка событий до получения IP
kubeletАгент K8s на узле. Получает Pod через watch API server. Видит, что Pod назначен на его узел.
CRI: RunPodSandboxkubelet вызывает container runtime (containerd, CRI-O) через CRI: создай pause container, подними его network namespace.
CNI ADDRuntime вызывает CNI plugin через бинарник: ему передают netns Pod-а. Plugin создаёт veth pair, выделяет IP из pool, настраивает routes.
kubelet StartContainerПосле того как sandbox с IP готов, kubelet запускает в нём app containers через CRI. Они присоединяются к namespace pause container — получают тот же IP.
API serverkubelet репортит IP назад в status.podIP. Теперь kubectl get pod -o wide показывает IP.

PodIP — эфемерный: после restart Pod (kubectl delete + Deployment пересоздаёт) IP почти всегда другой. Никогда не пишите код, который полагается на конкретный PodIP. Используйте Services (ClusterIP, headless) или DNS-имена — это стабильные endpoints.


hostname, subdomain и DNS-имя Pod

По умолчанию hostname Pod-а равен имени Pod (metadata.name). Это видно изнутри:

kubectl exec mypod -- hostname
# mypod

Если задать spec.hostname и spec.subdomain, и в том же namespace создать headless Service с тем же именем, что и subdomain, — Pod получит полноценное FQDN в cluster DNS:

apiVersion: v1
kind: Pod
metadata:
  name: db-0
spec:
  hostname: db-0
  subdomain: mydb
  containers:
  - name: postgres
    image: postgres:16

При наличии headless Service mydb в том же namespace, Pod будет резолвиться как db-0.mydb.<namespace>.svc.cluster.local. Это базовый механизм StatefulSet — у каждой реплики стабильное DNS-имя. Подробно — в модуле про Services.


Базовый Pod manifest

Самый минимальный валидный Pod:

apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  containers:
  - name: nginx
    image: nginx:1.27
    ports:
    - containerPort: 80

Применяется:

kubectl apply -f nginx.yaml
kubectl get pod nginx
kubectl describe pod nginx
kubectl logs nginx
kubectl exec -it nginx -- /bin/sh
kubectl delete pod nginx

В реальности голые Pod создаются редко — обычно через Deployment, который сам управляет ими. Но для CKAD понимание чистого Pod manifest обязательно: половина задач на экзамене начинается с kubectl run или kubectl create -f pod.yaml.


Killer момент: containerPort — это документация

В spec вы пишете:

ports:
- containerPort: 8080
  name: http
  protocol: TCP

Логичный вопрос: что произойдёт, если этого блока не указать? Ничего не сломается. Приложение продолжит слушать на 8080, и любой другой Pod (или сам узел) сможет до него достучаться по PodIP:8080.

Дело в том, что containerPort не открывает порт и не настраивает iptables. CNI plugin не читает это поле. Это чисто декларативная документация — для людей, читающих YAML, и для других объектов K8s, которые на него ссылаются. Реальное прослушивание порта определяется тем, какие порты открыло само приложение внутри контейнера.

WARNING

Тогда зачем containerPort указывать? Три причины: (1) документация — читатель сразу видит, на каких портах слушает Pod; (2) ServiceMonitor от Prometheus может ссылаться на порт по имени; (3) kubectl port-forward и Service.targetPort могут ссылаться на name из этого блока, что удобнее, чем хардкодить число.

hostPort — это уже другая история: он реально пробрасывает порт узла внутрь Pod (CNI это делает). Но hostPort в продакшене почти не используется — он ломает scheduling (только один Pod с этим портом на узле) и обходит абстракцию Service.


QoS class: предисловие

У каждого Pod есть QoS class — Guaranteed, Burstable, BestEffort. Он вычисляется автоматически из resources.requests и resources.limits контейнеров и влияет на то, какой Pod kubelet убьёт первым при нехватке ресурсов на узле. Подробно — в модуле 13 про Resource Management. Сейчас достаточно знать, что QoS — это computed поле, вы его не задаёте напрямую.


Проверка знанийKnowledge check
Два контейнера в одном Pod хотят оба слушать порт 8080. Что произойдёт?
ОтветAnswer
Второй контейнер не сможет забиндиться на 8080 и упадёт с ошибкой 'address already in use'. Network namespace в Pod общий, поэтому порт 8080 на 0.0.0.0 уже занят первым контейнером. Внутри Pod порты — общий ресурс.
Проверка знанийKnowledge check
Pod был перезапущен (один из контейнеров упал и поднялся). PodIP изменился?
ОтветAnswer
Нет. Restart контейнера не пересоздаёт Pod — pause container держит network namespace, IP остаётся тем же. IP меняется только при полном пересоздании Pod (например, Deployment удалил старый Pod и создал новый).
Проверка знанийKnowledge check
Зачем нужен pause container, если он ничего не делает?
ОтветAnswer
Он держит shared Linux namespaces (network, IPC, UTS) для всех остальных контейнеров Pod. Когда app container падает и kubelet его перезапускает, pause продолжает жить — поэтому Pod сохраняет тот же IP и доступ к volumes. Без pause каждый restart означал бы новый network namespace, новый IP и потерю всех соединений.

Проверьте понимание

Результат: 0 из 0
Прикладной
Вопрос 1 из 5. Два контейнера в одном Pod пытаются забиндиться на TCP-порт 8080. Какое поведение ожидается?

Закончили урок?

Отметьте его как пройденный, чтобы отслеживать свой прогресс

Войдите чтобы оценить урок

Прогресс модуля
0 из 5