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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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