Мы постоянно используем Cloud-native технологии, и запускаем системы в контейнерах на платформе Kubernetes. Эта технология отлично подходит для оркестрации контейнерных нагрузок благодаря гибкости и установке виртуальных машин прямо на железо (bare metal). Когда-то Kubernetes подходил только для простых рабочих нагрузок без сохранения состояния, теперь там стало возможным хранить базы данных, обучать машины и развертывать сложные приложения.

С тех пор, как Amazon EKS стал бесплатным в 2018 году, его чаще всего выбирают для запуска рабочих нагрузок Kubernetes на AWS. Размещать Kubernetes на собственных ресурсах дорого, сложно и в целом, с точки зрения бизнеса, сомнительно. Лучше управлять рабочими нагрузками и сервисами, например, с помощью Amazon EKS.

Amazon EKS отлично подходит для «мультитенантного» (multitenant) сервиса благодаря поддержке слоев оркестровки Kubernetes. Можно выполнять различные рабочие нагрузки на одном сервере. Это повысит плотность инстансов Amazon EC2. Сложности могут возникнуть с реализацией безопасности входа и multitenant-данных при запуске SaaS-приложений. Каждому, кто будет использовать Amazon EKS для SaaS, может пригодиться несколько полезных приемов.

Скрытый текст
Мы в компании сознательно не используем перевод термина multitenant, предложенный Википедией, так как считаем, что он не отображает в полной мере всей специфики архитектуры и ее составляющих.

Что нужно учесть при реализации multitenant кластера Amazon EKS


  • Для каждого tenant (он же «арендатор» приложения) нужно свое пространство имен
  • Создавать нужную изоляцию и взаимосвязи придется самостоятельно
  • Для операций с кластером используют AWS IAM и управление доступом
  • Через учетные записи AWS IAM можно управлять доступом к рабочим нагрузкам
  • Баланс доступа к сети между пространствами имен и tenant контролируют с помощью настройки сетевой политики объектов
  • Политика безопасности объектов для pod поможет следить за доступом к хостам Amazon EC2 и общим данным.

Для каждого tenant нужно свое пространство имен


Любая система подвержена уязвимостям и сбоям, особенно на серьезных нагрузках. Для того что бы уменьшить объем поражения системы, в случае возникновения проблемы, правильным было бы разделить систему логическими границами. Чтобы создать логические границы, разделяющие tenant в multitenant EKS кластере, используют пространство имен. Границы в дальнейшем дополняют настройками политик и безопасности, например, управлением доступом на основе ролей (RBAC) и квотами ресурсов. В итоге контактировать друг с другом будут только ресурсы с одинаковым пространством имен, а доступ извне будет контролироваться разрешениями.

Скрытый текст
Принцип взаимодействия похож на то, как защищают ресурсы в разных учетных записях AWS — tenant с разными пространствами имен никак не пересекаются и не влияют друг на друга.

Мягкая изоляция через ResourceQuota или как не позволить tenant оттягивать на себя ресурсы?


Пространство имен применяют не только для изоляции. Также для того, чтобы честно распределять возможности ресурсов, таких как CPU, память, хранилище. Что бы не монополизировать ресурсы для конкретных рабочих нагрузок в определенных namespace, может помочь ResourceQuota и метод мягкой изоляции. Объект ResourceQuota используют для ограничения общего потребления ресурсов в пространстве имен. Это работает и для tenant в соотношении маппинга 1:1 — namespace/tenant. Чтобы убедиться, что ни один контейнер не монополизирует ресурсы, лучше написать объект LimitRange. А доступные ресурсы назначать через глобально определенные PriorityClass-объекты, на основе приоритета рабочих нагрузок.

Скрытый текст
ResourceQuota также пригодится, если нужно назначить приоритеты для обычных и исключительных tenant в соответствии с соглашением между клиентом и SaaS-провайдером.

Жёсткая изоляция или 1:1 маппинг групп инстансов и tenant — что лучше?


Поды для множественных tenant могут делить одни и те же инстансы Amazon EC2, которые, в свою очередь, работают как узлы в одинаковых Amazon EKS кластерах. Пример, Soft Multitenant EKS, когда несколько tenant делят один пул рабочих узлов представлен ниже на картинке.


Если нужна большая автономность, например, если требуется полностью разделить группы узлов Amazon EC2, можно применить механизмы taint, tolerations и nodeSelector. Рассмотрим в качестве примера некоторое количество узлов, которые должны выполнять рабочие нагрузки только от tenant A. Прежде всего нужно привязать к этим узлам пару ключ/значение и назначить label.

kubectl taint nodes node1 tenant=A:NoSchedule 
kubectl label nodes node1 tenant=A

В результате узел сможет запустить только под с парой ключ/значение tenant A. Значение label помогает рабочим нагрузкам tenant A определить свое место. Значения taint и label похожи, но предназначены для разных целей.

Скрытый текст
Дополнительно нужно прописать toleration к нашему taint:
tolerations:
  - key: “tenant”
	value: “A”
	effect: “NoSchedule”


С помощью уже готовых labels подключим nodeSelector — это гарантирует выполнение рабочей нагрузки исключительно на выделенных узлах.

nodeSelector:
  tenant: A

С помощью tolerations и taints рабочие нагрузки namespaces/tenant A (или любых других tenant) можно связать только с нужными нам узлами. В результате рабочие нагрузки распределяются по разным группам узлов, зачастую автомасштабируемым, и контролируются из одной точки. (Благодаря такой тактике очень легко планировать бюджет на tenant — счета по этой статье расходов будут промаркированы) Пример, Hard Multitenant EKS, когда каждый tenant имеет выделенный пул рабочих узлов представлен ниже на картинке.


Вышеописанный эталонный сценарий жесткой изоляции посредством разделения узлов для tenant не может считаться окончательным без внедрения дополнительных мер безопасности, которые предотвратят злоупотребление рабочими нагрузками в группах узлов другими tenant. Как это реализовать? Мы используем ValidatingAdmissionWebhook. Это проект Open Policy Agent (OPA) от CNCF.

Скрытый текст
Руководство по запуску OPA на Amazon EKS можно найти в блоге AWS.

Интеграция Amazon IAM в кластер AWS EKS для контроля доступа


Как было сказано выше, описанный сценарий жесткой изоляции не может считаться окончательным без внедрения мер безопасности. Первое, что нужно сделать именно для multitenant кластера — это сделать настройку доступа, которую можно интегрировать с Amazon IAM.

Работая с RBAC в Kubernetes надо понять разницу между кластерными и обычными ролями. С одной стороны, кластерная роль понятная и логичная. К ней часто обращаются новички Kubernetes, ведь назначить роль админа кластера — это одна из самых простых операций. Когда подключается multitenant, все становится сложнее. Например, если объекту нужен доступ к пространству имен tenant, нужно назначать соответствующие namespace-роли. С другой стороны, допускается и установка одинаковых ролей для каждого tenant пространства имен. Оба способа жизнеспособны, все зависит от ваших личных предпочтений.

Скрытый текст
AWS IAM очень просто маппировать в роль EKS кластера. Есть четкая и понятная документация от AWS, в ней все детально разобрано.

Как обеспечить доступ к ресурсам AWS с помощью интеграции AWS IAM в рабочие нагрузки EKS


Роли AWS IAM можно использовать не только для управления объектами в кластере Amazon EKS, но и для доступа рабочих нагрузок к AWS. Чтобы обеспечить безопасность, недостаточно просто применить роли AWS IAM к базовым инстансам Amazon EC2. Нужно поработать над разрешениями действий для узлов, в частности, над теми, которые обеспечивают доступ всех возможных рабочих нагрузок на всех tenant.

В пуле рабочих узлов, которые могут выполнять multitenant нагрузки, невозможно настроить IAM роли в Amazon EC2 инстансах для tenant. Даже если это будет однотенантный кластер, роли IAM на узлах Amazon EC2 нельзя сделать безопасными, так как потребуется просто огромное количество разнообразных разрешений.

Скрытый текст
C сентября 2019 в AWS IAM добавлена поддержка рабочих нагрузок EKS на уровне пода.

Как это реализовать:

  1. Маппировать учетные записи пространства имен в роли AWS IAM;
  2. Связать учетные записи с нужными подами.

В итоге можно не только управлять доступом к ресурсам AWS с помощью рабочих нагрузок EKS кластера, но и ограничить их доступ только теми ресурсами, которые являются tenant.

Скрытый текст
Как пример. Часто в контейнерах Amazon S3 хранят постоянные данные, там же можно хранить данные tenant. Если присвоить каждому поду в EKS кластере разрешения IAM, это пресечет непреднамеренный доступ к данным разных tenant в одном контейнере. Каждый под будет иметь доступ исключительно к своему tenant. Детальнее можно прочитать об этом здесь.

Управление связями через сетевые настройки


Сетевые настройки управляют входящими и исходящими разрешениями на основании множественных критерий. Для multitenant EKS, tenant маппируют в пространство имен. Для ограничения обмена данными между namespaces и pods в одинаковых пространствах имен используют значения namespaceSelector и podSelector. При этом подразумевается, что namespace для tenant A уже настроили и связали с label.

kubectl label namespace/tenant-a tenant=a

Код ниже показывает, как единственный трафик общего пространства имен tenant получает под с labels app:api благодаря сетевым настройкам.

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: same-namespace-only
  namespace: tenant-a
spec:
  podSelector:
	matchLabels:
  	app: api
  policyTypes:
  - ingress
  - egress
  ingress:
  - from:
	- namespaceSelector:
    	matchLabels:
      	tenant: a
  egress:
  - to:
	- namespaceSelector:
    	matchLabels:
      	tenant: a

Изначально PodSecurityPolicy предназначен для ограничения доступа к базовому инстансу EC2 в кластере Amazon EKS, но его можно применить и для ограничения совместных ресурсов в multitenant кластере инстанса EC2.

Скрытый текст
Если вы не используете PodSecurityPolicy, то получится вот так:

apiVersion: policy/v1beta1
kind: PodSecurityPolicy
metadata:
  name: privileged
  annotations:
    seccomp.security.alpha.kubernetes.io/allowedProfileNames: '*'
spec:
  privileged: true
  allowPrivilegeEscalation: true
  allowedCapabilities:
  - '*'
  volumes:
  - '*'
  hostNetwork: true
  hostPorts:
  - min: 0
	max: 65535
  hostIPC: true
  hostPID: true
  runAsUser:
	rule: 'RunAsAny'
  seLinux:
	rule: 'RunAsAny'
  supplementalGroups:
	rule: 'RunAsAny'
  fsGroup:
	rule: 'RunAsAny'


Достаточно понаблюдать за всеми разрешениями томов, чтобы понять, какие проблемы безопасности считаются самыми важными. Если pods множественных namespaces/tenants делят базовые ресурсы инстансов Amazon EC2, нужно обязательно закрывать доступ к pods из хоста. В противном случае, велика вероятность непреднамеренно раскрыть данные между tenant, если эти данные хранятся или кешируются в связанных папках.

Заключение


В Amazon EKS много полезных вещей для управления постоянными данными. Кроме того, на нем все чаще размещают multitenant сервисы. Мы, команда OpsGuru, считаем важным уметь правильно разграничивать ресурсы и следить за безопасностью. Только так можно обеспечить корректную работу всей системы.