Multi-tenant Kubernetes
Что такое Multitenancy
Представьте, что вы строите город для ваших приложений. Каждому из них нужно собственное пространство, где оно будет хранить свои данные и жить своей жизнью. На первый взгляд, кажется логичным для каждого приложения построить отдельный коттедж — выделить свой кластер. На первых порах, когда у нас всего несколько приложений, этот подход кажется вполне приемлемым: у каждого приложения свой уютный домик, все изолировано и работает независимо друг от друга.
Но вот количество приложений начинает стремительно расти. И мы начинаем строить коттеджи один за другим. Каждый новый кластер требует не только выделения вычислительных ресурсов, хранилища и сетевой инфраструктуры, но и времени на установку и настройку инфраструктурных компонентов. Более того, возникает и другая проблема: многие из этих «домов» могут оставаться пустыми большую часть времени. Приложения могут иметь низкую нагрузку, и ресурсы, выделенные под эти кластеры, простаивают, а значит, мы просто-напросто расходуем их впустую. Чем больше кластеров, тем сложнее их обслуживать. Использовать подход Cluster-as-a-Service, конечно, можно, но весьма дорого.
Как же быть в таком случае? Как вариант, поселить всех в многоквартирных домах: все приложения живут под одной крышей, пользуются общими ресурсами, при этом у каждого приложения остается своя квартира с личными вещами. Такой подход называется multi-tenancy — несколько клиентов совместно используют общую инфраструктуру, при этом сохраняя изоляцию своих данных, настроек и конфигураций.
Namespace-as-a-Service
Самый простой способ реализовать multi-tenancy в Kubernetes — разделить кластер на пространства имен. Сами по себе пространства имен предлагают лишь логическое разделение. Это как нарисовать мелом линии на полу и сказать: «Вот здесь ваша квартира». Поэтому одних пространств имен multi-tenancy недостаточно. Рассмотрим дополнительные нативные объекты Kubernetes для реализации multi-tenancy.
Сетевая изоляция
Возведем реальные стены и запретим «жильцам» без спроса заходить друг к другу в гости. По умолчанию в кластере отсутствует сетевая изоляция: любой под может общаться с другим подом, даже если они из разных пространств имен. Вот где в игру вступают NetworkPolicies. Они позволяют задавать правила, чтобы контролировать как входящий, так и исходящий трафик на основе меток подов. Для их работы потребуется CNI-плагин, в противном случае ресурсы NetworkPolicy будут игнорироваться.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: deny-all-ingress
namespace: tenant-1
spec:
podSelector: {}
policyTypes:
- Ingress
Тут важно помнить, что Network Policies работают по принципу «запрещено все, что не разрешено явно». А также правила применяются последовательно, поэтому порядок их определения в YAML-файле имеет значение.
Изоляция API
Итак, мы получили что-то вроде квартир без дверей или, точнее, с дверями, но без замков. Поселим в нашем доме консьержа RBAC (Role-Based Access Control) для контроля за тем, кто куда входит и какие действия выполняет. RBAC позволяет создавать роли, которые точно определяют набор разрешений по конкретным ресурсам внутри заданного пространства имен, при помощи Role и связывать роли с конкретными субъектами при помощи RoleBinding.
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
namespace: tenant-1
name: tenant-admin
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: tenant-admin-binding
namespace: tenant-1
subjects:
- kind: ServiceAccount
name: myaccount
apiGroup: ""
roleRef:
kind: Role
name: tenant-admin
apiGroup: ""
Разделение ресурсов
ResourceQuotas
Повесим счетчики, чтобы контролировать потребление ресурсов каждой «квартирой». Решается это с помощью ResourceQuota — объекта, который устанавливает ограничения на совокупное использование ресурсов в определенном пространстве имен.
apiVersion: v1
kind: ResourceQuota
metadata:
name: resource-quota
namespace: tenant-1
spec:
hard:
pods: "10"
cpu: "4"
memory: "8Gi"
LimitRange
LimitRange позволяет задавать ограничение на использование ресурсов каждым отдельным объектом в пространстве имен, предотвращая ситуации, когда один контейнер «заберет» все ресурсы.
apiVersion: v1
kind: LimitRange
metadata:
name: limit-range
namespace: tenant-1
spec:
limits:
- max:
cpu: "500m"
memory: "1Gi"
min:
cpu: "200m"
memory: "512Mi"
type: Container
Стандарты безопасности для подов
Что если в нашем многоквартирном доме кто-то из жильцов случайно или намеренно сломает лифт? Так или иначе это повлияет на всех жителей дома, чего в multi-tenancy видеть бы не хотелось. Установим правила пользования общими ресурсами со стороны жильцов и назначим систему наблюдения за соблюдением стандартов безопасности. Pod Security Admission (PSA) проверяет каждый под на соответствие заданным стандартам безопасности и реагирует на нарушения: блокирует создание, выводит предупреждение или регистрирует событие в аудит-логе.
Итак, вроде дом построили, но некоторые основные компоненты и ресурсы Kubernetes, такие как Kubernetes API и kubelet, используются совместно. Если это проблема, то не спешим возвращаться в отдельные кластеры. Будем использовать виртуальные.
Control plane-as-a-Service
Подход controle plane-as-a-service реализуется через виртуальные кластеры. Как следует из названия, виртуальные кластеры имитируют концепцию виртуальных машин. Для клиента такой кластер выглядит как полнофункциональный: имеет собственный сервер API, менеджер контроллеров, хранилище данных, планировщик. Каждый такой кластер функционирует в изолированной среде, без возможности взаимодействия с другими виртуальными кластерами или с хост-кластером. Одним из популярных решений для реализации виртуальных кластеров является vCluster.
Архитектура vCluster
Как работают компоненты внутри виртуального кластера:
Поды, созданные внутри виртуального кластера, синхронизируются в хост-кластер, но под другими именами, чтобы предотвратить коллизии
Службы так же перезаписываются и создаются на хост-кластере. И виртуальный, и хост-кластер используют для сервисов один и тот же адрес IP
Синхронизация между хост-кластером и ConfigMap’ом и секретами происходит только если те монтированы в поды. Остальные же хранятся исключительно внутри виртуального кластера
Все другие ресурс (CRD, Deployment, StatefulSet и т. п.) с хост-кластером не синхронизируются
Все, что создано внутри vCluster, находится либо внутри самого виртуального кластера, либо внутри пространства имен хоста.
vCluster управляется контроллером (vCluster control plane), который работает как под. Контроллер отвечает за создание и управление подами в виртуальном кластере, а также обработку запросов и взаимодействие с хост-кластером. В зависимости от потребностей vCluster может быть развернут как StatefulSet (если требуется локальное постоянное хранилище), или как Deployment (если локальное постоянное хранилище не требуется, например, при использовании внешней базы данных).
Итоги
Базовый уровень multi-tenancy в Kubernetes — разделение одного кластера на логические пространства имен. Это дешево, но обеспечивает недостаточную изоляцию. Отдельные кластеры, напротив, обеспечивают полную независимость, но требуют значительных ресурсов и усложняют администрирование. Виртуальные кластеры — это «золотая середина» между пространствами имен и выделенными кластерами, объединяющая преимущества обоих решений. Хост-кластер используется лишь как платформа для подов и основных абстракций, тогда как виртуальный кластер обладает всеми правами на ресурсы.