изолировать нагрузки арендаторов квотами и сетевыми правилами
Мультитенантная изоляция ресурсов в Kubernetes: как остановить «шумного соседа»
14 минут
Одна пакетная задача с неограниченными requests может задушить чувствительные к задержке API на общем кластере. Сложите tiered namespace, ResourceQuota, LimitRange, default-deny NetworkPolicy и при необходимости выделенные пулы узлов — с метками затрат для финансов.
Почему общему кластеру нужна многослойная изоляция арендаторов
В кластере, которым пользуются несколько продуктовых команд, проблема «шумного соседа» не исчезает: одна нагрузка съедает планируемую ёмкость, вызывает эвикцию подов из-за давления на узел, дросселирует CPU чужих подов и каскадом ломает зависимости сервисов. Команда с пакетной обработкой, запускающая сотни подов без лимитов ресурсов, может нарушить SLO API-команды на тех же узлах. Без изоляции получаете срывы SLA, непредсказуемые расходы и горизонтальное перемещение между namespace. В Kubernetes есть ResourceQuota, LimitRange, NetworkPolicy и размещение на узлах — но чаще их включают по отдельности. Для продакшена нужна составная модель: потолки ресурсов, сетевые границы, выделенные узлы для gold-уровня и метки, связывающие потребление с владельцами.
Четыре слоя: tiered namespace, квоты, сеть и размещение на узлах
Изоляцию собирайте из четырёх слоёв. Tiered namespace — отдельное пространство имён на арендатора и окружение (team-alpha-prod, team-alpha-dev) с общими метками tenant, tier и cost-center вместо плоской структуры. ResourceQuota и LimitRange ограничивают суммарное потребление арендатора и каждый контейнер, даже если разработчик забыл указать requests. NetworkPolicy задаёт default-deny для ingress и egress с явными разрешениями для ingress-контроллера, одобренных соседей и DNS. Размещение на узлах выделяет пулы для приоритетных арендаторов через taint, toleration и topology spread — gold-уровень не делит CPU с batch-соседями. ResourceQuota действует на namespace; иерархия операционная — единые имена, метки и уровни квот в Git, а не автоматическое наследование, пока вы не внедрите HNC или vCluster на больших площадках.
Namespace арендатора с ResourceQuota и LimitRange
Метите каждый namespace арендатора полями tenant, tier, cost-center и environment. Задавайте суммарные квоты CPU, памяти, подов, хранилища и числа объектов на namespace. Дополняйте квоты LimitRange с defaults и max — один под не должен запрашивать четыре CPU только потому, что «так работало в staging». Gold-уровень получает более высокие потолки; silver остаётся жёстче на общих узлах.
apiVersion: v1
kind: Namespace
metadata:
name: team-alpha-prod
labels:
tenant: team-alpha
tier: gold
cost-center: engineering
environment: production
pod-security.kubernetes.io/enforce: baseline
---
apiVersion: v1
kind: ResourceQuota
metadata:
name: team-alpha-quota
namespace: team-alpha-prod
spec:
hard:
requests.cpu: "30"
requests.memory: 60Gi
limits.cpu: "60"
limits.memory: 120Gi
pods: "100"
services.loadbalancers: "2"
persistentvolumeclaims: "10"
requests.storage: 500Gi
---
apiVersion: v1
kind: LimitRange
metadata:
name: team-alpha-defaults
namespace: team-alpha-prod
spec:
limits:
- type: Container
default:
cpu: "500m"
memory: 512Mi
defaultRequest:
cpu: "100m"
memory: 128Mi
max:
cpu: "4"
memory: 8Gi
maxLimitRequestRatio:
cpu: "10"
memory: "4"Сегментация арендаторов через NetworkPolicy
В каждом tenant namespace начинайте с default-deny для ingress и egress. Разрешайте north-south трафик только от namespace ingress-контроллера. East-west — только к явно одобренным соседям, без неявной полной связности между арендаторами. Обязательно добавьте egress на kube-dns — без этого при deny-all имя не резолвится, и это ломает всё молча. Перед продакшеном проверяйте политики по наблюдаемым потокам.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
namespace: team-alpha-prod
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-ingress-controller
namespace: team-alpha-prod
spec:
podSelector: {}
policyTypes:
- Ingress
ingress:
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: ingress-nginx
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-dns
namespace: team-alpha-prod
spec:
podSelector: {}
policyTypes:
- Egress
egress:
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
podSelector:
matchLabels:
k8s-app: kube-dns
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53Выделенные пулы узлов для gold-арендаторов
Для жёсткой изоляции по производительности выделите пулы узлов приоритетным арендаторам. Пометьте узлы, добавьте taint NoSchedule и требуйте matching tolerations в Deployment арендатора. Добавьте topologySpreadConstraints, чтобы реплики распределялись по узлам пула, а не скапливались на одном «шумном» хосте. Silver-арендаторы остаются на общих пулах с квотами и сетевыми ограничениями.
kubectl label node node-pool-alpha-01 tenant=team-alpha node-pool=alpha-dedicated
kubectl taint nodes node-pool-alpha-01 tenant=team-alpha:NoScheduleapiVersion: apps/v1
kind: Deployment
metadata:
name: alpha-api
namespace: team-alpha-prod
spec:
replicas: 3
selector:
matchLabels:
app: alpha-api
template:
metadata:
labels:
app: alpha-api
tenant: team-alpha
spec:
nodeSelector:
tenant: team-alpha
tolerations:
- key: tenant
operator: Equal
value: team-alpha
effect: NoSchedule
topologySpreadConstraints:
- maxSkew: 1
topologyKey: kubernetes.io/hostname
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
app: alpha-api
containers:
- name: api
image: registry.example.com/alpha-api:v1.4.0
resources:
requests:
cpu: 500m
memory: 512Mi
limits:
cpu: "2"
memory: 2GiПример двух арендаторов: gold на выделенных узлах и silver на общих
Team Alpha на gold-уровне с более высокими квотами и выделенными узлами. Team Beta на silver с меньшими квотами на общем пуле. У обоих default-deny в сети и LimitRange; taint и spread — только у Alpha. Так платформа держит предсказуемую стоимость и защищает нагрузки, чувствительные к задержке.
apiVersion: v1
kind: Namespace
metadata:
name: team-beta-prod
labels:
tenant: team-beta
tier: silver
cost-center: engineering
environment: production
---
apiVersion: v1
kind: ResourceQuota
metadata:
name: team-beta-quota
namespace: team-beta-prod
spec:
hard:
requests.cpu: "10"
requests.memory: 20Gi
limits.cpu: "20"
limits.memory: 40Gi
pods: "30"
---
apiVersion: v1
kind: LimitRange
metadata:
name: team-beta-defaults
namespace: team-beta-prod
spec:
limits:
- type: Container
default:
cpu: 500m
memory: 512Mi
defaultRequest:
cpu: 100m
memory: 128Mi
max:
cpu: "2"
memory: 4GiПрактика: сначала наблюдение, затем принуждение и ежеквартальный пересмотр
Две–четыре недели наблюдайте потребление перед ужесточением квот — неожиданные OOMKill подрывают доверие разработчиков. Kyverno или Gatekeeper в режиме Audit ловят отсутствующие requests и привилегированные поды до жёсткого enforcement. Pod Security Standards на уровне namespace: baseline для большинства арендаторов, restricted для чувствительных нагрузок. Алертируйте при использовании квоты выше восьмидесяти процентов. Метки cost-center и tenant на каждом namespace — для showback в OpenCost или Kubecost. Для mTLS и контроля на уровне пути дополняйте NetworkPolicy L3/L4 политиками Cilium или service mesh. Раз в квартал пересматривайте уровни квот по истории Prometheus. Мультитенантность — не одна функция, а квоты, лимиты, сеть, узлы и управление, собранные в предсказуемую производительность и понятную ответственность.
groups:
- name: tenant-quota
rules:
- alert: TenantQuotaNearLimit
expr: |
sum by (namespace, resource) (
kube_resourcequota{type="used"}
)
/
sum by (namespace, resource) (
kube_resourcequota{type="hard"}
) > 0.8
for: 15m
labels:
severity: warning
annotations:
summary: "Namespace {{ $labels.namespace }} above 80% of {{ $labels.resource }} quota"apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: audit-container-resource-requests
spec:
validationFailureAction: Audit
background: true
rules:
- name: require-cpu-memory-requests
match:
any:
- resources:
kinds: [Pod]
validate:
message: "Containers must define CPU and memory requests."
pattern:
spec:
containers:
- resources:
requests:
cpu: "?*"
memory: "?*"Бюджеты namespace и метки cost-center продолжают подход из гайда по FinOps на уровне namespace с ResourceQuota и атрибуцией затрат.
Сегментация default-deny и политики на основе идентичности — в гайде по zero-trust сети в Kubernetes с Cilium.
