Introducción
Un cluster de Kubernetes corriendo v1.36 en producción puede pasar a v1.37 sin que nadie toque un archivo de configuración, y sin embargo, el kubelet de cada nodo ahora escribe valores en los cgroups de memoria que antes no escribía. O no, depende de qué campos estén presentes en el kubelet config. Esa ambigüedad es exactamente el riesgo operativo que la promoción de Memory QoS a Beta introduce para equipos que gestionan flotas de nodos heterogéneos.
El feature gate MemoryQoS existía como Alpha desde Kubernetes v1.22 y pasó por una expansión en v1.36 que sumó la política de reserva en capas (TieredReservation). Lo que cambia en v1.37 es concreto: el gate se habilita por defecto en todos los kubelets, el valor default de memoryThrottlingFactor pasa de 0.9 a null, y el kubelet deja de setear memory.high automáticamente en contenedores Burstable y BestEffort. Para la mayoría de los clusters esto significa «no cambia nada», pero para los que tenían configuración explícita o dependían del comportamiento Alpha implícito, el upgrade puede alterar la presión de memoria percibida por el kernel.
Qué ocurrió
SIG Node completó la graduación de MemoryQoS a Beta en Kubernetes v1.37, publicado el 14 de septiembre de 2026. A diferencia de otros feature gates que requieren habilitación explícita vía –feature-gates=MemoryQoS=true, en esta versión el kubelet arranca con el feature activo sin ninguna intervención del operador. La decisión se apoya en que la configuración por defecto del kubelet no habilita ni throttling ni reserva: no se escriben valores en memory.high, memory.min ni memory.low en los cgroups del nodo salvo que el administrador lo configure de forma explícita.
El cambio más relevante para los operadores es la modificación del default de memoryThrottlingFactor. En las releases Alpha (v1.22 a v1.35), ese campo defaulteaba a 0.9, lo que significaba que activar el feature gate hacía que el kubelet calculara y escribiera memory.high en los contenedores. En v1.37 el default es null, y el kubelet no setea memory.high a menos que el administrador configure un valor numérico entre 0 y 1. El motivo es directo: con el gate ahora encendido por defecto, un memory.high automático de 0.9x del límite habría throttled workloads que antes corrían sin restricciones, generando regresiones silenciosas en clusters que se actualizaban sin revisar configuración.
Impacto para DevOps / Infraestructura / Cloud / Seguridad
Para equipos que gestionan clusters con perfiles de kubelet heredados de Alpha, el impacto se concentra en un escenario específico: si el archivo kubelet-config.yaml no contiene la clave memoryThrottlingFactor explícitamente, el kubelet adopta null y deja de aplicar throttling en contenedores Burstable y BestEffort. Workloads que dependían de ese límite implícito para no acaparar memoria del nodo ahora pueden crecer hasta memory.max sin intervención del kernel. En clusters con alta densidad de pods Burstable, esto puede derivar en OOM kills en el QoS cgroup padre en lugar del throttling suave que el kernel aplicaba vía memory.high.
La política memoryReservationPolicy con valor TieredReservation introduce una restricción de granularidad que afecta directamente la planificación de workloads. Cuando está activa, todos los pods Guaranteed reciben memory.min y todos los Burstable reciben memory.low a nivel del cgroup del nodo. No existe un mecanismo para excluir pods individuales. Además, la reserva dura incluye la page cache cargada en el cgroup del contenedor: un pod que lee archivos grandes retiene memoria que el kernel ya no puede reutilizar para los vecinos del mismo nodo. En clusters de procesamiento batch que comparten nodos con servicios latencia-sensibles, esto puede degradar tail latency de forma no trivial. SIG Node está trabajando en ambas restricciones bajo el issue kubernetes/kubernetes#140246.
Detalles técnicos
Los requisitos del feature son estrictos: solo funciona en nodos Linux con cgroup v2. En nodos con cgroup v1 o en Windows, el kubelet ignora la configuración de Memory QoS sin generar errores. El componente que interactúa con el kernel es el memory controller de cgroup v2, que expone los archivos memory.high, memory.min, memory.low y memory.max en la jerarquía de cgroups del nodo (típicamente bajo /sys/fs/cgroup/kubepods.slice/).
La configuración se realiza en el archivo kubelet-config.yaml del nodo. Para habilitar throttling explícito:
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
memoryThrottlingFactor: 0.9
Para activar la reserva en capas:
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
memoryReservationPolicy: TieredReservation
El cálculo de memory.high varía según la QoS class del pod. Para Burstable, el kubelet aplica memoryThrottlingFactor sobre el límite efectivo de memoria del contenedor. Para BestEffort, se aplica sobre el total disponible del nodo. Los valores aceptados para memoryThrottlingFactor son floats entre 0 y 1; fuera de ese rango, el kubelet rechaza la configuración al arrancar.
Si se necesita deshabilitar el feature post-upgrade, hay que setear el feature gate a false y verificar que memoryThrottlingFactor no tenga un valor distinto de 0.9 (el antiguo default) y que memoryReservationPolicy no esté en TieredReservation. El kubelet valida estas condiciones y falla si detecta inconsistencias. Con el gate apagado, el kubelet limpia valores stale al arrancar: memory.min=0 y memory.low=0 en el cgroup raíz kubepods, y memory.low=0 en el cgroup Burstable.
Qué deberían hacer los administradores y equipos técnicos
Antes de subir los nodos a v1.37, auditá la configuración del kubelet en cada perfil de nodo. Ejecutá una búsqueda de memoryThrottlingFactor y memoryReservationPolicy en los archivos kubelet-config.yaml distribuidos por tu herramienta de gestión (Ansible, Terraform, DaemonSet de ConfigMap, etc.). Si encontrás un valor explícito de memoryThrottlingFactor, ese valor se preserva durante el upgrade y el throttling continúa funcionando igual. Si no existe la clave, asumí que el kubelet va a escribir memory.high a partir de v1.37 solo si vos lo configurás, pero los workloads que antes dependían del default Alpha de 0.9 van a perder ese límite.
Si tu cluster usa TieredReservation, revisá que no haya pods Guaranteed que carguen page cache masiva (procesamiento de logs, compresión, ETL de archivos grandes) compartiendo nodos con servicios de latencia crítica. La reserva dura de memory.min incluye esa cache y no se puede excluir por pod. Para mitigar, separá workloads por taints o nodeAffinity hasta que SIG Node entregue la granularidad por pod.
Para validar el comportamiento post-upgrade, verificá los cgroups en un nodo canario:
cat /sys/fs/cgroup/kubepods.slice/kubepods-burstable.slice/memory.high
cat /sys/fs/cgroup/kubepods.slice/kubepods-burstable.slice/memory.low
Si ambos muestran max o 0 respectivamente, el kubelet no está aplicando valores y el cluster se comporta como en v1.36 sin el feature gate. Si esperás throttling y no lo ves, agregá memoryThrottlingFactor: 0.9 explícito al kubelet config y reiniciá el kubelet: systemctl restart kubelet.
Conclusión
La graduación a Beta de Memory QoS en Kubernetes v1.37 es una señal de madurez del feature: SIG Node lo considera estable para producción y listo para retroalimentación a escala. El cambio de default de memoryThrottlingFactor a null elimina el riesgo de throttling automático no deseado, pero también obliga a los operadores a declarar explícitamente qué comportamiento de memoria quieren. Los equipos que gestionan clusters con políticas de memoria personalizadas deben tratar el upgrade a v1.37 como un cambio de configuración, no como un bump de versión trivial. El próximo hito es la graduación a GA, y los bugs reportados en kubernetes/kubernetes durante esta fase Beta van a definir los ajustes finales.
