Перейти до вмісту

Модуль 5.2: Збої застосунків

Hands-On Lab Available
K8s Cluster intermediate 45 min
Launch Lab ↗

Opens in Killercoda in a new tab

Складність: [СЕРЕДНЯ] — найпоширеніші сценарії діагностики.

Час на проходження: 45-55 хвилин.

Передумови: Модуль 5.1 (Методологія діагностики), Модулі 2.1-2.7 (Робочі навантаження).


Що ви зможете зробити

Розділ «Що ви зможете зробити»

Після цього модуля ви зможете:

  • Діагностувати збої застосунків, пов’язуючи фазу Пода, стан контейнера, події (Events), логи та коди завершення з конкретною першопричиною.
  • Виправляти збої застосунків, спричинені хибними образами, відсутніми ConfigMap, відсутніми Secret, некоректними пробами та обмеженнями ресурсів.
  • Налагоджувати збої Подів із кількома контейнерами, визначаючи контейнер застосунку чи init-контейнер, що дає збій, перш ніж обирати команду.
  • Простежувати збої розгортання Деплойменту від симптому застряглого розгортання через докази на рівні ReplicaSet, Пода та контейнера.

Чому цей модуль важливий

Розділ «Чому цей модуль важливий»

Гіпотетичний сценарій: ви — черговий адміністратор під час вікна релізу, і команда повідомляє вам, що їхній новий вебДеплоймент «лежить», хоча YAML застосувався без помилок. Перший Под показує ImagePullBackOff, другий Под застряг у ContainerCreating, а третій Под ненадовго досягає стану Running, перш ніж повернутися до CrashLoopBackOff. Жоден із цих станів сам по собі не дає вам повної відповіді, але кожен із них підказує, на якому етапі шляху запуску перебував Kubernetes, коли збій став помітним.

Діагностика збоїв застосунків — це момент, коли Kubernetes перестає сприйматися як набір визначень об’єктів і починає поводитися як операційна система для розподілених процесів. Планувальник, kubelet, середовище виконання контейнерів, реєстр, плагіни томів, проби та цикли контролерів — усі вони залишають докази в різних місцях. Якщо ви одразу беретеся редагувати YAML, ви можете виправити симптом, який випадково став помітним першим, проте проґавити першопричину, яка повернеться під час наступного розгортання.

Цей модуль навчає повторюваного діагностичного шляху для збоїв, які ви найчастіше зустрічатимете на іспиті CKA та в повсякденній експлуатації. Ви навчитеся переходити від фази Пода до подій, від подій до стану контейнера, від стану контейнера до попередніх логів, а від логів — до точної помилки в конфігурації, образі, ресурсах чи пробі, яку потрібно виправити. Мета не в тому, щоб запам’ятати кожен рядок статусу; мета в тому, щоб виробити звичку запитувати, який саме компонент Kubernetes намагався виконати роботу, що він повідомив і який об’єкт варто перевірити наступним.

Аналогія з ресторанною кухнею досі корисна, якщо застосовувати її обачно. Под подібний до замовлення, що рухається кухнею: замовлення мають прийняти, інгредієнти мають бути в наявності, етапи підготовки мають завершитися, страва має лишатися гарячою, а офіціант має знати, коли її можна подавати. Хибний образ — це поганий рецепт, відсутній ConfigMap — це відсутній інгредієнт, OOM-завершення — це кухар, якому забракло місця на столі, а помилка з пробою — це офіціант, що питає, чи готова страва, перш ніж вона дотушкувалася.

Та сама аналогія пояснює, чому порядок дій має значення під час діагностики. Якщо кухня взагалі не отримала замовлення, перевіряти, чи приправив кухар страву, — марна праця. Якщо інгредієнти не надійшли, скуштувати соус неможливо. Kubernetes дає вам схожі контрольні точки, і дисципліновані оператори шанують ці контрольні точки, перш ніж ганятися за теоріями на рівні застосунку. Ця дисципліна важлива під екзаменаційним тиском, бо кожна хвилина, витрачена не в тому шарі, скорочує час, доступний для простого виправлення.

Вам також варто помітити різницю між полагодженням і поясненням. Полагодження повертає робоче навантаження до здорового стану, але пояснення доводить, чому обране полагодження було правильним. Сильний адміністратор може сказати: «Под був запланований, kubelet не зміг змонтувати том ConfigMap, події назвали app-settings, ConfigMap був відсутній у цьому просторі імен, і його створення дозволило контейнеру стартувати». Це речення цінніше за «я створив ConfigMap, і воно запрацювало», бо його можна перевірити, передати іншим і повторити.

Читання шляху запуску Пода

Розділ «Читання шляху запуску Пода»

Kubernetes подає збої застосунків як слова статусу, але ці слова є скороченням для контрольних точок на довшому шляху. Под починає з планування, потім іде підготовка kubelet, потім завантаження образів і налаштування томів, потім init-контейнери, потім контейнери застосунку, а потім ворота готовності (readiness gates). Сприйняття цих контрольних точок як часової лінії запобігає поширеній помилці новачка: дивитися в логи контейнера, коли контейнер ще ніколи не стартував, або редагувати образ Деплойменту, коли планувальник так і не знайшов вузла.

Наведена нижче послідовність запуску — перший захищений діагностичний актив із оригінального уроку. Тримайте її в голові радше як карту, ніж як перелік команд. Коли Под у стані Pending, ймовірний фокус — планувальник і ємність кластера. Коли Под застряг під час створення, ймовірнішими стають підготовка kubelet, завантаження образу, монтування томів, ConfigMap, Secret чи налаштування CNI. Коли Под має лічильник перезапусків, у сюжет вступають процес застосунку або проба життєздатності (liveness).

Ця часова лінія також допомагає тлумачити відсутні докази. Порожні логи застосунку мають значення лише тоді, коли контейнер справді стартував. Відсутність лічильника перезапусків має значення лише тоді, коли Под досяг етапу, на якому процес міг би перезапуститися. Деплоймент із недоступними репліками має значення лише після того, як ви пов’яжете його з Подами, створеними найновішим ReplicaSet. Відсутність доказів — це не те саме, що доказ відсутності; часто це означає, що ви запитуєте інформацію не в того шару.

┌──────────────────────────────────────────────────────────────┐
│ POD STARTUP SEQUENCE │
│ │
│ 1. Scheduling 2. Preparation 3. Startup │
│ ┌──────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Pending │────▶│ Container │───▶│ Init │ │
│ │ │ │ Creating │ │ Containers │ │
│ └──────────┘ └──────────────┘ └──────────────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ • Node selection • Pull images • Run in order │
│ • Resource check • Mount volumes • Each must exit 0 │
│ • Taints/affinity • Setup network • Sequential only │
│ │
│ 4. Running 5. Ready │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ Main │─▶│ Readiness │ │
│ │ Containers │ │ Probes Pass │ │
│ └──────────────┘ └──────────────┘ │
│ │ │ │
│ ▼ ▼ │
│ • Start all • Pod marked Ready │
│ • Run probes • Added to Service │
│ │
└──────────────────────────────────────────────────────────────┘

Найшвидша перша команда — зазвичай kubectl describe pod, не тому що вона елегантна, а тому що вона поєднує кілька джерел істини в одному місці. Вона показує рішення планувальника, збої монтування томів, повідомлення про завантаження образу, збої проб, причини перезапусків та нещодавні події. Якщо Под у стані Pending, ця команда підказує, чи відхилив планувальник усі вузли через запити ресурсів, taint’и, селектори, спорідненість (affinity) чи обмеження персистентних томів.

У Kubernetes 1.35 та сучасних екзаменаційних кластерах події лишаються одним із найінформативніших джерел для збоїв запуску, бо їх емітують самі компоненти, що намагаються виконати роботу. Планувальник пояснює, чому розміщення не вдалося. Kubelet пояснює, чому не вдалася підготовка. Середовище виконання контейнерів повідомляє про проблеми із завантаженням і запуском через kubelet. Контролери пояснюють хід розгортання на вищому рівні. Читання подій — це не шпаргалка для новачка; це спосіб попросити відповідальний компонент описати те, що він щойно намагався зробити.

Terminal window
# Check why pod is pending
kubectl describe pod <pod> | grep -A 10 Events

Pending — це проблема планування, доки докази не доведуть інше. Планувальник не запитує, чи має вузол вільну пам’ять у побутовому сенсі; він оцінює оголошені запити (requests), taint’и, спорідненість, селектори та правила томів. Саме тому вузол може мати невикористану фізичну пам’ять, а планувальник усе одно повідомляє Insufficient memory: раніше заплановані Поди вже зарезервували придатний для виділення бюджет через свої запити.

Практична пастка — сприймати Pending як привід змінювати внутрішні параметри контейнера. Тег образу, команда, змінна середовища чи проба життєздатності не мають значення, доки вузол не прийме Под. Якщо події згадують запити ресурсів, виправлення стосується запитів ресурсів або ємності кластера. Якщо події згадують taint’и, виправлення стосується толерувань (tolerations) чи вибору вузла. Якщо події згадують прив’язку PVC, виправлення стосується сховища. Це тримає виправлення поруч із тим шаром, який відхилив Под.

ПовідомленняПричинаРозв’язання
0/3 nodes availableНемає придатних вузлівПеревірте taint’и вузлів, правила спорідненості
Insufficient cpuНедостатньо CPUЗменшіть запити або додайте ємність
Insufficient memoryНедостатньо пам’ятіЗменшіть запити або додайте ємність
node(s) had taint that pod didn't tolerateTaint’и блокуютьДодайте толерування або приберіть taint’и
node(s) didn't match node selectorНевідповідність nodeSelectorВиправте мітки чи селектор
persistentvolumeclaim not foundPVC відсутнійСтворіть PVC
persistentvolumeclaim not boundНемає відповідного PVПеревірте StorageClass, створіть PV

Допоміжні команди мають відповідати гіпотезі, яку породили події. Якщо події згадують нестачу ресурсів, перевірте розподіл ресурсів вузла та метрики. Якщо події згадують taint’и чи селектори вузлів, перевірте мітки та taint’и. Якщо події згадують прив’язку PVC, перевірте сам claim та його StorageClass. Копіювати в термінал узагальнений набір команд повільніше, ніж використовувати текст події як прожектор.

Terminal window
# Check node resources
kubectl describe nodes | grep -A 5 "Allocated resources"
kubectl top nodes
# Check node taints
kubectl get nodes -o custom-columns='NAME:.metadata.name,TAINTS:.spec.taints[*].key'
# Check node labels (for nodeSelector)
kubectl get nodes --show-labels

ContainerCreating означає, що планувальник уже розмістив Под на вузлі, тож розслідування переходить від планувальника до підготовки kubelet. На цьому етапі kubelet може завантажувати образи, монтувати томи, читати ConfigMap і Secret, готувати мережевий простір імен і просити середовище виконання контейнерів створити контейнери. Логи тут часто порожні, бо основний процес ще не стартував.

Збої підготовки kubelet часто спричинені залежностями, які в YAML виглядають дрібними, але є обов’язковими під час виконання. Одна неправильно написана назва Secret може завадити стартувати ідеальному в усьому іншому образу. PVC, який не прив’язано, може заблокувати монтування тома. Приватний образ без придатного pull-Secret може зупинити процес ще до того, як з’явиться код вашого застосунку. YAML може бути синтаксично коректним, проте все одно описувати робоче навантаження, яке неможливо матеріалізувати на вузлі.

Зупиніться та спрогнозуйте: якщо Под застряг у ContainerCreating на кілька хвилин, а логи застосунку порожні, яка зовнішня залежність найімовірніше блокує перехід від специфікації Пода до запускного контейнера? Ваша відповідь має вказувати на щось, що kubelet мусить підготувати, перш ніж процес зможе існувати, — наприклад, завантаження образу, монтування тома, відсутній Secret, відсутній ConfigMap чи збій налаштування мережі.

Terminal window
# Always check Events first
kubectl describe pod <pod> | grep -A 15 Events

Таблиця нижче зберігає оригінальну карту збоїв підготовки, але важлива звичка — читати повідомлення як підказку про компонент. ImagePullBackOff вказує на реєстр і посилання на образ, тоді як MountVolume.SetUp failed вказує на сховище або спроєктовану конфігурацію. Відсутній ConfigMap чи Secret — це ще не баг застосунку; це Kubernetes відмовляється стартувати контейнер, бо оголошена залежність не існує.

ПовідомленняПричинаРозв’язання
pulling image (застрягло)Повільний/великий образЗачекайте або використайте менший образ
ImagePullBackOffХибна назва образуВиправте посилання на образ
ErrImagePullНе вдалася автентифікація реєструПеревірте imagePullSecrets
MountVolume.SetUp failedПроблема монтування томаПеревірте, чи існують PVC, ConfigMap, Secret
configmap not foundВідсутній ConfigMapСтворіть ConfigMap
secret not foundВідсутній SecretСтворіть Secret
network not readyПроблеми CNIПеревірте Поди CNI

Такі команди перевіряють залежність, названу подією, замість того щоб гадати. Простори імен тут мають значення: ConfigMap чи Secret із такою самою назвою в іншому просторі імен невидимий для Пода. На іспиті ця деталь часто відрізняє швидке виправлення від довгого обхідного шляху, бо kubectl get configmap <name> без -n може звернутися не до того простору імен і створити хибнонегативний результат.

Terminal window
# Check image pull issues
kubectl get events --field-selector involvedObject.name=<pod>
# Check if ConfigMap/Secret exists
kubectl get configmap <name>
kubectl get secret <name>
# Check PVC status
kubectl get pvc
kubectl describe pvc <name>

Діагностика CrashLoopBackOff та доказів завершення

Розділ «Діагностика CrashLoopBackOff та доказів завершення»

CrashLoopBackOff — це не першопричина; це Kubernetes захищає вузол і реєстр від контейнера, який раз за разом завершується. Kubelet запускає контейнер, спостерігає завершення процесу, чекає з експоненційним відступом (backoff) і пробує знову, поки Под лишається під керівним контролером. Ваше завдання — знайти останній термінований стан і попередні логи, перш ніж наступний перезапуск зітре найкорисніший контекст із поточного огляду процесу.

Корисна ментальна модель — чітко розділяти два принципово різні випадки: «Kubernetes не зміг стартувати процес» і «Kubernetes стартував процес, але процес не лишився живим». ImagePullBackOff, відсутні томи та відсутні обов’язкові Secret зазвичай належать до першої категорії, бо вони блокують контейнер ще до того, як код застосунку отримає шанс виконатися. CrashLoopBackOff зазвичай належить до другої категорії, хоча проби можуть розмивати цю межу, вбиваючи процес, який Kubernetes уже стартував. Це розрізнення підказує, що саме перевіряти першим — події залежностей чи докази самого процесу, — і завдяки цьому ви не марнуєте час, шукаючи логи там, де процес ще навіть не запустився.

Захищену діаграму циклу варто читати повільно. Відступ починається з малого, зростає після повторних збоїв і обмежується п’ятьма хвилинами між спробами перезапуску. Після того як контейнер успішно пропрацював близько десяти хвилин, Kubernetes скидає відступ перезапуску, тому щойно виправлена конфігурація може все одно виглядати затриманою, якщо Под довго давав збій.

┌──────────────────────────────────────────────────────────────┐
│ CRASHLOOPBACKOFF CYCLE │
│ │
│ Container Start ──▶ Container Crash ──▶ Wait ──┐ │
│ ▲ │ │
│ └─────────────────────────────────────────┘ │
│ │
│ Backoff Times: │
│ 1st crash: wait 10s │
│ 2nd crash: wait 20s │
│ 3rd crash: wait 40s │
│ 4th crash: wait 80s │
│ 5th crash: wait 160s │
│ 6th+ crash: wait 300s (5 min max) │
│ │
│ After 10 minutes of running successfully, timer resets │
└──────────────────────────────────────────────────────────────┘

Практична послідовність — статус, події, поточний стан, попередні логи та код завершення. Статус підказує, чи існує цикл перезапусків. Події часто показують вбивства пробами, OOM-вбивства та повідомлення про образ чи монтування. Поточний стан підказує, чи контейнер зараз очікує, працює чи термінований. Попередні логи та lastState показують, що сталося з останнім екземпляром процесу, який зазвичай і містить справжню причину збою.

Порядок має значення, бо кожна команда відповідає на вужче питання, ніж попередня. kubectl get pod каже вам, що цикл перезапусків існує, але не доводить чому. kubectl describe pod каже, чи спостерігав Kubernetes докази OOM, проби чи планування. Попередні логи кажуть, чи повідомив застосунок власну причину перед завершенням. Код завершення потім допомагає класифікувати цю причину, не заміняючи логи. Перехід одразу до останньої команди дає крихкі висновки.

Зупиніться та спрогнозуйте: якщо Под перезапускався багато разів, але зараз показує Running, де ви шукатимете збій, що спричинив попередній перезапуск? Поточні логи можуть показувати лише найновіший екземпляр процесу, тож сильніша відповідь — попередні логи плюс деталі lastState.terminated контейнера, з прапорцем -c, коли в Поді кілька контейнерів.

Terminal window
# Step 1: Check pod status and restart count
kubectl get pod <pod>
# Look at RESTARTS column
# Step 2: Check events
kubectl describe pod <pod> | grep -A 10 Events
# Step 3: Check current container state
kubectl describe pod <pod> | grep -A 10 "State:"
# Step 4: Check PREVIOUS container logs (crucial!)
# For multi-container pods, identify the failing container and specify it with -c
# kubectl get pod <pod> -o jsonpath='{range .status.containerStatuses[*]}{.name}{"\t"}{.restartCount}{"\n"}{end}'
kubectl logs <pod> --previous # add -c <container-name> if multiple containers
# Step 5: Check exit code
kubectl get pod <pod> -o jsonpath='{.status.containerStatuses[0].lastState.terminated.exitCode}'

Коди завершення — це компактний доказ, а не магічна відповідь. Код 1 зазвичай означає, що застосунок сам вирішив завершитися з загальною помилкою, тож логи та конфігурація стають важливими. Код 127 вказує на команду чи точку входу (entrypoint), якої немає в образі. Код 137 означає SIGKILL, що може бути OOM-вбивством або іншим примусовим завершенням, тож ви маєте перевірити причину завершення, а не припускати, що кожен 137 — це витік пам’яті.

Код завершенняСигналЗначенняПоширена причина
0-УспіхНормальне завершення (не має спричиняти CrashLoop)
1-Помилка застосункуПомилка логіки застосунку, відсутня конфігурація
2-Неправильне використання вбудованої команди shellПомилка скрипта
126-Команда не виконуванаПроблема з правами
127-Команду не знайденоХибна точка входу/команда
128+NСигнал NВбито сигналомФатальна помилка від ОС
137SIGKILL (9)Примусово вбитоOOMKilled або kill -9
139SIGSEGV (11)Помилка сегментаціїБаг застосунку
143SIGTERM (15)Коректне завершенняНормальне вимкнення
255-Невідома/власна помилкаСпецифічна фатальна помилка застосунку

OOMKilled особливо легко неправильно прочитати, бо видимий симптом може виглядати як звичайний збій застосунку, тоді як насправді процес примусово завершило ядро. Kubernetes застосовує обмеження пам’яті контейнера через середовище виконання контейнерів вузла та механізми керування пам’яттю ядра; коли процес перевищує це обмеження, його може бути вбито, навіть якщо на вузлі загалом ще лишається вільна пам’ять, бо ліміт діє на рівні окремого контейнера, а не всього вузла. Виправленням може бути збільшення обмеження, зниження базового споживання застосунку, зміна поведінки під час запуску або відокремлення важкого робочого навантаження від контейнера, що обслуговує запити.

Іспит CKA зазвичай винагороджує пряме виправлення, але виробнича робота вимагає ще одного питання: чому це обмеження було хибним для цього робочого навантаження? Сплеск під час запуску може потребувати вищого обмеження або меншого попереднього завантаження. Справжній витік може вимагати відкату чи виправлення коду. Пакетне завдання, поміщене в Деплоймент, що обслуговує запити, може потребувати Job або окремого воркера. Симптом Kubernetes каже вам про подію примусового виконання; він не обирає автоматично довгострокову модель ємності.

Terminal window
# Check for OOMKilled status
kubectl describe pod <pod> | grep -i oom
# Check memory limits
kubectl get pod <pod> -o jsonpath='{.spec.containers[0].resources.limits.memory}'
# Check actual memory usage (if pod is running)
kubectl top pod <pod>
# Fix: Increase memory limit
kubectl patch deployment <name> -p '{"spec":{"template":{"spec":{"containers":[{"name":"<container>","resources":{"limits":{"memory":"512Mi"}}}]}}}}'

Найкорисніша діагностика CrashLoop поєднує три погляди: що залогував процес, чому Kubernetes каже, що він завершився, і що специфікація Пода попросила Kubernetes запустити. Якщо логи кажуть, що файл відсутній, а специфікація монтує цей файл із ключа ConfigMap, ви маєте збій залежності конфігурації. Якщо код завершення каже, що команду не знайдено, а специфікація перевизначає command, ви, ймовірно, неправильно замінили точку входу образу.

СимптомДіагнозВиправлення
Код завершення 1Помилка застосункуПеревірте логи, виправте застосунок
Код завершення 127Команду не знайденоВиправте command чи args у специфікації
Код завершення 137 + OOMKilledПеревищено пам’ятьЗбільшіть обмеження пам’яті
Код завершення 137 без OOMВбито ззовніПеревірте пробу життєздатності
Контейнер завершується одразуНемає процесу на передньому планіДодайте sleep infinity чи виправте команду
Логи показують “file not found”Відсутній ConfigMap/SecretПеревірте, що монтування існують
Логи показують “permission denied”Контекст безпекиВиправте runAsUser чи fsGroup

Поди з кількома контейнерами додають ще один шар складності: контейнер, що дає збій, може бути не першим контейнером у специфікації, і саме тому сліпе звертання до логів за замовчуванням часто вводить в оману. Sidecar-контейнери, init-контейнери та допоміжні контейнери мають окремі логи та окремі лічильники перезапусків. Перш ніж запускати kubectl logs, перелічіть статуси всіх контейнерів і оберіть саме той контейнер, чий лічильник перезапусків чи причина очікування відповідає симптому. Це дає змогу уникнути оманливого досвіду, коли ви читаєте здорові логи sidecar, тоді як контейнер застосунку дає збій поруч із ним, а ви цього не помічаєте.

Виправлення збоїв образів і реєстру

Розділ «Виправлення збоїв образів і реєстру»

Збої завантаження образу трапляються до того, як існує процес застосунку, тож вони лишають докази в подіях, а не в логах застосунку. Kubernetes спершу повідомляє про негайний збій завантаження, зазвичай ErrImagePull, а потім переходить до ImagePullBackOff після повторних збоїв. Той самий статус може походити від тегу з опискою, приватного реєстру без облікових даних, недоступності реєстру чи обмеження частоти (rate limit), тож текст події важливіший за короткий статус Пода.

Саме тому видалення Пода рідко є першою корисною дією, хоча інстинктивно багато хто починає саме з нього. Якщо керівний шаблон усе ще містить те саме хибне посилання на образ чи той самий відсутній pull-Secret, новий Под просто повторює той самий збій і може навіть скинути ті докази, які ви щойно збиралися прочитати. Виправте посилання, облікові дані чи шлях реєстру в шаблоні контролера, а потім дозвольте контролеру створити новий Под із виправленого бажаного стану. Контролер є джерелом повторення, тож зазвичай він і є джерелом виправлення.

┌──────────────────────────────────────────────────────────────┐
│ IMAGE PULL ERROR FLOW │
│ │
│ Attempt Pull ──▶ ErrImagePull ──▶ ImagePullBackOff │
│ │ │ │ │
│ │ │ │ │
│ (Success) (First failure) (Repeated failures) │
│ │
│ ErrImagePull causes: │
│ • Image doesn't exist │
│ • Registry unreachable │
│ • Authentication failed │
│ • Rate limited (Docker Hub) │
│ │
└──────────────────────────────────────────────────────────────┘

Почніть із точного тексту помилки зі спроби завантаження. manifest unknown означає, що репозиторій чи тег неможливо знайти. unauthorized означає, що облікові дані відсутні чи недійсні. Тайм-аути мережі вказують на доступність реєстру, DNS, проксі чи вихідний трафік вузла. toomanyrequests зазвичай означає, що реєстр обмежує частоту неавтентифікованих чи високооб’ємних завантажень.

Terminal window
# Check events for specific error
kubectl describe pod <pod> | grep -A 5 "Failed to pull"
# Common error messages:
# "manifest unknown" - Image tag doesn't exist
# "unauthorized" - Registry auth failed
# "timeout" - Registry unreachable
# "toomanyrequests" - Rate limited

Для хибної назви чи тегу образу краще оновити керівний контролер, а не редагувати збійний Под. Окремі Поди в лабораторіях одноразові, але реальні робочі навантаження зазвичай походять із Деплойментів, StatefulSet, Job чи CronJob. Якщо ви зміните лише створений Под, контролер може відтворити той самий збій з оригінального шаблону.

Назви образів несуть одразу кілька значень: хост реєстру, шлях репозиторію, назву образу, тег, а іноді й дайджест. Описка в будь-якій частині може спричинити збій завантаження, який на рівні статусу Пода виглядає схоже. Перш ніж змінювати тег, прочитайте повне посилання з Пода чи контролера й порівняйте його з очікуваним релізним артефактом. У кластерах, що використовують політики допуску чи дзеркала образів, також пам’ятайте, що написане вами посилання може бути трансформоване чи перевірене, перш ніж kubelet його завантажить.

Terminal window
# Check current image
kubectl get pod <pod> -o jsonpath='{.spec.containers[0].image}'
# Fix with set image
kubectl set image deployment/<name> <container>=<correct-image>
# Or edit directly
kubectl edit deployment <name>

Збої приватного реєстру вимагають узгодження Secret, простору імен, ServiceAccount та шаблону Пода. Secret реєстру Docker у хибному просторі імен невидимий для Пода. Secret, що існує, але не зазначений через imagePullSecrets, теж невидимий під час завантаження. Патчинг сервісного акаунта default може бути добрим виправленням на рівні простору імен у лабораторії, тоді як виробничі команди часто роблять цей вибір явно через специфічні для навантаження ServiceAccount.

Terminal window
# Create registry secret
kubectl create secret docker-registry regcred \
--docker-server=registry.example.com \
--docker-username=user \
--docker-password=your-registry-password-here \
--docker-email=user@example.com
# Add to pod spec
kubectl patch serviceaccount default -p '{"imagePullSecrets":[{"name":"regcred"}]}'
# Or add to specific deployment
kubectl patch deployment <name> -p '{"spec":{"template":{"spec":{"imagePullSecrets":[{"name":"regcred"}]}}}}'

Обмеження частоти відрізняється від хибного посилання на образ, бо той самий образ може спрацювати для одного вузла і дати збій для іншого залежно від історії завантажень та облікових даних. Автентифікація, внутрішні реєстри, попередньо завантажені образи й альтернативні публічні реєстри — усі вони є дієвими пом’якшеннями, але правильний вибір залежить від політики кластера. В екзаменаційному середовищі найімовірніше виправлення — це виправлене посилання на образ чи наявний pull-Secret, а не перепроєктування архітектури реєстру.

Terminal window
# Option 1: Use authenticated pulls
kubectl create secret docker-registry dockerhub \
--docker-server=https://index.docker.io/v1/ \
--docker-username=<username> \
--docker-password=<TOKEN>
# Option 2: Use alternative registry (e.g., quay.io, public.ecr.aws)
# nginx:latest -> public.ecr.aws/nginx/nginx:latest

Налагодження конфігурації, Secret та вхідних даних під час виконання

Розділ «Налагодження конфігурації, Secret та вхідних даних під час виконання»

Збої конфігурації лежать саме на межі між Kubernetes та застосунком, і ця межа визначає, хто й коли помітить проблему. Kubernetes може виявити відсутні ConfigMap, відсутні Secret, відсутні джерела томів та недійсні посилання ще до запуску контейнера, бо це залежності, які платформа зобов’язана матеріалізувати сама. Проте він не завжди може виявити хибну назву ключа, неочікуване значення чи специфічну для застосунку вимогу до змінної середовища; такі дефекти можуть проявитися лише після того, як процес уже почав роботу й завершився з помилкою застосунку.

Ця межа — причина, чому проблеми конфігурації можуть давати різні видимі стани. Відсутній змонтований ConfigMap може лишити Под застряглим під час створення контейнера, тоді як наявний ConfigMap із відсутнім ключем застосунку може дозволити контейнеру стартувати й аварійно завершитися. Опційне посилання на ключ може дозволити запуск із порожнім чи відсутнім значенням, зміщуючи збій у поведінку застосунку. Хороше налагодження запитує, чи відхилив залежність Kubernetes, чи дані відхилив сам застосунок.

Зупиніться та спрогнозуйте: який статус Пода ви очікували б, якби Под посилався на Secret, що не існує в просторі імен? Відповідь залежить від того, як споживається Secret, але звичайна ознака — це збій створення контейнера з подіями, які кажуть, що Secret не вдалося знайти, бо kubelet не може матеріалізувати середовище чи змонтований том, обіцяні специфікацією Пода.

Діагностика починається з читання специфікації Пода на предмет оголошених вхідних даних, а потім перевірки, чи існують ці об’єкти в тому самому просторі імен. Для змонтованих ConfigMap і Secret збій може проявитися як помилка налаштування тома. Для змінних середовища, що походять через valueFrom, Kubernetes може заблокувати запуск, якщо обов’язкове посилання на ключ неможливо розв’язати, хіба що посилання є опційним.

Terminal window
# Check what ConfigMaps/Secrets the pod needs
kubectl get pod <pod> -o yaml | grep -A 5 "configMap\|secret"
# Verify they exist
kubectl get configmap
kubectl get secret
# Check specific one
kubectl describe configmap <name>

Створення відсутнього об’єкта може бути дієвим аварійним виправленням, коли бажані дані відомі й безпечні для створення. У контрольованій екзаменаційній лабораторії літерального ключа часто достатньо, щоб довести, що ви визначили залежність. У виробництві варто підтвердити власника й джерело даних, перш ніж вигадувати значення, бо застосунок, який стартує з хибною конфігурацією, може дати збій тихіше, ніж той, що відмовляється стартувати.

Secret заслуговують особливої обережності, бо їхня наявність може задовольнити Kubernetes, тоді як їхній вміст лишається хибним. Secret із правильною назвою, але старим паролем, хибною назвою ключа чи значенням, закодованим із хибного джерела, може дозволити Поду стартувати й дати збій проти зовнішньої залежності. Kubernetes не автентифікує ваш пароль до бази даних під час створення Пода. Він лише проєктує байти в контейнер, тож логи застосунку та телеметрія залежностей можуть знадобитися й після того, як Kubernetes прийняв об’єкт.

Terminal window
# Create missing ConfigMap
kubectl create configmap <name> --from-literal=key=value
# Create missing Secret
kubectl create secret generic <name> --from-literal=password=your-password-here
# If you have the data file
kubectl create configmap <name> --from-file=config.yaml
kubectl create secret generic <name> --from-file=credentials.json

Хибні ключі тонші за відсутні об’єкти. ConfigMap із назвою app-settings може існувати й усе одно не мати ключа REDIS_HOST, на який очікує контейнер. Якщо застосунок читає шлях змонтованого файлу, логи можуть показати file not found. Якщо він читає змінну середовища, логи можуть показати помилку відсутньої змінної та код завершення 1, що означає: Kubernetes успішно стартував процес, але застосунок відхилив свої вхідні дані.

Terminal window
# Check what keys exist in ConfigMap
kubectl get configmap <name> -o yaml
# Check pod's expected keys
kubectl get pod <pod> -o yaml | grep -A 10 configMapKeyRef
# Compare expected vs actual

Патчинг ConfigMap змінює об’єкт, але не завжди оновлює запущений застосунок негайно. Змінні середовища фіксуються при старті контейнера, тож Под потрібно перестворити, щоб побачити змінені значення. Спроєктовані томи можуть оновлюватися на вузлі, але багато застосунків читають конфігурацію лише при запуску. Хороше діагностичне виправлення включає і виправлення даних, і забезпечення того, щоб робоче навантаження спожило виправлені дані.

Terminal window
# Add missing key to ConfigMap
kubectl patch configmap <name> -p '{"data":{"missing-key":"value"}}'
# Or recreate
kubectl create configmap <name> --from-literal=key1=val1 --from-literal=key2=val2 --dry-run=client -o yaml | kubectl apply -f -

Налагодження змінних середовища найкорисніше після того, як контейнер працює чи ненадовго запрацював. Порівняйте значення всередині середовища процесу зі значеннями, оголошеними в шаблоні Пода. Якщо Под аварійно завершується надто швидко для exec, скористайтеся попередніми логами та специфікацією Пода натомість. У багатоконтейнерних Подах пам’ятайте, що кожен контейнер має власне середовище та монтування.

Terminal window
# Check environment variables in running container
kubectl exec <pod> -- env
# Check what's defined in spec
kubectl get pod <pod> -o jsonpath='{.spec.containers[0].env[*]}'
# Common issue: ConfigMap key name doesn't match env var name
# Check with:
kubectl get pod <pod> -o yaml | grep -A 5 valueFrom

Простеження збоїв розгортання Деплойменту

Розділ «Простеження збоїв розгортання Деплойменту»

Збій розгортання Деплойменту — це симптом рівня контролера, який, проте, побудований із причин рівня Пода, тож шукати корінь проблеми треба на нижчому шарі. Контролер Деплойменту створює новий ReplicaSet з оновленого шаблону Пода, масштабує його вгору й чекає, доки нові Поди стануть доступними, зберігаючи при цьому загальну доступність відповідно до обраної стратегії розгортання. Якщо нові Поди ніколи не стають Ready, розгортання застрягає, навіть якщо старий ReplicaSet тим часом може й далі справно обслуговувати трафік.

Контролер Деплойменту навмисно консервативний, бо доступність є частиною контракту API. Під час послідовного оновлення (rolling update) він може лишати старі Поди працювати, намагаючись створити нові Поди, і це дає вам простір для діагностики, не виводячи сервіс одразу повністю з ладу. Та сама поведінка може робити збої заплутаними: сервіс усе ще працює для частини користувачів, Деплоймент каже, що хід неповний, і лише найновіші Поди показують справді зламаний шаблон.

Зупиніться та подумайте: якщо kubectl rollout status deployment/<name> зависає, який об’єкт варто описати наступним, щоб знайти справжні помилки створення? Умови (conditions) Деплойменту корисні, але найновіший ReplicaSet та його Поди зазвичай містять конкретні докази про образ, монтування, пробу, планування чи аварійне завершення, що пояснюють, чому доступність не просувається.

Перший прохід — прочитати Деплоймент, потім його ReplicaSet, потім Поди, що належать найновішому ReplicaSet. Не припускайте, що сам Деплоймент містить першопричину. Він часто повідомляє високорівневу умову на кшталт ProgressDeadlineExceeded, тоді як події Пода показують точний відсутній Secret, хибний тег образу чи збійну пробу готовності.

Terminal window
# Check deployment status
kubectl get deployment <name>
kubectl describe deployment <name>
# Check ReplicaSets
kubectl get rs -l app=<name>
# Check pods from new ReplicaSet
kubectl get pods -l app=<name>

Діаграма стану розгортання з оригінального модуля відображає операційний ризик. Розгортання може бути частково успішним: старі Поди все ще несуть трафік, тоді як нові Поди дають збій. Це безпечніший режим збою, ніж заміна всього одразу, але він усе одно може лишити вас під ємністю. Ваше рішення — швидко виправляти вперед, відкочуватися чи призупинитися та розслідувати, поки попередня версія лишається доступною.

┌──────────────────────────────────────────────────────────────┐
│ DEPLOYMENT ROLLOUT STATES │
│ │
│ Progressing Stuck │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ New RS │ │ New RS │ │
│ │ scaling up │ │ pods failing │ │
│ └──────────────┘ └──────────────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ Old RS │ │ Old RS │ │
│ │ scaling down │ │ still running│ │
│ └──────────────┘ └──────────────┘ │
│ │
│ Rollout waits for new pods to become Ready │
│ If pods never Ready, rollout stalls │
│ │
└──────────────────────────────────────────────────────────────┘

Пошук найновішого ReplicaSet — практичне скорочення, коли мітки узгоджені. Команда нижче сортує ReplicaSet за міткою часу створення, описує найновіший, а потім перевіряє збійні Поди. У реальних кластерах гігієна міток має значення: якщо селектор широкий чи неузгоджений, команда може включити більше, ніж потрібний вам Деплоймент, тож завжди підтверджуйте назви, перш ніж вносити зміни.

ReplicaSet цінні тим, що зберігають історію розгортання у формі об’єкта. Старий ReplicaSet представляє попередній шаблон Пода, а новий ReplicaSet представляє спробуваний шаблон. Порівняння їхніх образів, посилань на середовище, проб і налаштувань ресурсів може виявити точну зміну, що спричинила збій. Вам не потрібно зіставляти кожен рядок під час завдання з лімітом часу, але помічання того, що лише новий ReplicaSet містить хибний тег чи нову пробу, часто значно скорочує розслідування.

Terminal window
# Check deployment conditions
kubectl describe deployment <name> | grep -A 10 Conditions
# Check new ReplicaSet's pods
NEW_RS=$(kubectl get rs -l app=<name> --sort-by='.metadata.creationTimestamp' -o name | tail -1)
kubectl describe "$NEW_RS"
# Check why pods aren't ready
kubectl get pods -l app=<name> | grep -v Running
kubectl describe pod <failing-pod>

Відкат — це інструмент відновлення сервісу, а не визнання поразки. Якщо нова ревізія ламає ємність, а стара ревізія є відомо справною, kubectl rollout undo повертає Деплоймент до попереднього шаблону Пода, зберігаючи звичну поведінку контролера. Після стабілізації сервісу ви можете розслідувати збійну ревізію з меншим тиском і збудувати виправлений рух уперед.

Terminal window
# Check rollout history
kubectl rollout history deployment/<name>
# Rollback to previous version
kubectl rollout undo deployment/<name>
# Rollback to specific revision
kubectl rollout undo deployment/<name> --to-revision=2
# Verify rollback
kubectl rollout status deployment/<name>

Виправлення вперед доречне, коли першопричина очевидна й безпечна для виправлення, наприклад тег образу з опискою чи відсутній ключ ConfigMap. Відкат кращий, коли збій неоднозначний, ємність для клієнтів низька, або виправлення вимагає коду застосунку. Примусові перезапуски та операції зменшення масштабу потужні, але вони можуть приховати докази та перервати здорові Поди, тож тримайте їх як свідомі рішення, а не рефлекси.

Terminal window
# Option 1: Fix the issue and let rollout continue
kubectl set image deployment/<name> <container>=<fixed-image>
# Option 2: Rollback
kubectl rollout undo deployment/<name>
# Option 3: Force restart (deletes and recreates pods)
kubectl rollout restart deployment/<name>
# Option 4: Scale down then up (nuclear option)
kubectl scale deployment/<name> --replicas=0
kubectl scale deployment/<name> --replicas=3

Готовність, життєздатність та збої, спричинені пробами

Розділ «Готовність, життєздатність та збої, спричинені пробами»

Проби — це, по суті, контракти стану здоров’я між Kubernetes та вашим контейнером, і хибно складений контракт може створити збій, який збоку виглядає як справжній баг застосунку. Liveness відповідає на питання «Чи має kubelet перезапустити цей контейнер?». Readiness відповідає на питання «Чи мають Сервіси надсилати трафік цьому Поду?». Проби запуску (startup) відповідають на питання «Чи має liveness зачекати, поки цей повільний процес стане достатньо живим, щоб його взагалі можна було оцінювати?». Змішування цих трьох значень — один із найшвидших способів створити самозаподіяні збої, коли робоче навантаження насправді справне, а проблему породжує сама конфігурація проб.

Кінцеві точки здоров’я слід проєктувати з огляду на їхню дію в Kubernetes. Кінцева точка liveness має давати збій лише тоді, коли перезапуск контейнера ймовірно покращить ситуацію. Кінцева точка readiness може бути суворішою, бо її збій лише прибирає Под із трафіку. Проба запуску може бути терплячою, бо її призначення — дати повільній ініціалізації справедливий шанс. Коли всі три кінцеві точки вказують на ту саму дорогу перевірку залежності, збій залежності може спричинити перезапуски замість коректного прибирання з трафіку.

Збережена діаграма проб показує два найпоширеніші наслідки проб. Збійна проба liveness перезапускає контейнер, що може спричинити CrashLoopBackOff, коли проба надто агресивна. Збійна проба readiness прибирає Под із кінцевих точок Сервісу, що може зробити так, ніби застосунок лежить, хоча процес працює, а логи виглядають здоровими.

┌──────────────────────────────────────────────────────────────┐
│ PROBE TYPES │
│ │
│ LIVENESS READINESS │
│ Is container alive? Is container ready? │
│ │
│ Failure action: Failure action: │
│ RESTART container REMOVE from service │
│ │
│ Use for: Use for: │
│ • Deadlock detection • Startup dependencies │
│ • Hung processes • Warming caches │
│ │
│ Bad liveness config Bad readiness config │
│ = crash loops = no traffic │
│ │
└──────────────────────────────────────────────────────────────┘

Діагностика проб починається з налаштованого шляху, порту, команди, таймінгу та подій. Якщо події показують повторні збої liveness, після яких ідуть перезапуски, проба може вбивати контейнер ще до того, як застосунок справді зламався. Якщо readiness дає збій без перезапусків, трафік може бути заблокований, бо кінцева точка readiness повертає помилку, порт хибний, або застосунок залежить від бекенда, який ще не готовий.

Ручне тестування проби має відбуватися з тієї самої мережевої перспективи, що й проба, коли це можливо. Виконання команди всередині контейнера й виклик 127.0.0.1 перевіряє локальний слухач і шлях, що близько до того, що намагається перевірити HTTP-проба проти IP Пода. Якщо ручна перевірка спрацьовує, а події все ще показують збої, порівняйте назви портів, порти контейнера, схему, тайм-аут і те, чи інколи кінцева точка перевищує налаштований тайм-аут під навантаженням.

Terminal window
# Check probe configuration
kubectl get pod <pod> -o yaml | grep -A 10 "livenessProbe\|readinessProbe"
# Check for probe failures in events
kubectl describe pod <pod> | grep -i "unhealthy\|probe"
# Test probe manually (nginx images ship curl, not wget)
kubectl exec <pod> -- curl -sf http://127.0.0.1:8080/health
kubectl exec <pod> -- cat /tmp/healthy

Таймінг проби — це місце, де багато інакше коректних конфігурацій дають збій. Java-сервіс, міграція бази даних, прогрів кешу чи застосунок, що завантажує модель, можуть потребувати часу, перш ніж зможуть відповісти на /health. Якщо liveness починається надто рано, Kubernetes перезапускає контейнер під час нормального запуску й гарантує, що він ніколи не досягне готовності. Проба запуску чи довша початкова затримка дозволяють повільний запуск, не послаблюючи довгостроковий контракт liveness.

ПроблемаСимптомВиправлення
Хибний портПроба дає збій, контейнер працюєВиправте порт у специфікації проби
Хибний шлях404-помилки в подіяхВиправте шлях httpGet
Надто агресивнаКонтейнери постійно перезапускаютьсяЗбільшіть timeoutSeconds, periodSeconds
Відсутній initialDelaySecondsДає збій під час запускуДодайте initialDelaySeconds
Застосунок повільно стартуєCrashLoop при запускуВикористайте startupProbe

YAML нижче зберігає оригінальний приклад таймінгу liveness, і числа навмисно читабельні, а не універсальні. Правильний таймінг залежить від розподілу часу запуску, вартості кінцевої точки й толерантності до збоїв. Використовуйте startupProbe, коли запуск повільний, використовуйте readiness для воріт залежностей і прогріву, а liveness залишайте для умов, де перезапуск справді є найбезпечнішою дією відновлення.

livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 30 # Wait 30s before first probe
periodSeconds: 10 # Probe every 10s
timeoutSeconds: 5 # Timeout after 5s
failureThreshold: 3 # Restart after 3 failures

Який підхід ви обрали б тут і чому: сервіс завантажує дані тридцять секунд, перш ніж може відповідати на перевірки здоров’я, але після запуску він може заблокуватися (deadlock) через рідкісний баг. Сильна відповідь використовує пробу запуску для захисту нормального вікна запуску, пробу readiness для утримання трафіку подалі, доки сервіс справді не готовий, і пробу liveness, що виявляє блокування лише після завершення запуску.

Патерни та антипатерни

Розділ «Патерни та антипатерни»

Хороші патерни діагностики застосунків зменшують кількість рухомих частин, які ви перевіряєте одночасно. Ви починаєте з контрольної точки життєвого циклу, потім обираєте джерело доказів, що відповідає цій контрольній точці, потім вносите найменше виправлення в керівний об’єкт. Це повільніше за вгадування протягом перших кількох хвилин, але набагато швидше впродовж реального інциденту, бо кожне спостереження або підтверджує, або виключає клас причин.

ПатернКоли застосовуватиЧому працюєМіркування щодо масштабування
Тріаж за фазою спершуПод нездоровий, а причина незрозумілаФаза Пода звужує відповідальний компонент ще до перевірки логів чи YAMLНавчіть усю команду тієї самої карти «фаза → докази», щоб передачі були узгодженими
Захоплення попередніх логівЛічильник перезапусків більший за нульПопередній екземпляр процесу зазвичай містить повідомлення про збійЦентралізоване логування корисне, але kubectl logs --previous лишається швидкою локальною перевіркою
Виправлення спершу контролераЗбійний Под належить Деплойменту чи іншому контролеруРедагування шаблону контролера не дає відтворити той самий хибний ПодВикористовуйте історію розгортання й записи GitOps, щоб аварійні виправлення лишалися придатними для аудиту
Розділення пробСервіс має чіткі стани alive, ready та startupКожен тип проби має іншу дію Kubernetes і має перевіряти іншу обіцянкуСтандартизуйте семантику кінцевих точок між командами, не нав’язуючи однаковий таймінг

Антипатерни зазвичай походять зі сприйняття статусу як відповіді, а не як вказівника. CrashLoopBackOff не каже, чи процес аварійно завершився через відсутню конфігурацію, поганий код, OOM чи агресивну пробу. ImagePullBackOff не каже, чи репозиторій приватний, із опискою, недоступний чи обмежений за частотою. Надійна діагностика постійно запитує, які докази розрізнили б ймовірні причини.

АнтипатернЩо йде не такКраща альтернатива
Читання лише поточних логів у CrashLoopПоточний процес може бути надто новим чи порожнім, тоді як попередній екземпляр містив помилкуВикористовуйте kubectl logs --previous та перевіряйте lastState.terminated
Редагування збійного Пода з ДеплойментуДеплоймент відтворює той самий хибний шаблон під час наступного узгодженняПатчте чи редагуйте Деплоймент, потім спостерігайте за розгортанням
Сприйняття кожного 137 як витоку пам’ятіSIGKILL може походити від OOM чи зовнішнього шляху вбивстваПеревірте причину завершення та події, перш ніж змінювати обмеження
Використання liveness як перевірки readinessKubernetes перезапускає контейнери, яким просто потрібно більше часу чи залежностейПомістіть утримання трафіку в readiness, а захист повільного старту — у проби запуску

Фреймворк прийняття рішень

Розділ «Фреймворк прийняття рішень»

Наведений нижче фреймворк прийняття рішень — це компактний спосіб обрати наступну точку перевірки, не запам’ятовуючи кожне можливе повідомлення. Спершу запитайте, чи був Под запланований. Потім запитайте, чи підготував kubelet контейнер. Потім запитайте, чи стартував і завершився процес. Потім запитайте, чи процес працює, але не отримує трафіку. Кожен крок змінює джерело доказів і ймовірне виправлення.

Використовуйте фреймворк як цикл, а не як одноразову блок-схему. Після виправлення спостерігайте, як робоче навантаження переходить до наступної контрольної точки, і будьте готові до появи наступного збою. Под може перейти з ImagePullBackOff до CrashLoopBackOff після того, як ви виправите тег, бо завантаження образу лише розкриває наступну проблему в процесі застосунку. Це не провалене виправлення; це нормальне багатошарове налагодження в системі з кількома воротами запуску.

Видимий симптомПерше джерело доказівЙмовірна сім’я першопричинБезпечніша перша дія
PendingПодії kubectl describe podЗапити, taint’и, селектори, спорідненість, прив’язка PVCПеревірте повідомлення планувальника, перш ніж змінювати навантаження
ContainerCreatingПодії Пода й об’єкти, на які він посилаєтьсяЗавантаження образу, монтування тома, ConfigMap, Secret, CNIПеревірте названу залежність у тому самому просторі імен
ImagePullBackOffТекст збійної події завантаженняХибний тег, автентифікація, доступність, обмеження частотиВиправте образ чи pull-Secret у керівному контролері
CrashLoopBackOffПопередні логи та lastStateПомилка застосунку, помилка команди, OOM, вбивство пробоюЗахопіть попередні логи, перш ніж видаляти Под
Працює, але не ReadyПодії проб і стан кінцевих точокШлях readiness, порт, залежність, прогрівПеревірте кінцеву точку readiness зсередини контейнера
Розгортання Деплойменту застряглоДеплоймент, найновіший ReplicaSet, збійні ПодиНовий шаблон створює нездорові ПодиВиправте вперед, якщо очевидно; відкотіться, якщо ємність під ризиком

Використовуйте цей фреймворк на іспиті, проговорюючи шлях про себе: «фаза, події, стан, логи, керівний контролер». Цей маленький сценарій не дає вам зарано перескочити до привабливої команди. У виробництві той самий шлях допомагає написати чітку нотатку про інцидент, бо ви можете показати, що повідомила система, який об’єкт володів збійним шаблоном і чому обране виправлення усунуло першопричину.

  • CrashLoopBackOff має експоненційний відступ: Kubernetes зазвичай починає повтори приблизно з 10 секунд, потім 20 секунд, потім 40 секунд і зрештою обмежує повторні затримки перезапуску контейнера приблизно 5 хвилинами.
  • Init-контейнери виконуються перед контейнерами застосунку: якщо init-контейнер дає збій, основний контейнер ніколи не стартує, тож звичайний вивід kubectl logs <pod> може бути порожнім, доки ви явно не оберете init-контейнер.
  • Статуси збоїв завантаження образу змінюються з часом: Под може показати ErrImagePull після негайного збою завантаження й пізніше показати ImagePullBackOff, коли kubelet розставляє повторні спроби в часі.
  • OOMKilled не завжди означає витік пам’яті: контейнер може перевищити обмеження під час нормального запуску, якщо його базова потреба в пам’яті вища за налаштоване обмеження.
ПомилкаЧому стаєтьсяЯк виправити
Неперевірка --previousПоточний екземпляр контейнера приховує повідомлення про збій від попереднього екземпляраЗавжди перевіряйте попередні логи та lastState.terminated для CrashLoopBackOff
Ігнорування init-контейнерівОсновний контейнер ніколи не стартує, тож його логи виглядають порожніми й оманливимиПерелічіть статуси init-контейнерів і читайте логи з -c <init-container-name>
Виправлення симптомів замість першопричиниВидимий рядок статусу здається відповіддю, особливо під тиском часуВикористовуйте фазу, події, стан, логи й керівний контролер, перш ніж редагувати
Використання хибних одиниць ресурсівОдиниці CPU та пам’яті легко неправильно набрати, спричиняючи неочікувану поведінку планування чи OOMВикористовуйте m для мілікорів і Mi чи Gi для пам’яті, потім перевіряйте запити й обмеження
Надто агресивні проби livenessКоманди повторно використовують перевірки readiness і випадково перезапускають здорові контейнери з повільним стартомДодайте захист запуску й налаштуйте затримку, тайм-аут, період і поріг збоїв
Забування про imagePullSecretsПриватні образи дають збій, хоча Secret існує десь у кластеріПомістіть Secret у простір імен навантаження й посилайтеся на нього з Пода чи ServiceAccount
Використання restartPolicy: Never для ДеплойментівСемантику одноразового запуску плутають із семантикою довготривалого навантаженняВикористовуйте Деплойменти з Always; використовуйте Job для завдань до завершення
Нехтування контекстом простору іменКоманди звертаються до простору імен default і хибно повідомляють про відсутні об’єктиДодайте -n <namespace> чи встановіть свідомий контекст простору імен перед налагодженням
Питання 1: Новий Деплоймент входить у CrashLoopBackOff, попередні логи завершуються на `Error: REDIS_HOST not set`, а останній код завершення — `1`. Що саме дало збій і де це слід виправити?

Процес контейнера стартував достатньо успішно, щоб виконати код застосунку, потім застосунок завершився, бо обов’язкові вхідні дані під час виконання були відсутні. Код завершення 1 загальний, тож вирішальним доказом є повідомлення в лозі, яке називає REDIS_HOST. Виправте шаблон Деплойменту або ConfigMap чи Secret, на який є посилання, щоб змінна середовища існувала, а потім перезапустіть чи розгорніть навантаження так, щоб новий контейнер отримав виправлене середовище. Це не проблема завантаження образу й не проблема планувальника, бо процес досяг запуску застосунку.

Питання 2: Розробник оновлює образ Деплойменту до `nginx:1.255`, і Под спершу показує `ErrImagePull`, а пізніше `ImagePullBackOff`. Що варто перевірити, перш ніж щось змінювати?

Перевірте події Пода на предмет точного повідомлення про збійне завантаження, бо короткий статус не розрізняє тег, якого не існує, від автентифікації реєстру, доступності чи обмеження частоти. Якщо подія каже, що маніфест невідомий, виправте тег образу в Деплойменті дійсним тегом nginx. Якщо вона каже unauthorized, перевірте pull-Secret образу та його простір імен. Ключ у тому, що логів застосунку ще немає, бо образ контейнера ніколи не завантажувався, а процес ніколи не стартував.

Питання 3: Под лишається Pending, а події кажуть `0/3 nodes are available: 3 Insufficient memory`, але вузли начебто мають вільну фізичну пам'ять. Що блокує планування?

Планувальник оцінює придатну для виділення ємність проти оголошених запитів пам’яті, а не побутову вільну пам’ять, яку видно з операційної системи. Наявні Поди могли зарезервувати достатньо пам’яті через запити, тож жоден вузол не має достатньої нерозподіленої запитаної ємності для нового Пода. Зменшіть запит застряглого Пода, якщо він завеликий, перемістіть чи приберіть інші навантаження або додайте ємність. Не сприймайте це як проблему OOMKilled, бо контейнер ще не запускався.

Питання 4: Под аварійно завершується годинами, ви виправляєте ConfigMap, від якого він залежить, і ніщо не перезапускається негайно. Скільки Kubernetes може чекати, і що можна зробити, якщо потрібен швидший повтор?

Відступ перезапуску може вже бути на п’ятихвилинному обмеженні, тож kubelet може не стартувати контейнер знову до наступного запланованого повтору. Ця затримка захищає вузол від щільних циклів перезапуску, але вона може зробити так, що успішне виправлення на короткий час виглядає неефективним. Якщо негайний повтор безпечний, видаліть Под і дозвольте керівному контролеру перестворити його з виправленого шаблону чи залежності. Спершу збережіть логи, якщо вам ще потрібні докази від збійного екземпляра.

Питання 5: Деплоймент створює Под, але основний контейнер ніколи не стартує, а `kubectl logs ` порожній. Який прихований компонент варто дослідити?

Дослідіть спершу init-контейнери, бо Kubernetes виконує їх послідовно перед контейнерами застосунку. Якщо init-контейнер дає збій чи зависає, процес контейнера застосунку ніколи не запускається, тож звичайні логи застосунку порожні за задумом. Опишіть Под, щоб побачити стан init-контейнерів, потім запустіть kubectl logs <pod> -c <init-container-name> для збійного init-контейнера. Виправлення образу чи команди основного контейнера проґавило б справжні ворота.

Питання 6: Розгортання застрягло посередині, старі Поди все ще обслуговують трафік, а найновіший ReplicaSet має один Под у CrashLoopBackOff. Яке найшвидше безпечне відновлення ємності?

Якщо сервіс під ризиком, а стара ревізія є відомо справною, kubectl rollout undo deployment/<name> — це найшвидший безпечний шлях відновлення. Він повертає шаблон Деплойменту до попередньої ревізії й дає контролеру Деплойменту нормально масштабувати здорові Поди. Після стабілізації ємності перевірте докази збійного нового ReplicaSet і Пода, щоб вирішити, чи має наступне виправлення стосуватися образу, конфігурації, ресурсів чи проби. Виправлення вперед розумне лише тоді, коли причина очевидна, а решта ємності прийнятна.

Питання 7: Java-сервісу потрібно тридцять секунд, щоб прогріти кеші, але liveness перевіряє `/health` негайно й повторно перезапускає контейнер. Який дизайн проб має замінити це налаштування?

Використовуйте пробу запуску для захисту періоду прогріву, readiness — для утримання трафіку подалі, доки сервіс не зможе відповідати на справжні запити, і liveness — лише після завершення запуску. Поточна проба liveness діє надто рано, тож Kubernetes перериває нормальну ініціалізацію й створює CrashLoopBackOff. initialDelaySeconds може допомогти, але проби запуску чіткіше виражають намір для контейнерів із повільним стартом. Лише readiness не перезапустив би контейнер, а лише liveness не може розрізнити прогрів від мертвого процесу.

Питання 8: Под на ім'я `checkout` запускає два контейнери — `app` та `log-shipper`. Под показує CrashLoopBackOff, `kubectl logs checkout` друкує здорові рядки запуску sidecar, а `kubectl get pod checkout -o jsonpath='{range .status.containerStatuses[*]}{.name}{"\t"}{.restartCount}{"\n"}{end}'` показує `app` із лічильником перезапусків 4 і `log-shipper` із 0. Яка команда знаходить повідомлення про збій застосунку?

Спершу визначте контейнер, що дає збій, потім читайте його логи з -c. Запустіть kubectl describe pod checkout | grep -A5 "Last State", щоб підтвердити, який контейнер володіє перезапусками, потім kubectl logs checkout -c app --previous для виводу збою. Без -c app ви можете читати здорові логи sidecar, тоді як контейнер застосунку поруч продовжує давати збій. Якщо --previous не повертає логів, бо контейнер завершується миттєво, покладайтеся на describe та код завершення з jsonpath, перш ніж знову пробувати --previous.

Практична вправа: Сценарії збоїв застосунків

Розділ «Практична вправа: Сценарії збоїв застосунків»

Сценарій вправи: ви створите невеликий простір імен із чотирма навмисно зламаними робочими навантаженнями, продіагностуєте кожен збій і застосуєте найменше виправлення, що доводить першопричину. Сценарії навмисно прості, але шлях відображає реальну діагностику: опишіть Под, прочитайте події, оберіть відповідні логи чи поле стану, а потім виправте об’єкт чи відсутню залежність. Запускайте ці команди в одноразовому кластері чи лабораторному середовищі, бо кілька прикладів навмисно створюють збійні Поди.

Terminal window
# Create namespace
kubectl create ns app-debug-lab

Сценарій 1: CrashLoopBackOff

Розділ «Сценарій 1: CrashLoopBackOff»

Цей Под запускає контейнер BusyBox, друкує один рядок і завершується з кодом 1. Це робить його чистим прикладом збою на рівні процесу, а не проблеми образу, планування чи тома. Ваша мета — довести код завершення, а потім замінити одноразову команду на довготривалу, щоб Под міг лишатися живим.

Terminal window
cat <<'EOF' | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
name: crash-app
namespace: app-debug-lab
spec:
containers:
- name: app
image: busybox:1.36
command: ['sh', '-c', 'echo "Starting..."; exit 1']
EOF

Завдання: Знайдіть, чому він аварійно завершується і який у нього код завершення.

Розв'язання
Terminal window
kubectl describe pod crash-app -n app-debug-lab | grep -A5 "Last State"
kubectl get pod crash-app -n app-debug-lab -o jsonpath='{.status.containerStatuses[0].lastState.terminated.exitCode}'
# Exit code 1 - the command explicitly exits with error
# If kubelet retained logs from the prior instance, follow up with:
kubectl logs crash-app -n app-debug-lab --previous
# Fix: update the command to sleep instead of exit
kubectl get pod crash-app -n app-debug-lab -o yaml > crash.yaml
sed -i.bak 's/exit 1/sleep 3600/g' crash.yaml && rm crash.yaml.bak
kubectl replace --force -f crash.yaml
# Verify
kubectl get pod crash-app -n app-debug-lab

Сценарій 2: Відсутній ConfigMap

Розділ «Сценарій 2: Відсутній ConfigMap»

Цей Под посилається на том ConfigMap на ім’я app-settings, але ConfigMap ще не існує. Образ контейнера дійсний, тож завантаження образу — навряд чи причина. Докази мають з’явитися в подіях як проблема налаштування тома чи відсутнього ConfigMap, а виправлення — створити названий об’єкт у тому самому просторі імен.

Terminal window
cat <<'EOF' | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
name: config-app
namespace: app-debug-lab
spec:
containers:
- name: app
image: nginx:1.25
volumeMounts:
- name: config
mountPath: /etc/app
volumes:
- name: config
configMap:
name: app-settings
EOF

Завдання: Знайдіть, чому він застряг у ContainerCreating, і виправте це.

Розв'язання
Terminal window
# Diagnose
kubectl describe pod config-app -n app-debug-lab | grep -A 5 Events
# "configmap "app-settings" not found"
# Fix
kubectl create configmap app-settings -n app-debug-lab --from-literal=key=value
# Verify
kubectl get pod config-app -n app-debug-lab

Сценарій 3: Хибний тег образу

Розділ «Сценарій 3: Хибний тег образу»

Цей Под використовує тег nginx, якого не існує, тож kubelet не може створити контейнер. Корисних логів застосунку бути не повинно, бо процес ніколи не стартує. Правильний доказ — це збійна подія завантаження, а найшвидше лабораторне виправлення — перестворити окремий Под із дійсним тегом образу.

Terminal window
cat <<'EOF' | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
name: image-app
namespace: app-debug-lab
spec:
containers:
- name: app
image: nginx:v99.99.99
EOF

Завдання: Продіагностуйте та виправте збій завантаження образу.

Розв'язання
Terminal window
# Diagnose
kubectl describe pod image-app -n app-debug-lab | grep -A 5 "Failed\|Error"
# "manifest for nginx:v99.99.99 not found"
# Fix - delete and recreate with correct image
kubectl delete pod image-app -n app-debug-lab --force
kubectl run image-app -n app-debug-lab --image=nginx:1.25
# Verify
kubectl get pod image-app -n app-debug-lab

Сценарій 4: Обмеження ресурсів (OOM)

Розділ «Сценарій 4: Обмеження ресурсів (OOM)»

Цей Под навмисно просить stress-процес виділити більше пам’яті, ніж дозволяє обмеження контейнера. Результатом має бути завершення OOMKilled, що відрізняється від загального завершення застосунку. Ваша мета — довести причину завершення, а потім збільшити обмеження достатньо, щоб це синтетичне навантаження запрацювало.

Terminal window
cat <<'EOF' | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
name: oom-app
namespace: app-debug-lab
spec:
containers:
- name: app
image: polinux/stress
command: ["stress"]
args: ['--vm', '1', '--vm-bytes', '500M']
resources:
limits:
memory: "100Mi"
EOF

Завдання: Продіагностуйте, чому контейнер постійно вбивається.

Розв'язання
Terminal window
# Diagnose
kubectl describe pod oom-app -n app-debug-lab | grep -i oom
kubectl get pod oom-app -n app-debug-lab -o jsonpath='{.status.containerStatuses[0].lastState.terminated.reason}'
# "OOMKilled"
# The container tries to use 500MB but only has 100Mi limit
# Fix: increase memory limit by replacing the pod
kubectl get pod oom-app -n app-debug-lab -o yaml > oom.yaml
sed -i.bak 's/100Mi/600Mi/g' oom.yaml && rm oom.yaml.bak
kubectl replace --force -f oom.yaml
# Verify
kubectl get pod oom-app -n app-debug-lab
  • Діагностувати збої застосунків, визначивши код завершення crash-app як 1 за допомогою попередніх логів і стану контейнера.
  • Виправити збої застосунків, спричинені відсутніми ConfigMap, створивши app-settings у правильному просторі імен.
  • Налагодити збої Пода на основі образу, виправивши хибний тег image-app і підтвердивши, що Под стартує.
  • Простежити докази збою через обмеження ресурсів, визначивши OOMKilled для oom-app перед збільшенням обмеження пам’яті.
Terminal window
kubectl delete ns app-debug-lab

Практичні тренування

Розділ «Практичні тренування»

Ці тренування зберігають оригінальну практику швидких команд, але використовують повні запускні команди kubectl. Сприймайте їх як вправи на пригадування з лімітом часу лише після того, як ви зрозуміли шлях доказів, бо швидкість без діагностики може зробити вас упевнено хибними. Мета — зробити правильну наступну команду автоматичною, щойно фаза Пода й події вказали вам напрямок.

Тренування 1: Швидкий статус Пода (30 с)

Розділ «Тренування 1: Швидкий статус Пода (30 с)»
Terminal window
# Task: Show all pods with restart count > 0
kubectl get pods -A -o custom-columns='NAME:.metadata.name,RESTARTS:.status.containerStatuses[0].restartCount' | awk '$2 > 0'

Тренування 2: Попередні логи (30 с)

Розділ «Тренування 2: Попередні логи (30 с)»
Terminal window
# Task: Get last 50 lines from previous container instance
kubectl logs <pod> --previous --tail=50

Тренування 3: Перевірка коду завершення (1 хв)

Розділ «Тренування 3: Перевірка коду завершення (1 хв)»
Terminal window
# Task: Get exit code from crashed container
kubectl get pod <pod> -o jsonpath='{.status.containerStatuses[0].lastState.terminated.exitCode}'
# Or from describe:
kubectl describe pod <pod> | grep "Exit Code"

Тренування 4: Виправлення образу (1 хв)

Розділ «Тренування 4: Виправлення образу (1 хв)»
Terminal window
# Task: Update image in deployment
kubectl set image deployment/<name> <container>=<new-image>

Тренування 5: Створення відсутнього ConfigMap (1 хв)

Розділ «Тренування 5: Створення відсутнього ConfigMap (1 хв)»
Terminal window
# Task: Create ConfigMap from literal
kubectl create configmap <name> --from-literal=key=value
# From file
kubectl create configmap <name> --from-file=<filename>

Тренування 6: Налагодження змінної середовища (1 хв)

Розділ «Тренування 6: Налагодження змінної середовища (1 хв)»
Terminal window
# Task: Check all env vars in running container
kubectl exec <pod> -- env | sort

Тренування 7: Відкат Деплойменту (1 хв)

Розділ «Тренування 7: Відкат Деплойменту (1 хв)»
Terminal window
# Task: Rollback to previous version
kubectl rollout undo deployment/<name>
kubectl rollout status deployment/<name>

Тренування 8: Перевірка конфігурації проб (1 хв)

Розділ «Тренування 8: Перевірка конфігурації проб (1 хв)»
Terminal window
# Task: View probe configuration
kubectl get pod <pod> -o yaml | grep -A 15 livenessProbe
kubectl get pod <pod> -o yaml | grep -A 15 readinessProbe

Перевірка для учня

Розділ «Перевірка для учня»

image: polinux/stress з command: ["stress"] та args: ['--vm', '1', '--vm-bytes', '500M'] замінює образ progrium/stress зі схеми v1, щоб OOM-лабораторія працювала на Kubernetes 1.35.

Ви застосували Сценарій 4 з обмеженням пам’яті 100Mi й контейнером stress, що запитує 500M. Яку причину завершення має повернути kubectl get pod oom-app -o jsonpath='{.status.containerStatuses[0].lastState.terminated.reason}', перш ніж ви піднімете обмеження?


  • Pod Lifecycle — Підкріплює фази Подів, стани контейнерів, поведінку перезапусків, семантику CrashLoopBackOff, послідовність init-контейнерів, концепції життєвого циклу, пов’язані з готовністю, та загальну лексику діагностики стану Пода.
  • kubernetes.io: init containers — Офіційна концептуальна сторінка про init-контейнери стверджує, що init-контейнери виконуються перед контейнерами застосунку, послідовно, і обмежують запуск контейнера застосунку.
  • kubernetes.io: assign memory resource — Офіційне завдання щодо ресурсу пам’яті показує контейнер, що отримує OOMKilled, бо його використання пам’яті перевищує налаштоване обмеження, що підкріплює операційний аргумент.
  • kubectl logs — Підкріплює точну поведінку kubectl logs та прапорці на кшталт вибору контейнера, режиму стеження, міток часу, tail, since, попередніх логів та отримання всіх контейнерів.
  • kubernetes.io: deployment — Концептуальна документація Деплойменту явно стверджує, що в шаблоні Пода Деплойменту дозволено лише restartPolicy: Always.
  • kubernetes.io: job — Концептуальна документація Job явно визначає Job як одноразові завдання, що виконуються до завершення.
  • kubernetes.io: update deployment rolling — Завдання щодо послідовного оновлення каже, що застряглі розгортання зазвичай свідчать про те, що нові Поди не стартують, і рекомендує перевіряти умови та події Деплойменту.
  • Debug Running Pods — Охоплює основний робочий процес діагностики для describe, logs, events, exec та перевірки Пода.
  • Images — Документує політику завантаження образів, контекст автентифікації приватного реєстру та поведінку ImagePullBackOff.
  • Liveness, Readiness, and Startup Probes — Надає авторитетну семантику перезапусків, спричинених пробами, утримання трафіку через readiness та захисту запуску.

Продовжуйте до Модуля 5.3: Збої площини управління, щоб навчитися діагностувати проблеми з API-сервером, планувальником, менеджером контролерів та etcd.