Две настройки отличают «работает в кластере» от «работает надёжно»: проверки здоровья (probes) и ресурсы (requests и limits). Ошибки в них дают самые частые продовые инциденты: каскадные рестарты, OOMKilled и необъяснимые тормоза.
Три вида probes
| Probe | Вопрос | Если не прошла |
|---|---|---|
| startup | приложение уже запустилось? | пока не прошла, остальные probes не выполняются; при провале контейнер перезапускается |
| liveness | процесс жив, а не завис? | kubelet перезапускает контейнер |
| readiness | готов принимать трафик? | под убирают из эндпоинтов Service, перезапуска нет |
Типы проверок: httpGet (код 200-399 считается успехом), tcpSocket, exec (команда с кодом 0), grpc.
startupProbe:
httpGet: { path: /healthz, port: 8080 }
periodSeconds: 5
failureThreshold: 30 # до 150 секунд на старт
livenessProbe:
httpGet: { path: /healthz, port: 8080 }
periodSeconds: 10
failureThreshold: 3
readinessProbe:
httpGet: { path: /ready, port: 8080 }
periodSeconds: 5
Параметры: initialDelaySeconds, periodSeconds, timeoutSeconds (по умолчанию 1 секунда), failureThreshold, successThreshold.
Правила хороших probes
- Liveness проверяет только сам процесс. Никаких запросов в базу или к соседним сервисам. Если база легла, liveness всех подов упадёт, и kubelet начнёт перезапускать здоровые поды, превращая проблему базы в каскадный отказ.
- Readiness может учитывать зависимости, без которых под бесполезен, и прогрев (кеши, соединения). Под временно выйдет из балансировки и вернётся сам.
- Медленный старт (JVM, миграции, загрузка моделей) закрывают startupProbe, а не огромным
initialDelaySecondsу liveness. - Таймаут 1 секунда под нагрузкой легко превышается: GC-пауза или перегруженный event loop, и здоровый под убит. Таймауты и пороги выбирают с запасом.
- Liveness не обязательна. Если процесс при зависании падает сам, её можно не ставить, а неудачная liveness опаснее отсутствующей.
Requests и limits
resources:
requests:
cpu: 250m # 0.25 ядра
memory: 256Mi
limits:
memory: 256Mi
- requests это то, что планировщик резервирует на узле. Под попадёт только на узел, где сумма requests помещается в доступные ресурсы. Requests не ограничивают реальное потребление.
- limits это жёсткий потолок.
CPU и память ведут себя по-разному:
- CPU сжимаемый. При превышении лимита процесс не убивают, а троттлят: CFS-квота не даёт работать больше, чем
limitза период (100 мс). Симптомы: рост задержек при невысокой средней загрузке. Видно по метрикеcontainer_cpu_cfs_throttled_periods_total. - Память несжимаемая. При превышении лимита ядро убивает процесс в cgroup контейнера: статус
OOMKilled, код выхода 137.
Единицы: CPU в ядрах или милли-ядрах (500m), память в Mi, Gi (двоичные) или M, G (десятичные). 100M и 100Mi это разные числа.
QoS-классы
Kubernetes присваивает поду класс по ресурсам:
- Guaranteed: у каждого контейнера requests = limits и для CPU, и для памяти.
- Burstable: requests заданы хотя бы частично, но не равны limits.
- BestEffort: ничего не задано.
При нехватке памяти на узле kubelet вытесняет (evict) поды: сначала те, что больше всего превышают свои requests, в первую очередь BestEffort, затем Burstable. Guaranteed трогают в последнюю очередь.
Практические рекомендации
- Память: requests = limits. Тогда под не попадёт на узел, где ему не хватит памяти, и поведение предсказуемо.
- CPU: requests по реальному потреблению, а лимит CPU многие команды не ставят вовсе или ставят с запасом: троттлинг вредит задержкам, а свободное CPU узла простаивает. Это предмет споров, но объяснить свою позицию нужно уметь.
- Значения подбирают по метрикам (p95 потребления) и пересматривают. В помощь VPA в режиме рекомендаций.
- В языках с рантаймом выставляй лимит кучи меньше лимита контейнера: JVM
-XX:MaxRAMPercentage, Node.js--max-old-space-size, GoGOMEMLIMIT. - Многие рантаймы определяют число ядер по узлу, а не по лимиту: Go до версии 1.25 (
GOMAXPROCS), пулы потоков. Слишком много потоков при маленьком лимите CPU усиливает троттлинг.
LimitRange и ResourceQuota
- LimitRange задаёт значения по умолчанию и минимум/максимум для контейнеров в namespace. Спасает от подов без ресурсов.
- ResourceQuota ограничивает суммарные requests, limits и число объектов в namespace.
Частые ошибки
- Liveness проверяет базу и вызывает каскадные рестарты.
- Нет readiness: трафик идёт на неготовые поды при старте и деплое.
initialDelaySeconds: 120вместо startupProbe.- Нет requests: планировщик набивает узел, поды BestEffort вытесняются первыми.
- Лимит памяти меньше реального пика, поды в цикле OOMKilled.
- CPU-лимит слишком маленький, задержки растут из-за троттлинга.
Что спросят на собесе
- Чем liveness отличается от readiness, зачем startupProbe.
- Почему liveness не должна проверять базу.
- Что такое requests и limits, как ведут себя CPU и память при превышении.
- Какие бывают QoS-классы и кого вытесняют первым.
- Почему сервис тормозит при CPU 30% и что такое троттлинг.