🐳 Розділ 14 · Питання #19

Навіщо потрібні health checks

У Kubernetes існує три види проб (Health Probes):


🟢 Junior Level

30-секундна відповідь

Health Checks (Перевірки працездатності / Проби) — це вбудований діагностичний комплекс Kubernetes, за допомогою якого оркестратор безперервно відстежує фізичний і логічний стан контейнерів для реалізації автономного самовідновлення (Self-Healing) та розгортання без простою (Zero Downtime).

У Kubernetes існує три види проб (Health Probes):

  1. startupProbe (Проба запуску): відповідає на запитання: «Застосунок уже встиг завантажитися?» Дає повільним сервісам (наприклад, важким Java-монолітам) час на спокійний старт, тимчасово відключаючи решту перевірок.
  2. livenessProbe (Проба живучості): відповідає на запитання: «Процес усе ще живий чи він завис?» Якщо проба падає, Kubernetes примусово вбиває та перезапускає контейнер.
  3. readinessProbe (Проба готовності): відповідає на запитання: «Чи готовий под приймати користувацькі запити?» Якщо проба падає, контейнер НЕ перезапускається, але його IP тимчасово видаляється з балансувальника навантаження.

Наочна аналогія з життя

Уявіть відкриття нової кав’ярні:

  • startupProbe: вивіска «Триває ремонт та завезення обладнання». Інспектори чекають і не штрафують кав’ярню за те, що каву ще не наливають.
  • livenessProbe: перевірка «Бариста притомний чи знепритомнів?» Якщо бариста впав без тями (Deadlock), менеджер викликає заміну (перезапуск).
  • readinessProbe: табличка на касі «Перерва 5 хвилин на помел зерен». Бариста живий, але просто зараз покупців не обслуговує (трафік перенаправляється на сусідню касу).

Зведений маніфест з усіма трьома пробами (Java 21 / Spring Boot 3)

apiVersion: v1
kind: Pod
metadata:
  name: order-service
spec:
  containers:
  - name: order-app
    image: order-service:1.0
    # 1. Захист на час старту JVM (до 30 * 5с = 150 секунд)
    startupProbe:
      httpGet:
        path: /actuator/health/liveness
        port: 8080
      periodSeconds: 5
      failureThreshold: 30

    # 2. Виявлення дедлоків та зависань процесу
    livenessProbe:
      httpGet:
        path: /actuator/health/liveness
        port: 8080
      periodSeconds: 10
      failureThreshold: 3

    # 3. Керування надходженням клієнтського трафіку
    readinessProbe:
      httpGet:
        path: /actuator/health/readiness
        port: 8080
      periodSeconds: 5
      failureThreshold: 2

🟡 Middle Level

Життєвий цикл і скінченний автомат перевірок (State Machine)

Kubelet координує виконання трьох проб за строгим алгоритмом:

               [ Контейнер запущений у Linux ]
                              │
                              ▼
               [ 1. Активна startupProbe ]
               (liveness та readiness заморожені)
                              │
              ┌───────────────┴───────────────┐
              │ Успіх                         │ Провал (вичерпано failureThreshold)
              ▼                               ▼
  [ startupProbe відключена назавжди ]   [ 🔴 Вбивство та перезапуск контейнера ]
              │
              ├───────────────────────────────┐
              ▼                               ▼
   [ 2. Активна livenessProbe ]    [ 3. Активна readinessProbe ]
   (Періодичне опитування)         (Періодичне опитування)
              │                               │
        Провал:                         Провал:
        🔴 Перезапуск контейнера        🟡 Виключення IP з Endpoints

Порівняльна таблиця тріади перевірок

| Критерій | startupProbe | livenessProbe | readinessProbe | | :— | :— | :— | :— | | Ключове питання | Чи завершився старт? | Чи живий робочий потік? | Чи готовий до обробки RPC? | | Період роботи | Тільки під час старту пода | Після успіху startup | Після успіху startup | | Реакція на відмову | 🔴 Вбивство контейнера | 🔴 Перезапуск контейнера | 🟡 Відключення від трафіку | | Вплив на інші | Блокує Liveness/Readiness| Незалежно | Незалежно | | Що перевіряти | Завантаження контексту Spring | Deadlock, Thread Starvation | Доступність кешів, пулів БД |


Катастрофічний антипатерн: «Єдиний ендпоінт /health»

Багато розробників через незнання прописують один і той самий ендпоінт /health в усі три секції:

# ❌ ГРУБА ПОМИЛКА АРХІТЕКТУРИ
livenessProbe:
  httpGet: { path: /health, port: 8080 } # Перевіряє БД та Redis!
readinessProbe:
  httpGet: { path: /health, port: 8080 }

Чому це вбиває систему:

  1. Якщо сервер Redis короткочасно перезавантажиться, ендпоінт /health поверне HTTP 503.
  2. Через спільний ендпоінт спрацює Liveness-проба.
  3. Kubernetes вирішить, що Java-застосунок помер, і перезапустить абсолютно всі поди бекенду в кластері!
  4. Сервіс піде в нескінченний CrashLoop, викликавши повну відмову бізнес-функціоналу.

🔴 Senior Level

Архітектура Kubelet: Внутрішня будова Prober Manager

Усередині демона kubelet (написаного на Go) за виконання перевірок відповідає модуль pkg/kubelet/prober:

  • Асинхронні воркери: на кожну оголошену пробу в поді Kubelet запускає окрему ізольовану горутину-воркер (worker.go).
  • Власні черги (WorkQueue): виконання проб не блокує основний цикл керування контейнерами Kubelet’а.
  • Обробка мережевих таймаутів: якщо сокет застосунку не відповідає за час timeoutSeconds, горутина примусово обриває контекст net.Dialer і реєструє відмову, запобігаючи витоку файлових дескрипторів на хості.

Тонкощі налаштування під віртуальну машину Java (HotSpot JVM)

При роботі з Java в контейнерах необхідно враховувати специфіку функціонування JVM:

1. Врахування пауз Stop-The-World (GC STW Pauses)

Якщо ви використовуєте збирач сміття G1 або Parallel GC на великих купах (Heap 8–32 GB):

  • Пауза Full GC може заморозити JVM на 1–4 секунди.
  • Якщо в маніфесті заданий таймаут timeoutSeconds: 1, Kubelet зафіксує хибний збій.
  • Правило: timeoutSeconds має бути завжди більшим за максимальну очікувану паузу збирача сміття (рекомендується 3–5 секунд).

2. Прогрів JIT-компілятора та Spring Boot AOT

У Java 21 зі Spring Boot 3 перші запити до застосунку викликають інтенсивну компіляцію C2 JIT:

  • startupProbe повинна давати достатній запас часу (failureThreshold: 30, periodSeconds: 5 = 150 сек).
  • Для миттєвого проходження startupProbe (менш ніж за 100 мс) застосовують компіляцію в нативний бінарний код (GraalVM Native Image) або технологію CRaC (Coordinated Restore at Checkpoint).

Взаємодія зі стадією Graceful Shutdown

Коли под виводиться з експлуатації, важливо запобігти гонитві між health-пробами та зупинкою:

1. kubectl delete pod -> Статус Terminating
2. Kubelet запускає preStop hook (sleep 10)
3. EndpointSlice Controller виключає IP пода з мережевих таблиць
4. Kubelet надсилає SIGTERM процесу Java
5. Spring Boot дочікується завершення активних HTTP-запитів (spring.lifecycle.timeout-per-shutdown-phase=30s)
6. Контейнер завершує роботу штатно з кодом 0

Під час фази Terminating агент Kubelet припиняє перезапускати контейнер за liveness probe, навіть якщо проба починає падати.


4 Tricky Questions

1. Що станеться з контейнером, якщо він вичерпає ліміт спроб failureThreshold у startupProbe? Чим відрізняється поведінка в сучасних версіях K8s від ранніх версій?

Відповідь:

  • У сучасному Kubernetes (1.20+): якщо startupProbe повертає відмову failureThreshold разів поспіль, Kubelet вважає, що застосунок не зміг ініціалізуватися, примусово вбиває контейнер сигналом SIGKILL і перезапускає його відповідно до політики restartPolicy (лічильник перезапусків збільшується).
  • У ранніх версіях (до 1.20): через баг у підсистемі prober відмова startupProbe призводила до вічного зависання пода в неготовності без ініціалізації процедури перезапуску контейнера (виправлено в PR #95190).

2. Чому у високонавантажених системах категорично не рекомендується використовувати перевірки типу exec для частих Health Checks?

Відповідь: При виконанні проби exec Kubelet кожні periodSeconds звертається до CRI (Container Runtime Interface, наприклад containerd) і виконує системні виклики ядра Linux clone() / fork() та execve():

  • Усередині простору імен контейнера породжується новий повноцінний процес (наприклад, /bin/sh або curl).
  • Це викликає різке зростання витрат CPU хостової системи, перемикання контекстів ядра Linux, блокування в cgroups та фрагментацію таблиці процесів PID.
  • Якщо рантайм containerd відчуває дефіцит ресурсів, fork може зависнути за таймаутом, викликавши хибне падіння проби. Використання httpGet або tcpSocket позбавлене цих проблем, оскільки Kubelet перевіряє відкритий мережевий сокет ззовні без породження дочірніх процесів.

3. Як поводяться проби Liveness та Readiness, якщо в маніфесті оголошено startupProbe, але вона ще не повернула першу успішну відповідь?

Відповідь: Поки startupProbe не поверне успішний статус (Success), обидві проби — і livenessProbe, і readinessProbe — повністю ВІДКЛЮЧЕНІ. Kubelet навіть не починає надсилати перевірочні запити на їхні адреси. Це зроблено для того, щоб важкий застосунок міг спокійно виконувати міграції Liquibase/Flyway, прогрівати кеш і компілювати класи протягом кількох хвилин, не ризикуючи бути передчасно вбитим агресивним livenessProbe.


4. Чи може впала readinessProbe захистити застосунок від нестачі пам’яті (OOMKilled) при різкому напливі вхідних HTTP-запитів?

Відповідь: Так, може. Якщо в застосунку реалізовано механізм саморегуляції (Backpressure / Shedding Load):

  • При заповненні пулу з’єднань Tomcat або перевищенні черги завдань сервіс програмно переводить ReadinessState у REFUSING_TRAFFIC (повертаючи HTTP 503 на пробу готовності).
  • Kubelet виключає под з EndpointSlice, і нові користувачі припиняють спрямовуватися на цей екземпляр.
  • Под спокійно дообробляє вже прийняті запити в пам’яті, сміття утилізується збирачем Garbage Collector, пам’ять звільняється, і процес уникає фатального виклику ядра Linux OOM Killer (Exit Code 137). Після нормалізації стану readiness-проба знову вмикається.

🎯 Шпаргалка для інтерв’ю

Головне про Health Checks

  • Тріада проб:
    • startupProbe — захист повільного старту (JVM, Spring Boot); блокує інші проби.
    • livenessProbe — виявлення дедлоків/зависань; при відмові $\to$ рестарт контейнера.
    • readinessProbe — готовність обслуговувати клієнтів; при відмові $\to$ зняття з балансування.
  • Категоричний антипатерн: єдиний ендпоінт /health, що перевіряє зовнішню БД в усіх пробах (призведе до падіння всіх подів при збої БД).
  • Таймаути в Java: timeoutSeconds повинен перевищувати максимальну паузу Full GC (не менше 3–5 секунд).
  • Механізми: віддавати перевагу httpGet та grpc замість важкого exec.
  • Архітектура Kubelet: проби виконуються асинхронно окремими горутинами через pkg/kubelet/prober.

Зведення дій при збої

| Проба | Дія K8s | Вплив на трафік | Застосування | | :— | :— | :— | :— | | startup | Рестарт контейнера | Трафік не подається | Старт Spring Boot / міграції БД | | liveness| Рестарт контейнера | Трафік скидається | Ліквідація Deadlock та зависань | | readiness| Зняття з балансування| Трафік іде на інші поди | Захист від перевантаження / прогрів кешу |

Червоні прапорці на співбесіді (Чого говорити не можна)

  • ❌ «Ми використовуємо один і той самий health-ендпоінт для Liveness та Readiness проб» — груба помилка, при проблемах у залежностях кластер впаде цілком.
  • ❌ «Readiness-проба вбиває под, якщо він довго не відповідає» — readiness ніколи не вбиває процес, лише ізолює від мережі.
  • ❌ «Для Java-сервісів startupProbe не потрібна, достатньо поставити livenessProbe з initialDelaySeconds: 5» — сервіс гарантовано потрапить у CrashLoopBackOff.

Пов’язані питання