Як виправити помилки CrashLoopBackOff в Kubernetes

Чи гальмують ваші розгортання помилки CrashLoopBackOff? У цій статті ми поговоримо про те, чому поди застряють у стані CrashLoopBackOff, як дізнатися, що под потрапив у цей стан і з якої причини, — усе це з прикладами.

Життєвий цикл подів у Kubernetes

Фази життєвого циклу подів у Kubernetes

Коли YAML-файл конфігурації пода (ресурсу) поступає в Kubernetes, сервер Kube API перевіряє конфігурацію і робить її доступною. Одночасно з цим планувальник слідкує за появою нових подів і розподіляє їх на вузли відповідно до їх ресурсних уподобань. Усього у пода є чотири фази життєвого циклу:

ЗначенняОпис
PendingПерший крок після створення пода. Планувальник Kubernetes розміщує под на вузол
RunningПод переходить у цей стан після успішного створення контейнера
SucceededПод переходить у цей стан після успішного завершення роботи головного контейнера
FailedПод переходить у цей стан, якщо якийсь із його контейнерів «впав» або його exit-код відрізняється від нуля

Дізнатися, у якій фазі знаходиться под, можна за допомогою наступної команди: $ kubectl get pod.

Стан контейнерів у подах

Як уже згадувалося вище, у подів є різні фази. Аналогічним чином Kubernetes відстежує стан контейнерів усередині подів. Усього таких статусів три: Waiting (очікую), Running (працюю) і Terminated (вимкнений). Після того як под запланований на вузол, kubelet починає запускати в відповідному рантаймі його контейнери.

Перевірити статус контейнера можна за допомогою наступної команди: $ kubectl describe pod <ім'я пода>.

СтанОпис
WaitingСтан вказує, що контейнер досі знаходиться в процесі створення і ще не готовий до роботи
RunningВсе нормально: ресурси (CPU, пам’ять) виділені успішно, процес контейнера запущений
TerminatedПроцес контейнера припинив свою роботу

Що таке CrashLoopBackOff

У Kubernetes стан CrashLoopBackOff вказує на те, що под застряг у циклі перезапусків. Це означає, що один або кілька контейнерів у поді не змогли стартувати.

Загалом кажуть, що под застряг у стані CrashLoop, коли один або кілька його контейнерів постійно запускаються, падають і знову запускаються.

Що таке BackOff-час і чому він важливий

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

Тобто після першої невдалої спроби ми деякий час чекаємо, перш ніж здійснити другу. Якщо і вона невдала — ми чекаємо трохи довше, а потім повторюємо спробу. Термін BackOff означає, що з кожною спробою період очікування поступово зростає. Ідея в тому, щоб у системи або мережі було час відновитися після збою. Крім того, такий підхід запобігає ефекту лавини.

Тобто BackOff — це час, який повинен пройти після того, як под завершив роботу. Тільки після того, як він мине, под спробує перезапуститися. Затримка дає системі час на відновлення і усунення помилки. Набір таких інтервалів «пригальмовує» перезапуски.

Уявімо, що под не запустився одразу. Тоді час очікування до перезапуску за замовчуванням (у конфігурації kubelet) становитиме 10 секунд. Зазвичай після кожної спроби він збільшується вдвічі. Тобто після першої спроби под буде чекати 10 секунд, після другої — 20 секунд, потім 40, 80 і так далі.

Політика перезапуску в Kubernetes

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

Конкретна поведінка контролюється конфігурацією під назвою restartPolicy у специфікації пода. Задаючи політику перезапуску, ми визначаємо, як Kubernetes буде обробляти збої контейнерів. Можливі значення: Always, OnFailure і Never. Значення за замовчуванням — Always.

Приклад специфікації пода із заданою політикою перезапуску K8s:

apiVersion: v1
kind: Pod
metadata:
  name: my-nginx
spec:
  containers:
  - name: nginx
    image: nginx:latest
    ports:
    - containerPort: 80
  restartPolicy: Always    # політика перезапуску

Як дізнатися, що под потрапив у стан CrashLoopBackOff

Статус подів можна перевірити за допомогою простої команди kubectl. Приклад її виводу:

Статус пода

Видно, що под my-nginx не готовий: його статус відрізняється від Ready і вказаний як CrashLoopBackOff. Також видно кількість перезапусків.

Тут відбувається саме те, про що говорилося вище: под падає і намагається перезапуститися. Поки под «відпочиває», можна спробувати встановити причину його перезапусків.

Наприклад, на платформі PerfectScale для цього треба перейти на вкладку Alerts — тут будуть відображені критичні алерти про ресурси Kubernetes і незвичайну активність у системі. Також можна подивитися докладну зведення алертів для конкретного тенанта:

Алерти

Поширені причини CrashLoopBackOff

1. Нестача ресурсів

Розподіл пам’яті відіграє вирішальну роль у забезпеченні безперебійної роботи розгортань у Kubernetes. Якщо вимоги пода до пам’яті не враховані, може виникнути стан CrashLoopBackOff.

Наприклад, якщо додаток «з’їдає» більше пам’яті, ніж йому виділено, система вбиває його по OOM (Out Of Memory). Результат — стан CrashLoopBackOff.

2. Проблеми, пов’язані з образами

  • Нестача дозволів — якщо у образу контейнера немає необхідних дозволів для доступу до ресурсів, той може впасти.
  • Неправильний образ контейнера — якщо под використовує неправильний образ для запуску контейнера, це може призводити до постійних перезавантажень і стану CrashLoopBackOff.

3. Помилки конфігурації

  • Синтаксичні помилки або опечатки — при конфігурації подів можливі помилки, такі як опечатки в назвах контейнерів, іменах образів і змінних середовища. Вони можуть завадити коректному запуску контейнерів.
  • Неправильно задані запити і ліміти ресурсів — помилки в налаштуванні запитуваних ресурсів (мінімально необхідної кількості) і лімітів (максимально допустимої кількості) можуть призводити до збоїв у роботі контейнера.
  • Відсутні залежності — залежності, які користувач забув внести в spec пода.

4. Проблеми із зовнішніми сервісами

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

5. Винятки у додатках

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

6. Неправильно налаштовані liveness-проби

Liveness-проби допомагають переконатися, що процес у контейнері працює (не завис). Якщо все ж це сталося, контейнер буде вбитий і перезапущений (якщо це передбачено політикою перезапуску restartPolicy пода). Поширеною помилкою є неправильне налаштування liveness-проби, в результаті якої контейнер перезапускається при тимчасових затримках з відповідями, наприклад через високе навантаження. Така проба погіршує проблему, а не вирішує її.

Виявлення причин CrashLoopBackOff

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

Припустимо, що команда kubectl get pods показала статус CrashLoopBackOff у одного або кількох подів:

$ kubectl get pods 
NAME                        READY   STATUS             RESTARTS         AGE
app                         1/1     Running            1 (3d12h ago)    8d
busybox                     0/1     CrashLoopBackOff   18 (2m12s ago)   70m 
hello-8n746                 0/1     Completed          0                8d
my-nginx-5c9649898b-ccknd   0/1     CrashLoopBackOff   17 (4m3s ago)    71m
my-nginx-7548fdb77b-v47wc   1/1     Running            0                71m

Спробуємо по кроках встановити причину:

  1. Перевіримо опис пода

Команда kubectl describe pod pod-name виводить докладну інформацію про конкретні поди і контейнери:

$ kubectl describe pod pod-name 

Name:           pod-name
Namespace:      default
Priority:       0
……………………
State:         Waiting
Reason:        CrashLoopBackOff
Last State:    Terminated
Reason:        StartError
……………………
Warning  Failed   41m (x13 over 81m)   kubelet  Error: container init was OOM-killed (memory limit too low?): unknown

У її виводі міститься різна інформація, наприклад:

  • State (Стан): Waiting (Очікування)
  • Reason (Причина): CrashLoopbackOff
  • Last state (Попередній стан): Terminated (Робота перервана)
  • Reason (Причина): StartError (Помилка при старті)

Цього достатньо, щоб зробити кілька припущень про причини. З останніх рядків виводу «kubelet Error: container init was OOM-killed (memory limit too low?)» випливає, що контейнер не запускається через нестачу пам’яті.

  1. Перевіримо логи пода

Логи містять докладну інформацію про ресурси Kubernetes, про запуск контейнера, про будь-які проблеми, що виникли на шляху, про припинення роботи ресурсу або навіть про її успішне завершення.

Перевірити логи подів можна за допомогою наступних команд:

$ kubectl logs pod-name   # витягнути логи пода з єдиним контейнером

Подивитися логи пода з кількома контейнерами:

$ kubectl logs pod-name --all-containers=true

Подивитися логи за певний проміжок часу. Наприклад, щоб вивести логи за останню годину, виконайте:

$ kubectl logs pod-name --since=1h
  1. Подивимось події

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

$ kubectl events 
LAST SEEN               TYPE      REASON    OBJECT                          MESSAGE
4h43m (x9 over 10h)     Normal    BackOff   Pod/my-nginx-5c9649898b-ccknd   Back-off pulling image "nginx:latest"
3h15m (x11 over 11h)    Normal    BackOff   Pod/busybox                     Back-off pulling image "busybox"
40m (x26 over 13h)      Warning   Failed    Pod/my-nginx-5c9649898b-ccknd   Error: failed to create containerd task: failed to create shim task: OCI runtime create failed: runc create failed: unable to start container process: container init was OOM-killed (memory limit too low?): unknown

Приклад роботи команди показаний вище.

Вивести список останніх подій для всіх просторів імен:

$ kubectl get events --all-namespaces

Вивести список подій для конкретного пода:

$ kubectl events --for pod/pod-name
  1. Перевіримо логи розгортання:
$ kubectl logs deployment deployment-name 

Found 2 pods, using pod/my-nginx-7548fdb77b-v47wc
/docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration
/docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d/
/docker-entrypoint.sh: Launching /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh
10-listen-on-ipv6-by-default.sh: info: Getting the checksum of /etc/nginx/conf.d/default.conf
10-listen-on-ipv6-by-default.sh: info: Enabled listen on IPv6 in /etc/nginx/conf.d/default.conf
/docker-entrypoint.sh: Sourcing 

Логи розгортання допомагають з’ясувати причину збою контейнерів і те, чому под опиняється в стані CrashLoopBackOff.

Висновок

У цій статті ми докладно поговорили про CrashLoopBackOff. Важливо зазначити, що саме по собі це не помилка, а стан, у якого можуть бути різні причини. Встановити їх допоможе аналіз логів і подій для конкретного пода.