Задача 4. Общий кластер на несколько команд

Одна команда выкатила релиз — легли сервисы соседей, хотя namespace'ы разные. Разбираем, что namespace изолирует, а что нет, и какой минимум ограничений закрывает основные способы навредить соседям.
Опубликовано:

Задача

Кластер делят три команды, у каждой свой namespace. Выкатка одной команды приводит к деградации сервисов двух других: поды соседей начинают вытесняться, часть уходит в Pending.

Разработчики недоумевают: «Мы же в своём namespace, как мы вообще могли задеть чужие сервисы?»

Вопрос: что именно namespace не изолирует и какой минимум ограничений закрывает эту дыру?

Что кажется очевидным

«Namespace — это изоляция. Разные namespace — разные песочницы».

Это самое живучее заблуждение про Kubernetes.

Как это работает на самом деле

Namespace — это область имён и точка приложения политик, а не граница изоляции. Он даёт:

Чего namespace не делает сам по себе:

РесурсИзолирован namespace’ом?
Имена объектов✅ Да
CPU и память нодНет — общий пул на весь кластер
СетьНет — под из A по умолчанию достучится до пода в B
НодыНет — поды разных команд едут на одни и те же ноды
Ядро ОСНет — контейнеры делят ядро ноды
Cluster-scoped объекты❌ Нет — узлы, PV, CRD, ClusterRole общие
etcd и API serverНет — общий control plane

В нашей задаче произошло типичное: команда выкатила Deployment без resources (или с большими значениями) и заняла на нодах столько памяти, что kubelet начал вытеснять чужие поды — приоритетно те, что в BestEffort и Burstable (см. задачу 3). Ноды-то общие.

Ответ

Namespace не изолирует вычислительные ресурсы, сеть и control plane. Сосед по кластеру может навредить как минимум четырьмя способами: съесть ресурсы нод, засыпать API server запросами, достучаться до чужих подов по сети, занять cluster-scoped ресурсы.

Минимальный набор, который это закрывает: ResourceQuota + LimitRange + NetworkPolicy + PriorityClass, а для по-настоящему недоверенных соседей — разные пулы нод.

Что с этим делать

1. ResourceQuota — потолок на namespace

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

YAML
apiVersion: v1
kind: ResourceQuota
metadata:
  name: team-a-quota
  namespace: team-a
spec:
  hard:
    requests.cpu: "20"
    requests.memory: 40Gi
    limits.cpu: "40"
    limits.memory: 80Gi
    persistentvolumeclaims: "20"
    count/deployments.apps: "50"
    pods: "100"
Нажмите, чтобы развернуть и увидеть больше

Важный побочный эффект: как только в namespace появляется квота на requests.cpu/memory, каждый под обязан их указывать, иначе он не будет создан. Это само по себе дисциплинирует.

2. LimitRange — значения по умолчанию

Квота задаёт потолок на namespace, LimitRange — правила для отдельного контейнера:

YAML
apiVersion: v1
kind: LimitRange
metadata:
  name: team-a-limits
  namespace: team-a
spec:
  limits:
    - type: Container
      default: # limits, если не указаны
        cpu: "500m"
        memory: 512Mi
      defaultRequest: # requests, если не указаны
        cpu: "100m"
        memory: 128Mi
      max:
        cpu: "4"
        memory: 8Gi
Нажмите, чтобы развернуть и увидеть больше

Это страховка от «забыли указать ресурсы» и от «один под попросил 200 ГиБ».

3. NetworkPolicy — сеть по умолчанию закрыта

Без политик под из любого namespace достучится до любого пода в кластере. Базовый приём — запретить всё входящее, потом открыть нужное:

YAML
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-ingress
  namespace: team-a
spec:
  podSelector: {}
  policyTypes: [Ingress]
Нажмите, чтобы развернуть и увидеть больше

Помните: NetworkPolicy работает, только если CNI её поддерживает (Calico, Cilium и т. п.). С плагином без поддержки манифест применится и не будет делать ничего — проверьте это явно.

4. PriorityClass — кого вытеснять первым

Чтобы выкатка одной команды не выбивала критичные сервисы другой, задайте приоритеты:

YAML
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
  name: production-critical
value: 1000000
globalDefault: false
description: "Критичные production-сервисы"
Нажмите, чтобы развернуть и увидеть больше

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

5. Разделение нод — когда соседи недоверенные

Всё вышеперечисленное защищает от случайного вреда, но не от намеренного: контейнеры делят ядро ОС, и эскалация из контейнера — реальный класс атак. Если команды не доверяют друг другу (или это внешние арендаторы):

Быстрая проверка

BASH
# Есть ли квоты во всех namespace
kubectl get resourcequota -A

# Namespace без сетевых политик
kubectl get networkpolicy -A

# Поды без requests — потенциальные нарушители
kubectl get pods -A -o json | jq -r '
  .items[] | select(.spec.containers[].resources.requests == null)
  | "\(.metadata.namespace)/\(.metadata.name)"'
Нажмите, чтобы развернуть и увидеть больше

Дальше: Задача 5. OOMKilled: кто и что на самом деле считает.

Начать поиск

Введите ключевые слова для поиска статей

↑↓
ESC
⌘K Горячая клавиша