Это не статья для новичков, которые только kubectl get pods выучили. Это гайд для тех, кто уже набил шишки на --privileged, кто ловил OOMKilled потому что кто-то не поставил limits, и кто устал объяснять, почему :latest в продакшене - это медленно текущая язва.
Держись, будет жёстко, честно, с примерами, которые можно копипастить прямо сейчас, и с подводными камнями, о которых молчат документации. Погнали.
1. Роль Admission Controllers в K8s: почему твой
Ты привык, что kubectl apply -f manifest.yaml - это священнодействие. Но на самом деле, когда ты жмёшь Enter, твой запрос проходит через цепочку обработчиков, которые могут как молча пропустить его, так и разорвать в клочья, даже если синтаксис идеален.1.1. Жизненный цикл API-запроса: от клиента до etcd
Давай быстро пробежимся по тому, что происходит, когда ты запускаешь команду. Это не просто «отправил запрос - получил ответ». Это целый конвейер, похожий на досмотр в аэропорту, только вместо бабушек с чемоданами - твои поды и сервисы.- Аутентификация (Authentication)
Ты стучишься в API-сервер. Кто ты? Токен, сертификат, клиентский сертификат? API-сервер проверяет твои учётные данные. Если не прошёл - 401 Unauthorized, иди лесом. Никакие политики тут не помогут, если ты не авторизован. - Авторизация (Authorization)
Ладно, ты прошёл. Теперь API-сервер смотрит: а можно ли тебе вообще делать то, что ты просишь? Тут вступают в игру RBAC (Role-Based Access Control), ABAC и прочие. Если твой ServiceAccount не имеет прав на создание Pod'ов в namespace production - получишь 403 Forbidden. Это уровень авторизации, он работает на основе ролей и правил. - Admission Control (Контроль доступа)
Вот тут начинается самое интересное. Ты уже аутентифицирован и авторизован. Ты имеешь право создать под. Но хорошо ли то, что ты создаёшь? Именно здесь живут Admission Controllers. Это последняя линия обороны перед тем, как объект попадёт в etcd.
Представь: ты создаёшь под. Запрос проходит мутационные вебхуки, которые могут подправить твой манифест (добавить метки, изменить securityContext, подставить sidecar). Потом проходят валидирующие вебхуки, которые проверяют итоговый объект (уже после мутаций!). Если хоть один из них скажет «нет» - запрос отклоняется, и ты видишь знакомое сообщение об ошибке.
И только после этого объект сохраняется в etcd. Это важно: если ты используешь и Mutating, и Validating вебхуки, мутации применяются перед валидацией. Поэтому политики валидации всегда видят уже изменённый объект.
Почему это важно для нас с Kyverno? Kyverno работает именно как динамический вебхук. Он регистрирует два вебхука: один mutating, другой validating. И он имеет возможность ещё и background scan - проверять уже существующие объекты, которые не были проверены при создании. Но об этом позже.
1.2. Mutating Admission Webhook - когда «сырьё»
Mutating вебхук может изменить объект ДО того, как он будет зафиксирован. Он получает запрос в формате AdmissionReview, может добавить/изменить/удалить поля и вернуть изменённую версию. Это мощно и опасно одновременно. Потому что разработчик пишет один манифест, а в кластер попадает совсем другой.Kyverno использует mutating вебхук для своих политик типа Mutate. Например, ты можешь автоматически добавить securityContext с runAsNonRoot: true в каждый под, который не имеет этого поля. Или вписать imagePullPolicy: Always для образов с тегом latest. Или принудительно добавить метку managed-by: kyverno. Всё это происходит незаметно для пользователя.
Важный момент: порядок выполнения мутационных вебхуков имеет значение. Если у тебя несколько мутационных вебхуков, они выполняются в порядке, определённом в webhooks[].matchPolicy и webhooks[].reinvocationPolicy. Kyverno по умолчанию использует reinvocationPolicy: "IfNeeded", что позволяет ему вызываться повторно, если другие вебхуки изменили объект после него. Это гарантирует, что политики Kyverno будут применяться к финальной версии объекта.
1.3. Validating Admission Webhook - последний страж
После того как все мутации применены, наступает время валидации. Validating вебхук может сказать «да» или «нет». Если он отвечает allowed: false, запрос отклоняется, и пользователь получает ошибку. Kyverno использует validating вебхук для политик типа Validate. Здесь ты можешь запретить privileged: true, потребовать наличие resources.limits, проверить, что образ не содержит :latest, и так далее.Но есть нюанс: если вебхук недоступен (например, Kyverno под упал), что делать? Это определяется failurePolicy. Fail - если вебхук не отвечает, запрос блокируется (безопасный подход, но может парализовать кластер). Ignore - пропускает запрос, но тогда политики не применяются. Выбор зависит от твоей толерантности к риску. В production я всегда начинаю с Ignore и постепенно, убедившись в надёжности, перехожу на Fail для критических политик.
2. Установка и настройка Kyverno в production: не просто
Ты наверняка видел десяток туториалов, где установка Kyverno - это helm install kyverno kyverno/kyverno --namespace kyverno --create-namespace. И всё, готово. А потом в 3 часа ночи вебхук начинает таймаутиться, поды съедают всю память, и ты не можешь даже kubectl delete pod сделать, потому что валидация блокирует.Давай настроим Kyverno так, чтобы он работал как часы, даже когда кто-то решит запустить kubectl apply -f . с тысячей объектов.
2.1. Helm chart: values для production (replicas, resources, webhookTimeoutSeconds)
Kyverno официально распространяется через Helm chart. Но дефолтные значения - это для минимального теста. Для production нужно пересмотреть всё.Начнём с того, что Kyverno состоит из нескольких компонентов:
- kyverno - основной контроллер, который обрабатывает вебхуки и background сканирование.
- kyverno-cleanup-controller - опциональный контроллер для политик очистки (Cleanup policies).
- kyverno-reports-controller - контроллер, генерирующий PolicyReport и ClusterPolicyReport.
- webhook - сертификаты, управляемые оператором.
YAML:
# Количество реплик - минимум 2 для HA. Но не забывай, что вебхук
# приходит на любой под, и если один под медленный, запросы могут таймаутиться.
replicaCount: 2
image:
tag: v1.11.0 # Бери последний стабильный, но не latest
resources:
requests:
cpu: 250m
memory: 256Mi
limits:
cpu: 1
memory: 1Gi
# Настройка вебхуков
admissionController:
# Таймаут вебхука - по умолчанию 10 секунд. Если твои политики сложные,
# могут быть задержки. Ставь 15-20, но помни: чем больше таймаут, тем дольше
# пользователь ждёт ответа. kube-apiserver имеет глобальный таймаут 30 сек.
webhookTimeoutSeconds: 15
# Политика отказа: можно "Fail" или "Ignore".
# Я рекомендую начинать с "Ignore", после проверки стабильности переключить на "Fail"
failurePolicy: Ignore
# Background сканирование
backgroundScan:
enabled: true
# Интервал сканирования: по умолчанию 24h. Для активного кластера можно уменьшить,
# но не чаще чем раз в час, чтобы не нагружать API.
interval: 24h
# Конфигурация политик
policyController:
# Количество воркеров для обработки политик
workers: 4
# Максимальное количество объектов, обрабатываемых одновременно
maxQueuedEvents: 1000
# Сертификаты - важный момент. Kyverno использует собственный оператор для управления
# сертификатами вебхука. Он создаёт secret с сертификатом и обновляет его автоматически.
# В production убедись, что оператор имеет права на обновление вебхуков.
# Если ты используесть cert-manager, можешь задать custom certificate.
certManager:
enabled: false # Если используешь cert-manager, то true и настрой ниже
# customCert: ...
Почему я ставлю webhookTimeoutSeconds: 15? Потому что если у тебя есть политики, которые делают сложные сопоставления (например, проверка наличия образа в частном реестре через OCI), они могут выполняться дольше стандартных 10 секунд. 15 секунд - золотая середина. Но не ставь 30, потому что у API-сервера есть общий таймаут на запрос, и если он истечёт раньше, чем вебхук ответит, ты получишь ошибку, даже если политика была satisfied.
failurePolicy: Ignore - для первого развёртывания я всегда выбираю Ignore. Почему? Представь, что ты только установил Kyverno, и по какой-то причине его поды не запустились (например, нет места на узлах). Если failurePolicy = Fail, то любой создаваемый объект будет блокироваться, потому что вебхук недоступен. Кластер фактически парализован. Поэтому сначала ставим Ignore, наблюдаем за стабильностью, потом переключаем на Fail для критических политик, а для некритических оставляем Ignore.
2.2. Background scanning: проверка существующих ресурсов
Одна из киллер-фич Kyverno - background scan. Обычные вебхуки работают только при создании/изменении объектов. А что с тем мусором, который уже живёт в кластере? Там могут быть поды с privileged: true, созданные до внедрения политик.Kyverno запускает фоновые сканеры, которые периодически проверяют все существующие ресурсы на соответствие политикам. Результаты складываются в CRD PolicyReport (для неймспейсных ресурсов) и ClusterPolicyReport (для кластерных). Эти отчёты потом можно просмотреть через kubectl, экспортировать в метрики или отобразить в дашборде.
Важно: background scan не изменяет объекты и не удаляет их. Он только отчитывается о нарушениях. Это как аудит, который ты можешь показать начальнику: «Смотри, 50% подов нарушают политику запрета privileged». Дальше ты сам решаешь, автоматически ли исправлять их или ждать, пока разработчики пересоздадут.
Настройка background scan через values:
YAML:
backgroundScan:
enabled: true
interval: 12h # каждые 12 часов
# Можно ограничить количество одновременно сканируемых ресурсов
maxWorkers: 10
Если у тебя кластер с тысячами объектов, не ставь интервал слишком маленьким - сканер будет постоянно грузить API-сервер. 24 часа - нормально для большинства случаев. Если ты часто меняешь политики и хочешь видеть изменения быстрее, можно поставить 6 часов, но мониторь нагрузку.
2.3. Failure policy: Fail vs Ignore - поведение при недоступности Kyverno
Это, пожалуй, самый важный параметр, который может спасти или уничтожить твой кластер. Каждый вебхук (и mutating, и validating) имеет поле failurePolicy. Оно определяет, что делать, если вебхук не отвечает (таймаут, 500 ошибка, нет эндпоинта).- Fail: если вебхук недоступен, запрос отклоняется. Это безопасно с точки зрения безопасности, потому что ты не допустишь создание невалидных объектов в обход политик. Но если Kyverno лежит, то вообще ничего нельзя будет создать/обновить. Это как «сейфовый режим».
- Ignore: если вебхук недоступен, запрос пропускается без проверки. Это позволяет кластеру продолжать работать, но политики перестают действовать.
Как настроить в values:
YAML:
admissionController:
failurePolicy: Ignore # глобальная, но может быть переопределена в политике
Пример переопределения в политике:
YAML:
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: disallow-privileged-containers
spec:
failurePolicy: Fail # даже если Kyverno не в ресурсе, блокируем запрос
validationFailureAction: Enforce # Enforce или Audit
rules:
- name: privileged-container
match:
any:
- resources:
kinds:
- Pod
validate:
message: "Privileged containers are not allowed."
pattern:
spec:
containers:
- securityContext:
privileged: false
Но будь осторожен: если ты используешь failurePolicy: Fail в политике, а сам Kyverno по какой-то причине недоступен, то запросы, подпадающие под эту политику, будут блокироваться, даже если ты глобально поставил Ignore. Это может привести к частичной блокировке кластера. Поэтому внедряй постепенно: сначала Audit режим, потом Enforce, и только потом Fail для критических политик.
3. Написание Validate-политик: искусство говорить «нет»
Теперь самое вкусное - политики. Kyverno даёт два основных режима проверки: validate и validate.pattern. Но есть ещё validate.deny для сложных условий и validate.anyPattern для нескольких вариантов. Мы начнём с самых насущных проблем, которые встречаются в каждом кластере.3.1. Запрет privileged: true и hostPID/hostNetwork
Привилегированные контейнеры - это дыра в безопасности. Они имеют доступ ко всем устройствам хоста, могут эскалировать привилегии и делать что угодно. Обычно это нужно только в очень специфических случаях (например, DaemonSet для мониторинга), но разработчики любят включать privileged: true для удобства.Запретим раз и навсегда.
Политика: disallow-privileged.yaml
YAML:
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: disallow-privileged-containers
annotations:
policies.kyverno.io/title: Disallow Privileged Containers
policies.kyverno.io/category: Security
policies.kyverno.io/severity: high
policies.kyverno.io/subject: Pod
policies.kyverno.io/description: >-
Privileged containers can break out of container isolation and compromise
the host. This policy ensures that no container is allowed to run with
privileged: true.
spec:
validationFailureAction: Enforce # Enforce блокирует, Audit только пишет отчёт
background: true # Сканировать существующие объекты
rules:
- name: privileged-containers
match:
any:
- resources:
kinds:
- Pod
validate:
message: "Privileged containers are not allowed. Please remove 'privileged: true'."
pattern:
spec:
containers:
- securityContext:
privileged: false
# Если есть initContainers, проверяем и их
initContainers:
- securityContext:
privileged: false
# ephemeral containers тоже
ephemeralContainers:
- securityContext:
privileged: false
Что здесь происходит:
- validationFailureAction: Enforce - блокируем создание/обновление, если правило нарушено.
- background: true - фоновая проверка существующих подов (будет видно в PolicyReport).
- pattern - описывает ожидаемую структуру. Поле privileged должно быть false. Если оно отсутствует или равно true, правило не выполняется.
- message - текст ошибки, который увидит пользователь при kubectl apply.
Усовершенствованный вариант с запретом любого под, где privileged отсутствует или равен true:
YAML:
- name: privileged-containers
match:
any:
- resources:
kinds:
- Pod
validate:
message: "Privileged containers are not allowed. Either set privileged: false or omit securityContext."
pattern:
spec:
containers:
- securityContext:
privileged: false
initContainers:
- securityContext:
privileged: false
ephemeralContainers:
- securityContext:
privileged: false
anyPattern: # альтернативный паттерн: разрешаем, если securityContext отсутствует
- spec:
containers:
- securityContext: null # или отсутствует
initContainers:
- securityContext: null
ephemeralContainers:
- securityContext: null
Но anyPattern работает как «или», поэтому если хотя бы один контейнер имеет privileged: true, всё равно провалится? Нет, anyPattern проверяет все контейнеры. Если один контейнер соответствует первому паттерну (privileged=false), а другой второму (securityContext null), то всё равно нужен один паттерн, который подходит всем контейнерам одновременно. Так что anyPattern здесь не идеален.
Лучше использовать deny с условием, которое проверяет, что ни один контейнер не имеет privileged: true. В Kyverno 1.8+ есть операторы preconditions и deny.
Вариант с deny:
YAML:
- name: deny-privileged
match:
any:
- resources:
kinds:
- Pod
preconditions:
all:
- key: "{{ request.operation }}"
operator: In
value:
- CREATE
- UPDATE
validate:
message: "Privileged containers are not allowed."
deny:
conditions:
any:
- key: "{{ request.object.spec.containers[*].securityContext.privileged || 'false' }}"
operator: Equals
value: true
- key: "{{ request.object.spec.initContainers[*].securityContext.privileged || 'false' }}"
operator: Equals
value: true
- key: "{{ request.object.spec.ephemeralContainers[*].securityContext.privileged || 'false' }}"
operator: Equals
value: true
Но это сложновато. На практике я использую более простой подход: требовать явное указание securityContext с privileged: false через pattern + исключаем случаи, когда securityContext отсутствует, с помощью deny:
YAML:
- name: require-privileged-false
match:
any:
- resources:
kinds:
- Pod
validate:
message: "SecurityContext must be defined with privileged=false for all containers."
anyPattern:
- spec:
containers:
- securityContext:
privileged: false
initContainers:
- securityContext:
privileged: false
ephemeralContainers:
- securityContext:
privileged: false
- spec:
containers:
- securityContext: null
initContainers:
- securityContext: null
ephemeralContainers:
- securityContext: null
Этот вариант разрешает либо явное privileged: false, либо полное отсутствие securityContext. Он не блокирует поды без securityContext. Если хочешь полностью запретить даже без securityContext, убирай второй паттерн.
Теперь про hostPID, hostNetwork, hostIPC и hostPorts. Это тоже опасные вещи. Политика:
YAML:
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: disallow-host-namespaces
spec:
validationFailureAction: Enforce
rules:
- name: host-namespaces
match:
any:
- resources:
kinds:
- Pod
validate:
message: "Using host namespaces is prohibited."
pattern:
spec:
hostPID: false
hostNetwork: false
hostIPC: false
- name: host-ports
match:
any:
- resources:
kinds:
- Pod
validate:
message: "Using host ports is prohibited."
pattern:
spec:
containers:
- ports:
- hostPort: null
3.2. Обязательные resource requests/limits для всех контейнеров
Это классика. Без requests и limits твой кластер превращается в «свалку» - один под может сожрать всю память, а kube-scheduler не может нормально размещать поды. Заставим разработчиков указывать ресурсы.Политика require-resource-limits.yaml:
YAML:
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-resource-limits
annotations:
policies.kyverno.io/title: Require Resource Limits
spec:
validationFailureAction: Enforce
rules:
- name: validate-resources
match:
any:
- resources:
kinds:
- Pod
validate:
message: "Each container must specify cpu and memory requests and limits."
pattern:
spec:
containers:
- resources:
requests:
memory: "?*"
cpu: "?*"
limits:
memory: "?*"
cpu: "?*"
initContainers:
- resources:
requests:
memory: "?*"
cpu: "?*"
limits:
memory: "?*"
cpu: "?*"
"?*" означает «должно присутствовать и быть непустым». Но учти, что requests и limits могут быть указаны только в контейнере, а не в поде. Однако эта политика проверяет каждый контейнер.
Что если у тебя есть поды с ephemeralContainers? Их тоже стоит проверить, но они не всегда обязательны. Можно добавить ещё одно правило или расширить anyPattern.
Также имей в виду, что requests и limits можно указывать в виде строк или чисел. Kyverno понимает оба формата.
Если ты используешь LimitRange в namespace, то некоторые поды могут не иметь requests, но получат дефолтные. Однако лучше требовать явное указание.
3.3. Запрет тега
Образы с тегом :latest - это бомба замедленного действия. Ты никогда не знаешь, какой именно образ будет подтянут при перезапуске. Воспроизводимость сборок летит к чёрту. Давайте запретим.Политика disallow-latest-tag.yaml:
YAML:
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: disallow-latest-tag
spec:
validationFailureAction: Enforce
rules:
- name: require-image-tag
match:
any:
- resources:
kinds:
- Pod
validate:
message: "Using ':latest' tag is not allowed. Specify an explicit tag or use digest."
pattern:
spec:
containers:
- image: "!*:latest"
initContainers:
- image: "!*:latest"
ephemeralContainers:
- image: "!*:latest"
"!*:latest" означает «не соответствует шаблону, оканчивающемуся на :latest». Но это разрешает теги вроде v1.2.3 и также image@sha256:... (digest). Однако если разработчик напишет просто nginx (без тега), Kubernetes подставит :latest, и политика его пропустит? Нет, потому что nginx не содержит :latest, но по факту будет использован nginx:latest. Это проблема: политика проверяет строку, которую ввёл пользователь. Если пользователь не указал тег, то это nginx - он не заканчивается на :latest, поэтому пропускается. А потом kubelet подтянет nginx:latest.
Чтобы это исправить, нужно либо требовать явный тег или digest, либо мутировать отсутствующий тег в конкретный (например, ставить :stable). Лучше комбинировать: валидация запрещает :latest и требует, чтобы либо был тег (кроме latest), либо digest.
Более строгая политика:
YAML:
- name: require-image-digest-or-tag
validate:
message: "Image must use a digest (e.g., @sha256:...) or a tag other than 'latest'."
pattern:
spec:
containers:
- image: "?*"
# Проверка, что image либо содержит '@' (digest), либо имеет тег, не равный latest
# Используем JMESPath в preconditions, но можно и так:
deny:
conditions:
any:
- key: "{{ regex_match('.*:latest$', request.object.spec.containers[*].image) }}"
operator: Equals
value: true
- key: "{{ regex_match('^[^@]+$', request.object.spec.containers[*].image) && !regex_match('.*:.*', request.object.spec.containers[*].image) }}"
operator: Equals
value: true
Этот вариант сложен. Проще использовать мутацию: если разработчик не указал тег, мутировать в :stable или конкретный digest, а валидацией запретить :latest. Но мутация - это следующий раздел.
Когда базовые validate-политики уже на месте, полезно посмотреть на этот уровень шире - не только как на набор YAML-ограничений, а как на часть общей модели защиты кластера. В недавней статье: "LOTL в мире контейнеров: уязвимости и защиты" мы рассказали, как admission-контроль, Pod Security, сетевые политики и runtime-наблюдение складываются в одну практическую схему, где Kyverno - не отдельный артефакт, а часть рабочего контура безопасности.
4. Mutate и Generate политики: когда недостаточно просто запретить
Иногда просто запретить - недостаточно. Разработчики всё равно будут забывать про securityContext или labels. Им нужна помощь: автодополнение. Mutate политики позволяют добавлять или изменять поля в объектах до того, как они попадут в кластер. Generate политики позволяют создавать новые объекты (например, NetworkPolicy) при создании namespace.4.1. Auto-injection: добавление securityContext в pods без него
Допустим, ты хочешь, чтобы каждый под имел securityContext с runAsNonRoot: true и runAsUser: 1000. Но разработчики ленятся. Давай добавим это автоматически, если поле отсутствует.Mutate политика add-security-context.yaml:
YAML:
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: add-default-security-context
spec:
rules:
- name: add-security-context
match:
any:
- resources:
kinds:
- Pod
mutate:
patchStrategicMerge:
spec:
containers:
- (name): "*"
securityContext:
+(runAsNonRoot): true
+(runAsUser): 1000
+(capabilities):
+(drop):
- ALL
initContainers:
- (name): "*"
securityContext:
+(runAsNonRoot): true
+(runAsUser): 1000
+(capabilities):
+(drop):
- ALL
Здесь используется patchStrategicMerge. Конструкция (name): "*" означает «для всех контейнеров, у которых есть поле name». Мы добавляем securityContext, если его нет. + означает «добавить, если отсутствует». Это безопасно: если разработчик уже указал securityContext, мы не перезаписываем его поля, а только добавляем недостающие.
Но важно: если разработчик уже указал securityContext, но без runAsNonRoot, то мы добавим runAsNonRoot: true. Если он указал runAsNonRoot: false, то поле уже есть, и мы не перезаписываем, потому что используем +. Это может привести к несоответствию, если разработчик сознательно хочет runAsNonRoot: false. В таком случае лучше добавить валидацию, которая запрещает явный runAsNonRoot: false. Или использовать overwrite вместо +.
Можно также мутировать imagePullPolicy, добавлять метки и аннотации.
Добавление default меток:
YAML:
mutate:
patchStrategicMerge:
metadata:
labels:
+(managed-by): kyverno
+(environment): production
4.2. Default labels: автоматическое добавление team/env/cost-center labels
Это часто требуется для отслеживания затрат или организации. Ты хочешь, чтобы на каждый новый под, деплоймент, сервис ставились определённые метки. Но их могут забыть. Можно мутировать не только поды, но и другие ресурсы.Политика для добавления меток ко всем ресурсам (с использованием match):
YAML:
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: inject-labels
spec:
rules:
- name: add-labels
match:
any:
- resources:
kinds:
- Deployment
- StatefulSet
- DaemonSet
- Service
- ConfigMap
mutate:
patchStrategicMerge:
metadata:
labels:
+(team): platform
+(cost-center): "12345"
Но будь осторожен: если ты применишь это к Deployment, то лейблы появятся только на самом Deployment, а не на создаваемых подах. Чтобы добавить лейблы в поды, нужно мутировать template.metadata.labels.
Для подов внутри Deployment:
YAML:
- name: add-labels-to-pod-template
match:
any:
- resources:
kinds:
- Deployment
mutate:
patchStrategicMerge:
spec:
template:
metadata:
labels:
+(team): platform
Или ты можешь сделать одну политику, которая добавляет метки на любой объект, содержащий spec.template.metadata.labels. Но это уже сложнее.
4.3. Auto-generate: создание NetworkPolicy при создании нового namespace
Generate политики - это мощный инструмент для создания сопутствующих ресурсов. Например, ты хочешь, чтобы каждый новый namespace автоматически получал дефолтную NetworkPolicy, запрещающую весь входящий трафик, или ResourceQuota.Политика generate-networkpolicy.yaml:
YAML:
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: generate-default-networkpolicy
spec:
rules:
- name: generate-networkpolicy
match:
any:
- resources:
kinds:
- Namespace
generate:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
name: deny-all-ingress
namespace: "{{ request.object.metadata.name }}"
synchronize: true # если true, то при удалении NetworkPolicy Kyverno восстановит её
data:
spec:
podSelector: {}
policyTypes:
- Ingress
После создания namespace Kyverno создаст NetworkPolicy с именем deny-all-ingress, которая запрещает весь входящий трафик к подам в этом namespace (если нет более разрешающих политик). synchronize: true означает, что если кто-то удалит эту NetworkPolicy, Kyverno восстановит её.
Generate для ResourceQuota:
YAML:
generate:
apiVersion: v1
kind: ResourceQuota
name: default-quota
namespace: "{{ request.object.metadata.name }}"
synchronize: true
data:
spec:
hard:
requests.cpu: "4"
requests.memory: 8Gi
limits.cpu: "8"
limits.memory: 16Gi
persistentvolumeclaims: "10"
Это гарантирует, что в каждом новом namespace будут установлены квоты.
5. Мониторинг и compliance: как жить с политиками и не сойти с ума
Внедрить политики - полдела. Нужно ещё понимать, что происходит в кластере, какие объекты нарушают правила, и как это всё отслеживать. Kyverno предоставляет для этого PolicyReport CRD, а также интеграцию с дашбордами.5.1. PolicyReport CRD: структурированные отчёты о нарушениях
Kyverno генерирует объекты PolicyReport для каждого неймспейса и ClusterPolicyReport для кластерных ресурсов. Эти отчёты содержат список нарушений (violations) по каждой политике.Посмотреть отчёты можно через kubectl:
Bash:
kubectl get policyreport -A
kubectl get clusterpolicyreport
Структура PolicyReport:
YAML:
apiVersion: wgpolicyk8s.io/v1alpha2
kind: PolicyReport
metadata:
name: kyverno-report
namespace: default
results:
- policy: disallow-privileged-containers
rule: privileged-containers
resources:
- kind: Pod
name: bad-pod
namespace: default
message: "Privileged containers are not allowed."
status: Fail
timestamp:
seconds: 1700000000
summary:
fail: 1
pass: 4
skip: 0
warn: 0
Ты можешь использовать эти отчёты для автоматического мониторинга, например, через Prometheus (экспортер Kyverno собирает метрики), или через интеграцию с другими инструментами.
Совет: не держи тысячи старых PolicyReport. Kyverno автоматически удаляет отчёты для удалённых ресурсов, но если у тебя много нарушений, количество отчётов может расти. Включи cleanup контроллер для периодической очистки.
5.2. Kyverno Dashboard (Policy Reporter UI) - визуализация compliance
Для человеко-читаемого интерфейса существует проект Policy Reporter (GitHub - kyverno/policy-reporter: Monitoring and Observability Tool for the PolicyReport CRD with an optional UI.). Он собирает PolicyReport со всего кластера, отображает их в веб-интерфейсе, позволяет фильтровать, экспортировать в PDF, интегрироваться с SIEM.Установка через Helm:
Bash:
helm repo add policy-reporter https://kyverno.github.io/policy-reporter
helm upgrade --install policy-reporter policy-reporter/policy-reporter \
--namespace policy-reporter --create-namespace
После установки открой доступ к сервису (через NodePort или Ingress) и получишь красивый дашборд с графиками, списком нарушений и возможностью просмотреть, какие именно ресурсы нарушают какие политики.
Это особенно полезно, когда нужно показать руководству «мы заблокировали X нарушений за неделю».
5.3. Интеграция с CI/CD: kyverno apply --cluster для pre-deployment проверки
Если ты используешь CI/CD (GitLab CI, Jenkins, GitHub Actions), ты можешь проверять манифесты на соответствие политикам ещё до того, как они попадут в кластер. Kyverno предоставляет CLI-команду kyverno apply, которая имитирует обработку политик на локальных файлах.Пример проверки:
Bash:
kyverno apply /path/to/manifests \
--policy /path/to/policies \
--resource /path/to/resources \
--cluster # если политики кластерные
Эта команда выведет результат в консоль и вернёт ненулевой код, если есть нарушения. Ты можешь встроить её в пайплайн:
YAML:
# .gitlab-ci.yml
test-policies:
stage: test
image: kyverno/kyverno-cli:latest
script:
- kyverno apply ./k8s/ --policy ./kyverno-policies/ --cluster
only:
- merge_requests
Так ты не допустишь в мастер манифесты, которые не проходят валидацию. Это сдвигает безопасность влево (shift left).
Сравнение Kyverno vs OPA/Gatekeeper: почему я выбрал YAML
Я не могу не затронуть эту тему. Если ты работал с Open Policy Agent (OPA) и Gatekeeper, ты знаешь, что там всё строится на Rego - языке, который сам по себе требует изучения. Да, Rego мощный, но его синтаксис напоминает Prolog, и для большинства простых политик (как запрет privileged) ты пишешь многословные конструкции.Kyverno:
- Политики на YAML, который ты и так пишешь каждый день.
- Низкий порог входа.
- Встроенные функции для патчей, генерации, мутации.
- Background scanning из коробки.
- Rego даёт больше гибкости для сложных логических условий, особенно если нужно пересекать данные из разных источников.
- Более зрелая экосистема, интеграция с многими системами.
- Возможность использовать внешние данные (data.inventory) для более сложных решений.
Заключение: что мы получили
Мы развернули Kyverno в production с настройками HA, подготовили политики валидации, которые не пропускают привилегированные контейнеры, образы с :latest и поды без ресурсов. Настроили автоматическое добавление securityContext, меток и генерацию NetworkPolicy для новых неймспейсов. Всё это контролируется через PolicyReport и визуализируется в дашборде.Теперь твой кластер не просто «рабочий», он ещё и безопасный. Разработчики могут писать манифесты как раньше, но Kyverno будет их поправлять и не давать делать глупости. А если кто-то попытается обойти политики - получит понятное сообщение об ошибке.
Конечно, это только начало. Политик может быть десятки: запрет на использование определённых образов, проверка наличия liveness/readiness probes, требование аннотаций для сервисов, ограничение на порты NodePort, блокировка определённых API-версий. Но теперь у тебя есть фундамент.
Последнее редактирование модератором: