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