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

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
.DS_Store

View File

@@ -0,0 +1,66 @@
# Урок 1.1 — Что такое Kyverno и зачем он нужен
Этот урок — теоретический. Ниже — сравнительная шпаргалка и ссылки для подготовки к следующим урокам.
## Сравнение: Kyverno vs OPA Gatekeeper
| Критерий | Kyverno | OPA Gatekeeper |
|----------|---------|----------------|
| Язык политик | YAML + JMESPath | Rego |
| Порог входа | Низкий | Высокий |
| Kubernetes-native | Да | Частично |
| Готовых политик | 300+ (kyverno.io/policies) | Много (github.com/open-policy-agent/gatekeeper-library) |
| Mutation | Да | Ограниченно |
| Generation | Да | Нет |
| Image verification | Да (Cosign) | Нет |
## Типы политик
```
ClusterPolicy — кластерный ресурс (все namespace)
Policy — namespace-scoped ресурс
Операции:
validate → проверить ресурс, отклонить при нарушении
mutate → изменить ресурс при создании/обновлении
generate → создать вспомогательные ресурсы
verifyImages → проверить подпись образа
```
## Архитектура Kyverno
```
kubectl apply -f pod.yaml
API Server
├──► Mutating Webhook ──► Kyverno (mutate rules)
│ │
│ изменённый ресурс
│ │
├──► Validating Webhook ──► Kyverno (validate rules)
│ │
│ pass / deny
etcd
Background Controller ──► сканирует существующие ресурсы
Reports Controller ──► создаёт PolicyReport
Generate Controller ──► создаёт вспомогательные ресурсы
Cleanup Controller ──► удаляет по расписанию
```
## Задание
1. Изучите [официальную документацию](https://kyverno.io/docs/)
2. Просмотрите готовые политики на [kyverno.io/policies](https://kyverno.io/policies/)
3. Подготовьте кластер для следующего урока:
```bash
# Локально через kind
kind create cluster --name kyverno-lab
# Или minikube
minikube start --memory=4096 --cpus=2
```

View File

@@ -0,0 +1,123 @@
# Урок 1.2 — Установка и настройка Kyverno
## 1. Добавить Helm репозиторий
```bash
helm repo add kyverno https://kyverno.github.io/kyverno/
helm repo update
# Посмотреть доступные версии
helm search repo kyverno/kyverno --versions | head -10
```
## 2. Установка
### Development / Lab
```bash
helm install kyverno kyverno/kyverno \
--namespace kyverno \
--create-namespace \
--values values-development.yaml
```
### Production
```bash
helm install kyverno kyverno/kyverno \
--namespace kyverno \
--create-namespace \
--values values-production.yaml
```
## 3. Проверка установки
```bash
# Статус подов
kubectl get pods -n kyverno
# Все поды должны быть Running:
# NAME READY STATUS RESTARTS
# kyverno-admission-controller-xxx 1/1 Running 0
# kyverno-background-controller-xxx 1/1 Running 0
# kyverno-cleanup-controller-xxx 1/1 Running 0
# kyverno-reports-controller-xxx 1/1 Running 0
# Проверить webhook конфигурации
kubectl get validatingwebhookconfigurations | grep kyverno
kubectl get mutatingwebhookconfigurations | grep kyverno
# Проверить что caBundle не пустой (признак корректной регистрации webhook)
kubectl get validatingwebhookconfigurations \
kyverno-resource-validating-webhook-cfg \
-o jsonpath='{.webhooks[0].clientConfig.caBundle}' | wc -c
# Посмотреть события
kubectl get events -n kyverno --sort-by='.lastTimestamp' | tail -20
# Логи admission controller
kubectl logs -n kyverno \
-l app.kubernetes.io/component=admission-controller \
--tail=50
```
## 4. Тестовая политика
```bash
# Создать тестовую политику
kubectl apply -f test-policy.yaml
# Попробовать создать ConfigMap без обязательного лейбла (должна быть ошибка)
kubectl create configmap test-bad --from-literal=key=value -n default
# Создать правильный ConfigMap
kubectl apply -f - <<EOF
apiVersion: v1
kind: ConfigMap
metadata:
name: test-good
namespace: default
labels:
test: "true"
data:
key: value
EOF
# Удалить тестовую политику
kubectl delete -f test-policy.yaml
kubectl delete configmap test-good -n default
```
## 5. Обновление Kyverno
```bash
# Обновить CRD отдельно (важно!)
helm upgrade kyverno kyverno/kyverno \
--namespace kyverno \
--set crds.install=true \
--values values-production.yaml
# Проверить статус политик после обновления
kubectl get clusterpolicies -o wide
```
## 6. Удаление Kyverno
```bash
# ВНИМАНИЕ: сначала переведите критичные политики в Audit режим
# Удалить через Helm
helm uninstall kyverno -n kyverno
# Удалить CRD
kubectl delete crd $(kubectl get crd | grep kyverno | awk '{print $1}')
# Удалить webhook конфигурации (если остались)
kubectl delete validatingwebhookconfigurations \
$(kubectl get validatingwebhookconfigurations | grep kyverno | awk '{print $1}') \
2>/dev/null || true
kubectl delete mutatingwebhookconfigurations \
$(kubectl get mutatingwebhookconfigurations | grep kyverno | awk '{print $1}') \
2>/dev/null || true
```

View File

@@ -0,0 +1,28 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: test-installation-policy
annotations:
policies.kyverno.io/title: "Тестовая политика установки"
policies.kyverno.io/description: >-
Временная политика для проверки корректности установки Kyverno.
Удалите после проверки.
spec:
validationFailureAction: Enforce
background: false
rules:
- name: require-test-label
match:
resources:
kinds:
- ConfigMap
namespaces:
- default
validate:
message: >-
ConfigMap в namespace default должен иметь лейбл test=true.
Это тестовая политика для проверки установки Kyverno.
pattern:
metadata:
labels:
test: "?*"

View File

@@ -0,0 +1,31 @@
# values-development.yaml
# Минимальная конфигурация для dev/lab окружения
replicaCount: 1
admissionController:
replicas: 1
resources:
limits:
cpu: 500m
memory: 256Mi
requests:
cpu: 100m
memory: 128Mi
backgroundController:
replicas: 1
resources:
limits:
cpu: 200m
memory: 128Mi
reportsController:
replicas: 1
cleanupController:
replicas: 1
config:
webhooks:
timeoutSeconds: 10

View File

@@ -0,0 +1,73 @@
# values-production.yaml
# Production-ready конфигурация с HA и мониторингом
replicaCount: 3
admissionController:
replicas: 3
podDisruptionBudget:
enabled: true
minAvailable: 2
resources:
limits:
cpu: 1000m
memory: 512Mi
requests:
cpu: 100m
memory: 128Mi
# Распределение по разным нодам
topologySpreadConstraints:
- maxSkew: 1
topologyKey: kubernetes.io/hostname
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
app.kubernetes.io/component: admission-controller
# Метрики для Prometheus Operator
serviceMonitor:
enabled: true
metricsService:
create: true
port: 8000
extraArgs:
- --v=2
- --loggingFormat=json
backgroundController:
replicas: 2
resources:
limits:
cpu: 500m
memory: 256Mi
requests:
cpu: 100m
memory: 128Mi
extraArgs:
- --v=2
- --loggingFormat=json
reportsController:
replicas: 2
resources:
limits:
cpu: 200m
memory: 128Mi
extraArgs:
- --v=2
cleanupController:
replicas: 2
config:
webhooks:
# Если Kyverno не ответил за 10 секунд — API Server применяет failurePolicy
timeoutSeconds: 10
# Автоматическая очистка webhook-конфигураций при удалении Kyverno
webhooksCleanup:
enabled: true

View File

@@ -0,0 +1,56 @@
# Урок 1.3 — Структура и синтаксис политик Kyverno
## Анатомия политики
Откройте `annotated-policy.yaml` — в нём каждое поле прокомментировано.
## Операторы в паттернах
| Оператор | Значение | Пример |
|----------|----------|--------|
| `?*` | поле существует и не пустое | `app: "?*"` |
| `*` | wildcard, любое значение | `image: "*.azurecr.io/*"` |
| `^(a\|b)$` | регулярное выражение | `env: "^(dev\|staging\|production)$"` |
| `>=2` | числовое сравнение | `replicas: ">=2"` |
| `=(field)` | опциональное поле | `=(privileged): false` |
| `+(field)` | добавить только если нет (в mutate) | `+(label): value` |
## Структура match
```yaml
match:
any: # ИЛИ — любое из условий
- resources:
kinds: [Pod]
namespaces: [production]
all: # И — все условия одновременно
- resources:
kinds: [Deployment]
- subjects:
- kind: ServiceAccount
name: my-sa
```
## Практические задания
```bash
# 1. Изучить структуру политики
cat annotated-policy.yaml
# 2. Применить политику
kubectl apply -f annotated-policy.yaml
# 3. Протестировать через kyverno CLI без кластера
kyverno apply annotated-policy.yaml --resource test-resources/pod-good.yaml
kyverno apply annotated-policy.yaml --resource test-resources/pod-bad.yaml
# 4. Применить тестовые ресурсы в кластер
kubectl apply -f test-resources/
# 5. Посмотреть результаты в PolicyReport
kubectl get policyreports -n default -o yaml
# 6. Удалить ресурсы
kubectl delete -f test-resources/
kubectl delete -f annotated-policy.yaml
```

View File

@@ -0,0 +1,68 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy # ClusterPolicy = все namespace, Policy = один namespace
metadata:
name: annotated-example-policy
annotations:
# Мета-информация для Kyverno UI и PolicyReport
policies.kyverno.io/title: "Пример структуры политики"
policies.kyverno.io/category: "Best Practices"
policies.kyverno.io/severity: medium # critical | high | medium | low
policies.kyverno.io/subject: "Pod"
policies.kyverno.io/description: >-
Пример политики с подробными комментариями.
Требует наличия лейбла app у всех Podов.
policies.kyverno.io/version: "1.0.0"
spec:
# Enforce = отклонять запросы при нарушении
# Audit = пропускать, но фиксировать в PolicyReport
validationFailureAction: Audit
# true = проверять также уже существующие ресурсы (через background controller)
background: true
rules:
- name: require-app-label # уникальное имя правила внутри политики
# match: к каким ресурсам применяется правило
match:
any: # ИЛИ: ресурс попадает хотя бы под одно условие
- resources:
kinds:
- Pod
namespaces:
- default
- production
- staging
# exclude: ресурсы-исключения (не будут проверяться)
exclude:
resources:
namespaces:
- kube-system
- kyverno
selector:
matchLabels:
kyverno.io/skip-validation: "true"
# preconditions: дополнительные условия применения правила
# (match фильтрует по типу, preconditions — по содержимому)
preconditions:
any:
- key: "{{ request.operation }}"
operator: In
value: ["CREATE", "UPDATE"] # не проверять DELETE
# validate: правило проверки
validate:
# Сообщение об ошибке (поддерживает переменные)
message: >-
Pod '{{ request.object.metadata.name }}'
в namespace '{{ request.object.metadata.namespace }}'
должен иметь лейбл 'app'.
Добавьте: metadata.labels.app: <имя-приложения>
# pattern: описание того, как должен выглядеть ресурс
pattern:
metadata:
labels:
app: "?*" # поле должно существовать и быть не пустым

View File

@@ -0,0 +1,10 @@
apiVersion: v1
kind: Pod
metadata:
name: pod-bad
namespace: default
# нет лейбла app — политика зафиксирует нарушение
spec:
containers:
- name: nginx
image: nginx:1.25

View File

@@ -0,0 +1,11 @@
apiVersion: v1
kind: Pod
metadata:
name: pod-good
namespace: default
labels:
app: my-application # лейбл присутствует — политика пропустит
spec:
containers:
- name: nginx
image: nginx:1.25

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

View File

@@ -0,0 +1,82 @@
# Урок 3.1 — Основы мутации ресурсов
## Файлы
| Файл | Описание |
|------|----------|
| `add-standard-labels.yaml` | Автодобавление лейблов managed-by, monitored, cost-center |
| `add-default-resource-requests.yaml` | Дефолтные requests для контейнеров без них |
| `add-default-security-context.yaml` | Безопасный SecurityContext по умолчанию |
## Как проверить результат мутации
```bash
# Применить политики
kubectl apply -f .
# Создать под без лейблов и requests
kubectl apply -f test-resources/pod-before-mutation.yaml
# Посмотреть что изменилось — сравнить с оригиналом
kubectl get pod pod-before-mutation -o yaml | \
grep -A 20 "labels:"
kubectl get pod pod-before-mutation -o yaml | \
grep -A 10 "resources:"
# Dry-run — увидеть результат без реального создания
kubectl apply -f test-resources/pod-before-mutation.yaml \
--dry-run=server -o yaml | \
diff test-resources/pod-before-mutation.yaml - || true
```
## Отладка мутаций через Kyverno CLI
```bash
# Показывает изменённый ресурс в stdout
kyverno apply add-standard-labels.yaml \
--resource test-resources/pod-before-mutation.yaml \
--detailed-results
# Сохранить мутированный ресурс
kyverno apply . \
--resource test-resources/pod-before-mutation.yaml \
> test-resources/pod-after-mutation.yaml
cat test-resources/pod-after-mutation.yaml
```
## Типы patch операций
```yaml
# Strategic Merge — наиболее читаемый, для добавления полей
mutate:
patchStrategicMerge:
metadata:
labels:
+(new-label): value # + = добавить только если нет
# JSON Patch RFC 6902 — для точечных изменений и удаления полей
mutate:
patchesJson6902: |-
- op: add
path: /metadata/annotations/company.com~1reviewed # ~1 = / в имени
value: "true"
- op: replace
path: /spec/replicas
value: 3
- op: remove
path: /spec/template/metadata/annotations/debug
```
## Порядок применения политик
Если несколько политик мутируют один ресурс — они применяются
в **алфавитном порядке** имён политик.
```bash
# Управляйте порядком через числовые префиксы в именах:
# 01-add-labels → 02-add-annotations → 03-add-sidecar
kubectl get mutatingwebhookconfigurations -o yaml | \
grep "name:" | grep kyverno
```

View File

@@ -0,0 +1,36 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: add-default-resource-requests
annotations:
policies.kyverno.io/title: "Дефолтные resource requests"
policies.kyverno.io/category: Resources
policies.kyverno.io/severity: low
policies.kyverno.io/subject: Pod
policies.kyverno.io/description: >-
Автоматически добавляет дефолтные resource requests контейнерам,
у которых они не указаны. Requests нужны для корректного планирования.
Символ + добавляет только отсутствующие поля.
spec:
rules:
- name: add-default-requests
match:
resources:
kinds:
- Pod
exclude:
resources:
namespaces:
- kube-system
- kyverno
mutate:
foreach:
- list: "request.object.spec.containers"
patchStrategicMerge:
spec:
containers:
- name: "{{ element.name }}"
resources:
requests:
+(memory): "128Mi"
+(cpu): "100m"

View File

@@ -0,0 +1,55 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: add-default-security-context
annotations:
policies.kyverno.io/title: "Дефолтный SecurityContext"
policies.kyverno.io/category: Security
policies.kyverno.io/severity: medium
policies.kyverno.io/subject: Pod
policies.kyverno.io/description: >-
Автоматически применяет безопасный SecurityContext к подам и контейнерам,
если поля не заданы явно. Работает в связке с validation политиками
(сначала mutate, потом validate).
spec:
rules:
- name: add-pod-security-context
match:
resources:
kinds:
- Pod
exclude:
resources:
namespaces:
- kube-system
mutate:
patchStrategicMerge:
spec:
+(securityContext):
+(runAsNonRoot): true
+(runAsUser): 1000
+(seccompProfile):
+(type): RuntimeDefault
- name: add-container-security-context
match:
resources:
kinds:
- Pod
exclude:
resources:
namespaces:
- kube-system
mutate:
foreach:
- list: "request.object.spec.containers"
patchStrategicMerge:
spec:
containers:
- name: "{{ element.name }}"
+(securityContext):
+(allowPrivilegeEscalation): false
+(readOnlyRootFilesystem): true
+(capabilities):
+(drop):
- ALL

View File

@@ -0,0 +1,33 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: add-standard-labels
annotations:
policies.kyverno.io/title: "Добавление стандартных лейблов"
policies.kyverno.io/category: Governance
policies.kyverno.io/severity: low
policies.kyverno.io/subject: Deployment,StatefulSet,DaemonSet
policies.kyverno.io/description: >-
Автоматически добавляет стандартные лейблы к workload ресурсам,
если они отсутствуют. Символ + означает "добавить только если нет".
spec:
rules:
- name: add-managed-by-label
match:
resources:
kinds:
- Deployment
- StatefulSet
- DaemonSet
exclude:
resources:
namespaces:
- kube-system
mutate:
patchStrategicMerge:
metadata:
labels:
+(managed-by): "kyverno"
+(monitored): "true"
# Динамически берём namespace как cost-center
+(cost-center): "{{ request.object.metadata.namespace }}"

View File

@@ -0,0 +1,16 @@
apiVersion: v1
kind: Pod
metadata:
name: pod-before-mutation
namespace: default
# нет лейблов managed-by, monitored, cost-center
spec:
containers:
- name: app
image: nginx:1.25.3
# нет resources.requests — будут добавлены мутацией
# нет securityContext — будет добавлен мутацией
resources:
limits:
memory: "128Mi"
cpu: "100m"

View File

@@ -0,0 +1,82 @@
# Урок 3.2 — Автоматическое добавление sidecar контейнеров
## Файлы
| Файл | Описание |
|------|----------|
| `inject-fluent-bit.yaml` | Opt-in injection Fluent Bit (по аннотации) |
| `fluent-bit-configmap.yaml` | ConfigMap с конфигурацией Fluent Bit |
| `inject-prometheus-exporter.yaml` | Opt-in injection Prometheus exporter |
## Подготовка
```bash
# Применить ConfigMap (нужен до создания подов с Fluent Bit)
kubectl apply -f fluent-bit-configmap.yaml
# Применить политики injection
kubectl apply -f inject-fluent-bit.yaml
kubectl apply -f inject-prometheus-exporter.yaml
```
## Тестирование Fluent Bit injection
```bash
# Под БЕЗ аннотации — sidecar не добавится
kubectl apply -f test-resources/pod-no-logging.yaml
kubectl get pod pod-no-logging -o jsonpath='{.spec.containers[*].name}'
# Вывод: app
# Под С аннотацией — sidecar добавится автоматически
kubectl apply -f test-resources/pod-with-logging.yaml
kubectl get pod pod-with-logging -o jsonpath='{.spec.containers[*].name}'
# Вывод: app fluent-bit
# Проверить конфигурацию sidecar
kubectl describe pod pod-with-logging | grep -A 30 "fluent-bit:"
# Удалить тесты
kubectl delete pod pod-no-logging pod-with-logging
```
## Тестирование Prometheus exporter injection
```bash
kubectl apply -f test-resources/pod-with-monitoring.yaml
kubectl get pod pod-with-monitoring \
-o jsonpath='{.spec.containers[*].name}'
# Вывод: app prometheus-exporter
# Проверить аннотации
kubectl get pod pod-with-monitoring \
-o jsonpath='{.metadata.annotations}' | jq .
kubectl delete pod pod-with-monitoring
```
## Opt-in vs Opt-out
```yaml
# Opt-in: срабатывает только при наличии аннотации
preconditions:
- key: "{{ request.object.metadata.annotations.\"logging.company.com/enabled\" }}"
operator: Equals
value: "true"
# Opt-out: срабатывает везде, кроме явно исключённых
preconditions:
- key: "{{ request.object.metadata.annotations.\"logging.company.com/disabled\" || 'false' }}"
operator: NotEquals
value: "true"
```
## Защита от дублирования
```bash
# Попробовать создать под, который уже содержит fluent-bit
kubectl apply -f test-resources/pod-already-has-sidecar.yaml
# Sidecar НЕ будет дублирован (precondition NotIn защищает)
kubectl get pod pod-already-has-sidecar \
-o jsonpath='{.spec.containers[*].name}'
```

View File

@@ -0,0 +1,41 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: fluent-bit-config
namespace: default
data:
fluent-bit.conf: |
[SERVICE]
Flush 5
Log_Level info
Daemon off
[INPUT]
Name tail
Path /var/log/containers/*.log
Parser docker
Tag kube.*
Refresh_Interval 5
Mem_Buf_Limit 5MB
Skip_Long_Lines On
[FILTER]
Name kubernetes
Match kube.*
Kube_URL https://kubernetes.default.svc:443
Kube_CA_File /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
Kube_Token_File /var/run/secrets/kubernetes.io/serviceaccount/token
Merge_Log On
Keep_Log Off
[OUTPUT]
Name stdout
Match *
# Замените на ваш реальный output (Elasticsearch, Loki, etc.)
parsers.conf: |
[PARSER]
Name docker
Format json
Time_Key time
Time_Format %Y-%m-%dT%H:%M:%S.%L

View File

@@ -0,0 +1,57 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: inject-fluent-bit-sidecar
annotations:
policies.kyverno.io/title: "Автовнедрение Fluent Bit sidecar"
policies.kyverno.io/category: Logging
policies.kyverno.io/severity: low
policies.kyverno.io/subject: Pod
policies.kyverno.io/description: >-
Автоматически добавляет Fluent Bit sidecar контейнер ко всем подам
с аннотацией logging.company.com/enabled: "true".
Подход Opt-in: разработчик явно запрашивает injection.
ЗАМЕНИТЕ образ registry.company.com на ваш внутренний реестр.
spec:
rules:
- name: inject-fluent-bit
match:
resources:
kinds:
- Pod
preconditions:
all:
# Opt-in: только поды с явной аннотацией
- key: "{{ request.object.metadata.annotations.\"logging.company.com/enabled\" }}"
operator: Equals
value: "true"
# Не добавлять если sidecar уже есть (защита от дублирования)
- key: "fluent-bit"
operator: NotIn
value: "{{ request.object.spec.containers[].name }}"
mutate:
patchStrategicMerge:
spec:
containers:
- name: fluent-bit
image: fluent/fluent-bit:2.1 # замените на внутренний реестр
resources:
limits:
cpu: 100m
memory: 128Mi
requests:
cpu: 50m
memory: 64Mi
volumeMounts:
- name: varlog
mountPath: /var/log
readOnly: true
- name: fluent-bit-config
mountPath: /fluent-bit/etc/
volumes:
- name: varlog
hostPath:
path: /var/log
- name: fluent-bit-config
configMap:
name: fluent-bit-config

View File

@@ -0,0 +1,58 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: inject-prometheus-exporter
annotations:
policies.kyverno.io/title: "Автовнедрение Prometheus exporter"
policies.kyverno.io/category: Monitoring
policies.kyverno.io/severity: low
policies.kyverno.io/subject: Pod
policies.kyverno.io/description: >-
Добавляет node-exporter sidecar ко всем подам с аннотацией
monitoring.company.com/scrape: "true".
Порт scraping берётся из аннотации monitoring.company.com/port
или дефолт 8080.
spec:
rules:
- name: inject-exporter
match:
resources:
kinds:
- Pod
preconditions:
all:
- key: "{{ request.object.metadata.annotations.\"monitoring.company.com/scrape\" }}"
operator: Equals
value: "true"
- key: "prometheus-exporter"
operator: NotIn
value: "{{ request.object.spec.containers[].name }}"
mutate:
patchStrategicMerge:
metadata:
annotations:
# Аннотация для Prometheus autodiscovery
+(prometheus.io/scrape): "true"
+(prometheus.io/port): >-
{{ request.object.metadata.annotations.\"monitoring.company.com/port\" || '9100' }}
+(prometheus.io/path): "/metrics"
spec:
containers:
- name: prometheus-exporter
image: prom/node-exporter:v1.7.0
ports:
- name: metrics
containerPort: 9100
protocol: TCP
resources:
limits:
cpu: 100m
memory: 64Mi
requests:
cpu: 50m
memory: 32Mi
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
runAsNonRoot: true
runAsUser: 65534

View File

@@ -0,0 +1,21 @@
apiVersion: v1
kind: Pod
metadata:
name: pod-already-has-sidecar
namespace: default
annotations:
logging.company.com/enabled: "true"
spec:
containers:
- name: app
image: nginx:1.25.3
resources:
limits:
memory: "128Mi"
cpu: "100m"
- name: fluent-bit # sidecar уже есть — injection не сработает
image: fluent/fluent-bit:2.1
resources:
limits:
memory: "128Mi"
cpu: "100m"

View File

@@ -0,0 +1,14 @@
apiVersion: v1
kind: Pod
metadata:
name: pod-no-logging
namespace: default
# нет аннотации logging.company.com/enabled — sidecar не добавится
spec:
containers:
- name: app
image: nginx:1.25.3
resources:
limits:
memory: "128Mi"
cpu: "100m"

View File

@@ -0,0 +1,15 @@
apiVersion: v1
kind: Pod
metadata:
name: pod-with-logging
namespace: default
annotations:
logging.company.com/enabled: "true" # триггер для injection
spec:
containers:
- name: app
image: nginx:1.25.3
resources:
limits:
memory: "128Mi"
cpu: "100m"

View File

@@ -0,0 +1,16 @@
apiVersion: v1
kind: Pod
metadata:
name: pod-with-monitoring
namespace: default
annotations:
monitoring.company.com/scrape: "true"
monitoring.company.com/port: "8080" # кастомный порт scraping
spec:
containers:
- name: app
image: nginx:1.25.3
resources:
limits:
memory: "128Mi"
cpu: "100m"

View File

@@ -0,0 +1,131 @@
# Урок 3.3 — Продвинутые техники мутации и переменные
## Файлы
| Файл | Описание |
|------|----------|
| `kyverno-global-config.yaml` | ConfigMap с централизованными настройками |
| `set-dynamic-resource-limits.yaml` | Лимиты из ConfigMap по типу сервиса |
| `add-creator-audit-annotation.yaml` | Автоматический audit trail |
## Подготовка
```bash
# Сначала создать ConfigMap с конфигурацией
kubectl apply -f kyverno-global-config.yaml
# Затем политики
kubectl apply -f set-dynamic-resource-limits.yaml
kubectl apply -f add-creator-audit-annotation.yaml
```
## Тест динамических лимитов
```bash
# Pod типа "api" — получит 512Mi/500m
kubectl apply -f - <<EOF
apiVersion: v1
kind: Pod
metadata:
name: api-pod
namespace: default
labels:
service-type: api
spec:
containers:
- name: app
image: nginx:1.25.3
EOF
kubectl get pod api-pod -o jsonpath='{.spec.containers[0].resources}' | jq .
# Ожидаем: limits.memory=512Mi, limits.cpu=500m
# Pod типа "worker" — получит 1Gi/250m
kubectl apply -f - <<EOF
apiVersion: v1
kind: Pod
metadata:
name: worker-pod
namespace: default
labels:
service-type: worker
spec:
containers:
- name: app
image: nginx:1.25.3
EOF
kubectl get pod worker-pod -o jsonpath='{.spec.containers[0].resources}' | jq .
# Ожидаем: limits.memory=1Gi, limits.cpu=250m
# Изменить дефолты без изменения политики
kubectl edit configmap kyverno-global-config -n kyverno
# Измените default_memory: "512Mi", сохраните
# Новые поды получат новые дефолты (~30-60 сек на обновление кэша)
# Удалить тесты
kubectl delete pod api-pod worker-pod
```
## JMESPath функции — шпаргалка
```yaml
# Строки
{{ to_upper('hello') }} # HELLO
{{ to_lower('WORLD') }} # world
{{ trim(' spaces ') }} # spaces
{{ split('a,b,c', ',') }} # ["a","b","c"]
{{ join(['a','b','c'], '-') }} # a-b-c
{{ replace_all('foo/bar', '/', '-') }} # foo-bar
{{ truncate('long string', 5) }} # long
# Числа
{{ to_number('42') }} # 42
{{ divide(`10`, `3`) }} # 3.333
# Условия (Elvis operator)
{{ request.object.metadata.labels.env || 'default' }}
# Массивы
{{ length(request.object.spec.containers) }}
{{ request.object.spec.containers[0].name }}
{{ request.object.spec.containers[].name }} # все имена
# Время
{{ time_now_utc() }} # текущее UTC время
```
## Тест audit аннотаций
```bash
kubectl apply -f - <<EOF
apiVersion: apps/v1
kind: Deployment
metadata:
name: test-audit
namespace: default
spec:
replicas: 1
selector:
matchLabels:
app: test
template:
metadata:
labels:
app: test
spec:
containers:
- name: app
image: nginx:1.25.3
resources:
limits:
memory: "128Mi"
cpu: "100m"
EOF
kubectl get deployment test-audit \
-o jsonpath='{.metadata.annotations}' | jq .
# Увидите: created-by, created-at, user-groups
kubectl delete deployment test-audit
```

View File

@@ -0,0 +1,34 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: add-creator-audit-annotation
annotations:
policies.kyverno.io/title: "Аннотация аудита создателя ресурса"
policies.kyverno.io/category: Governance
policies.kyverno.io/severity: low
policies.kyverno.io/subject: Deployment,StatefulSet
policies.kyverno.io/description: >-
При создании Deployment или StatefulSet автоматически добавляет
аннотации: кто создал, когда, из каких групп.
Создаёт автоматический audit trail без дополнительных инструментов.
spec:
rules:
- name: add-creator-annotation
match:
resources:
kinds:
- Deployment
- StatefulSet
preconditions:
any:
- key: "{{ request.operation }}"
operator: Equals
value: CREATE
mutate:
patchStrategicMerge:
metadata:
annotations:
audit.company.com/created-by: "{{ request.userInfo.username }}"
audit.company.com/created-at: "{{ time_now_utc() }}"
audit.company.com/user-groups: >-
{{ request.userInfo.groups | join(', ', @) }}

View File

@@ -0,0 +1,22 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: kyverno-global-config
namespace: kyverno
labels:
app: kyverno-config
data:
# Дефолтные resource limits по типу сервиса
api_memory: "512Mi"
api_cpu: "500m"
worker_memory: "1Gi"
worker_cpu: "250m"
default_memory: "256Mi"
default_cpu: "250m"
# Node selector defaults
default_node_type: "standard"
region: "eu-west-1"
# Allowed registries (через запятую)
allowed_registries: "registry.company.com,gcr.io/company-project"

View File

@@ -0,0 +1,44 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: set-dynamic-resource-limits
annotations:
policies.kyverno.io/title: "Динамические resource limits из ConfigMap"
policies.kyverno.io/category: Resources
policies.kyverno.io/severity: low
policies.kyverno.io/subject: Pod
policies.kyverno.io/description: >-
Устанавливает resource limits на основе лейбла service-type пода.
Значения берутся из ConfigMap kyverno-global-config в namespace kyverno.
Изменение лимитов — это kubectl edit configmap, не изменение политики.
Лейблы: service-type: api | worker | (default)
spec:
rules:
- name: set-limits-from-config
match:
resources:
kinds:
- Pod
exclude:
resources:
namespaces:
- kube-system
- kyverno
context:
- name: globalConfig
configMap:
name: kyverno-global-config
namespace: kyverno
mutate:
foreach:
- list: "request.object.spec.containers"
patchStrategicMerge:
spec:
containers:
- name: "{{ element.name }}"
resources:
limits:
+(memory): >-
{{ globalConfig.data.\"{{ request.object.metadata.labels.\"service-type\" || 'default' }}_memory\" || '256Mi' }}
+(cpu): >-
{{ globalConfig.data.\"{{ request.object.metadata.labels.\"service-type\" || 'default' }}_cpu\" || '250m' }}

View File

@@ -0,0 +1,96 @@
# Урок 4.1 — Автоматическое создание ConfigMaps и Secrets
## Файлы
| Файл | Триггер | Что создаёт |
|------|---------|-------------|
| `generate-namespace-config.yaml` | Namespace | ConfigMap с мета-данными namespace |
| `generate-default-networkpolicy.yaml` | Namespace | NetworkPolicy deny-all |
| `generate-resource-quota.yaml` | Namespace | ResourceQuota по tier |
| `quota-defaults-configmap.yaml` | — | ConfigMap с квотами (нужен заранее) |
| `clone-registry-secret.yaml` | Namespace | Копия registry Secret |
| `generate-developer-rolebinding.yaml` | Namespace+label | RoleBinding для команды |
| `developer-clusterrole.yaml` | — | ClusterRole developer (нужна заранее) |
## Подготовка (применить до политик)
```bash
# 1. Создать ConfigMap с квотами
kubectl apply -f quota-defaults-configmap.yaml
# 2. Создать ClusterRole developer
kubectl apply -f developer-clusterrole.yaml
# 3. Создать мастер-секрет для registry (замените данные на реальные)
kubectl create secret docker-registry registry-credentials-master \
--docker-server=registry.company.com \
--docker-username=robot-account \
--docker-password=your-token \
--namespace=kyverno
# 4. Применить все политики
kubectl apply -f generate-namespace-config.yaml
kubectl apply -f generate-default-networkpolicy.yaml
kubectl apply -f generate-resource-quota.yaml
kubectl apply -f clone-registry-secret.yaml
kubectl apply -f generate-developer-rolebinding.yaml
```
## Тест: создать namespace и проверить генерацию
```bash
# Создать тестовый namespace
kubectl create namespace test-team \
--dry-run=client -o yaml | \
kubectl apply -f -
kubectl label namespace test-team \
team=platform \
tier=standard \
environment=staging
# Подождать ~5 секунд для генерации, затем проверить
kubectl get configmap,networkpolicy,resourcequota,rolebinding \
-n test-team
# Детали сгенерированных ресурсов
kubectl get configmap namespace-config -n test-team -o yaml
kubectl get networkpolicy default-deny-all -n test-team -o yaml
kubectl get resourcequota default-quota -n test-team -o yaml
```
## Тест synchronize — защита от случайного удаления
```bash
# Удалить NetworkPolicy вручную
kubectl delete networkpolicy default-deny-all -n test-team
# Kyverno восстановит её через несколько секунд
sleep 10
kubectl get networkpolicy -n test-team
# default-deny-all снова присутствует
```
## Тест cascade delete
```bash
# Удалить namespace — все сгенерированные ресурсы удалятся вместе с ним
kubectl delete namespace test-team
# Проверить что ресурсы не зависли
kubectl get generaterequests -n kyverno
```
## Мониторинг GenerateRequests
```bash
# Просмотр очереди генерации
kubectl get generaterequests -n kyverno -o wide
# Детали при ошибке
kubectl describe generaterequests -n kyverno | grep -A 20 "Status:"
# Метрики генерации
kubectl port-forward -n kyverno svc/kyverno-svc-metrics 8000:8000 &
curl -s http://localhost:8000/metrics | grep kyverno_generate
```

View File

@@ -0,0 +1,39 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: clone-registry-secret
annotations:
policies.kyverno.io/title: "Копирование Registry Secret во все Namespace"
policies.kyverno.io/category: Security
policies.kyverno.io/severity: medium
policies.kyverno.io/subject: Namespace
policies.kyverno.io/description: >-
При создании Namespace копирует Secret с кредентиалами реестра
из namespace kyverno (мастер-копия) в новый namespace.
synchronize: true — при обновлении мастер-секрета все копии обновятся.
Создайте мастер-секрет: kubectl create secret docker-registry
registry-credentials-master -n kyverno ...
spec:
rules:
- name: clone-registry-credentials
match:
resources:
kinds:
- Namespace
exclude:
resources:
names:
- kube-system
- kube-public
- kube-node-lease
- kyverno
generate:
apiVersion: v1
kind: Secret
name: registry-credentials
namespace: "{{ request.object.metadata.name }}"
synchronize: true
# clone копирует существующий ресурс вместо создания из шаблона
clone:
namespace: kyverno
name: registry-credentials-master

View File

@@ -0,0 +1,36 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: developer
labels:
app: kyverno-config
rules:
- apiGroups: [""]
resources:
- pods
- pods/log
- pods/exec
- services
- configmaps
- persistentvolumeclaims
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- apiGroups: ["apps"]
resources:
- deployments
- statefulsets
- daemonsets
- replicasets
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- apiGroups: ["batch"]
resources:
- jobs
- cronjobs
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- apiGroups: ["networking.k8s.io"]
resources:
- ingresses
verbs: ["get", "list", "watch", "create", "update", "patch"]
- apiGroups: [""]
resources:
- secrets
verbs: ["get", "list", "watch"] # только чтение секретов

View File

@@ -0,0 +1,51 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: generate-default-networkpolicy
annotations:
policies.kyverno.io/title: "Генерация NetworkPolicy по умолчанию"
policies.kyverno.io/category: Security
policies.kyverno.io/severity: high
policies.kyverno.io/subject: Namespace
policies.kyverno.io/description: >-
При создании Namespace автоматически создаёт NetworkPolicy
"запрети весь трафик по умолчанию". Zero Trust сетевая модель:
команды явно разрешают нужный трафик поверх этой базовой политики.
spec:
rules:
- name: generate-deny-all-networkpolicy
match:
resources:
kinds:
- Namespace
exclude:
resources:
names:
- kube-system
- kube-public
- kube-node-lease
- kyverno
- monitoring
- logging
generate:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
name: default-deny-all
namespace: "{{ request.object.metadata.name }}"
synchronize: true
data:
kind: NetworkPolicy
apiVersion: networking.k8s.io/v1
metadata:
name: default-deny-all
labels:
generated-by: kyverno
annotations:
description: >-
Запрещает весь входящий и исходящий трафик по умолчанию.
Добавьте явные NetworkPolicy для разрешения нужного трафика.
spec:
podSelector: {} # применяется ко всем подам
policyTypes:
- Ingress
- Egress

View File

@@ -0,0 +1,52 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: generate-developer-rolebinding
annotations:
policies.kyverno.io/title: "Генерация RoleBinding для команды"
policies.kyverno.io/category: RBAC
policies.kyverno.io/severity: medium
policies.kyverno.io/subject: Namespace
policies.kyverno.io/description: >-
При создании Namespace с лейблом team: <name> автоматически создаёт
RoleBinding, дающий группе <name>-developers права ClusterRole developer.
Namespace с лейблом team=payments → группа payments-developers получает доступ.
spec:
rules:
- name: generate-team-rolebinding
match:
resources:
kinds:
- Namespace
selector:
matchExpressions:
- key: team
operator: Exists
exclude:
resources:
names:
- kube-system
- kube-public
- kube-node-lease
- kyverno
generate:
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
name: developer-access
namespace: "{{ request.object.metadata.name }}"
synchronize: true
data:
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: developer-access
labels:
generated-by: kyverno
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: developer # ClusterRole должна существовать
subjects:
- kind: Group
name: "{{ request.object.metadata.labels.team }}-developers"
apiGroup: rbac.authorization.k8s.io

View File

@@ -0,0 +1,45 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: generate-namespace-config
annotations:
policies.kyverno.io/title: "Генерация ConfigMap при создании Namespace"
policies.kyverno.io/category: Governance
policies.kyverno.io/severity: low
policies.kyverno.io/subject: Namespace
policies.kyverno.io/description: >-
При создании нового Namespace автоматически создаёт ConfigMap
с базовой конфигурацией. synchronize: true — ConfigMap обновляется
при изменении namespace и удаляется вместе с ним.
spec:
rules:
- name: generate-config
match:
resources:
kinds:
- Namespace
exclude:
resources:
names:
- kube-system
- kube-public
- kube-node-lease
- kyverno
generate:
apiVersion: v1
kind: ConfigMap
name: namespace-config
namespace: "{{ request.object.metadata.name }}"
synchronize: true
data:
kind: ConfigMap
metadata:
labels:
generated-by: kyverno
policy: generate-namespace-config
data:
namespace: "{{ request.object.metadata.name }}"
environment: "{{ request.object.metadata.labels.environment || 'unknown' }}"
team: "{{ request.object.metadata.labels.team || 'unknown' }}"
created-at: "{{ time_now_utc() }}"
documentation: "https://wiki.company.com/kubernetes/namespaces"

View File

@@ -0,0 +1,59 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: generate-resource-quota
annotations:
policies.kyverno.io/title: "Генерация ResourceQuota для Namespace"
policies.kyverno.io/category: Resources
policies.kyverno.io/severity: medium
policies.kyverno.io/subject: Namespace
policies.kyverno.io/description: >-
При создании Namespace генерирует ResourceQuota.
Квота зависит от лейбла tier: standard | premium.
Значения квот берутся из ConfigMap quota-defaults.
spec:
rules:
- name: generate-quota
match:
resources:
kinds:
- Namespace
exclude:
resources:
names:
- kube-system
- kube-public
- kube-node-lease
- kyverno
context:
- name: quotaConfig
configMap:
name: quota-defaults
namespace: kyverno
generate:
apiVersion: v1
kind: ResourceQuota
name: default-quota
namespace: "{{ request.object.metadata.name }}"
synchronize: true
data:
kind: ResourceQuota
apiVersion: v1
metadata:
name: default-quota
labels:
generated-by: kyverno
spec:
hard:
# Квота CPU зависит от tier namespace
requests.cpu: >-
{{ quotaConfig.data.\"{{ request.object.metadata.labels.tier || 'standard' }}_cpu_request\" || '4' }}
requests.memory: >-
{{ quotaConfig.data.\"{{ request.object.metadata.labels.tier || 'standard' }}_memory_request\" || '8Gi' }}
limits.cpu: >-
{{ quotaConfig.data.\"{{ request.object.metadata.labels.tier || 'standard' }}_cpu_limit\" || '8' }}
limits.memory: >-
{{ quotaConfig.data.\"{{ request.object.metadata.labels.tier || 'standard' }}_memory_limit\" || '16Gi' }}
pods: "50"
services: "20"
persistentvolumeclaims: "10"

View File

@@ -0,0 +1,25 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: quota-defaults
namespace: kyverno
labels:
app: kyverno-config
data:
# Standard tier (по умолчанию)
standard_cpu_request: "4"
standard_memory_request: "8Gi"
standard_cpu_limit: "8"
standard_memory_limit: "16Gi"
# Premium tier (для нагруженных сервисов)
premium_cpu_request: "16"
premium_memory_request: "32Gi"
premium_cpu_limit: "32"
premium_memory_limit: "64Gi"
# Dev tier (для разработки)
dev_cpu_request: "2"
dev_memory_request: "4Gi"
dev_cpu_limit: "4"
dev_memory_limit: "8Gi"

View File

@@ -0,0 +1,106 @@
# Урок 4.2 — Управление жизненным циклом генерируемых ресурсов
## Файлы
| Файл | Описание |
|------|----------|
| `cleanup-debug-pods.yaml` | CleanupPolicy — автоудаление debug подов старше 4ч |
## Параметр synchronize — детально
```yaml
generate:
synchronize: true # живая ссылка: обновляется, восстанавливается, удаляется вместе
synchronize: false # создать один раз, дальше независимо
```
| Событие | synchronize: true | synchronize: false |
|---------|-------------------|-------------------|
| Источник изменился | Генерируемый обновляется | Без изменений |
| Генерируемый удалён | Восстанавливается | Не восстанавливается |
| Источник удалён | Генерируемый удаляется | Остаётся |
## Применение CleanupPolicy
```bash
# CleanupPolicy требует Kyverno >= 1.9
kubectl apply -f cleanup-debug-pods.yaml
# Проверить статус
kubectl get clustercleanuppolicies
```
## Тест CleanupPolicy
```bash
# Создать debug-под
kubectl run debug-test \
--image=busybox:1.36 \
--labels="purpose=debug" \
--restart=Never \
-- sleep 3600
# Принудительно запустить cleanup (изменив schedule на ближайшую минуту)
# Или подождать час и проверить:
kubectl get pod debug-test
# Проверить логи cleanup controller
kubectl logs -n kyverno \
-l app.kubernetes.io/component=cleanup-controller \
--tail=30
```
## Диагностика GenerateRequests
```bash
# Все GenerateRequests
kubectl get generaterequests -n kyverno
# Зависшие в Processing статусе
kubectl get generaterequests -n kyverno \
-o jsonpath='{range .items[?(@.status.state=="Failed")]}{.metadata.name}{"\n"}{end}'
# Детали конкретного
kubectl describe generaterequest <name> -n kyverno
# Типичные причины ошибок:
# 1. Нет прав у Kyverno ServiceAccount
kubectl get clusterrolebinding | grep kyverno
# 2. Ресурс уже существует (не управляется Kyverno)
# 3. Синтаксическая ошибка в шаблоне generate
```
## Взять под управление существующий ресурс
```bash
# Если ресурс уже существует и нужно передать его Kyverno:
kubectl label configmap namespace-config \
-n my-namespace \
kyverno.io/managed=true
# Или удалить ресурс, Kyverno пересоздаст его
kubectl delete configmap namespace-config -n my-namespace
# Kyverno создаст новый через background controller
```
## Bootstrap Namespace — полный сценарий
```bash
# Одна команда создаёт полностью настроенный namespace:
kubectl create namespace payments-service
kubectl label namespace payments-service \
team=payments \
tier=premium \
environment=production
# Через ~10 секунд:
kubectl get all,configmap,secret,networkpolicy,resourcequota,rolebinding \
-n payments-service
# Ожидаемый результат:
# configmap/namespace-config
# networkpolicy/default-deny-all
# resourcequota/default-quota (с premium квотами)
# secret/registry-credentials
# rolebinding/developer-access (для группы payments-developers)
```

View File

@@ -0,0 +1,29 @@
apiVersion: kyverno.io/v2alpha1
kind: ClusterCleanupPolicy
metadata:
name: cleanup-debug-pods
annotations:
policies.kyverno.io/title: "Автоудаление debug подов"
policies.kyverno.io/category: Governance
policies.kyverno.io/severity: low
policies.kyverno.io/description: >-
Удаляет поды с лейблом purpose=debug старше 4 часов.
Запускается каждый час по расписанию.
Предотвращает накопление забытых отладочных подов в кластере.
spec:
schedule: "0 * * * *" # каждый час в :00
match:
resources:
kinds:
- Pod
namespaces:
- default
- staging
selector:
matchLabels:
purpose: debug
conditions:
any:
- key: "{{ time_since('', request.object.metadata.creationTimestamp, '') }}"
operator: GreaterThan
value: "4h"

View File

@@ -0,0 +1,134 @@
# Урок 5.1 — Переменные окружения и ConfigMaps
## Файлы
| Файл | Описание |
|------|----------|
| `kyverno-global-config.yaml` | Централизованный ConfigMap с настройками |
| `set-resource-limits-from-config.yaml` | Мутация: лимиты из ConfigMap |
| `inherit-namespace-labels.yaml` | Мутация: лейблы из Namespace (через apiCall) |
## Применение
```bash
# Сначала ConfigMap
kubectl apply -f kyverno-global-config.yaml
# Затем политики
kubectl apply -f set-resource-limits-from-config.yaml
kubectl apply -f inherit-namespace-labels.yaml
```
## Типы источников переменных в Kyverno
```yaml
# 1. Данные запроса (всегда доступны)
{{ request.object.metadata.name }}
{{ request.object.metadata.namespace }}
{{ request.operation }} # CREATE | UPDATE | DELETE
{{ request.userInfo.username }}
{{ request.userInfo.groups }}
# 2. ConfigMap
context:
- name: myConfig
configMap:
name: my-config
namespace: kyverno
# Использование:
{{ myConfig.data.my-key }}
# 3. Прямой API вызов
context:
- name: nsInfo
apiCall:
urlPath: "/api/v1/namespaces/{{ request.object.metadata.namespace }}"
jmesPath: "metadata.labels"
# Использование:
{{ nsInfo.environment }}
# 4. Встроенная переменная
context:
- name: defaults
variable:
value:
memory: "256Mi"
cpu: "250m"
# Использование:
{{ defaults.memory }}
# 5. Генерируемые значения
{{ random('[0-9]{6}') }} # случайные 6 цифр
{{ time_now_utc() }} # текущее UTC время
```
## Тест переменных из ConfigMap
```bash
# Создать под — должны примениться лимиты из ConfigMap
kubectl apply -f - <<EOF
apiVersion: v1
kind: Pod
metadata:
name: test-config-vars
namespace: default
spec:
containers:
- name: app
image: nginx:1.25.3
EOF
# Проверить лимиты
kubectl get pod test-config-vars \
-o jsonpath='{.spec.containers[0].resources}' | jq .
# Ожидаем: limits.memory=512Mi, limits.cpu=500m (из ConfigMap)
# Изменить дефолты в ConfigMap и подождать ~60 секунд
kubectl patch configmap kyverno-global-config -n kyverno \
--type merge \
-p '{"data":{"default.memory.limit":"1Gi"}}'
# Новый под получит 1Gi
kubectl run test-after-change --image=nginx:1.25.3 --restart=Never
kubectl get pod test-after-change \
-o jsonpath='{.spec.containers[0].resources.limits.memory}'
# Ожидаем: 1Gi
# Уборка
kubectl delete pod test-config-vars test-after-change
```
## Тест наследования лейблов из Namespace
```bash
# Создать namespace с лейблами
kubectl create namespace labeled-ns
kubectl label namespace labeled-ns environment=production team=backend
# Создать под в этом namespace
kubectl run test-pod --image=nginx:1.25.3 \
--restart=Never -n labeled-ns
# Проверить унаследованные лейблы
kubectl get pod test-pod -n labeled-ns \
-o jsonpath='{.metadata.labels}' | jq .
# Ожидаем: environment=production, team=backend
kubectl delete pod test-pod -n labeled-ns
kubectl delete namespace labeled-ns
```
## Дефолтные значения (защита от отсутствующих полей)
```yaml
# Опасно: упадёт если лейбл не задан
{{ request.object.metadata.labels.env }}
# Безопасно: вернёт "unknown" если лейбл отсутствует
{{ request.object.metadata.labels.env || 'unknown' }}
# Цепочка дефолтов
{{ request.object.metadata.labels.region ||
globalConfig.data.default_region ||
'us-east-1' }}
```

View File

@@ -0,0 +1,36 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: inherit-namespace-labels
annotations:
policies.kyverno.io/title: "Наследование лейблов из Namespace"
policies.kyverno.io/category: Governance
policies.kyverno.io/severity: low
policies.kyverno.io/subject: Pod
policies.kyverno.io/description: >-
Копирует лейблы environment и team из Namespace в Pod.
Позволяет автоматически тегировать поды данными их namespace
без ручного дублирования в каждом манифесте.
spec:
rules:
- name: copy-namespace-labels
match:
resources:
kinds:
- Pod
exclude:
resources:
namespaces:
- kube-system
- kyverno
context:
- name: nsLabels
apiCall:
urlPath: "/api/v1/namespaces/{{ request.object.metadata.namespace }}"
jmesPath: "metadata.labels"
mutate:
patchStrategicMerge:
metadata:
labels:
+(environment): "{{ nsLabels.environment || 'unknown' }}"
+(team): "{{ nsLabels.team || 'unknown' }}"

View File

@@ -0,0 +1,27 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: kyverno-global-config
namespace: kyverno
labels:
app: kyverno-config
data:
# --- Resource defaults ---
default.memory.request: "128Mi"
default.memory.limit: "512Mi"
default.cpu.request: "100m"
default.cpu.limit: "500m"
# --- Registry ---
allowed.registries: "registry.company.com,gcr.io/company-project"
# --- Environment ---
production.replicas.minimum: "2"
staging.replicas.minimum: "1"
# --- Required labels (через запятую) ---
required.labels: "app,team,environment"
# --- Node settings ---
default.node.type: "standard"
default.region: "eu-west-1"

View File

@@ -0,0 +1,44 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: set-resource-limits-from-config
annotations:
policies.kyverno.io/title: "Resource limits из централизованного ConfigMap"
policies.kyverno.io/category: Resources
policies.kyverno.io/severity: low
policies.kyverno.io/subject: Pod
policies.kyverno.io/description: >-
Устанавливает дефолтные resource limits из ConfigMap kyverno-global-config.
Изменение лимитов для всего кластера — это kubectl edit configmap,
а не изменение и деплой политики.
spec:
rules:
- name: set-limits-from-configmap
match:
resources:
kinds:
- Pod
exclude:
resources:
namespaces:
- kube-system
- kyverno
context:
- name: globalConfig
configMap:
name: kyverno-global-config
namespace: kyverno
mutate:
foreach:
- list: "request.object.spec.containers"
patchStrategicMerge:
spec:
containers:
- name: "{{ element.name }}"
resources:
requests:
+(memory): "{{ globalConfig.data.\"default.memory.request\" }}"
+(cpu): "{{ globalConfig.data.\"default.cpu.request\" }}"
limits:
+(memory): "{{ globalConfig.data.\"default.memory.limit\" }}"
+(cpu): "{{ globalConfig.data.\"default.cpu.limit\" }}"

View File

@@ -0,0 +1,127 @@
# Урок 5.2 — Контекстные данные и информация о кластере
## Файлы
| Файл | Описание |
|------|----------|
| `deployment-freeze-config.yaml` | ConfigMap для управления freeze |
| `restrict-deploys-during-freeze.yaml` | Блокировка деплоев во время freeze |
| `restrict-privileged-non-admins.yaml` | Привилегии только для admin-групп |
## Применение
```bash
kubectl apply -f deployment-freeze-config.yaml
kubectl apply -f restrict-deploys-during-freeze.yaml
kubectl apply -f restrict-privileged-non-admins.yaml
```
## Тест deployment freeze
```bash
# Убедиться что freeze выключен
kubectl get configmap deployment-freeze-config -n kyverno \
-o jsonpath='{.data.freeze_active}'
# Должно быть: false
# Создать тестовый deployment — должен пройти
kubectl create deployment test-deploy \
--image=nginx:1.25.3 --replicas=1
# Активировать freeze
kubectl patch configmap deployment-freeze-config -n kyverno \
--type merge \
-p '{"data":{"freeze_active":"true"}}'
# Подождать обновления кэша (~60 сек), затем попробовать деплой
kubectl create deployment blocked-deploy \
--image=nginx:1.25.3 --replicas=1
# Ожидаем ошибку: Деплойменты заморожены...
# Деактивировать freeze
kubectl patch configmap deployment-freeze-config -n kyverno \
--type merge \
-p '{"data":{"freeze_active":"false"}}'
# Уборка
kubectl delete deployment test-deploy 2>/dev/null || true
```
## Доступные контекстные данные запроса
```yaml
# Информация о ресурсе
request.object.metadata.name
request.object.metadata.namespace
request.object.metadata.labels
request.object.metadata.annotations
# Информация об операции
request.operation # CREATE | UPDATE | DELETE | CONNECT
request.dryRun # true при --dry-run=server
# Старый объект (только при UPDATE)
request.oldObject.spec.replicas
# Информация о пользователе
request.userInfo.username
request.userInfo.uid
request.userInfo.groups # список групп
request.userInfo.extra # дополнительные атрибуты
# Пример: разные правила для CREATE vs UPDATE
preconditions:
any:
- key: "{{ request.operation }}"
operator: Equals
value: CREATE
```
## Тест userInfo — проверка групп
```bash
# Посмотреть свои группы в кластере
kubectl auth whoami
# Попробовать создать привилегированный под (должен быть отклонён)
kubectl apply -f - <<EOF
apiVersion: v1
kind: Pod
metadata:
name: test-privileged
namespace: default
spec:
containers:
- name: app
image: nginx:1.25.3
securityContext:
privileged: true
resources:
limits:
memory: "128Mi"
cpu: "100m"
EOF
# Ожидаем: error... не имеет права создавать привилегированные контейнеры
```
## Cross-resource проверка через apiCall
```yaml
# Читаем лейблы namespace во время обработки Pod запроса
context:
- name: nsLabels
apiCall:
urlPath: "/api/v1/namespaces/{{ request.object.metadata.namespace }}"
jmesPath: "metadata.labels"
# Читаем список всех Deployments в namespace
context:
- name: existingDeployments
apiCall:
urlPath: "/apis/apps/v1/namespaces/{{ request.object.metadata.namespace }}/deployments"
jmesPath: "items[].metadata.name"
# ВАЖНО: каждый apiCall = HTTP запрос к API Server.
# Используйте с умом — это влияет на латентность admission.
# Для часто используемых данных предпочитайте ConfigMap.
```

View File

@@ -0,0 +1,13 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: deployment-freeze-config
namespace: kyverno
labels:
app: kyverno-config
data:
# Установите freeze_active: "true" для блокировки деплойментов
freeze_active: "false"
freeze_start: "2024-12-24T00:00:00Z"
freeze_end: "2025-01-02T09:00:00Z"
freeze_reason: "Новогодний feature freeze. Экстренные деплои — через oncall."

View File

@@ -0,0 +1,52 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: restrict-deploys-during-freeze
annotations:
policies.kyverno.io/title: "Блокировка деплойментов во время freeze"
policies.kyverno.io/category: Governance
policies.kyverno.io/severity: high
policies.kyverno.io/subject: Deployment
policies.kyverno.io/description: >-
Блокирует создание новых Deployments когда в ConfigMap
deployment-freeze-config поле freeze_active=true.
Пользователи группы emergency-deployers и system:masters
могут деплоить даже во время freeze.
Активируйте freeze: kubectl patch configmap deployment-freeze-config
-n kyverno --type merge -p '{"data":{"freeze_active":"true"}}'
spec:
rules:
- name: check-freeze
match:
resources:
kinds:
- Deployment
preconditions:
any:
- key: "{{ request.operation }}"
operator: Equals
value: CREATE
context:
- name: freezeConfig
configMap:
name: deployment-freeze-config
namespace: kyverno
validate:
message: >-
Деплойменты заморожены с {{ freezeConfig.data.freeze_start }}
до {{ freezeConfig.data.freeze_end }}.
Причина: {{ freezeConfig.data.freeze_reason }}
Для экстренного деплоя обратитесь к oncall инженеру.
deny:
conditions:
all:
# Freeze активен
- key: "{{ freezeConfig.data.freeze_active }}"
operator: Equals
value: "true"
# Пользователь НЕ в группе экстренного деплоя
- key: "{{ request.userInfo.groups }}"
operator: AllNotIn
value:
- emergency-deployers
- system:masters

View File

@@ -0,0 +1,45 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: restrict-privileged-for-non-admins
annotations:
policies.kyverno.io/title: "Привилегированные контейнеры — только для администраторов"
policies.kyverno.io/category: Security
policies.kyverno.io/severity: high
policies.kyverno.io/subject: Pod
policies.kyverno.io/description: >-
Запрещает создание привилегированных контейнеров для обычных пользователей.
Администраторы (system:masters, cluster-admins) могут создавать
привилегированные поды для системных нужд.
Использует request.userInfo для проверки прав запрашивающего.
spec:
rules:
- name: restrict-privileged-non-admin
match:
resources:
kinds:
- Pod
# Precondition: применять только если пользователь НЕ администратор
preconditions:
all:
- key: "system:masters"
operator: NotIn
value: "{{ request.userInfo.groups }}"
- key: "cluster-admins"
operator: NotIn
value: "{{ request.userInfo.groups }}"
validate:
message: >-
Пользователь '{{ request.userInfo.username }}' не имеет права
создавать привилегированные контейнеры.
Обратитесь к cluster-admin для выполнения системных задач.
foreach:
- list: >-
request.object.spec.containers[] |
merge(request.object.spec.initContainers[] || `[]`, @)
deny:
conditions:
any:
- key: "{{ element.securityContext.privileged }}"
operator: Equals
value: true

View File

@@ -0,0 +1,128 @@
# Урок 5.3 — Переиспользуемые шаблоны политик (Helm Chart)
## Структура
```
kyverno-policies/
├── Chart.yaml
├── values.yaml # дефолтные значения
├── values-staging.yaml # overrides для staging
├── values-production.yaml # overrides для production
├── templates/
│ ├── require-resource-limits.yaml
│ ├── disallow-privileged.yaml
│ ├── require-labels.yaml
│ └── generate-networkpolicy.yaml
└── tests/
├── kyverno-test.yaml
└── resources/
├── pod-compliant.yaml
├── pod-no-limits.yaml
├── pod-privileged.yaml
└── deployment-no-labels.yaml
```
## Запуск тестов (без кластера)
```bash
cd kyverno-policies
# Сгенерировать манифесты из шаблонов и протестировать
helm template . -f values.yaml | \
kyverno apply - --resource tests/resources/ --table
# Запустить встроенные kyverno тесты
# (сначала нужно отрендерить шаблоны в обычные YAML)
helm template . -f values.yaml --output-dir /tmp/rendered-policies
kyverno test tests/ \
--policy-report \
--detailed-results
```
## Деплой в кластер
```bash
# Staging — всё в Audit
helm upgrade --install kyverno-policies . \
--namespace kyverno \
--create-namespace \
-f values-staging.yaml
# Production — Enforce
helm upgrade --install kyverno-policies . \
--namespace kyverno \
--create-namespace \
-f values-production.yaml
# Проверить задеплоенные политики
kubectl get clusterpolicies -l helm.sh/chart=kyverno-policies-1.0.0
# Rollback если что-то пошло не так
helm rollback kyverno-policies 1 -n kyverno
```
## PolicyException — управляемые исключения
```bash
# Применить временное исключение для legacy-сервиса
kubectl apply -f - <<EOF
apiVersion: kyverno.io/v2beta1
kind: PolicyException
metadata:
name: legacy-app-exception
namespace: production
annotations:
exception.company.com/expires: "2025-03-01"
exception.company.com/reason: "Migration in progress — JIRA-1234"
exception.company.com/approved-by: "platform-team"
spec:
exceptions:
- policyName: require-resource-limits
ruleNames:
- check-container-limits
match:
resources:
kinds:
- Pod
namespaces:
- production
names:
- legacy-app-*
EOF
# Посмотреть все активные исключения
kubectl get policyexceptions -A
```
## CI/CD для Helm chart политик
```bash
# Линтинг
helm lint . -f values.yaml
helm lint . -f values-production.yaml
# Dry-run деплой
helm upgrade --install kyverno-policies . \
--namespace kyverno \
-f values-production.yaml \
--dry-run
# diff перед применением (требует helm-diff plugin)
helm plugin install https://github.com/databus23/helm-diff
helm diff upgrade kyverno-policies . \
--namespace kyverno \
-f values-production.yaml
```
## Версионирование изменений
```yaml
# При изменении политики — обновляйте версию в Chart.yaml
# и добавляйте запись в аннотацию политики:
annotations:
policies.kyverno.io/version: "1.1.0"
policies.kyverno.io/changelog: |
1.1.0: Добавлена проверка ephemeral containers
1.0.0: Начальная версия
```

View File

@@ -0,0 +1,16 @@
apiVersion: v2
name: kyverno-policies
description: >-
Корпоративные политики безопасности Kyverno.
Содержит базовый набор validation, mutation и generation политик.
type: application
version: 1.0.0
appVersion: "1.11"
keywords:
- kyverno
- policy
- security
- kubernetes
maintainers:
- name: Platform Team
email: platform@company.com

View File

@@ -0,0 +1,42 @@
{{- if .Values.disallowPrivileged.enabled }}
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/version: {{ .Chart.Version }}
helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version }}
spec:
validationFailureAction: {{ .Values.disallowPrivileged.failureAction | default .Values.global.failureAction }}
background: true
rules:
- name: privileged-containers
match:
resources:
kinds:
- Pod
exclude:
resources:
namespaces:
{{- range .Values.global.excludedNamespaces }}
- {{ . }}
{{- end }}
validate:
message: >-
Контейнер '{{ "{{" }} element.name {{ "}}" }}' имеет privileged: true.
Привилегированные контейнеры запрещены политикой безопасности.
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
{{- end }}

View File

@@ -0,0 +1,44 @@
{{- if .Values.generateNetworkPolicy.enabled }}
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: generate-default-networkpolicy
annotations:
policies.kyverno.io/title: "Генерация NetworkPolicy по умолчанию"
policies.kyverno.io/category: Security
policies.kyverno.io/severity: high
policies.kyverno.io/version: {{ .Chart.Version }}
helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version }}
spec:
rules:
- name: generate-deny-all
match:
resources:
kinds:
- Namespace
exclude:
resources:
names:
{{- range (concat .Values.global.excludedNamespaces .Values.generateNetworkPolicy.excludedNamespaces) | uniq }}
- {{ . }}
{{- end }}
generate:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
name: default-deny-all
namespace: "{{ "{{" }} request.object.metadata.name {{ "}}" }}"
synchronize: true
data:
kind: NetworkPolicy
apiVersion: networking.k8s.io/v1
metadata:
name: default-deny-all
labels:
generated-by: kyverno
helm-release: {{ .Release.Name }}
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
{{- end }}

View File

@@ -0,0 +1,40 @@
{{- if .Values.requiredLabels.enabled }}
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/version: {{ .Chart.Version }}
helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version }}
spec:
validationFailureAction: {{ .Values.requiredLabels.failureAction | default .Values.global.failureAction }}
background: true
rules:
- name: check-required-labels
match:
resources:
kinds:
- Deployment
- StatefulSet
- DaemonSet
exclude:
resources:
namespaces:
{{- range .Values.global.excludedNamespaces }}
- {{ . }}
{{- end }}
validate:
message: >-
Ресурс '{{ "{{" }} request.object.metadata.name {{ "}}" }}'
должен иметь все обязательные лейблы:
{{ .Values.requiredLabels.labels | join ", " }}
pattern:
metadata:
labels:
{{- range .Values.requiredLabels.labels }}
{{ . }}: "?*"
{{- end }}
{{- end }}

View File

@@ -0,0 +1,41 @@
{{- if .Values.resourceLimits.enabled }}
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/version: {{ .Chart.Version }}
helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version }}
spec:
validationFailureAction: {{ .Values.resourceLimits.failureAction | default .Values.global.failureAction }}
background: true
rules:
- name: check-container-limits
match:
resources:
kinds:
- Pod
exclude:
resources:
namespaces:
{{- range .Values.global.excludedNamespaces }}
- {{ . }}
{{- end }}
validate:
message: >-
Контейнер '{{ "{{" }} element.name {{ "}}" }}' не имеет resource limits.
Добавьте resources.limits.memory и resources.limits.cpu.
foreach:
- list: >-
request.object.spec.containers[] |
merge(request.object.spec.initContainers[] || `[]`, @) |
merge(request.object.spec.ephemeralContainers[] || `[]`, @)
pattern:
resources:
limits:
memory: "?*"
cpu: "?*"
{{- end }}

View File

@@ -0,0 +1,31 @@
name: kyverno-policies-chart-tests
policies:
- ../templates/require-resource-limits.yaml
- ../templates/disallow-privileged.yaml
- ../templates/require-labels.yaml
resources:
- resources/pod-compliant.yaml
- resources/pod-no-limits.yaml
- resources/pod-privileged.yaml
- resources/deployment-no-labels.yaml
results:
- policy: require-resource-limits
rule: check-container-limits
resource: pod-compliant
namespace: default
result: pass
- policy: require-resource-limits
rule: check-container-limits
resource: pod-no-limits
namespace: default
result: fail
- policy: disallow-privileged-containers
rule: privileged-containers
resource: pod-privileged
namespace: default
result: fail
- policy: require-standard-labels
rule: check-required-labels
resource: deployment-no-labels
namespace: default
result: fail

View File

@@ -0,0 +1,23 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: deployment-no-labels
namespace: default
# нет лейблов app, team, environment
spec:
replicas: 1
selector:
matchLabels:
run: app
template:
metadata:
labels:
run: app
spec:
containers:
- name: app
image: nginx:1.25.3
resources:
limits:
memory: "128Mi"
cpu: "100m"

View File

@@ -0,0 +1,16 @@
apiVersion: v1
kind: Pod
metadata:
name: pod-compliant
namespace: default
spec:
containers:
- name: app
image: nginx:1.25.3
resources:
requests:
memory: "64Mi"
cpu: "50m"
limits:
memory: "128Mi"
cpu: "100m"

View File

@@ -0,0 +1,9 @@
apiVersion: v1
kind: Pod
metadata:
name: pod-no-limits
namespace: default
spec:
containers:
- name: app
image: nginx:1.25.3

View File

@@ -0,0 +1,15 @@
apiVersion: v1
kind: Pod
metadata:
name: pod-privileged
namespace: default
spec:
containers:
- name: app
image: nginx:1.25.3
securityContext:
privileged: true
resources:
limits:
memory: "128Mi"
cpu: "100m"

View File

@@ -0,0 +1,21 @@
# Production overrides
global:
failureAction: Enforce
resourceLimits:
enabled: true
maxMemory: "8Gi"
maxCpu: "8"
disallowLatestTag:
enabled: true
failureAction: Enforce
requiredLabels:
enabled: true
failureAction: Enforce
labels:
- app
- team
- environment
- version

View File

@@ -0,0 +1,10 @@
# Staging overrides — всё в Audit для наблюдения
global:
failureAction: Audit
disallowPrivileged:
enabled: true
failureAction: Audit # в staging даже security в Audit
generateNetworkPolicy:
enabled: false # в staging не генерируем NetworkPolicy

View File

@@ -0,0 +1,55 @@
# Дефолтные значения для kyverno-policies Helm chart.
# Переопределите в values-staging.yaml или values-production.yaml.
global:
# Audit = фиксировать нарушения, Enforce = блокировать
failureAction: Audit
excludedNamespaces:
- kube-system
- kube-public
- kube-node-lease
- kyverno
# --- Политика: resource limits ---
resourceLimits:
enabled: true
failureAction: "" # пусто = использовать global.failureAction
maxMemory: "4Gi"
maxCpu: "4"
# --- Политика: запрет latest тега ---
disallowLatestTag:
enabled: true
failureAction: ""
# --- Политика: обязательные лейблы ---
requiredLabels:
enabled: true
failureAction: ""
labels:
- app
- team
- environment
# --- Политика: привилегированные контейнеры ---
disallowPrivileged:
enabled: true
# security политики всегда Enforce
failureAction: Enforce
# --- Политика: запрет host namespaces ---
disallowHostNamespaces:
enabled: true
failureAction: Enforce
# --- Политика: автодобавление лейблов (mutation) ---
addStandardLabels:
enabled: true
# --- Политика: генерация NetworkPolicy ---
generateNetworkPolicy:
enabled: true
excludedNamespaces:
- monitoring
- logging
- istio-system

View File

@@ -0,0 +1,134 @@
# Урок 6.1 — Логирование и мониторинг Kyverno
## Файлы
| Файл | Описание |
|------|----------|
| `grafana-dashboard.json` | Готовый Grafana dashboard |
| `../03-reporting/prometheus-alert-rules.yaml` | Alerting правила |
| `../03-reporting/service-monitor.yaml` | ServiceMonitor |
## Настройка уровня логирования
```bash
# Изменить уровень логирования без перезапуска (через Helm upgrade)
helm upgrade kyverno kyverno/kyverno \
--namespace kyverno \
--reuse-values \
--set admissionController.extraArgs="{--v=4\,--loggingFormat=json}"
# Уровни:
# --v=1 production (warnings + important events)
# --v=2 informational (policy apply/deny events)
# --v=4 debug (full AdmissionReview)
# --v=6 trace (очень детально, только для отладки)
```
## Просмотр логов
```bash
# Логи admission controller
kubectl logs -n kyverno \
-l app.kubernetes.io/component=admission-controller \
--tail=100 -f
# Логи background controller (для generate политик)
kubectl logs -n kyverno \
-l app.kubernetes.io/component=background-controller \
--tail=50
# Фильтрация — только нарушения
kubectl logs -n kyverno \
-l app.kubernetes.io/component=admission-controller \
--tail=200 | grep '"result":"fail"'
# Фильтрация — конкретная политика
kubectl logs -n kyverno \
-l app.kubernetes.io/component=admission-controller \
--tail=200 | grep '"policy":"require-resource-limits"'
```
## Метрики через port-forward
```bash
# Открыть порт metrics
kubectl port-forward -n kyverno \
svc/kyverno-svc-metrics 8000:8000 &
# Все метрики
curl -s http://localhost:8000/metrics | grep kyverno_ | head -40
# Конкретная метрика
curl -s http://localhost:8000/metrics | \
grep kyverno_policy_results_total
# Топ метрик по нарушениям
curl -s http://localhost:8000/metrics | \
grep 'kyverno_policy_results_total.*rule_result="fail"'
# Остановить port-forward
kill %1
```
## Импорт Grafana Dashboard
```bash
# Способ 1: через UI
# Grafana → Dashboards → Import → Upload JSON file → grafana-dashboard.json
# Способ 2: через ConfigMap (если используете Grafana Operator)
kubectl create configmap kyverno-dashboard \
--from-file=grafana-dashboard.json \
--namespace=monitoring
kubectl label configmap kyverno-dashboard \
-n monitoring \
grafana_dashboard=1
```
## Ключевые PromQL запросы
```promql
# Compliance rate (цель: 100%)
sum(rate(kyverno_policy_results_total{rule_result="pass"}[5m])) /
sum(rate(kyverno_policy_results_total[5m])) * 100
# p95 латентность в миллисекундах
histogram_quantile(0.95,
sum(rate(kyverno_admission_review_duration_seconds_bucket[5m])) by (le)
) * 1000
# Нарушений за сутки по политикам
topk(10, sum by(policy_name)(
increase(kyverno_policy_results_total{rule_result="fail"}[24h])
))
# Нарушений за сутки по namespace
sum by(resource_namespace)(
increase(kyverno_policy_results_total{rule_result="fail"}[24h])
)
# Процент ошибок обработки
rate(kyverno_admission_requests_total{admission_request_type="error"}[5m]) /
rate(kyverno_admission_requests_total[5m]) * 100
# CPU throttling Kyverno
rate(container_cpu_cfs_throttled_seconds_total{
namespace="kyverno",
container=~"kyverno.*"
}[5m])
```
## События Kubernetes
```bash
# PolicyViolation события
kubectl get events -A \
--field-selector reason=PolicyViolation \
--sort-by='.lastTimestamp'
# События от Kyverno
kubectl get events -A \
--field-selector source.component=kyverno-admission \
--sort-by='.lastTimestamp' | tail -20
```

View File

@@ -0,0 +1,130 @@
{
"title": "Kyverno Policy Dashboard",
"uid": "kyverno-main",
"tags": ["kyverno", "policy", "security"],
"timezone": "browser",
"refresh": "30s",
"panels": [
{
"id": 1,
"title": "Policy Compliance Rate",
"type": "stat",
"gridPos": {"h": 4, "w": 6, "x": 0, "y": 0},
"targets": [
{
"expr": "sum(rate(kyverno_policy_results_total{rule_result=\"pass\"}[5m])) / sum(rate(kyverno_policy_results_total[5m])) * 100",
"legendFormat": "Compliance %"
}
],
"options": {
"reduceOptions": {"calcs": ["lastNotNull"]},
"thresholds": {
"steps": [
{"color": "red", "value": 0},
{"color": "yellow", "value": 90},
{"color": "green", "value": 99}
]
}
}
},
{
"id": 2,
"title": "Admission Requests/s",
"type": "stat",
"gridPos": {"h": 4, "w": 6, "x": 6, "y": 0},
"targets": [
{
"expr": "sum(rate(kyverno_admission_requests_total[5m]))",
"legendFormat": "Requests/s"
}
]
},
{
"id": 3,
"title": "Admission Latency p95 (ms)",
"type": "stat",
"gridPos": {"h": 4, "w": 6, "x": 12, "y": 0},
"targets": [
{
"expr": "histogram_quantile(0.95, sum(rate(kyverno_admission_review_duration_seconds_bucket[5m])) by (le)) * 1000",
"legendFormat": "p95 latency"
}
],
"options": {
"thresholds": {
"steps": [
{"color": "green", "value": 0},
{"color": "yellow", "value": 100},
{"color": "red", "value": 500}
]
}
}
},
{
"id": 4,
"title": "Total Violations (24h)",
"type": "stat",
"gridPos": {"h": 4, "w": 6, "x": 18, "y": 0},
"targets": [
{
"expr": "sum(increase(kyverno_policy_results_total{rule_result=\"fail\"}[24h]))",
"legendFormat": "Violations"
}
],
"options": {
"thresholds": {
"steps": [
{"color": "green", "value": 0},
{"color": "yellow", "value": 10},
{"color": "red", "value": 100}
]
}
}
},
{
"id": 5,
"title": "Top Violated Policies (1h)",
"type": "bargauge",
"gridPos": {"h": 8, "w": 12, "x": 0, "y": 4},
"targets": [
{
"expr": "topk(10, sum by(policy_name)(increase(kyverno_policy_results_total{rule_result=\"fail\"}[1h])))",
"legendFormat": "{{policy_name}}"
}
]
},
{
"id": 6,
"title": "Violations by Namespace (24h)",
"type": "table",
"gridPos": {"h": 8, "w": 12, "x": 12, "y": 4},
"targets": [
{
"expr": "sort_desc(sum by(resource_namespace)(increase(kyverno_policy_results_total{rule_result=\"fail\"}[24h])))",
"legendFormat": "{{resource_namespace}}",
"instant": true
}
]
},
{
"id": 7,
"title": "Admission Latency over time",
"type": "timeseries",
"gridPos": {"h": 8, "w": 24, "x": 0, "y": 12},
"targets": [
{
"expr": "histogram_quantile(0.50, sum(rate(kyverno_admission_review_duration_seconds_bucket[5m])) by (le)) * 1000",
"legendFormat": "p50"
},
{
"expr": "histogram_quantile(0.95, sum(rate(kyverno_admission_review_duration_seconds_bucket[5m])) by (le)) * 1000",
"legendFormat": "p95"
},
{
"expr": "histogram_quantile(0.99, sum(rate(kyverno_admission_review_duration_seconds_bucket[5m])) by (le)) * 1000",
"legendFormat": "p99"
}
]
}
]
}

View File

@@ -0,0 +1,203 @@
# Урок 6.2 — Отладка проблем с политиками
## Файлы
| Файл | Описание |
|------|----------|
| `policy-exception-example.yaml` | Пример PolicyException |
| `debug-context-inspector.yaml` | DEBUG политика для инспекции переменных |
## Диагностический чеклист
Когда политика не работает — проверяйте по порядку:
### Шаг 1: Политика применена и Ready?
```bash
kubectl get clusterpolicies
# Колонка READY должна быть True
kubectl get clusterpolicy my-policy -o yaml | grep -A 10 "status:"
# conditions[0].status: "True"
# conditions[0].type: Ready
```
### Шаг 2: Match срабатывает?
```bash
# Kyverno CLI — самый быстрый способ проверить
kyverno apply my-policy.yaml \
--resource my-resource.yaml \
--detailed-results
# Вывод:
# Rule my-rule -> resource: PASS/FAIL/SKIP
# SKIP = match не сработал
```
### Шаг 3: Ресурс не попадает в exclude?
```bash
# Проверить лейблы namespace
kubectl get namespace my-namespace --show-labels
# Проверить лейблы ресурса
kubectl get pod my-pod --show-labels
```
### Шаг 4: Логи admission controller
```bash
# Включить более детальные логи временно
kubectl set env deployment/kyverno-admission-controller \
-n kyverno \
KYVERNO_LOG_LEVEL=4
# Затем смотреть логи
kubectl logs -n kyverno \
-l app.kubernetes.io/component=admission-controller \
--tail=50 -f | grep "my-policy\|my-resource"
# Вернуть обратно
kubectl set env deployment/kyverno-admission-controller \
-n kyverno \
KYVERNO_LOG_LEVEL=2
```
### Шаг 5: Dry-run для проверки мутаций
```bash
# Видеть итоговый ресурс после мутации без создания
kubectl apply -f my-pod.yaml --dry-run=server -o yaml
# Diff оригинала и мутированной версии
diff <(cat my-pod.yaml) \
<(kubectl apply -f my-pod.yaml --dry-run=server -o yaml 2>/dev/null)
```
## Типичные ошибки и решения
### Ошибка 1: JMESPath — экранирование аннотаций
```yaml
# НЕПРАВИЛЬНО — парсер не поймёт /
key: "{{ request.object.metadata.annotations.company.com/env }}"
# ПРАВИЛЬНО — экранировать / через \"
key: "{{ request.object.metadata.annotations.\"company.com/env\" }}"
```
### Ошибка 2: Опциональные поля
```yaml
# НЕПРАВИЛЬНО — упадёт если поле не задано
pattern:
spec:
securityContext:
runAsNonRoot: true
# ПРАВИЛЬНО — = для опциональных полей
pattern:
spec:
=(securityContext):
=(runAsNonRoot): true
```
### Ошибка 3: foreach по несуществующему массиву
```yaml
# НЕПРАВИЛЬНО — упадёт если initContainers не задан
foreach:
- list: "request.object.spec.initContainers"
# ПРАВИЛЬНО — fallback на пустой массив
foreach:
- list: "request.object.spec.initContainers || `[]`"
```
### Ошибка 4: Match по kind без учёта controller
```yaml
# Если хотите проверять Deployment — матчите Deployment
match:
resources:
kinds:
- Deployment # проверяет сам Deployment
# Если хотите проверять Pod через Deployment — матчите Pod
# (Deployment controller создаёт Pod — Kyverno поймает его)
match:
resources:
kinds:
- Pod
```
## Инспекция переменных через debug-политику
```bash
# Создать namespace для отладки
kubectl create namespace debug-namespace
# Применить debug политику (она всегда выдаёт fail с данными)
kubectl apply -f debug-context-inspector.yaml
# Создать тестовый под — получить ошибку с содержимым переменных
kubectl run debug-pod --image=nginx:1.25.3 \
--restart=Never -n debug-namespace \
--dry-run=server 2>&1
# Вывод покажет все переменные в сообщении ошибки:
# [DEBUG] Pod: debug-pod
# Namespace: debug-namespace
# Namespace labels: map[kubernetes.io/metadata.name:debug-namespace]
# ...
# Удалить debug политику после использования
kubectl delete -f debug-context-inspector.yaml
kubectl delete namespace debug-namespace
```
## Работа с PolicyException
```bash
# Применить исключение
kubectl apply -f policy-exception-example.yaml
# Проверить что исключение работает
kubectl run legacy-app-test \
--image=nginx:1.25.3 \
--restart=Never \
-n production
# Под без limits должен создаться без ошибок
# Посмотреть все активные исключения
kubectl get policyexceptions -A
# Найти истекающие исключения (скрипт)
kubectl get policyexceptions -A -o json | \
jq -r '.items[] |
.metadata.name + " expires: " +
.metadata.annotations["exception.company.com/expires"]' | \
sort
```
## Анализ PolicyReport для диагностики
```bash
# Найти ресурс который нарушает политику
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)"'
# Сводная статистика
kubectl get policyreports -A -o json | \
jq '{
total: [.items[].results[]] | length,
pass: [.items[].results[] | select(.result=="pass")] | length,
fail: [.items[].results[] | select(.result=="fail")] | length
}'
```

View File

@@ -0,0 +1,42 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: debug-context-inspector
annotations:
policies.kyverno.io/title: "DEBUG: Инспектор контекстных переменных"
policies.kyverno.io/description: >-
ТОЛЬКО ДЛЯ ОТЛАДКИ. Всегда отклоняет запрос, выводя содержимое
переменных в сообщении ошибки. Помогает понять, что видит политика.
УДАЛИТЕ после отладки — не держите в production.
spec:
validationFailureAction: Enforce
background: false # не запускать в background — только для живых запросов
rules:
- name: print-context
match:
resources:
kinds:
- Pod
namespaces:
- debug-namespace # применяем только в отдельном namespace
context:
- name: nsLabels
apiCall:
urlPath: "/api/v1/namespaces/{{ request.object.metadata.namespace }}"
jmesPath: "metadata.labels"
validate:
# Всегда fail — выводим данные в сообщении
message: >-
[DEBUG] Pod: {{ request.object.metadata.name }}
Namespace: {{ request.object.metadata.namespace }}
Namespace labels: {{ nsLabels }}
Pod labels: {{ request.object.metadata.labels }}
User: {{ request.userInfo.username }}
Groups: {{ request.userInfo.groups }}
Operation: {{ request.operation }}
Containers: {{ request.object.spec.containers[].name }}
deny:
conditions:
- key: "true"
operator: Equals
value: "true"

View File

@@ -0,0 +1,24 @@
apiVersion: kyverno.io/v2beta1
kind: PolicyException
metadata:
name: legacy-app-resource-limits-exception
namespace: production
annotations:
# Обязательные поля для аудита — заполняйте всегда
exception.company.com/expires: "2025-06-01"
exception.company.com/reason: "Legacy приложение в процессе миграции. JIRA-4321"
exception.company.com/approved-by: "platform-team"
exception.company.com/created-by: "john.doe"
spec:
exceptions:
- policyName: require-resource-limits
ruleNames:
- check-container-limits
match:
resources:
kinds:
- Pod
namespaces:
- production
names:
- legacy-app-* # только legacy поды

View File

@@ -0,0 +1,185 @@
# Урок 6.3 — Производительность и оптимизация
## Измерение текущей производительности
```bash
# Открыть метрики
kubectl port-forward -n kyverno svc/kyverno-svc-metrics 8000:8000 &
# p95 и p99 латентность
curl -s http://localhost:8000/metrics | \
grep 'kyverno_admission_review_duration_seconds_bucket' | \
tail -20
# Найти самые медленные политики (через Prometheus)
# topk(10,
# histogram_quantile(0.99,
# sum by(policy_name, rule_name, le)(
# rate(kyverno_admission_review_duration_seconds_bucket[5m])
# )
# )
# )
# Измерить время admission вручную
time kubectl run perf-test \
--image=nginx:1.25.3 \
--restart=Never \
--dry-run=server
kubectl delete pod perf-test 2>/dev/null || true
kill %1
```
## Пороговые значения
| Метрика | Хорошо | Приемлемо | Проблема |
|---------|--------|-----------|---------|
| p95 латентность | < 100ms | < 500ms | > 500ms |
| p99 латентность | < 200ms | < 1000ms | > 1000ms |
| CPU throttling | 0% | < 5% | > 10% |
| Error rate | 0% | < 0.1% | > 0.1% |
## Оптимизация 1: точные selectors
```yaml
# МЕДЛЕННО — матчит всё подряд
match:
resources:
kinds:
- "*"
# БЫСТРО — только нужные ресурсы и namespace
match:
resources:
kinds:
- Pod
namespaces:
- production
- staging
```
## Оптимизация 2: preconditions для быстрого выхода
```bash
# Добавить precondition перед дорогим context.apiCall
# Правило запустит apiCall только если precondition прошёл
```
```yaml
rules:
- name: expensive-check
match:
resources:
kinds: [Pod]
preconditions:
any:
# Быстрая проверка лейбла ДО дорогого apiCall
- key: "{{ request.object.metadata.labels.\"needs-check\" }}"
operator: Equals
value: "true"
context:
- name: externalData # вызывается только если precondition прошёл
apiCall:
urlPath: "..."
```
## Оптимизация 3: разбивать мегаполитики
```bash
# Посмотреть сколько правил в каждой политике
kubectl get clusterpolicies -o json | \
jq -r '.items[] | "\(.metadata.name): \(.spec.rules | length) rules"' | \
sort -t: -k2 -rn | head -10
# Политики с >5 правилами — кандидаты на разбивку
# Каждая политика применяется параллельно с другими
# Правила внутри политики — последовательно
```
## Оптимизация 4: ресурсы для Kyverno
```bash
# Проверить текущее потребление ресурсов
kubectl top pods -n kyverno
# Проверить CPU throttling
kubectl get --raw /apis/metrics.k8s.io/v1beta1/namespaces/kyverno/pods | \
jq '.items[] | {name: .metadata.name, cpu: .containers[].usage.cpu}'
# Увеличить ресурсы если нужно
helm upgrade kyverno kyverno/kyverno \
--namespace kyverno \
--reuse-values \
--set admissionController.resources.limits.cpu=2000m \
--set admissionController.resources.limits.memory=1Gi
```
## Оптимизация 5: замена apiCall на ConfigMap
```yaml
# МЕДЛЕННО — apiCall при каждом запросе
context:
- name: registries
apiCall:
urlPath: "/api/v1/namespaces/kyverno/configmaps/allowed-registries"
jmesPath: "data.list"
# БЫСТРО — ConfigMap кэшируется Kyverno
context:
- name: registries
configMap:
name: allowed-registries
namespace: kyverno
```
## Оптимизация 6: HPA для Kyverno
```bash
# Включить HPA для admission controller
kubectl apply -f - <<EOF
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: kyverno-admission-hpa
namespace: kyverno
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: kyverno-admission-controller
minReplicas: 3
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 60
EOF
kubectl get hpa -n kyverno
```
## Нагрузочное тестирование политик
```bash
# Создать много подов быстро и измерить время
time for i in $(seq 1 50); do
kubectl run perf-pod-$i \
--image=nginx:1.25.3 \
--restart=Never \
--dry-run=server > /dev/null 2>&1
done
# Запустить параллельно
seq 1 20 | xargs -P 10 -I {} \
kubectl run perf-pod-{} \
--image=nginx:1.25.3 \
--restart=Never \
--dry-run=server
# Во время нагрузки мониторить latency
watch -n 2 'curl -s http://localhost:8000/metrics | \
grep admission_review_duration_seconds_sum'
```

View File

@@ -0,0 +1,154 @@
name: Kyverno Policy CI
on:
pull_request:
branches:
- main
paths:
- 'policies/**'
- 'k8s/**'
- 'helm/**'
push:
branches:
- main
paths:
- 'policies/**'
jobs:
# -------------------------------------------------------
# JOB 1: Линтинг и тестирование политик
# -------------------------------------------------------
test-policies:
name: Test Kyverno Policies
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install Kyverno CLI
uses: kyverno/action-install-cli@v0.2.0
with:
release: 'v1.11.4'
- name: Verify Kyverno CLI
run: kyverno version
- name: Lint policies (validate YAML structure)
run: |
find policies/ -name '*.yaml' -exec kubectl apply \
--dry-run=client -f {} \; 2>&1 | \
grep -v "^$" | \
tee lint-results.txt
# Завершиться с ошибкой если есть failures
grep -q "error\|Error" lint-results.txt && exit 1 || exit 0
- name: Run Kyverno tests
run: |
kyverno test policies/ \
--detailed-results \
2>&1 | tee test-results.txt
# Проверить что все тесты прошли
grep -q "Tests Summary" test-results.txt || exit 1
grep "Passed" test-results.txt
# Завершиться с ошибкой если есть Failed тесты
grep -q "^Failed: [^0]" test-results.txt && exit 1 || exit 0
- name: Upload test results
uses: actions/upload-artifact@v4
if: always()
with:
name: kyverno-test-results
path: test-results.txt
# -------------------------------------------------------
# JOB 2: Проверка Kubernetes манифестов против политик
# -------------------------------------------------------
validate-manifests:
name: Validate K8s Manifests
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install Helm
uses: azure/setup-helm@v4
with:
version: 'v3.14.0'
- name: Install Kyverno CLI
uses: kyverno/action-install-cli@v0.2.0
with:
release: 'v1.11.4'
- name: Generate manifests from Helm
run: |
helm template my-app ./helm/my-app \
-f helm/my-app/values-production.yaml \
--namespace production \
> /tmp/rendered-manifests.yaml
- name: Validate manifests against policies
run: |
kyverno apply policies/ \
--resource /tmp/rendered-manifests.yaml \
--detailed-results \
--table \
2>&1 | tee kyverno-apply-results.txt
# Завершиться с ошибкой если есть FAIL
grep -q "^| FAIL" kyverno-apply-results.txt && exit 1 || exit 0
- name: Comment PR with violations
if: failure() && github.event_name == 'pull_request'
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const results = fs.readFileSync('kyverno-apply-results.txt', 'utf8');
const body = `## ❌ Kyverno Policy Violations\n\n` +
`Следующие манифесты нарушают политики безопасности:\n\n` +
`\`\`\`\n${results}\`\`\`\n\n` +
`Исправьте нарушения перед merge.`;
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: body
});
# -------------------------------------------------------
# JOB 3: Деплой политик в staging (только main ветка)
# -------------------------------------------------------
deploy-staging:
name: Deploy Policies to Staging
runs-on: ubuntu-latest
needs: [test-policies, validate-manifests]
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
environment: staging
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install Helm
uses: azure/setup-helm@v4
- name: Configure kubectl
run: |
echo "${{ secrets.STAGING_KUBECONFIG }}" | \
base64 -d > /tmp/kubeconfig
echo "KUBECONFIG=/tmp/kubeconfig" >> $GITHUB_ENV
- name: Deploy policies via Helm
run: |
helm upgrade --install kyverno-policies \
./05-variables/03-templates/kyverno-policies \
--namespace kyverno \
-f ./05-variables/03-templates/kyverno-policies/values-staging.yaml \
--wait \
--timeout 5m
- name: Verify deployment
run: |
kubectl get clusterpolicies \
-l helm.sh/chart=kyverno-policies-1.0.0 \
-o wide

View File

@@ -0,0 +1,153 @@
# Урок 7.1 — Интеграция с CI/CD пайплайнами
## Файлы
| Файл | Описание |
|------|----------|
| `.github/workflows/policy-ci.yaml` | GitHub Actions: тест + валидация + деплой |
| `argocd-application.yaml` | ArgoCD Application для GitOps деплоя |
## Kyverno CLI — локальная проверка
```bash
# Установка
brew install kyverno # macOS
# или
curl -LO https://github.com/kyverno/kyverno/releases/latest/download/kyverno-cli_linux_amd64.tar.gz
tar -xzf kyverno-cli_*.tar.gz && sudo mv kyverno /usr/local/bin/
kyverno version
# Проверить манифест против политики (без кластера)
kyverno apply \
../../02-validation/01-resource-validation/require-resource-limits.yaml \
--resource ../../02-validation/01-resource-validation/test-resources/pod-no-limits.yaml
# Проверить папку политик против папки манифестов
kyverno apply ../../02-validation/ \
--resource ./test-k8s-manifests/ \
--table \
--detailed-results
# Запустить тесты
kyverno test ../../02-validation/01-resource-validation/tests/
```
## Локальная симуляция CI pipeline
```bash
# 1. Сгенерировать манифесты из Helm
helm template my-app ./helm-chart/ \
-f ./helm-chart/values-production.yaml \
> /tmp/manifests.yaml
# 2. Запустить Kyverno проверку
kyverno apply ../../02-validation/ \
--resource /tmp/manifests.yaml \
--table 2>&1 | tee results.txt
# 3. Проверить наличие нарушений
if grep -q "^| FAIL" results.txt; then
echo "❌ Policy violations found!"
cat results.txt
exit 1
else
echo "✅ All policies passed"
fi
```
## Тестовые манифесты для CI
```bash
mkdir -p test-k8s-manifests
# Манифест с нарушениями — pipeline должен упасть
cat > test-k8s-manifests/bad-deployment.yaml <<EOF
apiVersion: apps/v1
kind: Deployment
metadata:
name: bad-app
namespace: default
spec:
replicas: 1
selector:
matchLabels:
app: bad-app
template:
metadata:
labels:
app: bad-app
spec:
containers:
- name: app
image: nginx:latest # нарушение: latest тег
# нет resources # нарушение: нет limits
EOF
# Запустить проверку — должна быть ошибка
kyverno apply ../../02-validation/ \
--resource test-k8s-manifests/bad-deployment.yaml \
--table
# Ожидаем: FAIL для disallow-latest-tag и require-resource-limits
```
## Настройка GitOps с ArgoCD
```bash
# Предварительно установите ArgoCD
kubectl create namespace argocd
kubectl apply -n argocd \
-f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
# Применить Application
kubectl apply -f argocd-application.yaml
# Проверить статус синхронизации
kubectl get application kyverno-policies -n argocd
# Принудительная синхронизация
argocd app sync kyverno-policies
# История деплоев
argocd app history kyverno-policies
# Откат к предыдущей версии
argocd app rollback kyverno-policies 1
```
## CODEOWNERS для политик
```bash
# Создать файл для контроля ревью изменений в политиках
cat > .github/CODEOWNERS <<EOF
# Изменения в security политиках — обязательное ревью от security team
policies/02-validation/02-security/** @company/security-team
# Изменения в generation политиках — platform team
policies/04-generation/** @company/platform-team
# Helm chart — оба
policies/05-variables/03-templates/** @company/platform-team @company/security-team
EOF
```
## JUnit отчёт для CI систем
```bash
# Kyverno CLI может генерировать JUnit XML
kyverno test policies/ \
--junit-report kyverno-junit.xml
# GitLab CI — загрузка артефакта
# artifacts:
# reports:
# junit: kyverno-junit.xml
# GitHub Actions — публикация результатов
# uses: dorny/test-reporter@v1
# with:
# artifact: kyverno-junit
# name: Kyverno Tests
# path: kyverno-junit.xml
# reporter: java-junit
```

View File

@@ -0,0 +1,39 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: kyverno-policies
namespace: argocd
annotations:
argocd.argoproj.io/sync-wave: "1" # применить после установки Kyverno (wave 0)
spec:
project: platform
source:
repoURL: https://github.com/company/platform-policies.git
targetRevision: main
path: 05-variables/03-templates/kyverno-policies
helm:
valueFiles:
- values.yaml
- values-production.yaml
destination:
server: https://kubernetes.default.svc
namespace: kyverno
syncPolicy:
automated:
prune: true # удалять политики удалённые из Git
selfHeal: true # восстанавливать при ручных изменениях
syncOptions:
- ServerSideApply=true # нужно для CRD-based ресурсов
- CreateNamespace=true
retry:
limit: 3
backoff:
duration: 30s
factor: 2
maxDuration: 3m
# Уведомления (если настроен argocd-notifications)
# Добавьте аннотации для Slack/Teams/Email нотификаций

View File

@@ -0,0 +1,172 @@
# Урок 7.2 — Работа с внешними данными и API
## Файлы
| Файл | Описание |
|------|----------|
| `check-image-vulnerabilities.yaml` | Проверка уязвимостей через внешний API |
| `external-data-cache.yaml` | ConfigMap + CronJob + RBAC для кэша внешних данных |
| `validate-registry-from-cache.yaml` | Валидация реестра через кэшированный ConfigMap |
## Паттерн 1: Прямой вызов внешнего API
```yaml
# Требует включения в Kyverno:
# admissionController.extraArgs:
# - --enableExternalDataCall=true
context:
- name: result
apiCall:
urlPath: "https://your-api.company.com/check"
method: POST
data:
- key: image
value: "{{ request.object.spec.containers[0].image }}"
jmesPath: "status"
```
### Включение external data calls
```bash
helm upgrade kyverno kyverno/kyverno \
--namespace kyverno \
--reuse-values \
--set admissionController.extraArgs="{--enableExternalDataCall=true}"
# Проверить
kubectl get deployment kyverno-admission-controller -n kyverno \
-o jsonpath='{.spec.template.spec.containers[0].args}' | \
grep enableExternalDataCall
```
## Паттерн 2: Кэш через ConfigMap (рекомендуется)
Прямые вызовы внешних API медленные и создают зависимость.
Лучший паттерн: CronJob обновляет ConfigMap → политика читает из ConfigMap.
```
Внешний API ──(каждые N минут)──► CronJob ──► ConfigMap
Kyverno политика ◄───┘
(кэш, быстро)
```
### Применение кэша
```bash
# Применить всё: ConfigMap + CronJob + RBAC
kubectl apply -f external-data-cache.yaml
# Проверить ConfigMap
kubectl get configmap external-data-cache -n kyverno -o yaml
# Запустить CronJob вручную для немедленного обновления
kubectl create job --from=cronjob/update-policy-cache \
manual-update -n kyverno
# Следить за логами
kubectl logs -n kyverno \
-l job-name=manual-update \
--follow
# Применить политику использующую кэш
kubectl apply -f validate-registry-from-cache.yaml
```
## Паттерн 3: HashiCorp Vault интеграция
```bash
# Проверить доступность Vault перед деплоем с Vault секретами
kubectl apply -f - <<EOF
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: check-vault-availability
spec:
rules:
- name: verify-vault-health
match:
resources:
kinds: [Pod]
preconditions:
any:
- key: "{{ request.object.metadata.annotations.\"vault.hashicorp.com/agent-inject\" }}"
operator: Equals
value: "true"
context:
- name: vaultHealth
apiCall:
urlPath: "https://vault.company.com:8200/v1/sys/health"
jmesPath: "initialized"
validate:
message: "Vault недоступен. Деплой с Vault секретами временно заблокирован."
deny:
conditions:
- key: "{{ vaultHealth }}"
operator: NotEquals
value: true
EOF
```
## Обработка недоступности внешнего API
```bash
# Fail-open vs Fail-closed
# Выбор зависит от критичности проверки
# Fail-open (разрешить если API недоступен):
# jmesPath: "result || 'ALLOWED'"
# deny если result == "DENIED"
# При недоступности: result = "ALLOWED" → под создаётся
# Fail-closed (запретить если API недоступен):
# jmesPath: "result || 'DENIED'"
# deny если result != "ALLOWED"
# При недоступности: result = "DENIED" → под НЕ создаётся
```
## Настройка timeout для внешних вызовов
```bash
# Webhook timeout влияет на все вызовы включая внешние API
helm upgrade kyverno kyverno/kyverno \
--namespace kyverno \
--reuse-values \
--set config.webhooks.timeoutSeconds=15
# По умолчанию 10 секунд. Увеличьте если внешний API медленный.
# Максимум — 30 секунд (ограничение Kubernetes).
```
## Мониторинг внешних вызовов
```bash
# Проверить что CronJob работает регулярно
kubectl get cronjobs -n kyverno
kubectl get jobs -n kyverno | grep update-policy-cache
# Последнее обновление кэша
kubectl get configmap external-data-cache -n kyverno \
-o jsonpath='{.data.last-updated}'
# Алерт если кэш устарел (добавьте в PrometheusRule)
# Используйте kube_configmap_info метрику и проверяйте
# что last-updated не старше N минут
```
## Безопасность при работе с внешними данными
```bash
# НЕ хардкодьте токены в политиках — используйте Secret
kubectl create secret generic external-api-token \
--from-literal=token=your-secret-token \
-n kyverno
# В политике читаем из Secret
# context:
# - name: apiToken
# apiCall:
# urlPath: "/api/v1/namespaces/kyverno/secrets/external-api-token"
# jmesPath: "data.token | base64_decode(@)"
```

View File

@@ -0,0 +1,54 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: check-image-vulnerabilities
annotations:
policies.kyverno.io/title: "Проверка уязвимостей образов через внешний API"
policies.kyverno.io/category: Security
policies.kyverno.io/severity: critical
policies.kyverno.io/subject: Pod
policies.kyverno.io/description: >-
Проверяет образ контейнера через внешний vulnerability scanning API
перед деплоем в production. Блокирует деплой если найдены
критические уязвимости.
НАСТРОЙТЕ URL вашего сканера (Trivy, Snyk, Grype и т.д.)
Требует: admissionController.extraArgs: [--enableExternalDataCall=true]
spec:
validationFailureAction: Enforce
background: false # только для живых запросов, не background scan
rules:
- name: check-critical-vulnerabilities
match:
resources:
kinds:
- Pod
namespaces:
- production
preconditions:
any:
- key: "{{ request.operation }}"
operator: In
value: [CREATE, UPDATE]
context:
- name: scanResult
apiCall:
# Замените на URL вашего vulnerability API
urlPath: "https://vuln-api.company.com/v1/scan"
method: POST
data:
- key: image
value: "{{ request.object.spec.containers[0].image }}"
- key: severity
value: CRITICAL
jmesPath: "critical_count || `0`"
validate:
message: >-
Образ '{{ request.object.spec.containers[0].image }}'
содержит {{ scanResult }} критических уязвимостей.
Деплой в production запрещён.
Обновите базовый образ и пересоберите: https://vuln-api.company.com/report
deny:
conditions:
- key: "{{ scanResult }}"
operator: GreaterThan
value: "0"

View File

@@ -0,0 +1,105 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: external-data-cache
namespace: kyverno
labels:
app: kyverno-config
data:
# Список разрешённых реестров (обновляется CronJob)
allowed-registries: |
registry.company.com
gcr.io/company-project
public.ecr.aws/company
# Список одобренных StorageClass
approved-storage-classes: |
standard-ssd
premium-ssd
backup-hdd
# Последнее обновление (проставляется CronJob)
last-updated: "2024-01-01T00:00:00Z"
---
apiVersion: batch/v1
kind: CronJob
metadata:
name: update-policy-cache
namespace: kyverno
annotations:
description: >-
Обновляет ConfigMap external-data-cache данными из внешних API.
Позволяет политикам использовать актуальные данные без прямых apiCall
к внешним сервисам на каждый запрос.
spec:
schedule: "*/10 * * * *" # каждые 10 минут
concurrencyPolicy: Forbid
jobTemplate:
spec:
template:
spec:
serviceAccountName: policy-cache-updater
restartPolicy: OnFailure
containers:
- name: cache-updater
image: bitnami/kubectl:1.28
env:
- name: EXTERNAL_API_URL
value: "https://api.company.com/v1"
- name: CONFIGMAP_NAME
value: "external-data-cache"
- name: NAMESPACE
value: "kyverno"
command:
- /bin/bash
- -c
- |
set -e
echo "Fetching allowed registries from external API..."
# В реальности заменить на curl к вашему API
REGISTRIES=$(echo -e "registry.company.com\ngcr.io/company-project")
echo "Updating ConfigMap..."
kubectl patch configmap ${CONFIGMAP_NAME} \
-n ${NAMESPACE} \
--type merge \
-p "{\"data\":{
\"allowed-registries\": \"${REGISTRIES}\",
\"last-updated\": \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"
}}"
echo "Cache updated successfully"
resources:
limits:
cpu: 100m
memory: 64Mi
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: policy-cache-updater
namespace: kyverno
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: configmap-updater
namespace: kyverno
rules:
- apiGroups: [""]
resources: ["configmaps"]
resourceNames: ["external-data-cache"]
verbs: ["get", "patch", "update"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: policy-cache-updater
namespace: kyverno
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: configmap-updater
subjects:
- kind: ServiceAccount
name: policy-cache-updater
namespace: kyverno

View File

@@ -0,0 +1,54 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: validate-registry-from-cache
annotations:
policies.kyverno.io/title: "Проверка реестра через кэшированные данные"
policies.kyverno.io/category: Security
policies.kyverno.io/severity: high
policies.kyverno.io/subject: Pod
policies.kyverno.io/description: >-
Проверяет образ контейнера против списка разрешённых реестров,
хранящегося в ConfigMap external-data-cache.
Список обновляется каждые 10 минут CronJob из внешнего API.
Паттерн "кэш в ConfigMap" — быстрее чем прямые apiCall к внешним API.
spec:
validationFailureAction: Enforce
background: true
rules:
- name: check-registry-from-cache
match:
resources:
kinds:
- Pod
exclude:
resources:
namespaces:
- kube-system
- kyverno
context:
- name: allowedRegistries
configMap:
name: external-data-cache
namespace: kyverno
validate:
message: >-
Образ '{{ element.image }}' из недоверенного реестра.
Список разрешённых реестров (обновлён {{ allowedRegistries.data.\"last-updated\" }}):
{{ allowedRegistries.data.\"allowed-registries\" }}
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/"
- key: "{{ element.image }}"
operator: NotStartsWith
value: "public.ecr.aws/company/"

46
README.md Normal file
View File

@@ -0,0 +1,46 @@
# Kyverno: Полный контроль Kubernetes
Репозиторий с практическими материалами курса. Каждая папка соответствует уроку и содержит готовые к запуску манифесты и инструкции.
## Структура курса
| Раздел | Тема |
|--------|------|
| [01-introduction](./01-introduction/) | Введение, установка, структура политик |
| [02-validation](./02-validation/) | Validation политики |
| [03-mutation](./03-mutation/) | Mutation политики |
| [04-generation](./04-generation/) | Generation политики |
| [05-variables](./05-variables/) | Переменные и контекст |
| [06-monitoring](./06-monitoring/) | Мониторинг и отладка |
| [07-advanced](./07-advanced/) | CI/CD и внешние интеграции |
## Требования
- Kubernetes >= 1.26
- Helm >= 3.10
- kubectl
- [Kyverno CLI](https://kyverno.io/docs/kyverno-cli/) (для локального тестирования)
```bash
# Установка Kyverno CLI (macOS)
brew install kyverno
# Linux
curl -LO https://github.com/kyverno/kyverno/releases/latest/download/kyverno-cli_linux_amd64.tar.gz
tar -xzf kyverno-cli_linux_amd64.tar.gz && sudo mv kyverno /usr/local/bin/
```
## Быстрый старт
```bash
# 1. Установить Kyverno
helm repo add kyverno https://kyverno.github.io/kyverno/
helm repo update
helm install kyverno kyverno/kyverno -n kyverno --create-namespace
# 2. Проверить установку
kubectl get pods -n kyverno
# 3. Применить первую политику
kubectl apply -f 02-validation/01-resource-validation/require-resource-limits.yaml
```