Модуль 4 / 19
Pod: життєвий цикл, init containers, sidecars і multi-container модель
Зміст
1. Цілі та результат модуля
Після модуля ви зможете читати Pod як одиницю виконання Kubernetes, пояснювати його lifecycle на рівні Pod та окремих контейнерів, обирати init/sidecar модель і діагностувати відмову до запуску застосунку та після нього.
- Pod — мінімальна планована одиниця, а не довговічна віртуальна машина
- Контейнери Pod спільно використовують мережу й можуть спільно монтувати volumes
- phase, Conditions і container states відповідають на різні питання
- Звичайні init containers завершуються послідовно
- Native sidecar задається як init container з restartPolicy: Always
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.
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.
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 повторить детерміновану помилку.
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.
6. Теорія: завершення Pod
За звичайного видалення API встановлює deletionTimestamp. Kubelet запускає preStop hooks, надсилає TERM процесу 1 і чекає terminationGracePeriodSeconds, після чого решта процесів отримує KILL. Endpoint controllers паралельно виключають Pod, що завершується, зі звичайного трафіку.
Застосунок має прийняти TERM, припинити новий трафік, завершити поточні запити та скинути дані за обмежений час. Force deletion може залишити фактичний процес на недоступному Node і застосовується лише з розумінням наслідків.
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
8. Лабораторна робота: init, sidecar і спільний volume
Звичайний init container створює HTML в emptyDir. Native sidecar пише heartbeat у той самий volume, а основний nginx читає його read-only. Це демонструє спільний lifecycle, мережу та storage без змішування файлових систем образів.
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=120s9. Лабораторна робота: спостереження стану
Зіставте загальну phase та Conditions зі статусами звичайного init, sidecar і app container. Перевірка через localhost виконується всередині спільного network namespace Pod.
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.log10. Діагностика: Init:ImagePullBackOff
Створіть Pod із неіснуючим образом init container. Основний контейнер не стартує. Докази містяться в initContainerStatuses та Events; після дослідження видаліть несправний Pod.
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=true11. Діагностика: CrashLoopBackOff
Застосунок завершується з кодом 42, а restartPolicy запускає його знову. Backoff може залишити current state Waiting, тоді причина завершення міститься в lastState і попередньому екземплярі logs.
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. Контроль, критерії засвоєння та cleanup
- Що контейнери Pod поділяють, а що ні?
- Чому Running не дорівнює Ready?
- Чим phase відрізняється від container state?
- Що зберігається за restart контейнера?
- Коли звичайний init container блокує app?
- Як kubelet розпізнає native sidecar?
- Чому logs --previous важливі?
- Як відбувається graceful termination?
- Чому новий Pod із тим самим ім’ям не той самий об’єкт?
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.