Представь типичную картину. В кластере десяток команд, у каждой свой namespace, свой CI, свои привычки. Кто-то катит образ с тегом latest, кто-то забыл прописать resources.requests, кто-то на проде поднял Pod с privileged: true "чтобы быстро отладить" и забыл выключить. Код-ревью это не ловит - YAML огромный, глаз замыливается. SecurityContext проверяет один человек по пятницам. В итоге кластер превращается в проходной двор, а ты узнаешь о privileged-контейнере только когда из него вылезли в host.
Ровно эту дыру закрывает policy as code kubernetes - кластерные политики как код. Идея простая до гениальности: ты один раз описываешь правила ("в проде нельзя latest", "у всех Pod обязаны быть requests", "образы только из нашего реестра") в виде манифестов, кладёшь их в Git рядом с остальной инфраструктурой, и кластер сам отклоняет всё, что нарушает. Не человек на ревью, а admission-контроллер на входе в API-сервер. Машина не устаёт, не делает исключений по пятницам и не пропускает privileged.
В 2026 это уже не экзотика, а гигиенический минимум зрелого кластера. Два игрока делят рынок: kyverno (политики прямо в YAML, k8s-нативно, проще порог входа) и opa gatekeeper (язык Rego, мощнее, но сложнее). Разберём оба под капотом, сравним честно и покажем, как внедрить без того, чтобы в первый же день положить прод.

Механика: где именно живут эти kubernetes политики
Чтобы понять, как работает policy as code, нужно вспомнить путь запроса в Kubernetes. Когда ты делаешь kubectl apply, манифест летит в kube-apiserver и проходит конвейер: аутентификация -> авторизация (RBAC) -> admission-контроллеры -> запись в etcd. Admission - это последний шлагбаум перед сохранением. И он делится на две фазы.
- Mutating admission - может изменить объект на лету. Дописать недостающую метку, навесить sidecar, проставить дефолтный securityContext.
- Validating admission - может только сказать "да" или "нет". Если объект нарушает правило - запрос отклоняется с ошибкой, и в etcd ничего не попадает.
Второй режим работы - background scan (фоновое сканирование, оно же audit). Webhook ловит только новые и изменённые объекты. А что с тем, что уже лежит в кластере? Для этого оба инструмента периодически проходят по существующим ресурсам и пишут отчёты о нарушениях, ничего не ломая. Это и есть основа безопасного внедрения - сначала смотрим, кто нарушает, потом включаем блокировку.
Kyverno: политики на чистом YAML без нового языка
Главная фишка Kyverno в том, что политика - это обычный Kubernetes-ресурс. Никакого отдельного языка: ты описываешь правило теми же match/pattern, что и манифесты. Базовый объект - ClusterPolicy (на весь кластер) или Policy (в рамках namespace), apiVersion kyverno.io/v1. У Kyverno четыре типа действий: validate (проверить), mutate (изменить), generate (создать сопутствующий ресурс) и cleanup (удалить по расписанию).
Вот политика, запрещающая тег latest - классика жанра. Разберём по полям.
Код: Выделить всё
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: disallow-latest-tag
spec:
validationFailureAction: Enforce
background: true
rules:
- name: require-image-tag
match:
any:
- resources:
kinds:
- Pod
validate:
message: "Тег образа обязателен, latest запрещен"
pattern:
spec:
containers:
- image: "!*:latest"
Теперь mutate - где Kyverno сам чинит объект. Допустим, хотим всем Pod без метки team навешивать team: unassigned, чтобы потом не было сирот.
Код: Выделить всё
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: add-default-team-label
spec:
rules:
- name: add-team
match:
any:
- resources:
kinds:
- Pod
mutate:
patchStrategicMerge:
metadata:
labels:
+(team): unassigned
Применяем и смотрим, что отвечает кластер на нарушителя:
Код: Выделить всё
$ kubectl run bad --image=nginx:latest
Error from server: admission webhook "validate.kyverno.svc-fail" denied the request:
resource Pod/default/bad was blocked due to the following policies
disallow-latest-tag:
require-image-tag: 'validation error: Тег образа обязателен, latest запрещен.
rule require-image-tag failed at path /spec/containers/0/image/'
Отдельно стоит знать про новое поколение Kyverno. В свежих версиях (актуальная linия - 1.18.x на середину 2026) появились отдельные CRD ValidatingPolicy и MutatingPolicy с apiVersion policies.kyverno.io/v1alpha1 - они построены на CEL и сближают Kyverno с нативным механизмом ValidatingAdmissionPolicy самого Kubernetes. Старые ClusterPolicy при этом никуда не делись и остаются основным рабочим инструментом. Если видишь оба синтаксиса в чужих репозиториях - не пугайся, это переходный период.
OPA Gatekeeper: Rego, ConstraintTemplate и Constraint
Gatekeeper - это проекция проекта Open Policy Agent (OPA) на Kubernetes. Здесь логика политики пишется на Rego - декларативном языке запросов, заточенном под "достать из объекта данные и проверить условие". Rego мощнее YAML-паттернов: в нём есть полноценные циклы по спискам, агрегации, обращения к другим объектам кластера. Цена - его надо учить, и отлаживать Rego сложнее, чем YAML.
Архитектура gatekeeper kubernetes держится на двух ресурсах, и их разделение - ключевая идея.
- ConstraintTemplate - шаблон политики. Это как класс: внутри лежит Rego-код с логикой и описание параметров, которые политика принимает. Создавая ConstraintTemplate, ты заодно регистрируешь в кластере новый CRD.
- Constraint - экземпляр шаблона с конкретными параметрами. Это как объект класса: "примени шаблон required-labels с параметром labels=[team] вот к этим kinds".
Шаблон, требующий наличие заданных меток:
Код: Выделить всё
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: k8srequiredlabels
spec:
crd:
spec:
names:
kind: K8sRequiredLabels
validation:
openAPIV3Schema:
type: object
properties:
labels:
type: array
items:
type: string
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8srequiredlabels
violation[{"msg": msg}] {
provided := {label | input.review.object.metadata.labels[label]}
required := {label | label := input.parameters.labels[_]}
missing := required - provided
count(missing) > 0
msg := sprintf("Отсутствуют обязательные метки: %v", [missing])
}
Теперь сам Constraint, который включает шаблон в работу:
Код: Выделить всё
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sRequiredLabels
metadata:
name: ns-must-have-team
spec:
enforcementAction: deny
match:
kinds:
- apiGroups: [""]
kinds: ["Namespace"]
parameters:
labels: ["team"]
Kyverno против Gatekeeper: что выбрать в 2026
Честное сравнение без маркетинга.
- Порог входа. Kyverno выигрывает с разгромом для команд без опыта в policy as code: YAML ты уже знаешь. Gatekeeper требует Rego - это отдельная компетенция, и в маленькой команде её может физически не быть.
- Мощность. Gatekeeper берёт сложные кейсы: "запретить два Ingress с одинаковым host во всём кластере", агрегации, межобъектные проверки через data.inventory. На Rego это пишется, на YAML-паттернах Kyverno - больно или никак.
- Mutate и generate. Это сильная сторона Kyverno и почти его монополия: автодобавление меток, генерация NetworkPolicy на каждый новый namespace, проброс ConfigMap. Gatekeeper исторически про валидацию (мутации у него появились позже и слабее).
- Отчётность. Kyverno из коробки пишет PolicyReport - нативные k8s-объекты с нарушениями, которые удобно тащить в Grafana через policy-reporter. У Gatekeeper аудит через статус Constraint.
Связь с Pod Security Standards и место в CI
Логичный вопрос: в Kubernetes же есть встроенный Pod Security Admission (PSS) - три уровня privileged/baseline/restricted, включаются меткой на namespace. Зачем тогда Kyverno?
PSS - это фиксированный набор правил безопасности Pod, его нельзя расширить. Он отлично закрывает "не давать privileged, hostNetwork, hostPath". Но он не умеет твоих правил: "образы только из registry.cyberlake.ru", "обязательна метка cost-center", "у Service запрещён type LoadBalancer в dev". Вот тут и нужен policy engine - он дополняет PSS произвольной логикой бизнеса и платформы. Грамотная схема 2026: PSS включён на baseline/restricted как база, а Kyverno/Gatekeeper навешивает organization-specific правила сверху.
И второй фронт - CI. Главная мысль: одну и ту же политику надо проверять дважды. В кластере через admission - это страховка. И в пайплайне до кластера - это быстрая обратная связь разработчику. Лучше отклонить плохой манифест за 5 секунд в merge request, чем через 10 минут на деплое. Kyverno умеет это нативно своим CLI:
Код: Выделить всё
$ kyverno apply ./policies/ --resource ./manifests/deployment.yaml
Applying 3 policies to 1 resource...
policy disallow-latest-tag -> resource default/Deployment/web failed:
1. require-image-tag: validation error: latest запрещен.
pass: 0, fail: 1, warn: 0, error: 0, skip: 0
Грабли внедрения: сразу Enforce - и прод лежит
Главная ошибка новичка, набившая шишек половине индустрии: написать политику в режиме Enforce/deny и сразу выкатить на боевой кластер. Что происходит дальше? Старые Deployment с latest, легаси без requests, чей-то privileged-DaemonSet мониторинга - всё это при следующем рестарте Pod не сможет создаться. HPA не отскейлит, упавший Pod не поднимется, ночью у тебя инцидент. Admission блокирует не только новое, но и пересоздание существующего.
Правильный путь внедрения - в три шага:
- Сначала Audit/warn. В Kyverno ставь validationFailureAction: Audit, в Gatekeeper enforcementAction: warn или dryrun. Политика работает, но ничего не блокирует - только пишет отчёты. Дай ей покрутиться дни-недели.
- Собери нарушения. kubectl get policyreport -A в Kyverno или статус Constraint в Gatekeeper покажут реальный масштаб. Почини существующее, договорись с командами.
- Только потом Enforce. И то лучше точечно - через exclude/match по namespace, чтобы kube-system и старые namespace не задеть.
Третья - webhook как точка отказа. Admission webhook синхронный: если pod Kyverno/Gatekeeper лёг, а failurePolicy: Fail - API-сервер начнёт отклонять запросы, и ты не сможешь даже починить кластер. Держи контроллер политик в нескольких репликах, следи за его здоровьем, и на критичных namespace продумывай failurePolicy осознанно. Антипаттерн - одна реплика движка с Fail на весь кластер.
Четвёртая, тихая - слишком широкий match без namespaceSelector. Политика на "все Pod" будет дёргаться на каждый системный Pod и тормозить admission. Сужай match, исключай служебное.
Мини-лаба: запрети latest и проверь в CI
Делай руками на любом кластере (kind, minikube, k3s - подойдёт всё).
- Поставь Kyverno: helm repo add kyverno https://kyverno.github.io/kyverno/ && helm install kyverno kyverno/kyverno -n kyverno --create-namespace
- Создай ClusterPolicy disallow-latest-tag из урока, но сначала с validationFailureAction: Audit.
- Запусти kubectl run bad --image=nginx:latest - Pod создастся (режим Audit). Посмотри отчёт: kubectl get policyreport -A и найди там нарушение.
- Переключи политику на Enforce, повтори kubectl run - теперь получишь отказ admission webhook. Сравни поведение.
- Скачай kyverno CLI и прогони kyverno apply ./policy.yaml --resource ./bad-pod.yaml - убедись, что нарушение ловится без кластера. Это твой будущий шаг в CI.
- Бонус: повтори тот же запрет на Gatekeeper через ConstraintTemplate + Constraint и сравни, насколько Rego многословнее YAML.
- В какую фазу admission (mutating или validating) попадает политика "запретить latest", а в какую - "дописать метку team"? Почему порядок фаз важен?
- Чем ConstraintTemplate отличается от Constraint в Gatekeeper и зачем их разделили на два объекта?
- Почему нельзя выкатывать новую политику сразу в режиме Enforce/deny на боевой кластер и какая последовательность безопасна?
- Зачем проверять политики и в CI, и в admission, если admission и так всё ловит?
Policy as code превращает разрозненные договорённости команд в исполняемые правила, которые кластер применяет сам. Kyverno даёт быстрый старт на YAML и силён в мутациях и генерации; OPA Gatekeeper берёт мощью Rego и переиспользуемыми шаблонами. Оба дополняют, а не заменяют PSS, и оба обязаны жить в Git и проверяться в CI до кластера. Запомни главное правило эксплуатации: начинай с Audit, собирай нарушения, исключай системные namespace - и только потом включай Enforce. Тогда политики защищают прод, а не роняют его.