init
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
.DS_Store
|
||||
66
01-introduction/01-what-is-kyverno/README.md
Normal file
66
01-introduction/01-what-is-kyverno/README.md
Normal 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
|
||||
```
|
||||
123
01-introduction/02-installation/README.md
Normal file
123
01-introduction/02-installation/README.md
Normal 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
|
||||
```
|
||||
28
01-introduction/02-installation/test-policy.yaml
Normal file
28
01-introduction/02-installation/test-policy.yaml
Normal 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: "?*"
|
||||
31
01-introduction/02-installation/values-development.yaml
Normal file
31
01-introduction/02-installation/values-development.yaml
Normal 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
|
||||
73
01-introduction/02-installation/values-production.yaml
Normal file
73
01-introduction/02-installation/values-production.yaml
Normal 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
|
||||
56
01-introduction/03-policy-structure/README.md
Normal file
56
01-introduction/03-policy-structure/README.md
Normal 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
|
||||
```
|
||||
68
01-introduction/03-policy-structure/annotated-policy.yaml
Normal file
68
01-introduction/03-policy-structure/annotated-policy.yaml
Normal 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: "?*" # поле должно существовать и быть не пустым
|
||||
@@ -0,0 +1,10 @@
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: pod-bad
|
||||
namespace: default
|
||||
# нет лейбла app — политика зафиксирует нарушение
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx:1.25
|
||||
@@ -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
|
||||
81
02-validation/01-resource-validation/README.md
Normal file
81
02-validation/01-resource-validation/README.md
Normal 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"}}'
|
||||
```
|
||||
@@ -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/"
|
||||
# Добавьте дополнительные условия по аналогии
|
||||
@@ -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: ":"
|
||||
46
02-validation/01-resource-validation/require-labels.yaml
Normal file
46
02-validation/01-resource-validation/require-labels.yaml
Normal 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)$"
|
||||
@@ -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"
|
||||
@@ -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: "?*"
|
||||
@@ -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"
|
||||
@@ -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 отклонит
|
||||
@@ -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"
|
||||
30
02-validation/01-resource-validation/tests/kyverno-test.yaml
Normal file
30
02-validation/01-resource-validation/tests/kyverno-test.yaml
Normal 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
|
||||
75
02-validation/02-security/README.md
Normal file
75
02-validation/02-security/README.md
Normal 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 — Аудит (неделя 1–2)
|
||||
|
||||
```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
|
||||
```
|
||||
@@ -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
|
||||
57
02-validation/02-security/disallow-host-namespaces.yaml
Normal file
57
02-validation/02-security/disallow-host-namespaces.yaml
Normal 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"
|
||||
@@ -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
|
||||
41
02-validation/02-security/require-drop-all-capabilities.yaml
Normal file
41
02-validation/02-security/require-drop-all-capabilities.yaml
Normal 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[] || `[]` }}"
|
||||
58
02-validation/02-security/require-run-as-non-root.yaml
Normal file
58
02-validation/02-security/require-run-as-non-root.yaml
Normal 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
|
||||
52
02-validation/02-security/require-seccomp-profile.yaml
Normal file
52
02-validation/02-security/require-seccomp-profile.yaml
Normal 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
|
||||
37
02-validation/02-security/restrict-automount-sa-token.yaml
Normal file
37
02-validation/02-security/restrict-automount-sa-token.yaml
Normal 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
|
||||
21
02-validation/02-security/test-resources/pod-insecure.yaml
Normal file
21
02-validation/02-security/test-resources/pod-insecure.yaml
Normal 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"
|
||||
28
02-validation/02-security/test-resources/pod-secure.yaml
Normal file
28
02-validation/02-security/test-resources/pod-secure.yaml
Normal 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"
|
||||
126
02-validation/03-reporting/README.md
Normal file
126
02-validation/03-reporting/README.md
Normal 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
|
||||
```
|
||||
99
02-validation/03-reporting/prometheus-alert-rules.yaml
Normal file
99
02-validation/03-reporting/prometheus-alert-rules.yaml
Normal 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]))
|
||||
17
02-validation/03-reporting/service-monitor.yaml
Normal file
17
02-validation/03-reporting/service-monitor.yaml
Normal 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
|
||||
82
03-mutation/01-basics/README.md
Normal file
82
03-mutation/01-basics/README.md
Normal 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
|
||||
```
|
||||
36
03-mutation/01-basics/add-default-resource-requests.yaml
Normal file
36
03-mutation/01-basics/add-default-resource-requests.yaml
Normal 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"
|
||||
55
03-mutation/01-basics/add-default-security-context.yaml
Normal file
55
03-mutation/01-basics/add-default-security-context.yaml
Normal 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
|
||||
33
03-mutation/01-basics/add-standard-labels.yaml
Normal file
33
03-mutation/01-basics/add-standard-labels.yaml
Normal 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 }}"
|
||||
@@ -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"
|
||||
82
03-mutation/02-sidecar/README.md
Normal file
82
03-mutation/02-sidecar/README.md
Normal 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}'
|
||||
```
|
||||
41
03-mutation/02-sidecar/fluent-bit-configmap.yaml
Normal file
41
03-mutation/02-sidecar/fluent-bit-configmap.yaml
Normal 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
|
||||
57
03-mutation/02-sidecar/inject-fluent-bit.yaml
Normal file
57
03-mutation/02-sidecar/inject-fluent-bit.yaml
Normal 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
|
||||
58
03-mutation/02-sidecar/inject-prometheus-exporter.yaml
Normal file
58
03-mutation/02-sidecar/inject-prometheus-exporter.yaml
Normal 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
|
||||
@@ -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"
|
||||
14
03-mutation/02-sidecar/test-resources/pod-no-logging.yaml
Normal file
14
03-mutation/02-sidecar/test-resources/pod-no-logging.yaml
Normal 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"
|
||||
15
03-mutation/02-sidecar/test-resources/pod-with-logging.yaml
Normal file
15
03-mutation/02-sidecar/test-resources/pod-with-logging.yaml
Normal 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"
|
||||
@@ -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"
|
||||
131
03-mutation/03-advanced/README.md
Normal file
131
03-mutation/03-advanced/README.md
Normal 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
|
||||
```
|
||||
34
03-mutation/03-advanced/add-creator-audit-annotation.yaml
Normal file
34
03-mutation/03-advanced/add-creator-audit-annotation.yaml
Normal 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(', ', @) }}
|
||||
22
03-mutation/03-advanced/kyverno-global-config.yaml
Normal file
22
03-mutation/03-advanced/kyverno-global-config.yaml
Normal 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"
|
||||
44
03-mutation/03-advanced/set-dynamic-resource-limits.yaml
Normal file
44
03-mutation/03-advanced/set-dynamic-resource-limits.yaml
Normal 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' }}
|
||||
96
04-generation/01-configmaps-secrets/README.md
Normal file
96
04-generation/01-configmaps-secrets/README.md
Normal 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
|
||||
```
|
||||
@@ -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
|
||||
@@ -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"] # только чтение секретов
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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"
|
||||
@@ -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"
|
||||
@@ -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"
|
||||
106
04-generation/02-lifecycle/README.md
Normal file
106
04-generation/02-lifecycle/README.md
Normal 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)
|
||||
```
|
||||
29
04-generation/02-lifecycle/cleanup-debug-pods.yaml
Normal file
29
04-generation/02-lifecycle/cleanup-debug-pods.yaml
Normal 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"
|
||||
134
05-variables/01-configmaps/README.md
Normal file
134
05-variables/01-configmaps/README.md
Normal 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' }}
|
||||
```
|
||||
36
05-variables/01-configmaps/inherit-namespace-labels.yaml
Normal file
36
05-variables/01-configmaps/inherit-namespace-labels.yaml
Normal 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' }}"
|
||||
27
05-variables/01-configmaps/kyverno-global-config.yaml
Normal file
27
05-variables/01-configmaps/kyverno-global-config.yaml
Normal 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"
|
||||
@@ -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\" }}"
|
||||
127
05-variables/02-context/README.md
Normal file
127
05-variables/02-context/README.md
Normal 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.
|
||||
```
|
||||
13
05-variables/02-context/deployment-freeze-config.yaml
Normal file
13
05-variables/02-context/deployment-freeze-config.yaml
Normal 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."
|
||||
52
05-variables/02-context/restrict-deploys-during-freeze.yaml
Normal file
52
05-variables/02-context/restrict-deploys-during-freeze.yaml
Normal 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
|
||||
45
05-variables/02-context/restrict-privileged-non-admins.yaml
Normal file
45
05-variables/02-context/restrict-privileged-non-admins.yaml
Normal 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
|
||||
128
05-variables/03-templates/README.md
Normal file
128
05-variables/03-templates/README.md
Normal 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: Начальная версия
|
||||
```
|
||||
16
05-variables/03-templates/kyverno-policies/Chart.yaml
Normal file
16
05-variables/03-templates/kyverno-policies/Chart.yaml
Normal 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
|
||||
@@ -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 }}
|
||||
@@ -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 }}
|
||||
@@ -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 }}
|
||||
@@ -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 }}
|
||||
@@ -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
|
||||
@@ -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"
|
||||
@@ -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"
|
||||
@@ -0,0 +1,9 @@
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: pod-no-limits
|
||||
namespace: default
|
||||
spec:
|
||||
containers:
|
||||
- name: app
|
||||
image: nginx:1.25.3
|
||||
@@ -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"
|
||||
@@ -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
|
||||
@@ -0,0 +1,10 @@
|
||||
# Staging overrides — всё в Audit для наблюдения
|
||||
global:
|
||||
failureAction: Audit
|
||||
|
||||
disallowPrivileged:
|
||||
enabled: true
|
||||
failureAction: Audit # в staging даже security в Audit
|
||||
|
||||
generateNetworkPolicy:
|
||||
enabled: false # в staging не генерируем NetworkPolicy
|
||||
55
05-variables/03-templates/kyverno-policies/values.yaml
Normal file
55
05-variables/03-templates/kyverno-policies/values.yaml
Normal 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
|
||||
134
06-monitoring/01-logging/README.md
Normal file
134
06-monitoring/01-logging/README.md
Normal 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
|
||||
```
|
||||
130
06-monitoring/01-logging/grafana-dashboard.json
Normal file
130
06-monitoring/01-logging/grafana-dashboard.json
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
203
06-monitoring/02-debugging/README.md
Normal file
203
06-monitoring/02-debugging/README.md
Normal 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
|
||||
}'
|
||||
```
|
||||
42
06-monitoring/02-debugging/debug-context-inspector.yaml
Normal file
42
06-monitoring/02-debugging/debug-context-inspector.yaml
Normal 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"
|
||||
24
06-monitoring/02-debugging/policy-exception-example.yaml
Normal file
24
06-monitoring/02-debugging/policy-exception-example.yaml
Normal 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 поды
|
||||
185
06-monitoring/03-performance/README.md
Normal file
185
06-monitoring/03-performance/README.md
Normal 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'
|
||||
```
|
||||
154
07-advanced/01-cicd/.github/workflows/policy-ci.yaml
vendored
Normal file
154
07-advanced/01-cicd/.github/workflows/policy-ci.yaml
vendored
Normal 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
|
||||
153
07-advanced/01-cicd/README.md
Normal file
153
07-advanced/01-cicd/README.md
Normal 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
|
||||
```
|
||||
39
07-advanced/01-cicd/argocd-application.yaml
Normal file
39
07-advanced/01-cicd/argocd-application.yaml
Normal 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 нотификаций
|
||||
172
07-advanced/02-external-data/README.md
Normal file
172
07-advanced/02-external-data/README.md
Normal 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(@)"
|
||||
```
|
||||
@@ -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"
|
||||
105
07-advanced/02-external-data/external-data-cache.yaml
Normal file
105
07-advanced/02-external-data/external-data-cache.yaml
Normal 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
|
||||
@@ -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
46
README.md
Normal 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
|
||||
```
|
||||
Reference in New Issue
Block a user