This commit is contained in:
2026-04-08 20:22:14 +07:00
commit 34fbdd1412
96 changed files with 5321 additions and 0 deletions

View File

@@ -0,0 +1,81 @@
# Урок 2.1 — Создание политик валидации ресурсов
## Файлы
| Файл | Описание |
|------|----------|
| `require-resource-limits.yaml` | Обязательные CPU и memory limits |
| `disallow-latest-tag.yaml` | Запрет тега :latest |
| `allow-only-trusted-registries.yaml` | Только внутренние реестры |
| `require-labels.yaml` | Обязательные стандартные лейблы |
| `require-min-replicas-production.yaml` | Минимум 2 реплики в production |
## Применение политик
```bash
# Применить все политики из папки
kubectl apply -f .
# Проверить статус
kubectl get clusterpolicies
# Подробный статус
kubectl get clusterpolicies -o wide
```
## Локальное тестирование через Kyverno CLI
```bash
# Протестировать одну политику
kyverno apply require-resource-limits.yaml \
--resource test-resources/pod-no-limits.yaml
# Протестировать все политики против всех ресурсов
kyverno apply . --resource test-resources/ --table
# Запустить встроенные тесты
kyverno test tests/
```
## Тестирование в кластере
```bash
# Попытка создать под без limits (должна быть ошибка)
kubectl apply -f test-resources/pod-no-limits.yaml
# Создать корректный под
kubectl apply -f test-resources/pod-with-limits.yaml
# Попытка создать под с образом :latest
kubectl apply -f test-resources/pod-latest-image.yaml
# Посмотреть PolicyReport после создания
kubectl get policyreports -n default
kubectl describe policyreport -n default
# Найти все нарушения
kubectl get policyreports -A -o json | \
jq -r '.items[] | .metadata.namespace as $ns |
.results[] | select(.result == "fail") |
"\($ns)/\(.resources[0].name): \(.policy)/\(.rule): \(.message)"'
```
## Режим Audit — миграция без риска
```bash
# Переключить политику в Audit режим для оценки нарушений
kubectl patch clusterpolicy require-resource-limits \
--type merge \
-p '{"spec":{"validationFailureAction":"Audit"}}'
# Посмотреть нарушения без блокировки
kubectl get policyreports -A -o json | \
jq '[.items[].results[] | select(.result == "fail") | .policy] |
group_by(.) | map({policy: .[0], violations: length}) |
sort_by(-.violations)[]'
# После исправления — переключить обратно
kubectl patch clusterpolicy require-resource-limits \
--type merge \
-p '{"spec":{"validationFailureAction":"Enforce"}}'
```

View File

@@ -0,0 +1,51 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: allow-only-trusted-registries
annotations:
policies.kyverno.io/title: "Только доверенные реестры"
policies.kyverno.io/category: Security
policies.kyverno.io/severity: high
policies.kyverno.io/subject: Pod
policies.kyverno.io/description: >-
Разрешает образы только из доверенных реестров компании.
Предотвращает использование образов из публичных реестров
без проверки безопасности.
НАСТРОЙТЕ список разрешённых реестров под вашу инфраструктуру.
spec:
validationFailureAction: Enforce
background: true
rules:
- name: check-trusted-registries
match:
resources:
kinds:
- Pod
exclude:
resources:
namespaces:
- kube-system
- kyverno
validate:
message: >-
Образ '{{ element.image }}' из недоверенного реестра.
Разрешены только:
- registry.company.com/
- gcr.io/company-project/
Загрузите образ в внутренний реестр и обновите манифест.
foreach:
- list: >-
request.object.spec.containers[] |
merge(request.object.spec.initContainers[] || `[]`, @)
deny:
conditions:
all:
# Образ НЕ из первого доверенного реестра
- key: "{{ element.image }}"
operator: NotStartsWith
value: "registry.company.com/"
# И НЕ из второго доверенного реестра
- key: "{{ element.image }}"
operator: NotStartsWith
value: "gcr.io/company-project/"
# Добавьте дополнительные условия по аналогии

View File

@@ -0,0 +1,44 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: disallow-latest-tag
annotations:
policies.kyverno.io/title: "Запрет тега latest"
policies.kyverno.io/category: Best Practices
policies.kyverno.io/severity: medium
policies.kyverno.io/subject: Pod
policies.kyverno.io/description: >-
Запрещает использование тега :latest и образов без тега.
Оба варианта резолвятся в "последний доступный образ",
что делает деплойменты невоспроизводимыми.
spec:
validationFailureAction: Enforce
background: true
rules:
- name: disallow-latest-tag
match:
resources:
kinds:
- Pod
exclude:
resources:
namespaces:
- kube-system
validate:
message: >-
Образ '{{ element.image }}' использует тег :latest или не имеет тега.
Используйте конкретный тег (например, nginx:1.25.3) или digest
(nginx@sha256:abc123...) для воспроизводимых деплойментов.
foreach:
- list: >-
request.object.spec.containers[] |
merge(request.object.spec.initContainers[] || `[]`, @)
deny:
conditions:
any:
- key: "{{ element.image }}"
operator: Contains
value: ":latest"
- key: "{{ element.image }}"
operator: NotContains
value: ":"

View File

@@ -0,0 +1,46 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-standard-labels
annotations:
policies.kyverno.io/title: "Обязательные стандартные лейблы"
policies.kyverno.io/category: Governance
policies.kyverno.io/severity: medium
policies.kyverno.io/subject: Deployment,StatefulSet,DaemonSet
policies.kyverno.io/description: >-
Требует наличия стандартных лейблов у workload ресурсов.
Лейблы используются для мониторинга, алертинга и распределения затрат.
Допустимые значения environment: dev | staging | production
spec:
validationFailureAction: Enforce
background: true
rules:
- name: check-required-labels
match:
resources:
kinds:
- Deployment
- StatefulSet
- DaemonSet
exclude:
resources:
namespaces:
- kube-system
- kyverno
validate:
message: >-
Ресурс '{{ request.object.metadata.name }}' должен иметь лейблы:
app, version, team, environment (dev|staging|production)
Пример:
labels:
app: my-service
version: "1.0.0"
team: payments
environment: production
pattern:
metadata:
labels:
app: "?*"
version: "?*"
team: "?*"
environment: "^(dev|staging|production)$"

View File

@@ -0,0 +1,31 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-min-replicas-production
annotations:
policies.kyverno.io/title: "Минимальное количество реплик в production"
policies.kyverno.io/category: Availability
policies.kyverno.io/severity: high
policies.kyverno.io/subject: Deployment
policies.kyverno.io/description: >-
В namespace production требуется минимум 2 реплики для Deployment.
Одна реплика = single point of failure при обновлении ноды или пода.
spec:
validationFailureAction: Enforce
background: true
rules:
- name: check-min-replicas
match:
resources:
kinds:
- Deployment
namespaces:
- production
validate:
message: >-
Deployment '{{ request.object.metadata.name }}' в production
имеет {{ request.object.spec.replicas }} реплику(и).
Минимально требуется 2 реплики для обеспечения доступности.
pattern:
spec:
replicas: ">=2"

View File

@@ -0,0 +1,49 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-resource-limits
annotations:
policies.kyverno.io/title: "Обязательные resource limits"
policies.kyverno.io/category: Resources
policies.kyverno.io/severity: high
policies.kyverno.io/subject: Pod
policies.kyverno.io/description: >-
Каждый контейнер (включая init и ephemeral) обязан иметь
limits.memory и limits.cpu. Без лимитов контейнер может
потребить все ресурсы ноды и убить соседей (noisy neighbor).
spec:
validationFailureAction: Enforce
background: true
rules:
- name: check-container-limits
match:
resources:
kinds:
- Pod
exclude:
resources:
namespaces:
- kube-system
- kyverno
validate:
message: >-
Контейнер '{{ element.name }}' в поде '{{ request.object.metadata.name }}'
(namespace: {{ request.object.metadata.namespace }}) не имеет resource limits.
Добавьте в манифест:
resources:
limits:
memory: "256Mi"
cpu: "500m"
Документация: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/
foreach:
- list: >-
request.object.spec.containers[] |
merge(request.object.spec.initContainers[] || `[]`, @) |
merge(request.object.spec.ephemeralContainers[] || `[]`, @)
pattern:
resources:
limits:
memory: "?*"
cpu: "?*"

View File

@@ -0,0 +1,19 @@
apiVersion: v1
kind: Pod
metadata:
name: pod-latest-image
namespace: default
spec:
containers:
- name: nginx
image: nginx:latest # тег :latest — политика отклонит
resources:
limits:
memory: "128Mi"
cpu: "100m"
- name: redis
image: redis # нет тега вообще — тоже отклонит
resources:
limits:
memory: "128Mi"
cpu: "100m"

View File

@@ -0,0 +1,10 @@
apiVersion: v1
kind: Pod
metadata:
name: pod-no-limits
namespace: default
spec:
containers:
- name: nginx
image: nginx:1.25.3
# нет блока resources — политика require-resource-limits отклонит

View File

@@ -0,0 +1,21 @@
apiVersion: v1
kind: Pod
metadata:
name: pod-with-limits
namespace: default
labels:
app: my-app
version: "1.0.0"
team: platform
environment: staging
spec:
containers:
- name: nginx
image: nginx:1.25.3 # конкретный тег — хорошо
resources:
requests:
memory: "64Mi"
cpu: "50m"
limits:
memory: "128Mi"
cpu: "100m"

View File

@@ -0,0 +1,30 @@
name: resource-validation-tests
policies:
- ../require-resource-limits.yaml
- ../disallow-latest-tag.yaml
- ../require-labels.yaml
resources:
- pod-with-limits.yaml
- pod-no-limits.yaml
- pod-latest-image.yaml
results:
- policy: require-resource-limits
rule: check-container-limits
resource: pod-with-limits
namespace: default
result: pass
- policy: require-resource-limits
rule: check-container-limits
resource: pod-no-limits
namespace: default
result: fail
- policy: disallow-latest-tag
rule: disallow-latest-tag
resource: pod-with-limits
namespace: default
result: pass
- policy: disallow-latest-tag
rule: disallow-latest-tag
resource: pod-latest-image
namespace: default
result: fail

View File

@@ -0,0 +1,75 @@
# Урок 2.2 — Политики безопасности и соответствия стандартам
## Файлы
| Файл | PSS профиль | Описание |
|------|-------------|----------|
| `disallow-privileged-containers.yaml` | Baseline | Запрет `privileged: true` |
| `disallow-dangerous-capabilities.yaml` | Baseline | Запрет опасных capabilities |
| `require-drop-all-capabilities.yaml` | Restricted | Обязательный `drop: [ALL]` |
| `require-run-as-non-root.yaml` | Restricted | Запрет запуска от root |
| `disallow-host-namespaces.yaml` | Baseline | Запрет hostNetwork/PID/IPC/Path |
| `require-seccomp-profile.yaml` | Restricted | Обязательный seccomp |
| `restrict-automount-sa-token.yaml` | CIS | Отключение автомонтирования токена |
## Стратегия внедрения (поэтапно)
### Этап 1 — Аудит (неделя 12)
```bash
# Применить все политики в режиме Audit
for f in *.yaml; do
kubectl apply -f "$f"
done
# Подождать 5 минут для background scan, затем:
kubectl get policyreports -A -o json | \
jq -r '[.items[].results[] | select(.result == "fail") | .policy] |
group_by(.) | map({policy: .[0], count: length}) |
sort_by(-.count)[] | "\(.count)\t\(.policy)"'
```
### Этап 2 — Оценка нарушений
```bash
# Детали нарушений по конкретной политике
POLICY="disallow-privileged-containers"
kubectl get policyreports -A -o json | \
jq --arg p "$POLICY" \
-r '.items[] | .metadata.namespace as $ns |
.results[] | select(.policy == $p and .result == "fail") |
"\($ns)/\(.resources[0].name): \(.message)"'
```
### Этап 3 — Тестирование через Kyverno CLI
```bash
# Проверить под с нарушениями
kyverno apply . \
--resource test-resources/pod-insecure.yaml \
--table
# Проверить корректный под
kyverno apply . \
--resource test-resources/pod-secure.yaml \
--table
```
### Этап 4 — Перевод в Enforce
```bash
# По одной политике
kubectl patch clusterpolicy disallow-privileged-containers \
--type merge \
-p '{"spec":{"validationFailureAction":"Enforce"}}'
```
## Тестовые ресурсы
```bash
# Под с нарушениями (должен быть отклонён)
kubectl apply -f test-resources/pod-insecure.yaml
# Безопасный под (должен пройти)
kubectl apply -f test-resources/pod-secure.yaml
```

View File

@@ -0,0 +1,59 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: disallow-dangerous-capabilities
annotations:
policies.kyverno.io/title: "Запрет опасных Linux Capabilities"
policies.kyverno.io/category: Pod Security Standards (Baseline)
policies.kyverno.io/severity: high
policies.kyverno.io/subject: Pod
policies.kyverno.io/description: >-
Запрещает добавление опасных Linux capabilities.
SYS_ADMIN, NET_ADMIN, SYS_PTRACE и другие дают контейнеру
привилегированный доступ к ядру и сети хоста.
Допустима только NET_BIND_SERVICE (порты < 1024).
spec:
validationFailureAction: Enforce
background: true
rules:
- name: disallow-dangerous-capabilities
match:
resources:
kinds:
- Pod
exclude:
resources:
namespaces:
- kube-system
validate:
message: >-
Контейнер '{{ element.name }}' добавляет запрещённые capabilities:
{{ element.securityContext.capabilities.add }}.
Разрешена только NET_BIND_SERVICE.
Пересмотрите необходимость этих привилегий.
foreach:
- list: >-
request.object.spec.containers[] |
merge(request.object.spec.initContainers[] || `[]`, @)
deny:
conditions:
any:
- key: "{{ element.securityContext.capabilities.add[] }}"
operator: AnyIn
value:
- SYS_ADMIN
- NET_ADMIN
- SYS_PTRACE
- SYS_MODULE
- SYS_RAWIO
- SYS_BOOT
- SYS_NICE
- SYS_RESOURCE
- SYS_TIME
- AUDIT_CONTROL
- MAC_ADMIN
- MAC_OVERRIDE
- SETUID
- SETGID
- KILL
- MKNOD

View File

@@ -0,0 +1,57 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: disallow-host-namespaces
annotations:
policies.kyverno.io/title: "Запрет host namespaces и HostPath"
policies.kyverno.io/category: Pod Security Standards (Baseline)
policies.kyverno.io/severity: critical
policies.kyverno.io/subject: Pod
policies.kyverno.io/description: >-
hostNetwork, hostPID, hostIPC и hostPath дают контейнеру прямой доступ
к соответствующим ресурсам ноды. Это нарушает изоляцию контейнеров
и является вектором для escape-атак.
spec:
validationFailureAction: Enforce
background: true
rules:
- name: disallow-host-namespaces
match:
resources:
kinds:
- Pod
exclude:
resources:
namespaces:
- kube-system
validate:
message: >-
Под '{{ request.object.metadata.name }}' использует host namespace.
Запрещены: hostNetwork, hostIPC, hostPID.
Эти настройки дают контейнеру доступ к сети/процессам/IPC ноды.
pattern:
spec:
=(hostNetwork): false
=(hostIPC): false
=(hostPID): false
- name: disallow-hostpath-volumes
match:
resources:
kinds:
- Pod
exclude:
resources:
namespaces:
- kube-system
validate:
message: >-
Под '{{ request.object.metadata.name }}' использует HostPath volume.
HostPath даёт контейнеру прямой доступ к файловой системе ноды.
Используйте emptyDir, configMap, secret или PersistentVolumeClaim.
deny:
conditions:
any:
- key: "{{ request.object.spec.volumes[].hostPath | length(@) }}"
operator: GreaterThan
value: "0"

View File

@@ -0,0 +1,43 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: disallow-privileged-containers
annotations:
policies.kyverno.io/title: "Запрет привилегированных контейнеров"
policies.kyverno.io/category: Pod Security Standards (Baseline)
policies.kyverno.io/severity: critical
policies.kyverno.io/subject: Pod
policies.kyverno.io/description: >-
Привилегированный контейнер имеет полный доступ к хост-системе —
эквивалент root на самой ноде. Компрометация такого контейнера
означает компрометацию всей ноды.
Проверяются: containers, initContainers, ephemeralContainers.
spec:
validationFailureAction: Enforce
background: true
rules:
- name: privileged-containers
match:
resources:
kinds:
- Pod
exclude:
resources:
namespaces:
- kube-system
validate:
message: >-
Контейнер '{{ element.name }}' имеет securityContext.privileged: true.
Привилегированные контейнеры запрещены — они получают полный доступ к хосту.
Удалите поле securityContext.privileged или установите значение false.
foreach:
- list: >-
request.object.spec.containers[] |
merge(request.object.spec.initContainers[] || `[]`, @) |
merge(request.object.spec.ephemeralContainers[] || `[]`, @)
deny:
conditions:
any:
- key: "{{ element.securityContext.privileged }}"
operator: Equals
value: true

View File

@@ -0,0 +1,41 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-drop-all-capabilities
annotations:
policies.kyverno.io/title: "Обязательный drop ALL capabilities"
policies.kyverno.io/category: Pod Security Standards (Restricted)
policies.kyverno.io/severity: medium
policies.kyverno.io/subject: Pod
policies.kyverno.io/description: >-
Каждый контейнер должен явно сбросить все capabilities через
securityContext.capabilities.drop: [ALL].
Это часть профиля Restricted согласно Pod Security Standards.
spec:
validationFailureAction: Enforce
background: true
rules:
- name: require-drop-all
match:
resources:
kinds:
- Pod
exclude:
resources:
namespaces:
- kube-system
validate:
message: >-
Контейнер '{{ element.name }}' не сбрасывает все capabilities.
Добавьте в securityContext:
capabilities:
drop:
- ALL
foreach:
- list: "request.object.spec.containers"
deny:
conditions:
all:
- key: "ALL"
operator: NotIn
value: "{{ element.securityContext.capabilities.drop[] || `[]` }}"

View File

@@ -0,0 +1,58 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-run-as-non-root
annotations:
policies.kyverno.io/title: "Запрет запуска от root"
policies.kyverno.io/category: Pod Security Standards (Restricted)
policies.kyverno.io/severity: high
policies.kyverno.io/subject: Pod
policies.kyverno.io/description: >-
Контейнеры не должны запускаться от пользователя root (UID 0).
Процесс от root может читать/писать файлы хоста через volume mounts
даже без привилегированного режима.
Установите runAsNonRoot: true или runAsUser >= 1000.
spec:
validationFailureAction: Enforce
background: true
rules:
- name: check-runasnonroot-pod-level
match:
resources:
kinds:
- Pod
exclude:
resources:
namespaces:
- kube-system
validate:
message: >-
Под '{{ request.object.metadata.name }}' должен иметь
spec.securityContext.runAsNonRoot: true.
Это гарантирует, что ни один контейнер не запустится от root.
pattern:
spec:
securityContext:
runAsNonRoot: true
- name: check-runasuser-not-root
match:
resources:
kinds:
- Pod
exclude:
resources:
namespaces:
- kube-system
validate:
message: >-
Контейнер '{{ element.name }}' использует runAsUser: 0 (root).
Установите runAsUser >= 1000.
foreach:
- list: "request.object.spec.containers"
deny:
conditions:
any:
- key: "{{ element.securityContext.runAsUser }}"
operator: Equals
value: 0

View File

@@ -0,0 +1,52 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-seccomp-profile
annotations:
policies.kyverno.io/title: "Обязательный Seccomp профиль"
policies.kyverno.io/category: Pod Security Standards (Restricted)
policies.kyverno.io/severity: medium
policies.kyverno.io/subject: Pod
policies.kyverno.io/description: >-
Seccomp ограничивает системные вызовы контейнера, уменьшая
поверхность атаки на ядро Linux.
Используйте RuntimeDefault (профиль рантайма) или Localhost (кастомный).
spec:
validationFailureAction: Enforce
background: true
rules:
- name: require-seccomp-profile
match:
resources:
kinds:
- Pod
exclude:
resources:
namespaces:
- kube-system
validate:
message: >-
Под '{{ request.object.metadata.name }}' не имеет seccomp профиля.
Добавьте в spec.securityContext:
seccompProfile:
type: RuntimeDefault
pattern:
spec:
securityContext:
seccompProfile:
type: "RuntimeDefault | Localhost"
- name: disallow-unconfined-seccomp
match:
resources:
kinds:
- Pod
validate:
message: >-
Тип Unconfined отключает seccomp защиту. Используйте RuntimeDefault.
deny:
conditions:
any:
- key: "{{ request.object.spec.securityContext.seccompProfile.type }}"
operator: Equals
value: Unconfined

View File

@@ -0,0 +1,37 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: restrict-automount-service-account-token
annotations:
policies.kyverno.io/title: "Ограничение автомонтирования ServiceAccount токена"
policies.kyverno.io/category: Security
policies.kyverno.io/severity: medium
policies.kyverno.io/subject: Pod
policies.kyverno.io/description: >-
По умолчанию Kubernetes монтирует токен ServiceAccount в каждый под.
Если приложение не использует Kubernetes API, этот токен — лишняя
поверхность атаки. CIS Benchmark рекомендует отключать автомонтирование
там, где токен не нужен.
spec:
validationFailureAction: Audit
background: true
rules:
- name: check-automount-service-account
match:
resources:
kinds:
- Pod
exclude:
resources:
namespaces:
- kube-system
validate:
message: >-
Под '{{ request.object.metadata.name }}' автоматически монтирует
ServiceAccount токен (automountServiceAccountToken: true по умолчанию).
Если под не обращается к Kubernetes API, добавьте:
spec:
automountServiceAccountToken: false
pattern:
spec:
automountServiceAccountToken: false

View File

@@ -0,0 +1,21 @@
apiVersion: v1
kind: Pod
metadata:
name: pod-insecure
namespace: default
spec:
hostNetwork: true # нарушение: host namespace
hostPID: true # нарушение: host namespace
containers:
- name: app
image: nginx:1.25.3
securityContext:
privileged: true # нарушение: привилегированный контейнер
runAsUser: 0 # нарушение: запуск от root
capabilities:
add:
- SYS_ADMIN # нарушение: опасная capability
resources:
limits:
memory: "128Mi"
cpu: "100m"

View File

@@ -0,0 +1,28 @@
apiVersion: v1
kind: Pod
metadata:
name: pod-secure
namespace: default
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1000
seccompProfile:
type: RuntimeDefault
automountServiceAccountToken: false
containers:
- name: app
image: nginx:1.25.3
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- ALL
resources:
requests:
memory: "64Mi"
cpu: "50m"
limits:
memory: "128Mi"
cpu: "100m"

View File

@@ -0,0 +1,126 @@
# Урок 2.3 — Обработка ошибок и отчётность
## Файлы
| Файл | Описание |
|------|----------|
| `prometheus-alert-rules.yaml` | PrometheusRule с алертами |
| `service-monitor.yaml` | ServiceMonitor для Prometheus Operator |
## Работа с PolicyReport
```bash
# Посмотреть все отчёты
kubectl get policyreports -A
kubectl get clusterpolicyreports
# Детальный отчёт по namespace
kubectl describe policyreport -n production
# Все нарушения в кластере (форматированный вывод)
kubectl get policyreports -A -o json | \
jq -r '.items[] | .metadata.namespace as $ns |
.results[] | select(.result == "fail") |
"\($ns)/\(.resources[0].name)\t\(.policy)/\(.rule)\t\(.message)"' | \
column -t -s $'\t'
# Топ-10 политик по нарушениям
kubectl get policyreports -A -o json | \
jq -r '[.items[].results[] | select(.result == "fail") | .policy] |
group_by(.) | map({policy: .[0], count: length}) |
sort_by(-.count)[:10][] | "\(.count)\t\(.policy)"'
# Нарушения по конкретной политике
POLICY="require-resource-limits"
kubectl get policyreports -A -o json | \
jq --arg p "$POLICY" \
-r '.items[] | .metadata.namespace as $ns |
.results[] | select(.policy == $p and .result == "fail") |
"\($ns)/\(.resources[0].name): \(.message)"'
# Compliance rate по namespace
kubectl get policyreports -A -o json | \
jq -r '.items[] | .metadata.namespace as $ns |
{ns: $ns, pass: [.results[] | select(.result=="pass")] | length,
fail: [.results[] | select(.result=="fail")] | length} |
"\(.ns): pass=\(.pass) fail=\(.fail)"'
```
## Prometheus метрики
```bash
# Проверить доступность метрик (port-forward если нет Ingress)
kubectl port-forward -n kyverno svc/kyverno-svc-metrics 8000:8000 &
# Посмотреть все метрики
curl -s http://localhost:8000/metrics | grep kyverno_
# Ключевые метрики
curl -s http://localhost:8000/metrics | grep kyverno_policy_results_total
curl -s http://localhost:8000/metrics | grep kyverno_admission_review_duration
```
## Полезные PromQL запросы (для Grafana)
```promql
# Compliance rate (цель: 1.0 = 100%)
sum(rate(kyverno_policy_results_total{rule_result="pass"}[5m])) /
sum(rate(kyverno_policy_results_total[5m]))
# Нарушений в час по политикам
topk(10, sum by(policy_name)(
increase(kyverno_policy_results_total{rule_result="fail"}[1h])
))
# p95 латентность admission
histogram_quantile(0.95,
sum(rate(kyverno_admission_review_duration_seconds_bucket[5m])) by (le)
)
# Нарушения по namespace за сутки
sum by(resource_namespace)(
increase(kyverno_policy_results_total{rule_result="fail"}[24h])
)
```
## Применение мониторинга
```bash
# Применить ServiceMonitor (требует Prometheus Operator)
kubectl apply -f service-monitor.yaml
# Применить правила алертов
kubectl apply -f prometheus-alert-rules.yaml
# Проверить что правила подхватились
kubectl get prometheusrule -n kyverno
```
## Режим Audit для существующего кластера
```bash
# Применить политику в Audit режиме
kubectl apply -f - <<EOF
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: audit-all-pods
spec:
validationFailureAction: Audit
background: true
rules:
- name: check-labels
match:
resources:
kinds: [Pod]
validate:
message: "Под должен иметь лейбл app"
pattern:
metadata:
labels:
app: "?*"
EOF
# Подождать background scan (30-60 секунд), потом:
kubectl get policyreports -A | grep -v 0/0
```

View File

@@ -0,0 +1,99 @@
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: kyverno-alerts
namespace: kyverno
labels:
prometheus: kube-prometheus
role: alert-rules
spec:
groups:
- name: kyverno.availability
rules:
- alert: KyvernoDown
expr: up{job="kyverno-svc-metrics"} == 0
for: 1m
labels:
severity: critical
annotations:
summary: "Kyverno недоступен"
description: >-
Admission controller Kyverno не отвечает более 1 минуты.
Проверьте поды: kubectl get pods -n kyverno
- alert: KyvernoAdmissionLatencyHigh
expr: >
histogram_quantile(0.95,
sum(rate(kyverno_admission_review_duration_seconds_bucket[5m])) by (le)
) > 0.5
for: 5m
labels:
severity: warning
annotations:
summary: "Высокая латентность Kyverno admission (p95 > 500ms)"
description: >-
p95 латентность: {{ $value | humanizeDuration }}.
Это замедляет деплойменты. Проверьте политики с apiCall в context.
- alert: KyvernoAdmissionErrors
expr: >
rate(kyverno_admission_requests_total{
admission_request_type="error"
}[5m]) > 0
for: 2m
labels:
severity: critical
annotations:
summary: "Ошибки обработки запросов в Kyverno"
description: "Kyverno возвращает ошибки. Проверьте логи: kubectl logs -n kyverno -l app.kubernetes.io/component=admission-controller"
- name: kyverno.policy
rules:
- alert: KyvernoCriticalPolicyViolation
expr: >
increase(kyverno_policy_results_total{
rule_result="fail",
policy_name=~"disallow-privileged.*|disallow-host.*|disallow-dangerous.*"
}[5m]) > 0
for: 0m
labels:
severity: critical
annotations:
summary: "Нарушение критической политики безопасности: {{ $labels.policy_name }}"
description: >-
Политика {{ $labels.policy_name }} была нарушена в namespace {{ $labels.resource_namespace }}.
Немедленно проверьте: kubectl get policyreports -n {{ $labels.resource_namespace }}
- alert: KyvernoHighViolationRate
expr: >
sum(increase(kyverno_policy_results_total{rule_result="fail"}[1h])) > 50
for: 5m
labels:
severity: warning
annotations:
summary: "Высокое количество нарушений политик (> 50 за час)"
description: >-
За последний час: {{ $value }} нарушений.
Проверьте отчёты: kubectl get policyreports -A
- name: kyverno.performance
rules:
- alert: KyvernoCPUThrottling
expr: >
rate(container_cpu_cfs_throttled_seconds_total{
namespace="kyverno",
container=~"kyverno.*"
}[5m]) > 0.1
for: 10m
labels:
severity: warning
annotations:
summary: "CPU throttling Kyverno — возможна деградация производительности"
description: "Увеличьте CPU limit для Kyverno admission controller."
- name: kyverno.recording
rules:
- record: kyverno:compliance_rate:5m
expr: >
sum(rate(kyverno_policy_results_total{rule_result="pass"}[5m])) /
sum(rate(kyverno_policy_results_total[5m]))

View File

@@ -0,0 +1,17 @@
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: kyverno-metrics
namespace: kyverno
labels:
prometheus: kube-prometheus
spec:
selector:
matchLabels:
app.kubernetes.io/name: kyverno
app.kubernetes.io/component: admission-controller
endpoints:
- port: metrics-port
interval: 30s
path: /metrics
honorLabels: true