← До програми курсу

Модуль 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.