ResourceQuota: namespace-level caps
ResourceQuota — namespaced объект (v1/ResourceQuota), который через admission controller ResourceQuota устанавливает жёсткие верхние границы на суммарные resources внутри namespace. В отличие от LimitRange (который про per-Pod policy), ResourceQuota — про per-namespace lifetime budget: «вся ваша команда не может занять больше 100 CPU и 200Gi памяти в этом namespace, ни на минуту, ни на секунду». Это основной инструмент multi-tenancy и cost-control в Kubernetes-кластерах с несколькими командами.
Формулы планирования ёмкости Kafka
Что можно квотировать
Возможные ключи в spec.hard:
Основные ключи:
spec:
hard:
# compute
requests.cpu: "10" # сумма requests.cpu всех Pods
requests.memory: 20Gi
limits.cpu: "20" # сумма limits.cpu
limits.memory: 40Gi
# ephemeral
requests.ephemeral-storage: 50Gi
limits.ephemeral-storage: 100Gi
# storage
requests.storage: 500Gi # сумма storage в PVC
persistentvolumeclaims: "10" # max количество PVC
fast.storageclass.storage.k8s.io/requests.storage: 100Gi # квота по конкретному StorageClass
# object counts
pods: "50"
services: "20"
services.loadbalancers: "2" # ограничить дорогие LB
services.nodeports: "5"
configmaps: "100"
secrets: "100"
count/jobs.batch: "10" # generic count/<resource>.<group>
count/deployments.apps: "20"
# extended
requests.nvidia.com/gpu: "4"
Как работает enforcement
Admission controller ResourceQuota подключён в kube-apiserver по умолчанию. На каждое создание объекта он:
- Берёт все ResourceQuota в namespace, где создаётся объект.
- Для каждой считает текущее использование (
status.used) из существующих объектов. - Прибавляет к
usedпотребности нового объекта (сколько он добавит к каждому квотируемому ключу). - Если сумма по какому-то ключу превышает
hard— admission rejected.
Error from server (Forbidden): error when creating "pod.yaml":
pods "name" is forbidden: exceeded quota: team-quota,
requested: requests.cpu=2, used: requests.cpu=9, limited: requests.cpu=10
Сообщение точное: называет имя quota, какой ключ превышен, текущее и hard значения. На CKAD это часто встречается — учитесь читать.
Состояние квоты:
kubectl describe quota -n team-a
# Name: team-quota
# Resource Used Hard
# requests.cpu 9 10
# requests.memory 18Gi 20Gi
# pods 47 50
Scopes: квоты не на всё подряд
spec.scopes или spec.scopeSelector позволяют применять quota только к Pods определённого вида:
spec:
scopes:
- BestEffort # квота применяется только к BestEffort Pods
hard:
pods: "5" # не больше 5 BestEffort Pods в namespace
Возможные scopes:
Terminating— Pods сactiveDeadlineSeconds >= 0(Jobs)NotTerminating— Pods безactiveDeadlineSeconds(long-running, Deployment-style)BestEffort— Pods QoS BestEffortNotBestEffort— Pods QoS Burstable или GuaranteedPriorityClass— черезscopeSelector, выбирает Pods конкретногоpriorityClassName
С scopeSelector можно делать сложные комбинации:
spec:
scopeSelector:
matchExpressions:
- scopeName: PriorityClass
operator: In
values: ["high"]
hard:
requests.cpu: "100"
requests.memory: 200Gi
Это значит: «для Pods с priorityClassName: high суммарные requests.cpu не превышают 100». Можно создавать разные квоты для разных priority tiers — типичный паттерн в multi-tenant cluster.
Killer момент: ResourceQuota требует requests/limits
Это важнейшая особенность ResourceQuota, на которой часто валятся на CKAD и в production.
Если в namespace есть ResourceQuota, который ограничивает requests.cpu/requests.memory/limits.cpu/limits.memory, то каждый создаваемый Pod ОБЯЗАН явно указать requests/limits для соответствующих ресурсов.
Ошибка:
Error from server (Forbidden): pods "name" is forbidden:
failed quota: team-quota: must specify limits.cpu for: app; requests.cpu for: app
Решение — связка LimitRange + ResourceQuota:
- LimitRange проставляет defaults на admission (
defaultRequest,default). - ResourceQuota видит Pod уже с заполненными requests/limits и проверяет квоту.
Без LimitRange ResourceQuota делает namespace недружелюбным: команды забывают и получают rejection. С LimitRange всё работает прозрачно — defaults подставляются, квота применяется к итоговым значениям.
# LimitRange (мутирует Pod, заполняет дефолты)
apiVersion: v1
kind: LimitRange
metadata:
name: defaults
namespace: team-a
spec:
limits:
- type: Container
defaultRequest: { cpu: 100m, memory: 128Mi }
default: { cpu: 500m, memory: 256Mi }
---
# ResourceQuota (проверяет суммы)
apiVersion: v1
kind: ResourceQuota
metadata:
name: team-quota
namespace: team-a
spec:
hard:
requests.cpu: "10"
requests.memory: 20Gi
limits.cpu: "20"
limits.memory: 40Gi
pods: "50"
Такой dual-setup — стандартная практика. На CKAD это типовая комбинация задач: «создайте LimitRange … и ResourceQuota … проверьте, что новый Pod без resources создаётся и считается в квоте».
Что произойдёт когда квота заполнена
Когда status.used достигает hard:
-
Новые объекты rejected на admission.
-
Существующие объекты продолжают работать без вмешательства.
-
Owner-controllers (Deployment, StatefulSet, Job) продолжают пытаться создать Pods — это видно в events:
Warning FailedCreate 3m ReplicaSet Error creating: pods "app-xyz" is forbidden: exceeded quota -
Replica controller не отменяет попытки; он повторяет с экспоненциальным backoff.
Это безопасно (cluster не падает), но видно только тому, кто читает events. В production нужны алерты типа kube_resourcequota{type="hard"} - kube_resourcequota{type="used"} == 0 в Prometheus.
ResourceQuota — это hard cap. Когда квота забита, новые Pods не создаются, а existing keep running. Это значит, что rolling update Deployment в namespace на грани квоты может зависнуть: новый ReplicaSet не может создать replicas (квота заполнена старым), а удалить старые тоже нельзя (они защищены min available). Решение — освободить квоту или временно поднять hard.
Object count квоты
Один из самых полезных вариантов — ограничивать счётчики объектов:
spec:
hard:
pods: "50"
secrets: "20"
services.loadbalancers: "2" # ограничить дорогие LBs
services.nodeports: "5"
persistentvolumeclaims: "10"
count/jobs.batch: "100" # max Jobs в namespace
count/cronjobs.batch: "20"
services.loadbalancers — особенно полезно в облаке: каждый Service type=LoadBalancer создаёт реальный cloud LB (~$20/мес и выше). Без квоты команда случайно ставит десяток LBs.
secrets, configmaps — защита от спама. Известный паттерн: Helm chart создаёт sealed-secrets на каждый apply, через год — тысячи объектов и медленный list в kubectl.