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

Мультитенантная изоляция ресурсов в 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 остаётся жёстче на общих узлах.

YAML · namespace арендатора с квотой и LimitRange
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 имя не резолвится, и это ломает всё молча. Перед продакшеном проверяйте политики по наблюдаемым потокам.

YAML · default-deny, ingress и egress на DNS
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-арендаторы остаются на общих пулах с квотами и сетевыми ограничениями.

Bash · taint для выделенного пула арендатора
kubectl label node node-pool-alpha-01 tenant=team-alpha node-pool=alpha-dedicated
kubectl taint nodes node-pool-alpha-01 tenant=team-alpha:NoSchedule
YAML · Deployment на выделенных узлах со spread
apiVersion: 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. Так платформа держит предсказуемую стоимость и защищает нагрузки, чувствительные к задержке.

YAML · silver namespace с более жёсткой квотой
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. Мультитенантность — не одна функция, а квоты, лимиты, сеть, узлы и управление, собранные в предсказуемую производительность и понятную ответственность.

YAML · алерт 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"
YAML · Kyverno Audit на отсутствующие requests
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.