Guides
Kubernetes

Kubernetes resource requests и limits: почему это важно

Requests и limits в Kubernetes: чём разница, что будет если не указать, OOMKilled, CPU throttling, QoS классы. Как прави

Привет, коллеги. Если вы разворачиваете что-то серьезнее демо-пода в продакшене, тема лимитов и запросов ресурсов — одна из первых, на которой вы обожжетесь. Разберем, как настроить это правильно, чтобы ваши поды не падали молча или не душили соседей по ноде.

База: Requests vs Limits

Requests (запросы) — это гарантированное количество ресурсов, которое планировщик Kubernetes резервирует для пода. Limits (лимиты) — жесткий потолок, выше которого под не может вырасти.

resources:
  requests:
    memory: "256Mi"
    cpu: "250m"
  limits:
    memory: "512Mi"
    cpu: "500m"

В этом примере под получает гарантию на 0.25 CPU и 256 МБ RAM. Он может использовать до 0.5 CPU, но память не может превысить 512 МБ.

Что будет, если не указать?

Если не указать requests, по умолчанию они считаются равными limits. Если не указаны и limits, то для пода нет ограничений. Это плохо по двум причинам:

  1. Память (OOMKilled): Без limits на memory под может съесть всю доступную память на ноде. Когда нода исчерпает память, kubelet начнет убивать процессы, чтобы спасти систему. Ваш под получит статус OOMKilled. Без requests планировщик может разместить ваш memory-прожорливый под на ноде, где уже не хватит памяти для его работы.
  2. CPU (Throttling): Без limits на CPU под может пытаться использовать все ядра. Без requests его могут поставить на загруженную ноду, где он не получит и минимума. Но ключевая проблема — throttling (подавление). Если указан limits, контейнер не может его превысить. При попытке это сделать, ядро Linux (cgroups) ограничит контейнер, заставив его ждать. В мониторинге это выглядит как высокий throttling и просадки в производительности при скачках нагрузки, хотя общая загрузка CPU ноды может быть низкой.

QoS-классы: последствия ваших настроек

В зависимости от того, как вы задали requests и limits, Kubernetes присваивает поду один из классов качества обслуживания (QoS):

  • Guaranteed: Самый высокий приоритет. Заданы limits и requests для всех контейнеров, и они равны для каждого ресурса. Или заданы только limits (тогда requests устанавливаются равными им). Эти поды убиваются в последнюю очередь при нехватке ресурсов.
  • Burstable: Заданы requests и limits, но они не равны. Или заданы requests без limits. У этих подов есть гарантированный минимум, но они могут бустить. Убиваются вторыми.
  • BestEffort: Ничего не задано. Нет гарантий, первыми на вылет при нехватке памяти.

Стратегия проста: для критичных воркеров стремитесь к Guaranteed. Это дает предсказуемое поведение.

Как выставлять для реальных воркеров: практика

  1. Начинайте с мониторинга. Запустите без limits (но с запасом по memory requests!) и соберите метрики потребления (CPU, memory) за несколько дней. Используйте инструменты вроде kubectl top pod, Prometheus с Grafana.

  2. Для memory: limits должен быть выше пикового потребления с запасом 15-25%. requests можно ставить ближе к среднему потреблению, но не ниже. Никогда не ставьте memory limit ниже memory request. Это путь к немедленному OOMKilled.

  3. Для CPU: Здесь гибче. Если приложение чувствительно к задержкам (база данных, RPC-сервис), ставьте limits с хорошим запасом от пиков, чтобы избежать throttling. Для фоновых задач или веб-сервисов с горизонтальным масштабированием можно сделать limits в 1.5-2 раза выше requests. Это позволит использовать свободные ресурсы ноды, но цена — возможный throttling.

  4. Шаблоны для разных нагрузок:

    • Python/Go веб-сервис: CPU requests/limits близки, memory limit с запасом.
    resources:
      requests:
        memory: "300Mi"
        cpu: "200m"
      limits:
        memory: "400Mi"
        cpu: "300m"
    
    • Java-сервис с JVM: Memory requests и limits обязательно равны и соответствуют Xmx/Xms. Это предотвратит лишние аллокации и OOM.
    resources:
      requests:
        memory: "1Gi"
        cpu: "500m"
      limits:
        memory: "1Gi"
        cpu: "800m"
    

Типичные грабли

  • Копипаст конфигов. Настройки, идеальные для Python-сервиса, убьют Java-приложение.
  • Игнорирование CPU throttling. Приложение “тормозит”, хотя метрики CPU показывают неполную загрузку — первым делом смотрите container_cpu_cfs_throttled_periods_total.
  • Заниженные memory limits для JVM. JVM, не видя лимита, может выставить слишком большой heap и быть убитой OOMKiller’ом, когда попытается его использовать.
  • Не учитывать memory не-heap. Память контейнера — это не только heap JVM или RSS процесса. Добавляйте запас на shared libraries, мета-данные, кэши.

Грамотные requests и limits — это не оптимизация, а необходимое условие стабильности кластера. Потратьте время на настройку один раз, чтобы не тушить пожары каждую неделю.