open atlas
↑ К треку
Деплой и инфра DEP · 10 · 03

Probes и лимиты ресурсов

Readiness гейтит трафик, liveness рестартит, startup щитит медленный старт; requests планируют, limits ограничивают — превышение CPU даёт throttling, памяти — OOMKilled. Настрой неверно — здоровое приложение уйдёт в CrashLoop или будет вытеснено.

DEP Middle ◷ 17 min
Уровень
ОсновыJuniorMiddleSenior

Сервис отгрузился нормально, а в 3 ночи поды начинают мигать. На дашборде CrashLoopBackOff растёт по всем репликам сразу. Никто не пушил код. Причина: livenessProbe с initialDelaySeconds: 5 на приложении, которому нужно 25 секунд, чтобы прогреть JIT и подключиться к базе. Каждый запуск проба падала три раза до того, как приложение было готово, Kubernetes считал контейнер мёртвым и рестартил его — бесконечно. Совершенно здоровое приложение казнила его собственная проверка здоровья. Фикс — четыре строки YAML. Урок занял ночь.

Три probe, отвечающие на три разных вопроса

Прежде чем настраивать любую probe, зафиксируй, на какой именно вопрос ты отвечаешь — потому что три probe выглядят похоже, но имеют противоположные последствия при срабатывании.

Kubernetes не считает запущенный контейнер работающим. Он спрашивает, раз за разом, через probes — и тип probe решает, что произойдёт, когда ответ «нет». Путаница между ними — главный источник самонанесённых аварий в этом уроке.

Readiness спрашивает может ли этот под обслуживать трафик прямо сейчас? Когда она падает, Kubernetes убирает под из endpoints’ов Service — трафик к нему прекращается, — но контейнер не рестартит. Под остаётся живым, просто вне ротации, и возвращается в момент, когда readiness снова проходит. Это гейт для роллаутов (новый под не получает трафик, пока реально не готов) и для временной перегрузки (под под давлением GC может сбросить трафик, не умирая). Сделай readiness отражающей реальную готовность: приложение забиндило порт, прогнало миграции и подтвердило, что критичные зависимости достижимы.

Liveness спрашивает жив ли ещё этот контейнер, или он завис? Когда она падает (за порогом), Kubernetes рестартит контейнер. Она существует ради одной задачи: восстановление из неустранимых состояний — дедлок, event loop, который перестал крутиться, утечка памяти, подвесившая процесс. Liveness — это кувалда. Если медленное-но-здоровое приложение или временный сбой зависимости её триггерит, ты получаешь тот самый restart loop из хука.

Startup спрашивает закончил ли этот контейнер загрузку? Она бежит первой; пока она падает, liveness и readiness probes полностью придержаны. Как только startup прошла один раз, она больше не запускается, и две другие берут управление. Это правильный щит для медленно стартующих приложений — старых JVM, всего с долгим прогревом, — чтобы держать liveness агрессивной для рантайм-сбоев, не давая ей убивать приложение во время старта.

containers:
- name: api
  image: registry.example.com/api:1.4.0
  startupProbe:            # щит старта: до 30 × 10s = 5 мин на оживление
    httpGet: { path: /healthz, port: 8080 }
    periodSeconds: 10
    failureThreshold: 30
  readinessProbe:          # гейт трафика; падение убирает из Service, без рестарта
    httpGet: { path: /ready, port: 8080 }
    periodSeconds: 5
    failureThreshold: 3
  livenessProbe:           # рестарт, если завис; падение убивает + рестартит контейнер
    httpGet: { path: /healthz, port: 8080 }
    periodSeconds: 10
    failureThreshold: 3
Почему это работает

Две ловушки probe бьют сеньоров. Первая — liveness, указывающая на зависимость: если твой /healthz проверяет базу, а у базы 30-секундная икота, liveness каждого пода падает разом, и Kubernetes рестартит весь твой флот — превращая короткий сбой зависимости в самонанесённую каскадную аварию. Liveness должна тестировать только сам процесс; пусть readiness отражает зависимости. Вторая — переиспользование одного эндпоинта для обоих: если /healthz делает тяжёлые проверки зависимостей и ты вешаешь на него liveness, ты тихо поставил liveness в зависимость от внешнего мира. Держи дешёвый локальный liveness-эндпоинт и более богатый readiness.

requests и limits: планирование против жёсткого потолка

У ресурсов два числа на контейнер, и они делают совершенно разную работу.

requests — это то, что поду гарантировано, и то, что планировщик использует для размещения: Kubernetes поставит под только на ноду, у которой свободно как минимум requests.cpu и requests.memory (суммарно по ноде). Это резервирование, а не потолок. Ставь requests на типичное, устойчивое потребление контейнера.

limits — это жёсткий потолок, который контейнер не может превысить в рантайме, — и то, что происходит при попытке, люди понимают неправильно, потому что CPU и память ведут себя противоположно:

  • CPU сверх лимита → throttling, не убийство. CPU сжимаемый. Превысишь limits.cpu — и CFS-планировщик ядра (Completely Fair Scheduler — планировщик процессорного времени Linux) просто даёт контейнеру меньше тайм-слотов; приложение продолжает работать, но становится медленным. Заниженный CPU-лимит проявляется как загадочная латентность, а не как падения.
  • Память сверх лимита → OOMKilled. Память несжимаема — нельзя дать процессу «меньше» RAM, чем он уже выделил. Превысишь limits.memory — и OOM-киллер ядра завершает контейнер с кодом выхода 137; ты видишь OOMKilled и рестарт. Заниженный лимит памяти проявляется как падения, а не как медлительность.
resources:
  requests:        # гарантия + планирование — ставь ≈ типичное потребление
    cpu: "250m"
    memory: "256Mi"
  limits:          # жёсткий потолок — защищает ноду от убежавшего контейнера
    cpu: "1"       # сверху → throttling CFS (медленно, не убит)
    memory: "512Mi"  # сверху → OOMKilled (выход 137, контейнер рестартит)

Правило сеньора: ставь requests ≈ типичное, чтобы планировщик честно паковал ноду, и ставь limits достаточно высоко, чтобы поглотить всплески, но достаточно низко, чтобы защитить ноду. Дальше следи за двумя сигналами сбоя — повторяющийся OOMKilled означает, что лимит памяти слишком низок (или есть утечка); устойчивый CPU throttling означает, что CPU-лимит слишком низок. Настраивай по реальным метрикам, а не по догадкам.

QoS-классы решают, кто умрёт первым

Соотношение requests и limits тихо назначает каждому поду класс качества обслуживания (QoS), и этот класс задаёт порядок вытеснения, когда у ноды кончается память:

  • Guaranteed — у каждого контейнера requests == limits и по CPU, и по памяти. Вытесняется последним. Используй для критичных по латентности, stateful-нагрузок.
  • Burstable — задан хотя бы один request, но не равный limits (частый случай). Вытесняется после BestEffort, в порядке того, насколько он вышел за свои requests.
  • BestEffort — никаких requests и limits вообще. Первый на вытеснение, первый на OOM-kill под давлением ноды. Годится только для одноразовой batch-работы.

Все три класса вместе означают, что судьбу pod-а решает не только планировщик — OOM-киллер ядра тоже, и он использует класс, чтобы выбрать, кто умрёт первым. Без явных requests и limits ты случайно получаешь BestEffort, что делает твой pod первой жертвой при всплеске «шумного соседа».

Параметрrequestslimits
РольГарантированное резервирование; драйвит планированиеЖёсткий рантайм-потолок на контейнер
Использует планировщик?Да — под попадёт только на ноду со столькими свободнымиНет — применяется в рантайме ядром
Сверх CPUн/д (request — это пол)throttling CFS — приложение тормозит, остаётся живым
Сверх памятин/дOOMKilled (выход 137) — контейнер рестартит
Ставь в≈ типичное устойчивое потреблениеВысоко для всплесков, низко для защиты ноды
Выбери лучший вариант

Медленно стартующему JVM-сервису (прогрев 25с) нужно восстанавливаться из редких дедлоков, не мигая на каждом рестарте. Как настроить его probes?

Викторина

Контейнер под нагрузкой раз за разом завершается с кодом выхода 137, затем рестартит. О чём это говорит?

Вспомните перед уходом
  1. 01
    Здоровое приложение застряло в CrashLoopBackOff сразу после деплоя. Пройдись по misconfiguration probe, который это вызывает, и по точному фиксу.
  2. 02
    В чём разница между OOMKilled и throttling контейнера и что каждое говорит изменить?
Итог

Kubernetes держит поды здоровыми и правильно размеренными двумя независимыми инструментами, и сбои идут от путаницы случаев. Теперь, когда встретишь CrashLoopBackOff на только что задеплоенном сервисе, твой первый вопрос: есть ли у liveness-пробы запас на реальное время загрузки — и не направлена ли она на зависимость? Есть три probes, отвечающие на три вопроса: readiness («может ли обслуживать?» — падение убирает под из endpoints’ов Service, но не рестартит, гейт для роллаутов и сброса временной нагрузки), liveness («завис ли он?» — падение рестартит контейнер, только для восстановления из дедлоков) и startup («загрузился ли он?» — бежит первой и придерживает liveness и readiness до конца старта, щит для медленно стартующих приложений). Две ловушки, кладущие здоровые флоты, — это слишком агрессивная liveness-проба, гоняющая в CrashLoop медленное-но-здоровое приложение, и liveness, направленная на зависимость, которая рестартит все поды разом при кратком сбое зависимости — держи liveness дешёвой и локальной, пусть readiness отражает зависимости. По ресурсам: requests — это гарантированное резервирование, которое планировщик использует для размещения (ставь их около типичного потребления), а limits — жёсткий рантайм-потолок, но два ресурса применяются противоположно: превысишь CPU-лимит — тебя троттлит CFS (медленно, не убит), превысишь лимит памяти — тебя OOMKilled с кодом выхода 137 (убит и рестартнут). Соотношение requests к limits назначает QoS-класс — Guaranteed (requests == limits, вытесняется последним), Burstable или BestEffort (без limits, вытесняется первым), — который задаёт порядок гибели подов под давлением ноды. Следи за повторяющимися OOMKill (лимит памяти слишком низок) и устойчивым throttling (CPU-лимит слишком низок) и настраивай по метрикам, а не по догадкам.

Практика

Начни сверху. Задачи идут от простого к сложному: вспомнить факт, применить к случаю, затем senior-уровень. Открой, попробуй, потом открой ответ.

вспомнитьприменитьуглубить0 из 5 завершено

Что-то непонятно?

Задай вопрос по этому уроку. Вопросы анонимны и попадают напрямую автору — урок станет лучше.

хоткеи развернуть
поиск
K
пред. пьеса
k
след. пьеса
j
тиры
t
это меню
?
sources2
expand
  1. 01
  2. 02

Trademarks belong to their respective owners. Editorial reference only.