Что такое 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.
Как работают компоненты внутри виртуального кластера:
Поды, созданные внутри виртуального кластера, синхронизируются в хост-кластер, но под другими именами, чтобы предотвратить коллизии
Службы так же перезаписываются и создаются на хост-кластере. И виртуальный, и хост-кластер используют для сервисов один и тот же адрес IP
Синхронизация между хост-кластером и ConfigMap'ом и секретами происходит только если те монтированы в поды. Остальные же хранятся исключительно внутри виртуального кластера
Все другие ресурс (CRD, Deployment, StatefulSet и т. п.) с хост-кластером не синхронизируются
Все, что создано внутри vCluster, находится либо внутри самого виртуального кластера, либо внутри пространства имен хоста.
vCluster управляется контроллером (vCluster control plane), который работает как под. Контроллер отвечает за создание и управление подами в виртуальном кластере, а также обработку запросов и взаимодействие с хост-кластером. В зависимости от потребностей vCluster может быть развернут как StatefulSet (если требуется локальное постоянное хранилище), или как Deployment (если локальное постоянное хранилище не требуется, например, при использовании внешней базы данных).
Создание виртуального кластера с помощью vClustet можно одной командой:
vcluster create my-vcluster
После выполнения команды мы окажемся в виртуальном кластере. Создадим в нем пространство имен и Deployment с Nginx:
kubectl create namespace nginx
kubectl create deployment nginx --image=nginx -n nginx -r 2
kubectl get pods -n nginx
NAME READY STATUS RESTARTS AGE
nginx-76d6c9b8c-j62h7 1/1 Running 0 18s
nginx-76d6c9b8c-57xk5 1/1 Running 0 18s
Вернемся в хост-кластер. "Лишних" пространств имен создано не было:
vcluster disconnect
kubectl get namespaces
NAME STATUS AGE
default Active 25m
kube-public Active 25m
kube-system Active 25m
example Active 20m
Внутри пространства имен виртуального кластера поселились два Deployment, но под другими именами, а также API-сервер my-vcluster-0 и CoreDNS:
kubectl get pods -n example
coredns-56d44fc4b4-kqwmx-x-kube-system-x-my-vcluster 1/1 Running 0 28m
my-vcluster-0 1/1 Running 0 28m
nginx-76d6c9b8c-57xk5-x-nginx-x-my-vcluster 1/1 Running 0 21m
nginx-76d6c9b8c-j62h7-x-nginx-x-my-vcluster 1/1 Running 0 21m
Итоги
Базовый уровень multi-tenancy в Kubernetes – разделение одного кластера на логические пространства имен. Это дешево, но обеспечивает недостаточную изоляцию. Отдельные кластеры, напротив, обеспечивают полную независимость, но требуют значительных ресурсов и усложняют администрирование. Виртуальные кластеры — это «золотая середина» между пространствами имен и выделенными кластерами, объединяющая преимущества обоих решений. Хост-кластер используется лишь как платформа для подов и основных абстракций, тогда как виртуальный кластер обладает всеми правами на ресурсы.