Сравнение Policy Engines в Kubernetes
Инженер Mustafa Erdinç Zorba прогнал бенчмарк, который сравнивает три способа управлять admission-политиками в Kubernetes: встроенные ValidatingAdmissionPolicy и MutatingAdmissionPolicy, Kyverno и Gatekeeper. В разборе на своём блоге merdincz.com он прогнал 144 000 dry-run запросов на kind-кластере с Kubernetes 1.36.1 и сравнил задержку, потребление ресурсов и возможности каждого движка.
Краткий ответ
Нативные ValidatingAdmissionPolicy и MutatingAdmissionPolicy выполняются через CEL прямо внутри kube-apiserver, без отдельного вызова вебхука, поэтому в бенчмарке Zorba они добавили наименьшую задержку и потребовали меньше ресурсов, чем Kyverno и Gatekeeper. Gatekeeper держал более стабильный хвост задержки, но заметно дороже базовой линии. Kyverno в части прогонов уходил в насыщение. Выбор между движками зависит от того, нужны ли политике внешние API-вызовы, сканирование существующих ресурсов или генерация объектов.
Что именно сравнивал автор
Zorba задал один policy-контракт и реализовал его тремя способами: добавить аннотацию policy-bench-injected: "true" при мутации, если её ещё нет, и потребовать на валидации аннотацию policy-bench-team: "platform" у подов с меткой policy-bench-subject: "true" в namespace policy-bench. Файлы политик не могут быть текстуально одинаковыми: Kyverno использует свой язык, Gatekeeper собирает Rego и CRD для мутаций, нативные политики написаны на CEL. Поэтому автор проверял семантическую эквивалентность — одинаковый результат на одних и тех же объектах. Перед каждым нагрузочным прогоном он прогонял 180 проверочных запросов, сверяя фактическое решение admission с ожидаемым.
Среда теста: kind 0.32.0 на Ubuntu 24.04.4 LTS (aarch64, 4 ядра Neoverse-N1, 7,5 ГиБ памяти), Kubernetes 1.36.1, Kyverno (Helm-чарт 3.8.2, приложение 1.18.2), Gatekeeper (Helm-чарт 3.23.0). На каждую конфигурацию отправлялось 12 000 валидных dry-run запросов CREATE пода при целевой нагрузке 200 RPS в течение 60 секунд с конкурентностью до 128 клиентов, после 15-секундного разогрева. Все 144 000 измеренных запросов завершились успешно. Dry-run выбран специально: он проходит admission, дефолты и валидацию схемы, но не пишет объект в хранилище, убирая шум от планирования и запуска подов.
Как работает admission control: вебхук против CEL внутри kube-apiserver
Запрос сначала проходит аутентификацию и авторизацию, затем мутирующие контроллеры и только потом валидирующие. Для вебхуков kube-apiserver шлёт AdmissionReview по HTTPS на сервис вебхука, тот возвращает патч или решение allow/reject. Мутирующие вебхуки вызываются последовательно, валидирующие — параллельно; это одна из причин, почему много мутирующих вебхуков дорого обходится.
CEL (Common Expression Language) — язык выражений, на котором Kubernetes вычисляет встроенные политики прямо в API-сервере. У native-политик нет отдельного сетевого прыжка к сервису вебхука и TLS-рукопожатия: MutatingAdmissionPolicy возвращает ApplyConfiguration или JSON Patch, ValidatingAdmissionPolicy — решение валидации, и всё это внутри одного процесса. Как указывает автор блога, официальные рекомендации Kubernetes по хорошим практикам для admission-вебхуков советуют использовать встроенный CEL-контроль, когда он покрывает задачу, и переходить на вебхуки только для сложной или внешней логики.
Результаты по задержке: кто быстрее
По объединённым данным трёх прогонов:
| Конфигурация | p50 | p95 | p99 |
|---|---|---|---|
| Без политик (baseline) | 4,16 мс | 7,60 мс | 14,27 мс |
| Native (Validating/MutatingAdmissionPolicy) | 6,38 мс | 12,77 мс | 24,38 мс |
| Gatekeeper | 8,76 мс | 19,72 мс | 51,83 мс |
| Kyverno | 25,03 мс | 1198,86 мс | 1892,25 мс |
Объединённая таблица скрывает разброс между прогонами. HTTP p95 Gatekeeper держался в районе 18–21 мс во всех трёх прогонах — стабильно. У Kyverno p95 по прогонам составил 66,76 мс, 758,36 мс и 1629,04 мс: в третьем прогоне движок ушёл в насыщение, очередь клиента на p95 достигла 3,454 секунды, а реально достигнутая пропускная способность после дренажа упала до 188,75 запроса в секунду вместо заявленных 200.
Сколько это стоит по CPU и памяти
Автор снимал метрики всего kind-узла раз в секунду: у native-политик нет отдельного контейнера, они работают внутри kube-apiserver. Средние значения под нагрузкой:
| Конфигурация | CPU, ядра (среднее) | Память, МиБ (среднее) |
|---|---|---|
| Baseline | 1,10 | 543,7 |
| Native | 1,44 | 556,95 |
| Gatekeeper | 2,03 | 701,7 |
| Kyverno | 2,66 | 1086,0 |
Для сравнения, контейнеры в namespace Gatekeeper потребляли в среднем 0,632 ядра и 40,2 МиБ, а контейнеры Kyverno — 1,249 ядра и 80,94 МиБ. Для теста автор отключил аудит Gatekeeper и background/cleanup/reporting-контроллеры Kyverno, чтобы измерить именно стоимость admission, а не дополнительных функций.
Чего не умеют нативные admission-политики
Kyverno — open-source policy-движок для Kubernetes с собственным YAML-языком политик, развивается под эгидой CNCF. Gatekeeper реализует admission control поверх Open Policy Agent (OPA), политики пишутся на Rego, проект тоже развивается под эгидой CNCF.
По матрице функций из разбора у native-политик CEL нет произвольных GET/LIST-запросов к API и нет исходящих HTTP-вызовов — только namespaceObject и параметры, выбранные биндингом. Gatekeeper в бета-режиме умеет вызывать внешний HTTPS-провайдер (External Data) и держит реплицированный инвентарь через data.inventory. Kyverno может напрямую ходить в API и по внешним HTTP(S), кэшируя ответы через GlobalContextEntry. Нативные политики работают только в момент admission и не сканируют уже существующие объекты — у Gatekeeper это делает Audit, у Kyverno — background scan. Генерировать, клонировать или синхронизировать сопутствующие ресурсы умеет только Kyverno через GeneratingPolicy, а удалять объекты по расписанию — только его DeletingPolicy. У нативных политик из отчётности есть только предупреждения клиенту и аудит-события Kubernetes, без накопленного реестра соответствия.
Что с этим делать платформенной команде
Сам Zorba давно использует Kyverno именно из-за DeletingPolicy и API-вызовов — для части его политик это обязательное требование, отказаться от которого он не может. Но не все его политики нуждаются во внешних вызовах, и по итогам бенчмарка он планирует переносить такие политики на нативные ValidatingAdmissionPolicy и MutatingAdmissionPolicy постепенно. Прежде чем переводить на них нагруженные окружения с большим числом узлов и подов, он хочет сначала проверить нативные политики на живом кластере рядом со всеми остальными компонентами платформы.
Для политик без внешних вызовов и сканирования существующих ресурсов нативные admission-policies выглядят в этом бенчмарке разумной заменой тяжёлых движков, но у Kyverno и Gatekeeper остаётся набор возможностей — генерация ресурсов, плановая очистка, богатая отчётность, — которые CEL-политики внутри kube-apiserver пока не покрывают. Автор отдельно предупреждает: он не активный пользователь Gatekeeper и собирал его матрицу функций с помощью ИИ-ассистента, поэтому не исключает неточностей именно в этой части и просит сообщать о расхождениях.
FAQ
Что такое ValidatingAdmissionPolicy и MutatingAdmissionPolicy?
Это встроенные в Kubernetes механизмы admission control, которые проверяют и изменяют объекты API на лету с помощью выражений CEL. По данным автора блога merdincz.com, ValidatingAdmissionPolicy стала общедоступной (GA) начиная с Kubernetes 1.30, а MutatingAdmissionPolicy — начиная с Kubernetes 1.36.
Чем нативные admission-политики отличаются от Kyverno и Gatekeeper по механике?
Нативные политики выполняются CEL-выражениями прямо внутри kube-apiserver, без отдельного сетевого вызова. Kyverno и Gatekeeper работают как внешние вебхуки: kube-apiserver отправляет им AdmissionReview по HTTPS и ждёт ответа, что добавляет сетевой прыжок и TLS-обработку на каждый запрос.
Можно ли полностью заменить Kyverno нативными admission-политиками?
По итогам бенчмарка — нет, не полностью. Нативные политики не умеют делать произвольные внешние HTTP-вызовы, сканировать уже существующие ресурсы, генерировать и клонировать объекты или удалять их по расписанию — всё это есть в Kyverno. Автор сам планирует переносить только те политики, которым эти возможности не нужны.
Какие данные легли в основу бенчмарка?
Автор прогнал 144 000 dry-run запросов на создание подов (36 000 на каждую из четырёх конфигураций: без политик, native, Gatekeeper, Kyverno) на kind-кластере с Kubernetes 1.36.1, измеряя HTTP-задержку, задержку очереди клиента и потребление CPU и памяти всего узла.
Насколько репрезентативны эти результаты для продакшен-кластера?
Это микробенчмарк на одном control-plane узле kind с dry-run запросами, без записи объектов в хранилище, планирования и запуска подов. Сам автор указывает, что перед переносом политик на живые кластеры хочет проверить нативные admission-policies под реальной нагрузкой вместе с остальными компонентами платформы.