--- title: "Cómo instalar Kyverno en Kubernetes y crear políticas" description: "Instala Kyverno con Helm y crea políticas de validación, mutación y generación en YAML, verifica firmas de imágenes con Cosign y revisa informes de auditoría." category: "Seguridad de Contenedores" language: es source: https://cubepath.com/es/docs/seguridad-de-contenedores/motor-de-politicas-kyverno-para-kubernetes updated: 2026-09-25 --- # Cómo instalar Kyverno en Kubernetes y crear políticas 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. - `kubectl` configurado con permisos de `cluster-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: ```bash 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: ```bash 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: ```bash 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: ```bash kubectl get pods -n kyverno ``` ```output 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: ```bash 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: ```bash nano require-labels.yaml ``` ```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. ```bash kubectl apply -f require-labels.yaml kubectl get clusterpolicy require-labels ``` ```output 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: ```bash 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: ```bash kubectl get policyreport -n demo ``` ```output 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: ```bash 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`: ```bash nano disallow-latest-tag.yaml ``` ```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" ``` ```bash kubectl apply -f disallow-latest-tag.yaml ``` Prueba a desplegar una imagen con `latest`: ```bash kubectl create deployment prueba --image=nginx:latest -n demo ``` ```output 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. > Nota: desde Kyverno 1.13 la acción se define en cada regla con `validate.failureAction`. El campo antiguo `spec.validationFailureAction` sigue 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: ```bash nano add-team-label.yaml ``` ```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 ``` ```bash kubectl apply -f add-team-label.yaml kubectl run mutado --image=nginx:1.27 -n demo kubectl get pod mutado -n demo --show-labels ``` ```output 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: ```bash nano kyverno-networkpolicies-rbac.yaml ``` ```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: ```bash nano add-default-deny.yaml ``` ```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: ```bash 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 ``` ```output NAME POD-SELECTOR AGE default-deny-ingress 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: ```bash nano verify-images.yaml ``` ```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----- ``` ```bash kubectl apply -f verify-images.yaml ``` Prueba con una imagen firmada y otra sin firmar del mismo registro: ```bash 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: ```bash kubectl get pod firmada -n demo -o jsonpath='{.spec.containers[0].image}{"\n"}' ``` ```output ghcr.io/your_user/demo-app:v1.0.0@sha256:4f1c3e8a9b2d... ``` > Importante: si el registro es privado, Kyverno necesita credenciales para leer las firmas. Crea un Secret de tipo `kubernetes.io/dockerconfigjson` en el namespace `kyverno` y configúralo siguiendo el apartado de registros privados de la documentación de `verifyImages` de 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: ```bash 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: ```bash kubectl describe clusterpolicy add-default-deny ``` **Una regla de generación no crea nada.** Revisa los logs del controlador de background: ```bash 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 `. 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.