Валидация манифестов Kubernetes
Updated Jul 2026
Проблема: Kubernetes принимает практически всё
Kubernetes поразительно терпим к тому, что принимает. Deployment без лимитов ресурсов, без проверок здоровья, запущенный от root, с тегом latest успешно развернётся. Kubernetes не предупредит вас, что он будет вести себя плохо — он просто сделает то, что вы попросили, а последствия вы обнаружите в продакшене.
Инструменты валидации манифестов выявляют эти ошибки конфигурации до того, как они попадут в кластер. Они обеспечивают соблюдение лучших практик, которые Kubernetes сам по себе не требует, но которые необходимы для промышленной эксплуатации.
Сравнение инструментов валидации
| Инструмент | Фокус | Подход | Поддержка CRD | Скорость |
|---|---|---|---|---|
| kubeval | Валидация схемы | Проверка по API-схемам K8s | Ограниченная | Быстро |
| kubeconform | Валидация схемы | Быстрая замена kubeval | Да (через плагины) | Очень быстро |
| kube-score | Лучшие практики | Опинионированные проверки (безопасность, надёжность) | Нет | Быстро |
| Polaris | Применение политик | Движок политик Fairwinds | Да | Быстро |
| Datree | Применение политик | Встроенные + пользовательские правила | Да | Быстро |
| OPA/Gatekeeper | Контроль допуска | Пользовательские политики Rego в кластере | Да | Быстро |
kubeval и kubeconform
# kubeval: validates manifests against K8s API schemas
kubeval deployment.yaml --kubernetes-version 1.29.0
# kubeconform: faster kubeval replacement with CRD support
kubeconform -strict -kubernetes-version 1.29.0 deployment.yaml
# Validate all manifests in a directory
kubeconform -strict -summary k8s/
# Validate Helm-rendered templates
helm template myapp ./charts/myapp/ | kubeconform -strict
# Use with custom resource definitions
kubeconform -strict \
-schema-location default \
-schema-location 'https://raw.githubusercontent.com/datreeio/CRDs-catalog/main/{{.Group}}/{{.ResourceKind}}_{{.ResourceAPIVersion}}.json' \
k8s/
kube-score
kube-score проверяет соблюдение лучших практик, а не валидность схемы:
# Score a deployment manifest
kube-score score deployment.yaml
# Output:
# apps/v1/Deployment myapp: (CRITICAL) Container has no readiness probe
# apps/v1/Deployment myapp: (CRITICAL) No network policy matching pod
# apps/v1/Deployment myapp: (WARNING) CPU limit is not set
# Score all manifests in a directory
kube-score score k8s/*.yaml
# Output as JSON for CI parsing
kube-score score deployment.yaml --output-format json
# Ignore specific checks
kube-score score deployment.yaml \
--ignore-test container-cpu-limit \
--ignore-test pod-networkpolicy
Polaris
Polaris предоставляет как CLI-сканирование, так и контроль допуска в кластере:
# Audit local manifests
polaris audit --audit-path ./k8s/ --format=pretty
# Checks: resource limits, health probes, security context,
# image pull policy, host network access, privilege escalation
# Audit with a minimum score threshold
polaris audit --audit-path ./k8s/ --set-exit-code-below-score 80
# Run Polaris as a Kubernetes admission controller
helm repo add fairwinds-stable https://charts.fairwinds.com/stable
helm install polaris fairwinds-stable/polaris --namespace polaris
Что валидировать в манифестах Kubernetes
Deployment, готовый к продакшену, должен включать ВСЕ перечисленные ниже элементы. Каждая аннотация объясняет, почему поле важно:
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
labels:
app: myapp
version: "1.2.3" # Pinned version, not "latest"
spec:
replicas: 3 # Multiple replicas for availability
selector:
matchLabels:
app: myapp
template:
spec:
securityContext:
runAsNonRoot: true # Never run as root
runAsUser: 1000
fsGroup: 2000
seccompProfile:
type: RuntimeDefault # Use default seccomp profile
containers:
- name: myapp
image: myapp:1.2.3 # Pinned tag, never :latest
resources:
requests: # Scheduler needs these
cpu: 100m
memory: 128Mi
limits: # Prevent noisy-neighbor issues
cpu: 500m
memory: 512Mi
readinessProbe: # When to send traffic
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
failureThreshold: 3
livenessProbe: # When to restart
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 15
periodSeconds: 20
failureThreshold: 3
startupProbe: # Grace period for slow starts
httpGet:
path: /healthz
port: 8080
failureThreshold: 30
periodSeconds: 10
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]
ports:
- containerPort: 8080
protocol: TCP
env:
- name: DB_PASSWORD
valueFrom:
secretKeyRef: # Never hardcode secrets
name: myapp-secrets
key: db-password
topologySpreadConstraints:
- maxSkew: 1
topologyKey: kubernetes.io/hostname
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
app: myapp
Чек-лист для ревью манифестов
| Проверка | Зачем | Критичность |
|---|---|---|
Тег образа не :latest |
Предотвращает непредсказуемые деплои | Критическая |
| Заданы requests и limits ресурсов | Предотвращает исчерпание ресурсов | Критическая |
| Настроен readiness probe | Предотвращает трафик на неготовые поды | Критическая |
| Настроен liveness probe | Обеспечивает автоматическое восстановление | Высокая |
runAsNonRoot: true |
Предотвращает выход из контейнера | Критическая |
readOnlyRootFilesystem: true |
Ограничивает запись файлов атакующим | Высокая |
allowPrivilegeEscalation: false |
Предотвращает повышение привилегий | Критическая |
| Все capabilities сброшены | Минимизирует доступ к ядру | Высокая |
Нет hostNetwork: true |
Предотвращает доступ к сети хоста | Критическая |
Нет hostPID: true |
Предотвращает видимость процессов | Критическая |
Секреты через secretKeyRef |
Никаких захардкоженных учётных данных | Критическая |
| Несколько реплик | Доступность при отказе узла | Высокая |
| PodDisruptionBudget существует | Предотвращает одновременное обновление всех подов | Средняя |
| NetworkPolicy существует | Сетевая сегментация по принципу нулевого доверия | Высокая |
Пользовательская валидация с OPA Gatekeeper
Для организационных правил, выходящих за рамки стандартных инструментов, разверните OPA Gatekeeper как контроллер допуска Kubernetes:
# constraint-template.yaml
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: k8snolatestimage
spec:
crd:
spec:
names:
kind: K8sNoLatestImage
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8snolatestimage
violation[{"msg": msg}] {
container := input.review.object.spec.template.spec.containers[_]
endswith(container.image, ":latest")
msg := sprintf("Container '%s' uses :latest tag. Pin a specific version.", [container.name])
}
violation[{"msg": msg}] {
container := input.review.object.spec.template.spec.containers[_]
not contains(container.image, ":")
msg := sprintf("Container '%s' has no image tag. Pin a specific version.", [container.name])
}
---
# constraint.yaml
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sNoLatestImage
metadata:
name: no-latest-image
spec:
match:
kinds:
- apiGroups: ["apps"]
kinds: ["Deployment", "StatefulSet", "DaemonSet"]
namespaces:
- production
- staging
Интеграция валидации в CI
# .github/workflows/k8s-validation.yml
name: Kubernetes Manifest Validation
on:
pull_request:
paths:
- 'k8s/**'
- 'charts/**'
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Schema validation
run: kubeconform -strict -summary k8s/
- name: Best practices check
run: |
kube-score score k8s/*.yaml
EXIT_CODE=$?
if [ $EXIT_CODE -ne 0 ]; then
echo "kube-score found issues. Review above output."
exit 1
fi
- name: Policy audit
run: |
polaris audit --audit-path k8s/ \
--set-exit-code-below-score 80 \
--format=pretty
Валидация манифестов — одна из наиболее ценных и наименее затратных практик в безопасности Kubernetes. Она выполняется за секунды, выявляет реальные проблемы и предотвращает ошибки конфигурации, на отладку которых в продакшене уходят часы.