Common Expression Language (CEL) en Kubernetes
CEL lleva varias releases siendo adoptado en Kubernetes. Aprende sus operadores clave y escribe políticas de validación nativas sin componentes externos.
¿Que es Common Expression Language (CEL)?
Common Expression Language (CEL) es un lenguaje de expresiones ligero creado por Google, diseñado para validaciones, evaluaciones lógicas rápidas y portabilidad. A diferencia de un lenguaje de programación completo, CEL es restringido y seguro: no permite ejecución de código arbitrario, lo que lo hace ideal para las APIs de Kubernetes.
Tendencia y adopción en Kubernetes
Durante los ultimos años está habiendo una tendencia ascendente y fuerte a utilizar Common Expression Language (CEL) para las políticas de validación, gobernanza y admission en Kubernetes.
Kubernetes incoporó Common Expression Language (CEL) en la versión 1.25 (CRD validation rules), extendiendo su uso a otras funcionalidad como el ValidatingAdmissionPolicy (GA en 1.30).
Los motivos que han impulsado su utilización:
Rendimiento: Se ejecuta directamente en el API Server, eliminando la necesidad del uso de webhooks externos, reduciendo así la latencia
Simplicidad y seguridad: Es un lenguaje de expresiones restringido y determinista. No se pueden realizar iteraciones (bucles), no hay acceso a sistemas externos ni ejecución de código arbitrario
Nativo: Las reglas se definen en los manifiestos YAML como parte de los propios objetos de Kubernetes (
ValidatingAdmissionPolicyyMutatingAdmissionPolicy)Portabilidad: Se está convirtiendo en un estándar, proyectos como Kyverno han comenzado a integrarlo como motor de expresiones. Al igual que otros proyectos como Google Cloud IAM Conditions o Envoy entre otros proyectos de la CNCF
¡Gracias por pasarte por aquí! 🙌
Si te interesa seguir recibiendo guías prácticas sobre Kubernetes, Azure, DevOps y contenedores directamente en tu email…
Suscríbete gratis y no te pierdas ninguna novedad. 🚀
9 ejemplos para comenzar a utilizar Common Expression Language (CEL)
Comprobar que exista un mínimo y máximo de replicas
CEL soporta el operador ternario condition ? valueIfTrue : valueIfFalse. En este caso se aplica una regla diferente según el namespace. Es una forma poderosa de crear políticas contextuales.
object.spec.replicas >= params.minReplicas && object.spec.replicas <= params.maxReplicas
# Al menos 2 réplicas para alta disponibilidad
object.spec.replicas >= 2
# Solo aplicar en namespace production
object.metadata.namespace == "production"
? object.spec.replicas >= 3
: object.spec.replicas >= 1Comprobar el prefijo y sufijo
startsWith() y endsWith() son funciones nativas de CEL sobre strings. Ideales para imponer convenciones de nomenclatura en recursos de Kubernetes.
# El nombre debe empezar con "app-"
object.metadata.name.startsWith("app-")
# El nombre debe terminar con "-prod"
object.metadata.name.endsWith("-prod")
# Combinación de ambos
object.metadata.name.startsWith("app-") &&
object.metadata.name.endsWith("-prod")
Comprobar que la imagen tenga un tag
Exige que todos los contenedores del Pod usen una imagen con tag explícito y distinto de :latest.
object.spec.containers.all(c, c.image.contains(’:’) && !c.image.endsWith(’:latest’))Comprobar la utilización de contenedores con privilegios
Cuando un campo es opcional, usar has() antes de acceder a él evita errores de evaluación. El cortocircuito de || hace que si !has() es true, la segunda parte no se evalúa. Este patrón es clave en políticas reales.
object.spec.containers.all(c,
!has(c.securityContext) ||
!has(c.securityContext.privileged) ||
c.securityContext.privileged != true
)
# Si existe securityContext, debe tener runAsNonRoot=true
!has(object.spec.securityContext) ||
object.spec.securityContext.runAsNonRoot == true
# Patrón: campo opcional con valor por defecto
has(object.spec.priority)
? object.spec.priority <= 10
: true
Comprobar que todos los contenedores usan request y limits de CPU y memoria
Garantiza que cada contenedor del Pod defina explícitamente requests y limits tanto de CPU como de memoria, evitando pods sin restricciones que puedan afectar al resto del cluster.
object.spec.containers.all(c,
has(c.resources) &&
has(c.resources.requests) && has(c.resources.requests.cpu) && has(c.resources.requests.memory) &&
has(c.resources.limits) && has(c.resources.limits.cpu) && has(c.resources.limits.memory)
)Comprobar si se utilizan volumenes hostPath y hostNetwork
Impide que un Pod acceda directamente al sistema de archivos o a la red del nodo donde se ejecuta. Ambas capacidades pueden comprometer el aislamiento del cluster si caen en manos de un contenedor malicioso.
(!has(object.spec.hostNetwork) || object.spec.hostNetwork == false) &&
(!has(object.spec.volumes) || object.spec.volumes.all(v, !has(v.hostPath)))Prohibir actualizar la imagen de un contenedor
Garantiza que una vez desplegado un Pod, las imágenes de sus contenedores no puedan cambiarse. Útil para entornos donde los cambios de imagen deben pasar por un nuevo despliegue controlado.
request.operation != ‘UPDATE’ ||
object.spec.template.spec.containers.all(newC,
oldObject.spec.template.spec.containers.exists(oldC,
oldC.name == newC.name && oldC.image == newC.image
)
)Comprobar que existen una label con varios valores predeterminados
Obliga a que todos los recursos tengan el label tier y que su valor sea uno de los permitidos por la organización.
has(object.metadata.labels.tier) &&
object.metadata.labels.tier in ['frontend', 'backend', 'database']Validar que un campo cumpla un patrón utilizando una expresión regular
matches() evalúa una expresión regular (el subconjunto de regex que usa Go). No soporta lookaheads ni backreferences. Muy útil para validar formatos de versiones, IPs, o nombres con convenciones estrictas.
# Solo minúsculas, números y guiones
object.metadata.name.matches("^[a-z0-9-]+$")
# Validar formato de versión semántica
object.spec.version.matches(
"^v[0-9]+\.[0-9]+\.[0-9]+$"
)Si este post te ha ayudado o aclarado algo, me harías muy feliz con un ❤️
Y si crees que le puede servir a alguien más… ¡compártelo! 🙌
Como agradecimiento por haber llegado hasta aquí, te dejo una tabla resumen con los operadores, macros y funciones más comunes en CEL.
Conclusión
Creo que CEL no es una moda pasajera: Kubernetes lleva utilizando esta tecnología desde hace varias versiones, y la tendencia es que otros proyectos de la CNCF también están adoptándola, consolidándose como el motor de expresiones estándar.
Con unos pocos operadores se pueden escribir la mayoría de las políticas que necesitamos en nuestro día a día, lo que hace que la curva de aprendizaje sea muy corta.
Creo que aprender CEL es una buena inversión: la misma sintaxis y patrones nos van a servir en diferentes aplicaciones, como Kubernetes, Kyverno y otros proyectos del ecosistema.
Referencias
https://github.com/google/cel-go
https://github.com/google/cel-spec/blob/master/doc/langdef.md
https://kubernetes.io/docs/reference/using-api/cel/
