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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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