ResourceQuotas y LimitRanges
Con RBAC decides quién puede crear cosas en un namespace. Con Pod Security decides cómo pueden ser esas cosas. Falta la tercera pregunta, la que acaba en una discusión con el equipo de al lado: cuánto pueden consumir.
En el capítulo de policies del libro, ResourceQuota y LimitRange aparecen en secciones seguidas. El motivo lo vas a descubrir aquí de la peor manera posible: intentando crear un Pod.
Dos objetos se reparten el trabajo, y solo tienen sentido juntos:
- La ResourceQuota pone techo al consumo total del namespace.
- El LimitRange pone valores por defecto y mínimos y máximos a cada contenedor individual.
El playground trae ya creado el namespace tienda. Trabaja desde la pestaña dev-machine.
Paso 1: La ResourceQuota
Crea quota.yaml:
cat << 'EOF' > quota.yaml
apiVersion: v1
kind: ResourceQuota
metadata:
name: cuota-tienda
namespace: tienda
spec:
hard:
pods: "5"
requests.cpu: "1"
requests.memory: 1Gi
limits.cpu: "2"
limits.memory: 2Gi
EOF
El YAML, explicado con preguntas y respuestas
¿Qué significa hard?
Que son límites duros: la API rechaza en el acto cualquier creación que los supere. No hay advertencias ni margen, el objeto simplemente no se crea.
¿Qué suma exactamente requests.cpu: "1"?
La suma de los requests de CPU de todos los Pods del namespace no puede pasar de un núcleo. Aquí se cierra el círculo con la lección de QoS: los requests no solo guían al scheduler, también son la moneda con la que se cobra la cuota. Y limits.cpu limita la suma de los techos, que es lo que el namespace podría llegar a consumir en un pico.
¿Puedo limitar otras cosas además de cómputo y Pods?
Sí: número de Services, de Secrets, de ConfigMaps, de PVCs, gigas totales de almacenamiento solicitado, e incluso Services de tipo LoadBalancer (útil, porque cada uno cuesta dinero de verdad en un cloud). Una quota es el presupuesto general del namespace, no solo el de CPU.
¿Y si quiero cobrar distinto según el tipo de carga?
Para eso están los scopes y los scopeSelector: una quota puede aplicarse solo a los Pods de una PriorityClass concreta, o solo a los que no son terminables (NotTerminating, es decir, los de larga duración frente a los Jobs). Es como se reserva capacidad para lo crítico sin ahogar lo demás.
Aplícala y mira el estado del presupuesto:
kubectl apply -f quota.yaml
kubectl describe resourcequota cuota-tienda -n tienda
La columna Used contra la columna Hard: así se lee una quota.
Paso 2: El rechazo que sorprende (y por qué el LimitRange no es opcional)
Antes de seguir, crea el Pod más inocente del mundo en el namespace gobernado:
kubectl run sin-recursos --image=ghcr.io/iximiuz/labs/nginx:alpine -n tienda
Rechazado. El mensaje es explícito: must specify limits.cpu, requests.cpu.... Y esto sorprende a todo el mundo la primera vez, así que conviene entenderlo bien:
¿Por qué la quota rechaza un Pod que no pide nada?
Porque una quota que limita requests.cpu necesita saber cuánto suma cada Pod. Un Pod sin requests declarados es un Pod que no se puede contabilizar, así que la API prefiere rechazarlo antes que dejar un agujero en el presupuesto. Consecuencia práctica: en el momento en que pones una quota de cómputo, declarar recursos deja de ser opcional para todo el mundo en ese namespace, y todos los YAML que no lo hacían dejan de funcionar de golpe.
Ese es exactamente el incidente que el LimitRange existe para evitar.
Paso 3: El LimitRange
Crea limitrange.yaml:
cat << 'EOF' > limitrange.yaml
apiVersion: v1
kind: LimitRange
metadata:
name: limites-tienda
namespace: tienda
spec:
limits:
- type: Container
defaultRequest:
cpu: 100m
memory: 64Mi
default:
cpu: 200m
memory: 128Mi
max:
cpu: "1"
memory: 1Gi
min:
cpu: 10m
memory: 16Mi
EOF
El YAML, explicado con preguntas y respuestas
¿Qué diferencia hay entre defaultRequest y default?
Nombres confusos para una pareja que ya conoces: defaultRequest es el request por defecto y default es el limit por defecto. Se aplican, en la admisión, a cada contenedor que no declare los suyos.
¿A qué se refiere type: Container?
Al ámbito de las reglas. Container es el habitual. También existen Pod (donde los mínimos y máximos se aplican a la suma de sus contenedores) y PersistentVolumeClaim (para acotar el tamaño del almacenamiento que alguien puede reclamar).
¿Y min y max?
La otra faceta del LimitRange: además de rellenar, prohíbe. Un contenedor que pida más de max o menos de min es rechazado. El max protege al clúster del Pod glotón; el min protege al desarrollador de sí mismo (un request ridículo produce Pods que el scheduler coloca en cualquier sitio y que luego se ahogan).
¿Se aplica esto a los Pods que ya existían?
No. Como la admisión de Pod Security, el LimitRange actúa en la puerta, sobre lo que se crea a partir de ahora. Los Pods viejos siguen igual.
Aplícalo y mira la demostración perfecta: crea un Pod sin recursos, el mismo comando que hace un minuto fallaba.
kubectl apply -f limitrange.yaml
kubectl run prueba-limites --image=ghcr.io/iximiuz/labs/nginx:alpine -n tienda
kubectl get pod prueba-limites -n tienda -o jsonpath='{.spec.containers[0].resources}'; echo
kubectl get pod prueba-limites -n tienda -o jsonpath='{.status.qosClass}'; echo
Ahora sí entra, y entra con recursos que tú no escribiste: 100m y 64Mi de request, 200m y 128Mi de limit. El LimitRange los inyectó en la admisión, y con ellos la quota ya puede contabilizarlo.
Y una consecuencia que cierra el círculo con la lección de QoS: ese Pod, que en el namespace default habría sido BestEffort, aquí se crea como Burstable. Sin que nadie escribiera una línea de resources.
Paso 4: La quota diciendo que no
Queda ver el techo en acción. Intenta crear un Pod que se salte el presupuesto entero:
kubectl run tragon --image=ghcr.io/iximiuz/labs/nginx:alpine -n tienda \
--overrides='{"spec":{"containers":[{"name":"tragon","image":"ghcr.io/iximiuz/labs/nginx:alpine","resources":{"requests":{"cpu":"10"}}}]}}'
Error inmediato: exceeded quota. Ni siquiera llegó a existir el Pod.
Esta es la diferencia que conviene grabar, porque los tres fallos se parecen y no lo son:
- Rechazo de quota: error de la API, instantáneo. El objeto no existe.
- Rechazo del LimitRange (
minomax): error de la API, instantáneo. El objeto no existe. Pendingdel scheduler: el objeto sí existe, pero nadie le encuentra nodo. Es el caso del Podprocesadorde aquel challenge.
Ante un fallo, la primera pregunta es siempre la misma: ¿el objeto llegó a crearse? La respuesta te dice si el problema está en la admisión o en el scheduling.
Resumen
- La ResourceQuota es el presupuesto duro del namespace: Pods, CPU, memoria, objetos, almacenamiento.
- El LimitRange rellena requests y limits por defecto e impone mínimos y máximos por contenedor.
- Quota sin LimitRange es media solución: obliga a declarar recursos y rompe todos los manifiestos despistados.
- Rechazo en la admisión (el objeto no existe) y
Pendingdel scheduler (el objeto existe sin nodo) son fallos distintos.
- Previous lesson
- Pod Security Admission
- Next lesson
- Afinidad y selección de nodo