Материал открыт для чтения

Войдите, чтобы сохранять прогресс и результаты.

Войти
Kubernetes — Продвинутая практика

Multi-container Pods: init, sidecar и роли контейнеров

чтение ~11 минУрок 1 из 28

Multi-container Pods: init, sidecar и роли контейнеров

module k8s ckad Image01

Обычно в Pod запускают один основной контейнер приложения. Это хорошее правило: один Pod - одна прикладная единица, которую Kubernetes планирует, перезапускает и удаляет целиком.

Multi-container Pod нужен не для того, чтобы положить несколько независимых сервисов в одно место. Он нужен, когда у приложения есть тесно связанный helper: контейнеры должны жить на одной node, делить localhost, общий volume или общий lifecycle.

Простой критерий:

контейнеры масштабируются и обновляются вместе -> можно думать про один Pod
контейнеры масштабируются и обновляются отдельно -> нужны разные workloads

Если backend и database имеют разные релизы, разные ресурсы и разное масштабирование, это разные Pod'ы и связь через Service. Если приложение пишет лог в файл, а соседний контейнер читает этот же файл и отправляет наружу, это уже нормальный кандидат на multi-container Pod.

Что общее внутри Pod

Контейнеры внутри одного Pod не становятся одним процессом. Но они получают несколько общих пространств и поэтому могут работать как одна прикладная единица.

Общее внутри Pod Практический смысл
IP-адрес Pod контейнеры могут обращаться друг к другу через localhost
network namespace порты внутри Pod должны не конфликтовать
volumes контейнеры могут обмениваться файлами
lifecycle Pod Pod планируется на одну node целиком

Что остаётся отдельным:

  • filesystem каждого container image;
  • process namespace по умолчанию;
  • env-переменные контейнера;
  • command/args;
  • securityContext контейнера;
  • логи каждого контейнера.

Из-за этого в multi-container Pod почти всегда важно явно указывать имя контейнера:

kubectl logs app-pod -c app
kubectl logs app-pod -c helper
kubectl exec app-pod -c app -- printenv

Флаг -c выбирает контейнер внутри Pod. Pod один, но потоков логов и командных окружений несколько.

Технические роли контейнеров

В Kubernetes есть несколько технических мест, куда можно положить контейнер:

Роль Где описывается Как живёт
app container spec.containers основной долгоживущий процесс приложения
init container spec.initContainers выполняется до app containers и должен завершиться
sidecar container обычно spec.containers, а в современном Kubernetes может быть init container с restartPolicy: Always работает рядом с приложением

Главная развилка:

нужно сделать один раз до старта -> init container
нужно работать всё время рядом -> sidecar container

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

Архитектурные роли

Важно не смешивать механику Kubernetes и архитектурные названия.

init container и sidecar container - это способы запуска контейнеров внутри Pod. А ambassador, adapter, log shipper, metrics exporter, proxy - это смысловые роли, которые чаще всего реализуются как sidecar.

Архитектурная роль Что делает
ambassador проксирует запросы приложения к внешней системе
adapter преобразует формат логов, метрик или API
log shipper читает локальные логи и отправляет их наружу
metrics exporter переводит внутренние метрики в формат Prometheus
proxy принимает или отправляет сетевой трафик рядом с app
module k8s ckad Image05

Init container

Init container запускается до основных контейнеров и должен успешно завершиться. Если init container падает, основной контейнер не стартует.

Это строгое поведение полезно: если подготовка не завершилась, запускать приложение опасно. Например, если init не создал конфиг или не выставил права на директорию, основной контейнер может стартовать в неправильном состоянии и начать отдавать ошибки пользователям.

Типичные задачи init:

Задача Пример
подготовить файл сгенерировать config в shared volume
дождаться зависимости проверить доступность API или Service
миграция/инициализация создать директории, права, начальные данные
загрузить данные скачать конфиг или шаблоны перед стартом app

Пример Pod с init container и общим emptyDir:

apiVersion: v1
kind: Pod
metadata:
  name: init-demo
spec:
  initContainers:
  - name: init-data
    image: nginx:alpine
    command: ["sh", "-c", "echo init done; echo init data > /data/init.txt"]
    volumeMounts:
    - name: data
      mountPath: /data
  containers:
  - name: app
    image: nginx:alpine
    command: ["sh", "-c", "cat /data/init.txt; sleep 3600"]
    volumeMounts:
    - name: data
      mountPath: /data
  volumes:
  - name: data
    emptyDir: {}

Здесь init container сначала записывает файл, затем завершается. Только после этого стартует контейнер app.

Разберём новые поля в этом манифесте:

Поле Что означает
spec.initContainers список контейнеров, которые запускаются до обычных контейнеров
spec.containers список основных контейнеров Pod'а
name имя контейнера или volume; по нему Kubernetes связывает части манифеста
image container image, из которого запускается контейнер
command команда, которая заменяет default command из image
volumeMounts список volume, которые нужно подключить внутрь конкретного контейнера
mountPath путь внутри контейнера, где будет виден volume
spec.volumes список volume, доступных Pod'у
emptyDir: {} временный пустой volume, который создаётся вместе с Pod

Самая важная связь здесь такая:

spec.volumes[].name: data
        |
        v
containers.volumeMounts.name: data

volumes объявляет volume на уровне Pod'а, а volumeMounts подключает этот volume в конкретный контейнер. Если два контейнера монтируют volume с одним и тем же name, они видят одну и ту же директорию, хотя путь mountPath внутри контейнеров может быть разным.

emptyDir в этом примере создаёт пустую директорию для Pod'а. Init container пишет туда файл через путь /data, а app container потом читает этот же файл через свой /data. Подробные типы volume и жизненный цикл данных будут в следующей статье, а пока достаточно запомнить: emptyDir живёт вместе с Pod и подходит для временного обмена файлами между контейнерами.

Проверка:

kubectl get pod init-demo
kubectl describe pod init-demo
kubectl logs init-demo -c init-data
kubectl logs init-demo -c app

В describe смотри секцию Init Containers. Если init падает, Pod может быть в статусе Init:CrashLoopBackOff, Init:0/1 или PodInitializing. Это не проблема основного контейнера: приложение ещё не запускалось.

Если init containers несколько, они идут строго в порядке списка:

spec:
  initContainers:
  - name: step1
    image: nginx:alpine
    command: ["sh", "-c", "echo step1 > /data/step1"]
    volumeMounts:
    - name: data
      mountPath: /data
  - name: step2
    image: nginx:alpine
    command: ["sh", "-c", "echo step2 > /data/step2"]
    volumeMounts:
    - name: data
      mountPath: /data
  containers:
  - name: app
    image: nginx:alpine
    command: ["sh", "-c", "cat /data/step1 /data/step2; sleep 3600"]
    volumeMounts:
    - name: data
      mountPath: /data
  volumes:
  - name: data
    emptyDir: {}

step2 не начнёт работу, пока step1 не завершится успешно. Контейнер app не начнёт работу, пока не завершатся оба init containers.

Sidecar container

Sidecar работает рядом с основным контейнером. Он не главный бизнес-процесс, а помощник: собирает логи, обновляет конфиг, проксирует трафик, экспортирует метрики.

Sidecar не должен быть способом спрятать второй самостоятельный сервис. Если контейнер имеет собственный lifecycle, отдельные релизы и масштабируется иначе, ему нужен отдельный workload. Sidecar оправдан, когда helper без app почти не имеет смысла.

Пример с общим volume:

apiVersion: v1
kind: Pod
metadata:
  name: sidecar-demo
spec:
  containers:
  - name: app
    image: nginx:alpine
    command: ["sh", "-c", "touch /logs/app.log; while true; do echo request >> /logs/app.log; sleep 5; done"]
    volumeMounts:
    - name: logs
      mountPath: /logs
  - name: log-reader
    image: nginx:alpine
    command: ["sh", "-c", "touch /logs/app.log; tail -F /logs/app.log"]
    volumeMounts:
    - name: logs
      mountPath: /logs
  volumes:
  - name: logs
    emptyDir: {}

Контейнер app пишет строки в /logs/app.log, а log-reader читает тот же файл из общего emptyDir.

Логи конкретного контейнера:

kubectl logs sidecar-demo -c app
kubectl logs sidecar-demo -c log-reader
kubectl logs sidecar-demo --all-containers=true

--all-containers=true удобно для быстрой проверки, но для точной диагностики лучше выбирать контейнер через -c.

Exec в конкретный контейнер:

kubectl exec sidecar-demo -c app -- sh
kubectl exec sidecar-demo -c log-reader -- sh

Если забыть -c, kubectl может выбрать контейнер по умолчанию или попросить уточнить имя.

Когда multi-container Pod оправдан

Multi-container Pod хорошо подходит, когда helper без основного приложения почти не имеет смысла.

Примеры:

  • init container готовит конфиг в общий volume перед стартом app;
  • sidecar читает файл логов из общего volume;
  • proxy-контейнер слушает localhost рядом с приложением;
  • helper периодически обновляет файл, который читает app;
  • exporter переводит внутренний формат метрик в формат Prometheus.

Здесь контейнеры образуют одну operational unit: если переносить приложение на другую node, helper должен ехать вместе с ним.

Когда лучше разные Pod'ы

Не стоит класть в один Pod frontend, backend, worker и database только потому, что они относятся к одному продукту.

Разделяй на разные workloads, если компоненты:

  • масштабируются с разной скоростью;
  • имеют разные rollout и rollback;
  • требуют разных ресурсов;
  • могут жить без друг друга;
  • должны иметь отдельные Service, probes или policy.

Иначе Pod станет маленькой виртуальной машиной, где сложно обновлять и диагностировать отдельные части.

Диагностика

Сначала смотри общий статус Pod, потом состояние каждого контейнера.

kubectl get pod sidecar-demo
kubectl describe pod sidecar-demo
kubectl get pod sidecar-demo -o jsonpath='{.spec.containers[*].name}'
kubectl logs sidecar-demo -c app
kubectl logs sidecar-demo -c log-reader

Для init-проблем:

kubectl describe pod init-demo
kubectl logs init-demo -c init-data

Для sidecar-проблем Pod может выглядеть Running, но один контейнер может перезапускаться или не выполнять свою роль. Тогда проверяй логи обоих контейнеров, volume mounts и Events в describe.

У multi-container Pod диагностика почти всегда контейнерная: нужно явно понимать, какой контейнер пишет файл, какой читает, кто слушает порт и у кого ошибка.

Как принять решение

Перед тем как добавить второй контейнер в Pod, задай несколько вопросов:

  1. Нужен ли общий localhost?
  2. Нужен ли общий volume?
  3. Должны ли контейнеры всегда быть на одной node?
  4. Должны ли они масштабироваться одинаково?
  5. Что произойдёт, если helper упадёт?
  6. Должен ли helper завершиться до старта app или жить рядом с ним?

Если ответы показывают тесную связь, multi-container Pod оправдан. Если связь слабая, лучше разные workloads и нормальная связь через Service.

Перед лабораторной запомни главное:

init container -> подготовить и завершиться
sidecar container -> помогать во время работы app
ambassador/adapter -> архитектурные роли, обычно реализуются sidecar'ом

Материал изучен?

Войдите, чтобы сохранить прогресс и перейти к практической лаборатории.

Войти и продолжить