Learning Platform
Глоссарий Troubleshooting
Урок 12.01 · 18 мин
Средний
Podmulti-containersidecarinit containerv1.33shared namespaces

Multi-container Pod: зачем и когда

Базовая единица планирования в Kubernetes — не контейнер, а Pod. Pod может содержать один контейнер (типичный случай), а может несколько. И когда вы вынуждены упаковать несколько процессов в один Pod, начинаются нетривиальные решения: какой паттерн использовать, что про lifecycle, как ведёт себя scheduler. С v1.33 окончательно стабилизировалась ещё одна категория — native Sidecar containers через initContainers с restartPolicy: Always (alpha v1.28, beta v1.29 с default on, GA v1.33) — которая решает целый класс старых проблем.


Airflow стек: webserver + scheduler + Postgres в compose

Pod = atomic unit

Pod — это группа контейнеров, которые планируются вместе на один Node, делят набор Linux namespaces и одну lifecycle границу. Это значит:

  • Все контейнеры в Pod на одном Node — scheduler не разнесёт их по разным машинам.
  • Контейнеры делят network namespace — у Pod один IP, контейнеры видят друг друга по localhost, конфликтуют по портам.
  • Контейнеры делят IPC namespace — могут использовать SystemV/POSIX shared memory.
  • Контейнеры могут делить PID namespace — через shareProcessNamespace: true (по умолчанию выключено).
  • Через volumes (особенно emptyDir) контейнеры обмениваются файлами.
  • Pod создаётся целиком и удаляется целиком. Нельзя «передеплоить один контейнер в Pod» — это всегда новый Pod.
Pod: что общее у контейнеров
network nsВсе контейнеры в Pod делят один network namespace: один Pod IP, общая loopback. Запрос к localhost:8080 от любого контейнера идёт в тот же сетевой стек. Конфликт портов — реальная проблема, если два контейнера хотят bind на одинаковый порт.
IPC nsIPC namespace shared по умолчанию. Контейнеры могут использовать shared memory (shm), semaphores, message queues. Это редко используется в стандартных workloads, но критично для legacy приложений.
volumesPod-level volumes (emptyDir, configMap, secret, PVC) можно mount-ить в несколько контейнеров. Это основной способ обмена файлами между контейнерами Pod — например app пишет логи в emptyDir, sidecar читает.
lifecyclePod создаётся атомарно и удаляется атомарно. Нельзя обновить только один контейнер — Pod recreate. Все контейнеры стартуют и стопаются в координации kubelet.
НЕ sharedMNT namespace по умолчанию отдельный (каждый контейнер видит свою файловую систему из образа). PID namespace отдельный, если не включён shareProcessNamespace: true. UTS namespace — отдельный (можно поменять hostname только Pod-wide).

Когда несколько контейнеров в одном Pod — это правильно

Главный критерий — tight coupling. Контейнеры должны:

  • разделять lifecycle (если один умирает — другой не имеет смысла без него);
  • разделять данные через volume или localhost (а не через сеть к другому Pod);
  • быть симбиотическими — основной процесс плюс «помощник», который без него бесполезен.

Канонические сценарии:

  • log-shipper, читающий логи приложения с того же emptyDir;
  • service mesh proxy (Envoy), через который ходит весь трафик приложения;
  • init container, готовящий конфиг до старта приложения;
  • adapter, transforming формат метрик приложения для Prometheus.

Когда несколько контейнеров — это антипаттерн

Если два процесса:

  • независимы по lifecycle (можно перезапустить один без другого);
  • общаются через HTTP/gRPC, а не localhost/volume;
  • могут масштабироваться независимо;

— это разные Deployments + Services, а не один Pod.

WARNING

«Положу backend и frontend в один Pod, чтобы они общались через localhost» — классическая ошибка. Backend и frontend должны масштабироваться независимо, иметь отдельные образы и rolling updates. Это разные Deployments. Один Pod — это про процессы, которые не имеют смысла друг без друга.


4 классических паттерна

Литература по Kubernetes (от Brendan Burns и далее) определяет 4 канонических паттерна multi-container Pod:

Канонические multi-container паттерны
InitinitContainer выполняет setup перед main: дожидается зависимости, генерирует конфиг, накатывает миграцию. Завершается до старта main. Sequential между собой.
SidecarКонтейнер, дополняющий функциональность main: log shipper, metrics exporter, service mesh proxy. Работает параллельно с main весь lifecycle Pod.
AdapterКонтейнер, преобразующий output main в формат для внешней системы: custom logs → JSON, internal metrics → Prometheus exposition. Помогает интеграции без модификации app.
AmbassadorКонтейнер-proxy для outbound соединений main. App connects to localhost:port, ambassador handles mTLS, retries, sharding, connection pooling. Главный пример — Envoy в service mesh.

В реальности граница между sidecar/adapter/ambassador часто размывается. Envoy в service mesh — это технически ambassador (proxy для outbound), но в реализации это sidecar container. Fluent-bit, который читает логи и парсит их в JSON — это и sidecar (дополнение), и adapter (transform). Разделение полезно для проектирования, но имена паттернов в манифесте Kubernetes не существует — есть только два механизма: initContainers и containers.


Killer момент: что такое Sidecar containers GA (v1.33)

Долгое время в Kubernetes sidecar не был отдельной сущностью — это был просто container в spec.containers рядом с main. И это порождало известные проблемы:

  • При завершении main контейнер sidecar мог умереть раньше, чем main допишет логи → последние логи теряются.
  • В Job sidecar (например, Envoy proxy) продолжает работать после завершения main → Pod зависает в Running, Job не Completed.
  • Нет порядка старта: main может стартовать до того, как sidecar готов принять трафик.

Эволюция: alpha в v1.28, beta в v1.29 (default on), GA в v1.33 — новая форма sidecar: container в spec.initContainers с полем restartPolicy: Always. kubelet распознаёт его как sidecar и:

  1. стартует его до main containers (как init, но не ждёт его completion);
  2. считает sidecar Ready (через startup/readiness probe) перед стартом main containers;
  3. при завершении main containers останавливает sidecars — это решает Job problem;
  4. перезапускает sidecar по restartPolicy: Always при падении в течение жизни Pod.
spec:
  initContainers:
  - name: log-shipper
    image: fluent/fluent-bit
    restartPolicy: Always       # это и делает его native sidecar (GA с v1.33)
    volumeMounts:
    - mountPath: /var/log
      name: app-logs
  containers:
  - name: app
    image: my-app:1.0
    volumeMounts:
    - mountPath: /var/log
      name: app-logs
  volumes:
  - name: app-logs
    emptyDir: {}
TIP

С v1.33 GA (beta с v1.29, default on) — sidecar containers через initContainers + restartPolicy: Always — это рекомендованный способ для log shippers, service mesh proxies, secret rotators. Старый способ (просто container в spec.containers) работает, но имеет известные lifecycle проблемы. На CKAD v1.35 ожидайте вопросы про оба способа и их отличия.


Простой пример: app + log-shipper

Минимальный multi-container Pod с shared volume — приложение пишет логи в файл, sidecar их вычитывает и отправляет наружу.

apiVersion: v1
kind: Pod
metadata:
  name: app-with-logs
spec:
  initContainers:
  - name: log-shipper
    image: fluent/fluent-bit:3.0
    restartPolicy: Always
    volumeMounts:
    - mountPath: /var/log/app
      name: logs
  containers:
  - name: app
    image: my-app:1.0
    volumeMounts:
    - mountPath: /var/log/app
      name: logs
  volumes:
  - name: logs
    emptyDir: {}

Здесь:

  • emptyDir создаётся при старте Pod, mount-ится в оба контейнера на /var/log/app;
  • log-shipper стартует первым (как sidecar в init), main app стартует после Ready log-shipper’а;
  • main пишет логи → log-shipper их вычитывает и шлёт наружу;
  • при terminate Pod main завершается → kubelet завершает sidecars → Pod Terminated.

Проверка знанийKnowledge check
Почему frontend и backend, общающиеся по HTTP, не должны жить в одном Pod?
ОтветAnswer
Они независимы по lifecycle и могут масштабироваться отдельно. Multi-container Pod — для tightly coupled процессов, у которых нет смысла друг без друга (sidecar к main app, init к main). Frontend и backend — это разные Deployments + Services. Иначе вы теряете возможность независимого scaling, rolling update только одного из них, и привязываете их количество реплик друг к другу.
Проверка знанийKnowledge check
Что делят контейнеры в одном Pod по умолчанию: network namespace, PID namespace, MNT namespace?
ОтветAnswer
Network namespace — да (один Pod IP, localhost между контейнерами, общие порты). MNT namespace — нет (каждый контейнер видит свою файловую систему из образа, обмен через volumes). PID namespace — по умолчанию нет (можно включить через shareProcessNamespace: true). Также shared: IPC namespace и UTS (hostname).
Проверка знанийKnowledge check
Чем sidecar container в spec.initContainers с restartPolicy: Always (GA v1.33) отличается от классического sidecar в spec.containers?
ОтветAnswer
Native sidecar (beta v1.29, GA v1.33): (1) гарантированно стартует ДО main containers, (2) считается Ready через startup probe перед стартом main, (3) при завершении main останавливается kubelet — это решает Job problem, (4) restarts по restartPolicy: Always. Классический sidecar — просто container в spec.containers, нет ordering, нет lifecycle awareness, в Job висит после завершения main.
Проверка знанийKnowledge check
Какой Linux namespace в Pod делится по умолчанию между контейнерами и почему это критично для sidecar/ambassador паттернов?
ОтветAnswer
Network namespace. Все контейнеры в Pod имеют один Pod IP и общий loopback. Это позволяет main приложению connecting на localhost:port для общения с ambassador (Envoy proxy), а sidecar — bind на localhost для экспорта метрик. Главное последствие — конфликт портов: два контейнера в Pod не могут bind на один и тот же порт.

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

Результат: 0 из 0
Концептуальный
Вопрос 1 из 5. Какой Linux namespace по умолчанию делят все контейнеры в одном Pod?

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

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

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

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