Представь общий кластер на десяток команд. Один разработчик выкатывает Pod без описания ресурсов, его JVM на ровном месте съедает 12 ГБ, нода уходит в своп, и вместе с виновником с этой ноды вылетают чужие сервисы, которые вели себя прилично. Утром в дежурном чате претензии, а в логах - туман. Знакомо? Это классический результат кластера, где никто не задал правила игры: кто на каком участке живет, сколько кому можно занять и что произойдет при нехватке.
Этот урок про три инструмента, которые превращают хаос в управляемую систему. Namespace - граница, которая делит один физический кластер на логические участки (dev, prod, команды). requests - заявка ресурсов, под которую планировщик резервирует место. limits - жесткий потолок, за который контейнер не пустят. Поверх них - QoS-классы, которые решают, кого убьют первым при дефиците, и квоты, которые не дают одной команде сожрать кластер. Разберем не "что вписать в YAML", а механику под капотом: почему kubernetes namespace это не папка, почему requests limits kubernetes напрямую влияют на то, выживет твой Pod ночью или нет, и где грабли, на которые наступают все.

Namespace - не папка, а граница имен и политик
Namespace в Kubernetes - это область видимости имен (отсюda и название) плюс точка приложения политик. Внутри одного namespace имена ресурсов уникальны: два Deployment с именем api в одном namespace невозможны, а в разных - запросто. Service получает внутреннее DNS-имя вида
Код: Выделить всё
api.production.svc.cluster.localВажно понимать, что namespace - это логическая, а не физическая изоляция. Pod из dev и Pod из prod могут оказаться на одной и той же ноде и по умолчанию свободно общаются по сети - namespace сам по себе не строит сетевой барьер. Чтобы запретить трафик между namespace, нужны NetworkPolicy (на CNI вроде Cilium или Calico). А чтобы развести по разным нодам - taints/affinity. Namespace дает другое: пространство имен, область для RBAC, область для квот и LimitRange.
Не все ресурсы живут в namespace. Есть namespaced-объекты (Pod, Deployment, Service, ConfigMap, Secret, PVC) и cluster-scoped (Node, PersistentVolume, StorageClass, ClusterRole, сами Namespace). Проверить, что где, можно так:
Код: Выделить всё
kubectl api-resources --namespaced=true | head
# NAME SHORTNAMES APIVERSION NAMESPACED KIND
# pods po v1 true Pod
# services svc v1 true Service
# deployments deploy apps/v1 true Deployment
kubectl api-resources --namespaced=false
# nodes, persistentvolumes, storageclasses, clusterroles, namespaces...
Код: Выделить всё
apiVersion: v1
kind: Namespace
metadata:
name: team-billing
labels:
team: billing
env: prod
pod-security.kubernetes.io/enforce: baseline
Код: Выделить всё
pod-security.kubernetes.io/enforceRBAC и квоты: namespace как единица управления
Namespace - естественная единица для раздачи прав. Role действует внутри одного namespace, ClusterRole - на весь кластер. Связка RoleBinding выдает команде права только на ее участок:
Код: Выделить всё
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: team-billing
name: developer
rules:
- apiGroups: ["", "apps"]
resources: ["pods", "deployments", "services", "configmaps"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
requests vs limits: гарантия против потолка
Вот сердце урока. У контейнера есть два числа на каждый ресурс - requests и limits. Это разные сущности, и путать их дорого.
requests - это заявка, на основе которой работает планировщик (kube-scheduler). Когда ты пишешь
Код: Выделить всё
requests.memory: 512Milimits - это жесткий потолок на рантайме, который применяет уже не планировщик, а ядро Linux через cgroups. И тут ключевая асимметрия между CPU и памятью, ее надо понять раз и навсегда.
CPU - сжимаемый ресурс. CPU limit реализуется через CFS-квоту планировщика ядра: контейнеру выдают X микросекунд процессорного времени за каждый период (обычно 100 мс). Уперся в limit - тебя throttling, то есть притормаживают, заставляют ждать следующего периода. Процесс не падает, он просто медленнее. Поэтому слишком тесный CPU limit - это латентность и тормоза на ровном месте, даже когда на ноде полно свободных ядер.
Память - несжимаемый ресурс. Память нельзя "притормозить" - она либо есть, либо нет. Превысил memory limit - ядро через OOM killer убивает процесс. В статусе Pod ты увидишь OOMKilled и reason 137 (это 128 + сигнал 9, SIGKILL). Никаких предупреждений, никакого throttling - мгновенная смерть контейнера. Это грабли номер один: люди ставят memory limit "на глаз", приложение в пике дотягивается до него и падает в самый нагруженный момент.
Код: Выделить всё
resources:
requests:
cpu: "250m" # 0.25 ядра гарантированно
memory: "512Mi" # 512 МБ забронированы
limits:
cpu: "1" # потолок 1 ядро, дальше throttling
memory: "512Mi" # потолок 512 МБ, дальше OOMKill (137)
Код: Выделить всё
250mКод: Выделить всё
1QoS-классы и порядок вытеснения (kubernetes qos)
Kubernetes автоматически присваивает каждому Pod один из трех QoS-классов на основе того, как заданы requests и limits. Ты не пишешь класс руками - он вычисляется. И именно kubernetes qos определяет, кого kubelet убьет первым, когда на ноде кончится память (eviction под node memory pressure).
- Guaranteed - у каждого контейнера в Pod заданы и requests, и limits, и они равны по cpu и по memory. Это самый защищенный класс. Такие Pod кубелет трогает последними.
- Burstable - заданы какие-то requests/limits, но условие равенства не выполнено (например, request меньше limit). Pod может "бурстить" сверх request, пока на ноде есть слабина, но под давлением не защищен.
- BestEffort - не задано вообще ничего, ни requests, ни limits. Полностью бесправный класс.
Код: Выделить всё
kubectl get pod api-7d9f -o jsonpath='{.status.qosClass}'
# Guaranteed
kubectl describe pod api-7d9f | grep -A1 'QoS Class'
# QoS Class: Burstable
LimitRange и ResourceQuota: правила на весь namespace
Два контейнера выше задают ресурсы поштучно. Но в реальном кластере на команды нельзя надеяться, что каждый все пропишет. Тут два namespace-объекта.
LimitRange работает на уровне отдельного контейнера/Pod в namespace: задает дефолты (если разработчик не указал requests/limits, они подставятся автоматически) и границы (min/max, чтобы нельзя было запросить 64 ядра или поставить смешные 1Mi).
Код: Выделить всё
apiVersion: v1
kind: LimitRange
metadata:
name: defaults
namespace: team-billing
spec:
limits:
- type: Container
default: # станет limits, если не задано
cpu: "500m"
memory: "512Mi"
defaultRequest: # станет requests, если не задано
cpu: "100m"
memory: "128Mi"
max:
cpu: "2"
memory: "2Gi"
min:
cpu: "50m"
memory: "64Mi"
ResourceQuota (resourcequota) работает на уровне всего namespace: ограничивает суммарное потребление и количество объектов. Это бюджет команды.
Код: Выделить всё
apiVersion: v1
kind: ResourceQuota
metadata:
name: team-budget
namespace: team-billing
spec:
hard:
requests.cpu: "10"
requests.memory: 20Gi
limits.cpu: "20"
limits.memory: 40Gi
pods: "50"
persistentvolumeclaims: "10"
count/services.loadbalancers: "2"
Код: Выделить всё
kubectl describe resourcequota team-budget -n team-billing
# Resource Used Hard
# -------- ---- ----
# limits.cpu 6 20
# limits.memory 18Gi 40Gi
# pods 23 50
# requests.cpu 3500m 10
# requests.memory 9Gi 20Gi
Типичные грабли и антипаттерны
- Нет requests вообще. Планировщик считает Pod "невесомым" и пихает на любую ноду. В пике все такие Pod дерутся за память, нода уходит в OOM, выселяются соседи. Всегда задавай requests хотя бы примерно.
- Memory limit == request, но впритык по факту. Класс Guaranteed - хорошо, но если limit равен реальному пику, любой всплеск дает OOMKilled 137. Memory нельзя throttle, потолок жесткий. Закладывай запас над наблюдаемым пиком.
- Слишком тесный CPU limit. Сервис тормозит, latency растет, а CPU на ноде свободен - это throttling. Часто CPU limit вообще лучше не ставить (только request), чтобы дать бурстить; многие команды осознанно отказались от CPU limits именно из-за паразитного throttling.
- request сильно больше потребления. Ноды полупустые по факту, но "забронированы" под requests - платишь за воздух. Обратная сторона недобора - это перебор.
- Один limit на всех "чтобы не думать". 4Gi всем подряд: мелочи переплачивают, прожорам не хватает. Размеры индивидуальны.
- Забыли про init/sidecar-контейнеры. Нативные sidecar (1.29+, как init с restartPolicy: Always) тоже потребляют ресурсы и тоже учитываются в requests Pod. Не заложил - получил неверное планирование.
Мини-лаба: повтори руками
- Создай namespace:
Код: Выделить всё
kubectl create namespace lab - Примени к нему LimitRange и ResourceQuota из примеров выше (поменяй namespace на lab).
- Запусти Pod без resources: . Проверь его QoS:
Код: Выделить всё
kubectl run be --image=nginx -n lab- убедись, что LimitRange сделал его Burstable, а не BestEffort, и посмотри, какие requests/limits подставились в describe.Код: Выделить всё
kubectl get pod be -n lab -o jsonpath='{.status.qosClass}' - Создай Pod с равными requests и limits (cpu 200m, memory 256Mi и там, и там) - проверь, что класс Guaranteed.
- Попробуй задать в Pod memory request 100Gi (выше max и выше квоты) - получи и разбери ошибку отказа.
- Глянь расход: - сопоставь Used и Hard.
Код: Выделить всё
kubectl describe resourcequota -n lab - Удали песочницу: - заодно увидишь, как удаление namespace каскадом сносит все внутри.
Код: Выделить всё
kubectl delete namespace lab
- Контейнер уперся в CPU limit и в memory limit - что произойдет в каждом случае и почему поведение разное?
- По какому числу планировщик выбирает ноду - по requests или по фактическому потреблению, и чем грозит сильный перебор requests?
- Какие три условия дают Pod класс Guaranteed, и в каком порядке kubelet выселяет классы при нехватке памяти на ноде?
- Зачем ResourceQuota и LimitRange почти всегда применяют в паре, а не по отдельности?
Namespace режет кластер на участки с собственными правами (RBAC), безопасностью (PSS-лейблы) и бюджетом (ResourceQuota). requests - это бронь под планировщик, limits - жесткий потолок на рантайме: CPU за потолком throttling (медленно), memory за потолком OOMKilled 137 (мгновенно). Из соотношения этих чисел автоматически рождается QoS-класс, который ночью под нагрузкой решает, кого выселят первым. LimitRange подставляет дефолты и поднимает забывчивых из BestEffort, ResourceQuota держит команду в бюджете - вместе они превращают общий кластер в систему, где соседи не роняют друг друга. А right-sizing с in-place resize 1.35 позволяет подгонять цифры по реальным метрикам, не пересоздавая Pod.