Оптимизация памяти в OPA:
переход на Kyverno для масштабирования Kubernetes

Многопользовательским кластерам Kubernetes нужны высокие требования к управлению политиками, чтобы гарантировать безопасность, соответствие нормативам и эффективное распределение ресурсов между всеми арендаторами.

Долгое время Open Policy Agent (OPA) служил основой политики безопасности платформы Kubernetes SCHIP, даже во время начального этапа перехода на Kyverno.

Однако по мере роста числа кластеров и арендаторов команда столкнулась с вызовами управления памятью, что подтолкнуло изменить подход.

В этой статье расскажем про функцию синхронизации данных в OPA, её влияние на потребление памяти и уроки, извлечённые из миграции на Kyverno. Сможете принимать более взвешенные решения о масштабировании и управлении политиками.
Ситуация
При развертывании множества кластеров и арендаторов стало практически невозможно создавать политики, не учитывая состояние других объектов.

OPA предлагает мощную функцию синхронизации внешних данных, что позволяет использовать дополнительный контекст из различных ресурсов Kubernetes. Однако активация этой функции увеличивает нагрузку на ресурсы, особенно в плане потребления памяти.

Цель — не отговорить вас от использования продвинутых функций OPA, но показать важность последствий.
Простая политика OPA
Простая политика OPA может работать с контекстом, предоставленным самим проверяемым объектом. Ниже приведён пример политики, которая проверяет поле host в объекте ingress, подлежащем валидации:
package policy.ingress_without_host

violation[{"msg": msg}] {
   ingress := input.review.object.spec.rules[_]
   not ingress.host
   msg := "Недопустимый ingress. Пожалуйста, добавьте host к ingress"
}
Однако реальные политики часто требуют доступа к другим объектам в кластере. Например, проверка уникальности метки среди всех подов и пространств имён невозможна, если политика не видит эти ресурсы.
Синхронизация OPA для сложных политик
Чтобы решить эту задачу, Open Policy Agent (OPA) позволяет синхронизировать данные объектов в клиент, чтобы ConstraintTemplates могли их использовать.

Gatekeeper предоставляет способ включения этой функции через конфигурацию синхронизации, как показано ниже:
apiVersion: config.gatekeeper.sh/v1alpha1
kind: Config
metadata:
  name: config
spec:
  sync:
    syncOnly:
      - group: ""
        version: "v1"
        kind: "Namespace"
      - group: ""
        version: "v1"
        kind: "Pod"
Также можно использовать SyncSet для достижения аналогичных результатов. После активации политики могут ссылаться на синхронизированные данные с помощью data.inventory.

Например, можно получить доступ к синхронизированной информации через data.inventory следующим образом:
data.inventory.namespace[ns][_]["RoleBinding"]
Эта функция чрезвычайно полезна и хорошо работала, пока команда не столкнулась с проблемами производительности.
Проблемы производительности
OPA стала тормозить, появились случайные OOMKills (Out-Of-Memory Kills) в нескольких кластерах. Хотя VerticalPodAutoscaler (VPA) помогал динамически корректировать распределение ресурсов, уведомления стали слишком шумными и мешали работе.

Расследование скачков использования памяти

При более детальном анализе обнаружили, что кластеры с высокой изменчивостью подов — где количество подов быстро увеличивается и уменьшается — были основными источниками проблем. Это типично для:

  • Кластеров с частыми деплоями, особенно полным перезапуском кластера.
  • Кластеров с большим количеством CronJobs.
Корреляция с использованием памяти

Команда наблюдала значительные колебания использования памяти в подах Gatekeeper-controller, часто на несколько гигабайт. По причине, что синхронизировали данные подов в инвентарь Gatekeeper, и это приводило к чрезмерному потреблению памяти при резком увеличении числа подов.
Как видно на графике, использование памяти колеблется в пределах гигабайт из-за изменений в количестве подов.
В нашем случае это связано с использованием информации о подах в инвентаре:
apiVersion: config.gatekeeper.sh/v1alpha1
kind: Config
metadata:
  name: config
spec:
  sync:
    syncOnly:
      - group: ""
        kind: Namespace
        version: v1
      - group: "networking.k8s.io"
        version: "v1"
        kind: "Ingress"
      - group: ""
        kind: Pod
        version: v1
      - group: "rbac.authorization.k8s.io"
        version: "v1"
        kind: "RoleBinding"
Оптимизация миграции для повышения эффективности
Учитывая ограничения, решили сначала перенести политики, использующие data.inventory, особенно те, что работают с объектами с большим объёмом данных, такими как поды.

Результаты миграции

После миграции политик, использующих data.inventory подов на Kyverno, команда исключила поды из конфигурации синхронизации Gatekeeper. Это сократило использование памяти с 8 ГБ до 2.7 ГБ — всего одной конфигурационной правкой в одном кластере.

Это значительный результат в среде с более чем 30 кластерами, каждый из которых запускает 3+ пода контроллера.
Удаляем Pod из конфигурации синхронизации
Использование памяти сократилось с 8 ГБ до 2,7 ГБ
Влияние на Kyverno
Kyverno не полагается на предварительно синхронизированный инвентарь, а вместо этого динамически извлекает данные через вызовы API.

Пример того, как Kyverno получает информацию о подах:
context:
  - name: pods-access
    apiCall:
      urlPath: "/api/v1/namespaces/{{request.object.metadata.name}}/pods"
      jmesPath: "items[? !starts_with(metadata.name, 'system')].metadata.name"
Баланс между памятью и нагрузкой на API
Этот подход снижает потребление памяти, но увеличивает нагрузку на сервер API Kubernetes. Чтобы смягчить это:

  • Мониторинг нагрузки на сервер API: убедитесь, что дополнительные запросы не перегружают сервер API
  • Использование специфичных предусловий: сократите ненужные проверки
preconditions:
- key: "{{ request.operation }}"
  operator: Equals
  value: "DELETE"
Итоговые мысли
Эта статья не ставит целью критиковать функцию синхронизации инвентаря Gatekeeper, которая остаётся чрезвычайно полезной. Однако важно проявлять осторожность при её включении, так как чрезмерное потребление памяти может повлиять на стабильность Gatekeeper — вплоть до блокировки новых деплоев и создания подов.

Применяя описанный подход, можно добиться значительного сокращения использования памяти, улучшить стабильность и снизить операционный шум — без ущерба для соблюдения политик.
Ответим на все вопросы по переносу, сборке, оркестрации и развертывании.
Настроим ваш Kubernetes