LimitRange, ResourceQuota и VPA: управление ресурсами на уровне namespace

Published: 2026-04-21

Kubernetes по умолчанию не ограничивает потребление ресурсов. Неправильно настроенное приложение может съесть всю память ноды и вытеснить всё остальное. Три инструмента предотвращают это: LimitRange (значения по умолчанию для контейнеров), ResourceQuota (лимиты на namespace) и VPA (рекомендации по right-sizing).


LimitRange

Подставляет requests/limits по умолчанию в контейнеры без явных значений и ограничивает максимум для одного контейнера:

yamlapiVersion: v1
kind: LimitRange
metadata:
  name: default-limits
  namespace: dev
spec:
  limits:
    - type: Container
      default:          # применяется, если limits: не задан
        cpu: "500m"
        memory: "512Mi"
      defaultRequest:   # применяется, если requests: не задан
        cpu: "100m"
        memory: "128Mi"
      max:              # контейнер не может превысить
        cpu: "4"
        memory: "4Gi"
      min:              # контейнер должен запросить минимум
        cpu: "50m"
        memory: "64Mi"
    - type: Pod
      max:
        cpu: "8"
        memory: "8Gi"
    - type: PersistentVolumeClaim
      max:
        storage: "50Gi"

Без этого под без блока resources: планируется с нулевыми requests — планировщик его не учитывает, на нодах возникает overcommit, приложения убивает OOM killer.

Проверка LimitRange:

bashkubectl describe limitrange default-limits -n dev

ResourceQuota

Ограничивает суммарное потребление ресурсов в namespace:

yamlapiVersion: v1
kind: ResourceQuota
metadata:
  name: dev-quota
  namespace: dev
spec:
  hard:
    requests.cpu: "20"
    requests.memory: "20Gi"
    limits.cpu: "40"
    limits.memory: "40Gi"
    pods: "100"
    services: "30"
    services.loadbalancers: "0"
    persistentvolumeclaims: "20"
    requests.storage: "200Gi"
    count/secrets: "100"
    count/configmaps: "100"

services.loadbalancers: "0" не даёт разработчикам случайно создать LoadBalancer Service (что в облаке влечёт создание реального балансировщика и дополнительные расходы).

При наличии ResourceQuota в namespace каждый под обязан иметь resource requests. LimitRange автоматически это обеспечивает.


Квоты по окружениям

yaml# namespace dev — щедро, быстрые итерации
limits.cpu: "40"
limits.memory: "40Gi"

# namespace test — приближено к production, но поменьше
limits.cpu: "20"
limits.memory: "20Gi"

# namespace sre — минимум, только для инструментов
limits.cpu: "8"
limits.memory: "8Gi"

Хранятся в spoke/namespaces/ как отдельные YAML-файлы. Kustomize-патчи переопределяют значения для каждого окружения.


VPA для рекомендаций по right-sizing

VPA в режиме Off вычисляет рекомендации без изменения подов:

yamlapiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
  name: elasticsearch
  namespace: observability
spec:
  targetRef:
    apiVersion: apps/v1
    kind: StatefulSet
    name: elasticsearch-master
  updatePolicy:
    updateMode: "Off"
  resourcePolicy:
    containerPolicies:
      - containerName: elasticsearch
        minAllowed:
          cpu: 100m
          memory: 1Gi
        maxAllowed:
          cpu: 8
          memory: 16Gi

Через несколько дней трафика проверить рекомендации:

bashkubectl describe vpa elasticsearch -n observability

Пример вывода:

Container Recommendations:
  Container Name: elasticsearch
  Lower Bound:
    cpu: 216m
    memory: 2Gi
  Target:
    cpu: 500m
    memory: 3Gi
  Upper Bound:
    cpu: 2
    memory: 5Gi

Использовать Target для обновления HelmRelease. Не использовать updateMode: Auto для stateful-приложений — чтобы применить изменения, VPA вытесняет поды, а это простой.


Проверка использования квоты

bashkubectl describe resourcequota -n dev
Name:        dev-quota
Namespace:   dev
Resource                Used     Hard
--------                ----     ----
limits.cpu              12500m   40
limits.memory           14Gi     40Gi
pods                    47       100
requests.cpu            3200m    20
requests.memory         5Gi      20Gi
services.loadbalancers  0        0

Если deployment падает с exceeded quota, этот вывод сразу показывает, какой ресурс исчерпан.


Диагностика ошибок квоты

Под не создаётся: pods "name" is forbidden: exceeded quota

bashkubectl describe resourcequota -n <namespace>
# Найти, какой ресурс достиг лимита
# Увеличить квоту или удалить неиспользуемые рабочие нагрузки

must specify limits.memory — ResourceQuota есть, LimitRange отсутствует. Добавить LimitRange со значениями по умолчанию.

Helm upgrade падает с ошибкой квоты — при rolling update новые поды создаются до удаления старых. Временно увеличить квоту по числу подов или ускорить завершение через terminationGracePeriodSeconds.


Priority classes

Для критических системных рабочих нагрузок, которые должны переживать eviction:

yamlapiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
  name: critical-addons
value: 100000000
preemptionPolicy: PreemptLowerPriority
globalDefault: false
description: "Для критических аддонов кластера"

Применить к подам:

yamlspec:
  priorityClassName: critical-addons

Поды с более высоким приоритетом вытесняются последними при нехватке памяти на ноде.