El planificador ya escribió la respuesta
`kubectl describe pod <nombre>` y directo a Events. El planificador deja un mensaje del tipo «0/12 nodes are available: 8 Insufficient memory, 4 node(s) had untolerated taint».
Ese mensaje es una cuenta completa: de doce nodos, ocho no tienen memoria suficiente y cuatro tienen una marca que este pod no tolera. No hay que adivinar nada, y cada categoría apunta a un arreglo distinto.
Si el mensaje dice «0/0 nodes are available», el cluster no tiene nodos disponibles en absoluto y el problema es del autoescalador, no del pod.
Las causas, y qué hacer con cada una
Insufficient cpu / Insufficient memory. Ningún nodo tiene libre lo que el pod pide en `requests`. Ojo con la trampa: lo que se compara es el request, no el consumo real. Un nodo puede estar al 30% de uso real y estar «lleno» para el planificador porque los pods que ya tiene reservaron todo.
Untolerated taint. Los nodos que quedan tienen una marca —dedicados a otra carga, con GPU, en modo de mantenimiento— y este pod no trae la tolerancia. Se resuelve agregando la tolerancia o quitando la marca, y conviene entender por qué estaba antes de quitarla.
Node affinity / selector no coincide. El pod pide una etiqueta que ningún nodo tiene. Suele ser una zona que ya no existe, un tipo de instancia que se dejó de usar, o una etiqueta escrita con un guion de diferencia.
PVC sin enlazar. El pod espera un volumen que no se pudo crear. `kubectl describe pvc` lo explica: puede ser una StorageClass que no existe, o un volumen en una zona distinta a la del único nodo con espacio. Un pod con un volumen de una zona no puede correr en otra.
Topology spread o anti-afinidad demasiado estrictas. Una regla que exige un pod por zona en un cluster con dos zonas y tres réplicas deja la tercera en Pending para siempre. Con `requiredDuringScheduling` es un bloqueo permanente; con `preferred` el planificador lo relaja.
El autoescalador no puede agregar nodos. Cuota de la cuenta agotada, el tipo de instancia sin capacidad en esa zona, o un límite máximo en el grupo. Los eventos del autoescalador lo dicen y hay que mirarlos aparte de los del pod: tanto el Cluster Autoscaler como Karpenter registran por qué no pudieron aprovisionar, y ese mensaje no aparece en los eventos del pod.
La trampa de las requests: pagas dos veces por el mismo error
Cuando alguien pone `requests` muy por encima del consumo real —«por si acaso», o copiando el manifiesto de otro servicio— pasan dos cosas malas a la vez.
La primera es que aparecen pods en Pending sin motivo real: el planificador cree que los nodos están llenos porque suma reservas, no uso. Un cluster al 30% de utilización efectiva puede rechazar pods.
La segunda es que el autoescalador reacciona a esa presión ficticia y agrega nodos que nadie va a usar. Terminas pagando capacidad para sostener reservas que no corresponden a trabajo real.
Es una de las fuentes de sobregasto más frecuentes en trabajos de FinOps, y tiene la propiedad incómoda de verse como un problema de capacidad: el equipo pide más nodos y el gasto sube, cuando lo que había que revisar era el manifiesto.
La forma de detectarlo es comparar reservado contra usado por namespace. Si la brecha es grande y sostenida, el arreglo es dimensionar los requests y no agregar nodos.
Ese dimensionamiento —el rightsizing— se puede automatizar en parte con el Vertical Pod Autoscaler, que recomienda requests a partir del consumo observado. Conviene usarlo primero en modo recomendación y no en modo automático: un VPA que ajusta solo puede reiniciar pods en el peor momento, y el objetivo era estabilidad, no otra fuente de reinicios.
Pending con autoescalado por precio
Con autoescaladores que eligen el tipo de instancia según precio y disponibilidad, como Karpenter, el Pending se vuelve más dinámico y a veces más confuso.
El comportamiento normal es que un pod quede unos segundos en Pending mientras se aprovisiona un nodo que le calce. Eso no es una falla: es el sistema funcionando.
Se vuelve falla cuando el pod pide algo que ningún tipo de instancia disponible ofrece —una combinación de CPU y memoria que no existe en el catálogo, o una arquitectura para la que no hay capacidad—, y ahí el autoescalador intenta y no consigue. Sus propios eventos lo explican.
Y un efecto secundario: nodos que aparecen y desaparecen a mayor velocidad hacen que el cobro por host de las herramientas de observabilidad crezca mucho más rápido, porque cada nodo que existió cuenta.
Si antes calzaba y ahora no, algo cambió
Un servicio que se desplegaba bien y ahora queda en Pending suele haber cambiado en una de tres cosas: subieron sus requests en un commit, se agregó una regla de afinidad o de distribución, o el cluster perdió capacidad porque otro equipo desplegó algo grande.
Las dos primeras están en el manifiesto y tienen fecha. La tercera no está en tu servicio en absoluto, y es la más difícil de ver desde adentro: tu pod no calza por algo que hizo otro.
Distinguir «cambió mi manifiesto» de «cambió el cluster» es lo que evita perseguir el problema en el lugar equivocado.
Cómo Moonin acorta esa parte
Moonin mantiene el inventario continuo de recursos del cluster y el historial de revisiones de cada servicio, incluidos los requests y las reglas de programación. Ver que las requests de tu servicio subieron en la revisión de ayer, o que otro namespace creció, son dos datos que están en el mismo lugar.
Eso responde la pregunta de si el cambio fue tuyo o del cluster, que es la bifurcación donde se pierde más tiempo.
Lo que no hace: no ajusta requests, no agrega nodos y no modifica reglas de programación. La única mutación posible en todo el producto es el ajuste de HPA y réplicas por el Scaling Rules Agent, que es opcional y viene desactivado.
Ver el inventario de tu propio cluster
La demo está abierta y sin formulario: puedes revisar cómo se ve el inventario de recursos con lo reservado contra lo usado, antes de instalar nada.
Ver el historial de revisiones en la demo abierta · Cómo se cobra Moonin por Unidad de Cómputo