Управление ресурсами в Kubernetes: квоты, лимиты и политики распределения | AdminWiki

Управление ресурсами в Kubernetes: квоты, лимиты и политики распределения

31 июля 2026 13 мин. чтения

Зачем нужно управлять ресурсами в Kubernetes

Кластер Kubernetes без настроенных ограничений ресурсов напоминает общежитие без правил: один жилец может занять всю кухню, и остальные останутся голодными. В production-среде это приводит к каскадным сбоям. Под с утечкой памяти способен занять всю RAM узла, kubelet выполнит OOM Kill других контейнеров, включая системные. Команда разработки, выкатившая десяток реплик без лимитов, может исчерпать квоту всего namespace и заблокировать развертывание других сервисов.

Kubernetes предоставляет три встроенных механизма для предотвращения таких ситуаций: ResourceQuota ограничивает суммарное потребление в рамках namespace, LimitRange задает границы для отдельных контейнеров и подов, а Pod Priority и Preemption определяют, какие поды получат ресурсы в первую очередь при их нехватке. Эта статья - практическое руководство по настройке всех трех инструментов с готовыми конфигурациями для dev, staging и production окружений.

Если вы сталкивались с тем, что поды зависают в статусе Pending без видимой причины или критичный сервис падает при пиковой нагрузке, корень проблемы почти всегда в настройке ресурсов. Разберем каждый механизм по шагам, начиная с фундамента - requests и limits.

Requests и Limits: основа управления ресурсами

В основе всех политик распределения лежат два параметра, указываемые для каждого контейнера в спецификации пода: requests и limits. Requests - это гарантированный объем ресурсов, который планировщик резервирует на узле при размещении пода. Limits - верхняя граница, которую контейнер не может превысить. Если контейнер попытается использовать больше CPU, чем указано в limits, Kubernetes начнет троттлить его. При превышении лимита по памяти контейнер будет убит с ошибкой OOMKilled.

Рассмотрим пример. Контейнер с requests: memory: 256Mi и limits: memory: 512Mi может использовать до 512 MiB памяти, но планировщик учтет только 256 MiB при выборе узла. Если на узле сумма requests всех подов равна доступной памяти, новые поды на нем размещены не будут, даже если фактическое потребление ниже. Это ключевой момент: планировщик оперирует requests, а не реальным потреблением.

Задать requests и limits можно в секции resources каждого контейнера:

apiVersion: v1
kind: Pod
metadata:
  name: nginx-demo
spec:
  containers:
  - name: nginx
    image: nginx:1.25
    resources:
      requests:
        memory: "256Mi"
        cpu: "250m"
      limits:
        memory: "512Mi"
        cpu: "500m"

CPU измеряется в милли-ядрах (m), где 1000m соответствует одному ядру. Память - в байтах с суффиксами Ki, Mi, Gi. Значения requests и limits для CPU и памяти влияют на класс обслуживания пода (Quality of Service, QoS), который определяет порядок вытеснения при нехватке ресурсов.

Классы QoS и их влияние на стабильность

Kubernetes автоматически назначает каждому поду один из трех классов QoS на основе соотношения requests и limits его контейнеров. Этот класс напрямую влияет на то, какие поды kubelet убьет первыми при нехватке памяти на узле.

  • Guaranteed - высший приоритет. Назначается, когда для каждого контейнера в поде requests и limits по CPU и памяти равны и не равны нулю. Такие поды получают гарантированные ресурсы и убиваются в последнюю очередь.
  • Burstable - средний приоритет. Хотя бы один контейнер имеет requests меньше limits или часть контейнеров не имеет заданных ресурсов. Поды могут использовать свободные ресурсы узла сверх requests.
  • BestEffort - низший приоритет. Ни один контейнер в поде не имеет заданных requests и limits. Эти поды первыми попадают под OOM Kill.

Механизм вытеснения работает через OOM Score - числовое значение, вычисляемое kubelet для каждого процесса. Чем выше score, тем выше вероятность быть убитым при нехватке памяти. BestEffort поды получают максимальный score. Для Burstable подов score пропорционален отношению использованной памяти к requests: чем сильнее превышение над гарантированным объемом, тем выше score. Guaranteed поды имеют минимальный score и защищены от вытеснения до последнего момента.

Практическая рекомендация: для критичных production-сервисов всегда используйте класс Guaranteed, устанавливая равные requests и limits. Это исключает неожиданные убийства процессов при пиковых нагрузках и делает поведение приложения предсказуемым. Подробнее о настройке отказоустойчивых конфигураций читайте в статье по управлению развертываниями в Kubernetes.

LimitRange: задаем границы для контейнеров и подов

В мультиарендном кластере разработчики часто забывают указывать requests и limits в манифестах. Результат - поды без ограничений, которые могут занять всю память узла. LimitRange решает эту проблему на уровне namespace: он задает значения по умолчанию и жесткие границы для ресурсов контейнеров и подов.

Объект LimitRange определяет правила для CPU и памяти: минимальное значение (min), максимальное (max), значение по умолчанию для limits (default) и для requests (defaultRequest). Можно также ограничить соотношение между requests и limits через параметр maxLimitRequestRatio. Если контейнер в поде не указывает ресурсы, применяются default и defaultRequest. Если указывает значения, выходящие за min/max, создание пода будет отклонено с ошибкой валидации.

LimitRange действует только на новые поды, создаваемые после его применения. Существующие поды не затрагиваются - это важно учитывать при внедрении политик в работающий кластер.

Пример конфигурации LimitRange для типового namespace разработки

Для dev-среды логично задать умеренные лимиты, которые предотвратят создание откровенно "прожорливых" подов, но не будут мешать экспериментам. Для staging границы ужесточаются, приближаясь к production-значениям. Вот готовый манифест для dev-namespace с комментариями:

apiVersion: v1
kind: LimitRange
metadata:
  name: dev-limits
  namespace: development
spec:
  limits:
  - type: Container       # Правила для отдельных контейнеров
    default:
      memory: "512Mi"     # Лимит по умолчанию, если не указан явно
      cpu: "500m"
    defaultRequest:
      memory: "256Mi"     # Requests по умолчанию
      cpu: "250m"
    min:
      memory: "128Mi"     # Минимальный request - контейнер не может запросить меньше
      cpu: "100m"
    max:
      memory: "2Gi"       # Максимальный лимит - защита от гигантских подов
      cpu: "2"
    maxLimitRequestRatio:
      memory: "4"         # Лимит не может превышать request более чем в 4 раза
      cpu: "4"
  - type: Pod              # Правила для суммы ресурсов всех контейнеров в поде
    max:
      memory: "4Gi"       # Суммарный лимит на под
      cpu: "4"

При попытке создать под без указания resources контейнер получит 256Mi requests и 512Mi limits автоматически. Если разработчик укажет requests: 64Mi, создание будет отклонено - значение меньше min. Если укажет limits: 8Gi - тоже отклонено, превышен max. Соотношение maxLimitRequestRatio не даст установить requests: 128Mi и limits: 2Gi, так как 2Gi / 128Mi = 16, что больше разрешенного 4.

Для staging-среды значения стоит приблизить к production: min memory 256Mi, max memory 4Gi, default memory 1Gi. Для production - строгие границы с обязательным указанием ресурсов. Если нужно полностью запретить создание подов без ресурсов, установите min > 0 - тогда defaultRequest без явного указания не сработает, и под будет отклонен.

ResourceQuota: контроль суммарного потребления в namespace

Если LimitRange задает правила для отдельных подов, то ResourceQuota ограничивает общее потребление всех подов в namespace. Это инструмент для разделения кластера между командами: каждой выделяется квота, и ни одна команда не может занять ресурсы другой.

ResourceQuota считает сумму requests и limits всех подов в namespace. При попытке создать новый под, который приведет к превышению квоты, API-сервер отклоняет запрос с ошибкой 403 Forbidden. Квотировать можно вычислительные ресурсы (CPU, память), количество объектов (подов, сервисов, secrets, PVC) и даже суммарный объем хранилища.

Базовый пример квоты на вычислительные ресурсы:

apiVersion: v1
kind: ResourceQuota
metadata:
  name: compute-quota
  namespace: team-backend
spec:
  hard:
    requests.cpu: "8"
    requests.memory: "16Gi"
    limits.cpu: "16"
    limits.memory: "32Gi"
    pods: "20"
    services: "10"
    persistentvolumeclaims: "5"

Эта квота разрешает команде backend суммарно запросить до 8 ядер CPU и 16 GiB памяти в requests, а лимиты всех подов не могут превышать 16 ядер и 32 GiB. Дополнительно ограничено количество подов (20), сервисов (10) и PVC (5).

Проверить текущее использование квоты можно командой:

kubectl describe quota compute-quota -n team-backend

Вывод покажет, сколько ресурсов уже занято и сколько осталось. Если квота близка к исчерпанию, новые поды не запустятся. Для мониторинга таких ситуаций настройте алерты - об этом подробнее в руководстве по диагностике неисправностей через метрики мониторинга.

Совместное использование ResourceQuota и LimitRange

По отдельности эти механизмы решают разные задачи, но вместе они создают эшелонированную защиту. LimitRange гарантирует, что каждый под имеет разумные лимиты и не создан с нулевыми requests. ResourceQuota ограничивает сумму. Без LimitRange пользователь может создать сотню подов с минимальными requests (по 1m CPU), но огромными limits (по 4 ядра). Формально квота по requests не превышена, но при росте нагрузки все поды попытаются забрать свои limits, что приведет к жесткой переподписке узла и OOM Kill.

Правильная связка: LimitRange задает min и max для контейнеров, а ResourceQuota - суммарный потолок. Например, LimitRange требует минимум 128Mi requests на контейнер, а ResourceQuota дает 16Gi на namespace. Команда может создать не более 128 контейнеров с минимальными requests, и каждый будет иметь вменяемый лимит, ограниченный max из LimitRange.

Готовые примеры YAML для такой связки с разбором частых ошибок собраны в статье по настройке ResourceQuota и LimitRange.

Pod Priority и Preemption: приоритеты и вытеснение

Квоты и лимиты решают проблему справедливого распределения, но не отвечают на вопрос: что делать, когда ресурсов все равно не хватает? В production-кластере критичные сервисы должны получать ресурсы в первую очередь, даже если для этого придется вытеснить менее важные поды. Механизм Pod Priority и Preemption решает эту задачу.

Суть проста: каждому поду назначается числовой приоритет через объект PriorityClass. Чем выше число, тем выше приоритет. Когда планировщик не может разместить новый под из-за нехватки ресурсов, он оценивает возможность вытеснения (preemption) подов с более низким приоритетом. Если сумма ресурсов вытесняемых подов достаточна для размещения нового, планировщик инициирует удаление низкоприоритетных подов и размещает высокоприоритетный.

Вытеснение - это штатное удаление пода с соблюдением graceful termination. Поды получают сигнал SIGTERM, отрабатывают terminationGracePeriodSeconds и удаляются. Если в кластере настроено автомасштабирование узлов, вытесненные поды могут быть перепланированы на новые узлы. Рекомендации по интеграции с Cluster Autoscaler и Horizontal Pod Autoscaler рассмотрены в сравнении контроллеров Kubernetes.

Создание PriorityClass и назначение подам

PriorityClass - это кластерный объект, не привязанный к namespace. Создайте несколько классов под разные уровни критичности:

apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
  name: high-priority
value: 1000000
globalDefault: false
description: "Критичные production-сервисы"
---
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
  name: medium-priority
value: 100000
globalDefault: true
description: "Стандартные сервисы"
---
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
  name: low-priority
value: 10000
globalDefault: false
description: "Фоновые задачи и dev-окружение"

Значение value - 32-битное целое число. Системные компоненты Kubernetes используют значения от 10^9 и выше, поэтому пользовательские приоритеты рекомендуется задавать до 10^8. Параметр globalDefault: true делает класс приоритетом по умолчанию для всех подов без явного указания priorityClassName.

Назначить приоритет поду просто - укажите имя PriorityClass в спецификации:

apiVersion: v1
kind: Pod
metadata:
  name: payment-service
spec:
  priorityClassName: high-priority
  containers:
  - name: app
    image: payment:latest
    resources:
      requests:
        memory: "512Mi"
        cpu: "500m"
      limits:
        memory: "512Mi"
        cpu: "500m"

Сценарий вытеснения: пошаговый разбор

Представьте узел с 8 GiB памяти, на котором работают пять подов с низким приоритетом, каждый с requests: 2Gi. Свободной памяти нет. В кластер приходит под payment-service с high-priority и requests: 1Gi.

Планировщик выполняет следующую последовательность:

  1. Проверяет все узлы - ни на одном нет свободных 1Gi.
  2. Анализирует поды на узлах: пять низкоприоритетных подов имеют priority 10000, новый под - 1000000.
  3. Вычисляет, что для освобождения 1Gi достаточно вытеснить один низкоприоритетный под (он занимает 2Gi).
  4. Выбирает под для вытеснения с учетом QoS: среди низкоприоритетных предпочтение отдается BestEffort, затем Burstable с наибольшим превышением над requests.
  5. Помечает выбранный под на удаление, тот получает статус Terminating.
  6. После освобождения ресурсов размещает high-priority под на узле.

Вытесненный под будет перепланирован на другом узле, если есть свободные ресурсы. Если нет - останется в Pending до появления места или масштабирования кластера. Критично: preemption не гарантирует мгновенного размещения - вытеснение и перепланирование занимают время, зависящее от graceful shutdown приложения.

Проектирование политики распределения ресурсов для мультиарендного кластера

Три механизма - ResourceQuota, LimitRange и Pod Priority - работают на разных уровнях и должны быть сведены в единую стратегию. Цель: команды получают справедливую долю ресурсов, критичные сервисы защищены, а неконтролируемое потребление невозможно.

Рекомендуемый подход для кластера с несколькими командами:

  • Для каждого namespace создается ResourceQuota с жесткими границами по сумме requests и limits.
  • Внутри каждого namespace действует LimitRange, задающий значения по умолчанию и предельные границы для контейнеров.
  • Критичные production-сервисы получают Guaranteed QoS (requests == limits) и высокий PriorityClass.
  • Сервисы staging - Burstable QoS и средний приоритет.
  • Dev-окружение и фоновые задачи - Burstable или BestEffort с низким приоритетом.

Пример распределения для трех команд:

ПараметрProductionStagingDevelopment
ResourceQuota: requests.cpu1684
ResourceQuota: requests.memory32Gi16Gi8Gi
LimitRange: default memory1Gi512Mi256Mi
LimitRange: max memory4Gi2Gi1Gi
PriorityClasshigh-prioritymedium-prioritylow-priority
QoSGuaranteedBurstableBurstable

Мониторинг использования квот - обязательная часть стратегии. Настройте алерты на приближение к лимитам (например, 80% от квоты) через Prometheus и Grafana. Метрики kube-state-metrics предоставляют данные о текущем потреблении в разрезе namespace. Без мониторинга команды будут упираться в квоты и получать отказы при деплое, не понимая причин.

Типовые ошибки и как их избежать

На основе практики внедрения в production-кластерах выделим пять частых ошибок:

  • Слишком жесткие квоты. Если ResourceQuota задана впритык к реальному потреблению, любое масштабирование или плановый деплой блокируется. Всегда держите запас 20-30% сверх текущего использования.
  • Отсутствие LimitRange. Без него разработчик может создать под с requests: 1m и limits: 8Gi. Квота по requests не превышена, но узел в итоге перегружен. Всегда комбинируйте ResourceQuota с LimitRange.
  • Одинаковый приоритет у всех подов. Если все поды имеют high-priority, preemption бесполезен - некого вытеснять. Разделите сервисы на уровни критичности и назначьте соответствующие PriorityClass.
  • Игнорирование мониторинга. Квоты работают молча до момента отказа. Без алертов вы узнаете о проблеме только когда деплой упадет с ошибкой. Настройте дашборды и оповещения.
  • BestEffort для production. Поды без requests и limits первыми уходят в OOM Kill при любом всплеске потребления. Для критичных сервисов всегда используйте Guaranteed QoS.

Если вам нужен облачный кластер для тестирования этих политик, Timeweb Cloud предоставляет управляемый Kubernetes с возможностью гибкого масштабирования ресурсов.

Заключение: проверенные конфигурации для быстрого старта

Ниже - три комплекта манифестов, готовых к применению. Адаптируйте значения под свои нагрузки, но сохраняйте общую структуру: ResourceQuota + LimitRange + PriorityClass в каждом окружении.

Development-окружение - мягкие лимиты, низкий приоритет, разрешено создание подов без явного указания ресурсов:

# PriorityClass
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
  name: dev-priority
value: 10000
globalDefault: false
---
# ResourceQuota
apiVersion: v1
kind: ResourceQuota
metadata:
  name: dev-quota
  namespace: development
spec:
  hard:
    requests.cpu: "4"
    requests.memory: "8Gi"
    limits.cpu: "8"
    limits.memory: "16Gi"
---
# LimitRange
apiVersion: v1
kind: LimitRange
metadata:
  name: dev-limits
  namespace: development
spec:
  limits:
  - type: Container
    default:
      memory: "256Mi"
      cpu: "250m"
    defaultRequest:
      memory: "128Mi"
      cpu: "100m"
    max:
      memory: "1Gi"
      cpu: "1"
    min:
      memory: "64Mi"
      cpu: "50m"

Staging-окружение - средние лимиты, обязательное указание ресурсов через min > 0:

# PriorityClass
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
  name: staging-priority
value: 100000
globalDefault: false
---
# ResourceQuota
apiVersion: v1
kind: ResourceQuota
metadata:
  name: staging-quota
  namespace: staging
spec:
  hard:
    requests.cpu: "8"
    requests.memory: "16Gi"
    limits.cpu: "16"
    limits.memory: "32Gi"
---
# LimitRange
apiVersion: v1
kind: LimitRange
metadata:
  name: staging-limits
  namespace: staging
spec:
  limits:
  - type: Container
    default:
      memory: "512Mi"
      cpu: "500m"
    defaultRequest:
      memory: "256Mi"
      cpu: "250m"
    max:
      memory: "2Gi"
      cpu: "2"
    min:
      memory: "128Mi"
      cpu: "100m"

Production-окружение - строгие лимиты, Guaranteed QoS, высокий приоритет. Здесь LimitRange задает узкие границы, а поды обязаны указывать ресурсы явно:

# PriorityClass
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
  name: production-priority
value: 1000000
globalDefault: false
---
# ResourceQuota
apiVersion: v1
kind: ResourceQuota
metadata:
  name: production-quota
  namespace: production
spec:
  hard:
    requests.cpu: "16"
    requests.memory: "32Gi"
    limits.cpu: "16"
    limits.memory: "32Gi"
    pods: "30"
---
# LimitRange
apiVersion: v1
kind: LimitRange
metadata:
  name: production-limits
  namespace: production
spec:
  limits:
  - type: Container
    default:
      memory: "1Gi"
      cpu: "1"
    defaultRequest:
      memory: "1Gi"
      cpu: "1"
    max:
      memory: "4Gi"
      cpu: "4"
    min:
      memory: "256Mi"
      cpu: "250m"

В production-конфигурации requests и limits в LimitRange совпадают (default == defaultRequest). Это подталкивает к использованию Guaranteed QoS. ResourceQuota также выравнивает requests и limits на уровне namespace - суммарные limits не могут превысить суммарные requests, что исключает переподписку.

Эти манифесты - отправная точка. Настройте значения под фактические паттерны потребления ваших приложений, используя данные мониторинга за несколько недель. Для автоматизации управления конфигурациями в масштабе кластера изучите подходы Infrastructure as Code - сравнение инструментов от kubectl до Crossplane есть в статье по IaC для Kubernetes.

Внедрение политик распределения ресурсов - не разовая акция, а процесс. Начните с LimitRange в dev-средах, добавьте ResourceQuota по мере роста числа команд, а Pod Priority введите, когда появятся критичные сервисы, требующие гарантий доступности. Последовательное применение этих механизмов превращает хаотичный кластер в предсказуемую платформу, где ресурсы расходуются эффективно, а сбои локализованы.

Поделиться:
Сохранить гайд? В закладки браузера