Kyverno es un motor de políticas para Kubernetes que funciona como controlador de admisión. A diferencia de OPA Gatekeeper, las políticas se escriben en YAML con la misma estructura que los recursos que validan, sin aprender Rego. En este tutorial instalarás Kyverno con Helm y crearás políticas de los cuatro tipos principales: validación (rechazar lo que no cumple), mutación (completar recursos automáticamente), generación (crear recursos asociados) y verificación de firmas de imágenes.
Requisitos previos
Para seguir esta guía necesitas:
- Un clúster Kubernetes en una versión mantenida (1.30 o superior), por ejemplo un clúster Kubernetes gestionado de CubePath o uno propio con kubeadm o k3s.
kubectlconfigurado con permisos decluster-admin.- Helm 3 instalado en tu equipo.
- Para la política de generación, un CNI que aplique NetworkPolicies (Calico o Cilium, por ejemplo).
- Para la verificación de imágenes, una imagen firmada con Cosign y su clave pública (consulta la guía de firma de imágenes con Cosign).
Paso 1: Instalar Kyverno con Helm
Añade el repositorio oficial y actualiza el índice:
helm repo add kyverno https://kyverno.github.io/kyverno/
helm repo update
Instala Kyverno en su propio namespace. Para un entorno de pruebas basta con los valores por defecto:
helm install kyverno kyverno/kyverno \
--namespace kyverno \
--create-namespace
En producción, ejecuta varias réplicas de cada controlador para que el webhook siga disponible durante actualizaciones o fallos de un nodo:
helm upgrade --install kyverno kyverno/kyverno \
--namespace kyverno \
--create-namespace \
--set admissionController.replicas=3 \
--set backgroundController.replicas=2 \
--set cleanupController.replicas=2 \
--set reportsController.replicas=2
Comprueba que los controladores están en marcha:
kubectl get pods -n kyverno
NAME READY STATUS RESTARTS AGE
kyverno-admission-controller-6d8f9c7b5d-x2k8p 1/1 Running 0 70s
kyverno-background-controller-5b7c9d8f6c-m4q9z 1/1 Running 0 70s
kyverno-cleanup-controller-7f6d8b9c5d-p7w2n 1/1 Running 0 70s
kyverno-reports-controller-8c5d7f9b6d-h3t6r 1/1 Running 0 70s
Cada controlador tiene una función: el de admisión valida y muta las peticiones, el de background ejecuta las reglas de generación y mutación de recursos existentes, el de limpieza borra recursos según políticas de limpieza y el de informes genera los PolicyReport.
Crea un directorio para los manifiestos y un namespace de pruebas:
mkdir -p ~/kyverno && cd ~/kyverno
kubectl create namespace demo
Paso 2: Crear una política de validación en modo Audit
Las políticas se definen con ClusterPolicy (todo el clúster) o Policy (un solo namespace). Cada regla indica a qué recursos se aplica (match), qué excluye (exclude) y qué hace. Empieza exigiendo la etiqueta app.kubernetes.io/name en los Pods, en modo Audit para no bloquear nada todavía:
nano require-labels.yaml
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-labels
spec:
background: true
rules:
- name: check-app-name-label
match:
any:
- resources:
kinds: ["Pod"]
exclude:
any:
- resources:
namespaces: ["kube-system", "kyverno"]
validate:
failureAction: Audit
message: "La etiqueta app.kubernetes.io/name es obligatoria."
pattern:
metadata:
labels:
app.kubernetes.io/name: "?*"
El patrón "?*" significa "cualquier valor con al menos un carácter". background: true hace que la política también evalúe los recursos que ya existían.
kubectl apply -f require-labels.yaml
kubectl get clusterpolicy require-labels
NAME ADMISSION BACKGROUND READY AGE MESSAGE
require-labels true true True 8s Ready
Aunque la regla se refiere a Pods, Kyverno genera automáticamente reglas equivalentes para Deployments, StatefulSets, DaemonSets, Jobs y CronJobs (autogen), de modo que el error se detecta en el propio Deployment y no solo en los Pods que crea. Puedes ver las reglas generadas con kubectl get clusterpolicy require-labels -o yaml, en status.autogen.
Paso 3: Revisar los informes de auditoría
Crea un Deployment que incumple la política:
kubectl create deployment web --image=nginx:1.27 -n demo
En modo Audit se acepta, pero queda registrado en un PolicyReport. Espera unos segundos y consulta los informes del namespace:
kubectl get policyreport -n demo
NAME KIND NAME PASS FAIL WARN ERROR SKIP AGE
3b1c2d4e-5f6a-7b8c-9d0e-1f2a3b4c5d6e Deployment web 0 1 0 0 0 15s
8a7b6c5d-4e3f-2a1b-0c9d-8e7f6a5b4c3d Pod web-6d4cf56db6-kq8x2 0 1 0 0 0 15s
Para ver el motivo del fallo:
kubectl get policyreport -n demo -o jsonpath='{range .items[*].results[*]}{.policy}/{.rule}: {.result} - {.message}{"\n"}{end}'
Para una visión global de todo el clúster, usa kubectl get policyreport -A. Corrige los recursos que fallan antes de pasar la política a Enforce.
Paso 4: Bloquear recursos en modo Enforce
Esta segunda política prohíbe usar el tag latest (o no indicar tag), porque impide saber qué versión se está ejecutando. Se aplica directamente en Enforce:
nano disallow-latest-tag.yaml
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: disallow-latest-tag
spec:
background: true
rules:
- name: require-image-tag
match:
any:
- resources:
kinds: ["Pod"]
exclude:
any:
- resources:
namespaces: ["kube-system", "kyverno"]
validate:
failureAction: Enforce
message: "Indica un tag de imagen concreto."
pattern:
spec:
containers:
- image: "*:*"
- name: validate-image-tag
match:
any:
- resources:
kinds: ["Pod"]
exclude:
any:
- resources:
namespaces: ["kube-system", "kyverno"]
validate:
failureAction: Enforce
message: "No se permite el tag latest."
pattern:
spec:
containers:
- image: "!*:latest"
kubectl apply -f disallow-latest-tag.yaml
Prueba a desplegar una imagen con latest:
kubectl create deployment prueba --image=nginx:latest -n demo
error: failed to create deployment: admission webhook "validate.kyverno.svc-fail" denied the request:
resource Deployment/demo/prueba was blocked due to the following policies
disallow-latest-tag:
autogen-validate-image-tag: 'validation error: No se permite el tag latest. rule
autogen-validate-image-tag failed at path /spec/template/spec/containers/0/image/'
El prefijo autogen- confirma que la regla generada para Deployments ha actuado. Con un tag concreto, como nginx:1.27, el Deployment se crea sin problemas.
Notadesde Kyverno 1.13 la acción se define en cada regla con
validate.failureAction. El campo antiguospec.validationFailureActionsigue funcionando pero está obsoleto.
Paso 5: Completar recursos con una política de mutación
Las políticas de mutación modifican el recurso antes de guardarlo. Esta añade la etiqueta team: sin-asignar a los Pods que no la tengan. El prefijo +( ) indica "añadir solo si no existe", así que nunca sobrescribe un valor que el usuario haya puesto:
nano add-team-label.yaml
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: add-team-label
spec:
rules:
- name: add-default-team
match:
any:
- resources:
kinds: ["Pod"]
exclude:
any:
- resources:
namespaces: ["kube-system", "kyverno"]
mutate:
patchStrategicMerge:
metadata:
labels:
+(team): sin-asignar
kubectl apply -f add-team-label.yaml
kubectl run mutado --image=nginx:1.27 -n demo
kubectl get pod mutado -n demo --show-labels
NAME READY STATUS RESTARTS AGE LABELS
mutado 1/1 Running 0 5s run=mutado,team=sin-asignar
Paso 6: Generar recursos automáticamente
Las políticas de generación crean recursos cuando aparece otro. Un caso habitual es añadir una NetworkPolicy que deniega el tráfico entrante en cada namespace nuevo, para que los equipos abran explícitamente lo que necesitan.
Kyverno necesita permiso para crear NetworkPolicies. Concédeselo con un ClusterRole que el chart agrega automáticamente a sus controladores mediante etiquetas:
nano kyverno-networkpolicies-rbac.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: kyverno:manage-networkpolicies
labels:
rbac.kyverno.io/aggregate-to-background-controller: "true"
rbac.kyverno.io/aggregate-to-admission-controller: "true"
rules:
- apiGroups: ["networking.k8s.io"]
resources: ["networkpolicies"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
Crea la política:
nano add-default-deny.yaml
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: add-default-deny
spec:
rules:
- name: default-deny-ingress
match:
any:
- resources:
kinds: ["Namespace"]
exclude:
any:
- resources:
names: ["kube-*", "kyverno", "default"]
generate:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
name: default-deny-ingress
namespace: "{{request.object.metadata.name}}"
synchronize: true
data:
spec:
podSelector: {}
policyTypes:
- Ingress
synchronize: true hace que Kyverno vuelva a crear la NetworkPolicy si alguien la borra y la actualice si cambias la política. Aplica ambos archivos y crea un namespace para comprobarlo:
kubectl apply -f kyverno-networkpolicies-rbac.yaml
kubectl apply -f add-default-deny.yaml
kubectl create namespace equipo-a
kubectl get networkpolicy -n equipo-a
NAME POD-SELECTOR AGE
default-deny-ingress <none> 3s
Paso 7: Exigir imágenes firmadas con Cosign
Las reglas verifyImages comprueban la firma de cada imagen antes de admitir el Pod. Sustituye el bloque de clave pública por el contenido de tu cosign.pub y ghcr.io/your_user/* por el prefijo de tus imágenes:
nano verify-images.yaml
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: verify-image-signature
spec:
background: false
rules:
- name: check-cosign-signature
match:
any:
- resources:
kinds: ["Pod"]
verifyImages:
- imageReferences:
- "ghcr.io/your_user/*"
failureAction: Enforce
attestors:
- entries:
- keys:
publicKeys: |-
-----BEGIN PUBLIC KEY-----
your_cosign_public_key
-----END PUBLIC KEY-----
kubectl apply -f verify-images.yaml
Prueba con una imagen firmada y otra sin firmar del mismo registro:
kubectl run firmada --image=ghcr.io/your_user/demo-app:v1.0.0 -n demo
kubectl run sin-firma --image=ghcr.io/your_user/otra-app:v1.0.0 -n demo
La primera se crea y la segunda se rechaza con un mensaje que incluye failed to verify image y no signatures found. Además, Kyverno reescribe la imagen admitida para fijarla por digest, así que el Pod ejecuta exactamente el contenido verificado:
kubectl get pod firmada -n demo -o jsonpath='{.spec.containers[0].image}{"\n"}'
ghcr.io/your_user/demo-app:v1.0.0@sha256:4f1c3e8a9b2d...
Importantesi el registro es privado, Kyverno necesita credenciales para leer las firmas. Crea un Secret de tipo
kubernetes.io/dockerconfigjsonen el namespacekyvernoy configúralo siguiendo el apartado de registros privados de la documentación deverifyImagesde tu versión de Kyverno.
Paso 8: Aplicar las políticas de seguridad estándar
Kyverno publica un chart con las políticas de Pod Security Standards (niveles baseline y restricted), útil como punto de partida:
helm install kyverno-policies kyverno/kyverno-policies \
--namespace kyverno \
--set podSecurityStandard=baseline
kubectl get clusterpolicy
Estas políticas se instalan en modo Audit por defecto. Revisa los PolicyReport durante unos días y pasa a Enforce las que no generen falsos positivos.
Solución de problemas
La política no pasa a READY True. Revisa el mensaje de estado; suele ser un error de sintaxis o falta de permisos en una regla de generación:
kubectl describe clusterpolicy add-default-deny
Una regla de generación no crea nada. Revisa los logs del controlador de background:
kubectl logs -n kyverno deployment/kyverno-background-controller
Todas las peticiones se ralentizan o fallan con context deadline exceeded. El webhook de admisión no responde a tiempo. Comprueba que los pods de kyverno-admission-controller están sanos y tienen CPU suficiente; las reglas verifyImages contra registros lentos son la causa más habitual.
Necesitas desactivar temporalmente una política. Pásala a Audit o elimínala con kubectl delete clusterpolicy <nombre>. No borres los webhooks a mano: Kyverno los gestiona y los volverá a crear.
Conclusión
Has instalado Kyverno con Helm y has creado políticas de validación en Audit y Enforce, una mutación que completa etiquetas, una generación de NetworkPolicies por namespace y una verificación de firmas de Cosign. Como siguientes pasos, guarda las políticas en Git y despliégalas con Argo CD o Flux, prueba las políticas en CI con la CLI de Kyverno (kyverno apply y kyverno test) y explora la biblioteca de políticas de kyverno.io para casos como límites de recursos o registros permitidos.
