← К программе курса

Модуль 4 / 19

Pod: жизненный цикл, init containers, sidecars и multi-container модель

KUBERNETES

Содержание
01

1. Цели и результат модуля

После модуля вы сможете читать Pod как единицу исполнения Kubernetes, объяснять его lifecycle на уровне Pod и отдельных контейнеров, выбирать init/sidecar модель и диагностировать отказ до запуска приложения и после него.

  • Pod — минимальная планируемая единица, а не долговечная виртуальная машина
  • Контейнеры Pod совместно используют сеть и могут совместно монтировать volumes
  • phase, Conditions и container states отвечают на разные вопросы
  • Обычные init containers завершаются последовательно
  • Native sidecar задаётся как init container с restartPolicy: Always
02

2. Теория: границы и идентичность Pod

Scheduler назначает целый Pod одному Node. Все его контейнеры получают общий network namespace: один IP, общий localhost и единое пространство портов. Filesystem образа у каждого контейнера свой; данными они обмениваются только через явно смонтированные volumes или сеть.

Pod имеет собственные uid и IP. После удаления новый Pod — другой объект, даже если имя похоже. Не храните важное состояние только в writable layer или emptyDir и не стройте клиентов на постоянстве Pod IP.

Несколько тесно связанных процессов помещают в один Pod, когда им нужен общий lifecycle и co-location. Независимо масштабируемые компоненты должны быть разными workloads.

03

3. Теория: phase, Conditions и container states

Pod phase — грубая сводка Pending, Running, Succeeded, Failed или Unknown. Running не означает, что приложение готово принимать трафик: хотя бы один основной контейнер работает или запускается, но readiness может быть false.

Conditions показывают PodScheduled, Initialized, ContainersReady, Ready и PodReadyToStartContainers. Для диагностики важны status, reason, message и lastTransitionTime.

Каждый контейнер находится в Waiting, Running или Terminated. Поля reason, exitCode, signal, startedAt и finishedAt дают первичные доказательства. CrashLoopBackOff и ImagePullBackOff — причины ожидания с backoff, а не отдельные Pod phases.

04

4. Теория: restartPolicy и backoff

restartPolicy Always, OnFailure или Never применяется kubelet к app containers Pod. Перезапускается контейнер внутри того же Pod: uid и IP сохраняются, а restartCount растёт. Controller может отдельно создать новый Pod — это другой механизм.

Repeated failure увеличивает задержку restart backoff. Смотрите текущий state, lastState, restartCount и logs --previous. Не лечите CrashLoopBackOff бесконечным delete: новый Pod повторит детерминированную ошибку.

05

5. Теория: init containers и native sidecars

Обычные init containers выполняются по порядку и должны успешно завершиться до запуска app containers. Они подходят для подготовки файлов, миграций с корректной идемпотентностью и ожидания обязательной зависимости. Отказ init container удерживает Pod в Init:* и app container ещё не имеет logs.

Native sidecar — запись в initContainers с container-level restartPolicy: Always. Kubelet считает его запущенным и продолжает следующие init/app containers, поддерживая sidecar весь lifecycle Pod. Он участвует в startup и shutdown ordering лучше, чем исторический шаблон с двумя обычными containers.

Sidecar должен иметь ограниченную ответственность: proxy, log adapter или локальный агент. Его отказ, ресурсы и shutdown влияют на весь Pod.

06

6. Теория: завершение Pod

При обычном удалении API выставляет deletionTimestamp. Kubelet запускает preStop hooks, отправляет TERM процессу 1 и ждёт terminationGracePeriodSeconds, после чего оставшиеся процессы получают KILL. Endpoint controllers параллельно исключают завершающийся Pod из обычного трафика.

Приложение должно принимать TERM, прекращать новый трафик, завершать текущие запросы и сбрасывать данные за ограниченное время. Force deletion может оставить фактический процесс на недоступном Node и применяется только с пониманием последствий.

07

7. Методические указания

  • Начинайте с get -o wide и Conditions, затем изучайте container states и Events
  • Всегда указывайте -c для multi-container Pod
  • Используйте logs --previous после restart
  • Различайте отказ scheduling, sandbox/network, image pull, init и app process
  • Не используйте sleep как доказательство готовности зависимости в production
  • Делайте init actions идемпотентными
  • Не превращайте Pod в набор несвязанных сервисов
  • Проверяйте graceful shutdown отдельно от успешного startup
08

8. Лабораторная работа: init, sidecar и общий volume

Обычный init container создаёт HTML в emptyDir. Native sidecar пишет heartbeat в тот же volume, а основной nginx читает его read-only. Это демонстрирует общий lifecycle, сеть и storage без смешивания файловых систем образов.

terminal
mkdir -p /tmp/k8s-m04
cat > /tmp/k8s-m04/pod.yaml <<'EOF'
apiVersion: v1
kind: Pod
metadata:
  name: web
  namespace: k8s-m04
  labels:
    app.kubernetes.io/name: pod-lab
spec:
  terminationGracePeriodSeconds: 20
  initContainers:
    - name: prepare-content
      image: busybox:1.37
      command: ["sh", "-c"]
      args:
        - printf '<h1>Kubernetes Pod laboratory</h1>\n' > /work/index.html
      volumeMounts:
        - name: work
          mountPath: /work
    - name: heartbeat
      image: busybox:1.37
      restartPolicy: Always
      command: ["sh", "-c"]
      args:
        - while true; do date -Iseconds >> /work/heartbeat.log; sleep 5; done
      volumeMounts:
        - name: work
          mountPath: /work
  containers:
    - name: web
      image: nginx:1.29-alpine
      ports:
        - name: http
          containerPort: 80
      volumeMounts:
        - name: work
          mountPath: /usr/share/nginx/html
          readOnly: true
  volumes:
    - name: work
      emptyDir: {}
EOF

kubectl create namespace k8s-m04
kubectl apply --dry-run=server -f /tmp/k8s-m04/pod.yaml
kubectl apply -f /tmp/k8s-m04/pod.yaml
kubectl -n k8s-m04 wait --for=condition=Ready pod/web --timeout=120s
09

9. Лабораторная работа: наблюдение состояния

Сопоставьте общую phase и Conditions со статусами обычного init, sidecar и app container. Проверка через localhost выполняется внутри общего network namespace Pod.

terminal
kubectl -n k8s-m04 get pod web -o wide
kubectl -n k8s-m04 describe pod web
kubectl -n k8s-m04 get pod web -o jsonpath='phase={.status.phase}{"\n"}{range .status.conditions[*]}condition={.type} status={.status} reason={.reason}{"\n"}{end}'
kubectl -n k8s-m04 get pod web -o jsonpath='{range .status.initContainerStatuses[*]}init={.name} ready={.ready} started={.started} restarts={.restartCount}{"\n"}{end}{range .status.containerStatuses[*]}app={.name} ready={.ready} restarts={.restartCount}{"\n"}{end}'
kubectl -n k8s-m04 logs web -c prepare-content
kubectl -n k8s-m04 logs web -c heartbeat --tail=5
kubectl -n k8s-m04 exec web -c web -- wget -qO- http://127.0.0.1
kubectl -n k8s-m04 exec web -c web -- cat /usr/share/nginx/html/heartbeat.log
10

10. Диагностика: Init:ImagePullBackOff

Создайте Pod с несуществующим образом init container. Основной контейнер не стартует. Доказательства находятся в initContainerStatuses и Events; после исследования удалите неисправный Pod.

terminal
cat > /tmp/k8s-m04/bad-init.yaml <<'EOF'
apiVersion: v1
kind: Pod
metadata:
  name: bad-init
  namespace: k8s-m04
spec:
  initContainers:
    - name: prepare
      image: busybox:does-not-exist
      command: ["sh", "-c", "echo ready"]
  containers:
    - name: app
      image: busybox:1.37
      command: ["sh", "-c", "sleep 3600"]
EOF
kubectl apply -f /tmp/k8s-m04/bad-init.yaml
kubectl -n k8s-m04 get pod bad-init
kubectl -n k8s-m04 describe pod bad-init
kubectl -n k8s-m04 get events --field-selector involvedObject.name=bad-init --sort-by=.metadata.creationTimestamp
kubectl -n k8s-m04 delete pod bad-init --wait=true
11

11. Диагностика: CrashLoopBackOff

Приложение завершается с кодом 42, а restartPolicy запускает его снова. Backoff может оставить current state Waiting, тогда причина завершения находится в lastState и предыдущем экземпляре logs.

terminal
cat > /tmp/k8s-m04/crash.yaml <<'EOF'
apiVersion: v1
kind: Pod
metadata:
  name: crash
  namespace: k8s-m04
spec:
  restartPolicy: Always
  containers:
    - name: app
      image: busybox:1.37
      command: ["sh", "-c"]
      args: ["echo 'starting application'; sleep 2; echo 'fatal configuration error' >&2; exit 42"]
EOF
kubectl apply -f /tmp/k8s-m04/crash.yaml
for attempt in $(seq 1 30); do
  restarts=$(kubectl -n k8s-m04 get pod crash -o jsonpath='{.status.containerStatuses[0].restartCount}' 2>/dev/null || true)
  [ "${restarts:-0}" -ge 1 ] && break
  sleep 2
done
kubectl -n k8s-m04 get pod crash
kubectl -n k8s-m04 describe pod crash
kubectl -n k8s-m04 logs crash -c app
kubectl -n k8s-m04 logs crash -c app --previous || true
kubectl -n k8s-m04 get pod crash -o jsonpath='waiting={.status.containerStatuses[0].state.waiting.reason}{"\n"}lastReason={.status.containerStatuses[0].lastState.terminated.reason}{"\n"}exitCode={.status.containerStatuses[0].lastState.terminated.exitCode}{"\n"}restarts={.status.containerStatuses[0].restartCount}{"\n"}'

Цепочка диагностики: status/Conditions → container current и last state → Events → current/previous logs → команда, конфигурация и внешние зависимости.

12

12. Контроль, критерии освоения и cleanup

  • Что контейнеры Pod разделяют, а что нет?
  • Почему Running не равно Ready?
  • Чем phase отличается от container state?
  • Что сохраняется при restart контейнера?
  • Когда обычный init container блокирует app?
  • Как kubelet распознаёт native sidecar?
  • Почему logs --previous важны?
  • Как проходит graceful termination?
  • Почему новый Pod с тем же именем не тот же объект?
terminal
kubectl delete namespace k8s-m04 --wait=true
rm -rf /tmp/k8s-m04
kubectl get namespace k8s-m04 2>&1 || true

Модуль освоен, если вы доказываете порядок init/sidecar/app, читаете текущий и прошлый container state и локализуете отказ до изменения manifest.