Priority y Preemption
Hasta ahora, cuando un Pod no cabía, se quedaba en Pending y ahí acababa la historia. Educado, pero ingenuo: si el Pod que no cabe es la pasarela de pagos y lo que ocupa el nodo es un trabajo de informes que puede esperar a mañana, alguien ha tomado la decisión equivocada.
La prioridad es cómo se le dice al scheduler que no todos los Pods valen lo mismo. Y la preemption (expulsión) es lo que hace con esa información: cuando un Pod importante no cabe en ningún sitio, el scheduler busca un nodo donde, echando a Pods menos importantes, sí quepa. Y los echa.
El playground ya ha calculado el tamaño exacto para que solo quepa un Pod de estos en node-01, y ha dejado los dos manifiestos en /home/laborant/manifests. Míralos desde la pestaña dev-machine:
cat /home/laborant/manifests/reports.yaml
cat /home/laborant/manifests/api.yaml
kubectl get node node-01 -o yaml | yq .status.allocatable
Son idénticos salvo el nombre. Tu trabajo es darles una prioridad distinta y ver qué pasa.
Paso 1: Las PriorityClasses
Crea prioridades.yaml:
cat << 'EOF' > prioridades.yaml
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
name: baja
value: 100
globalDefault: false
description: "Cargas que pueden esperar: informes, procesos por lotes, experimentos."
---
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
name: alta
value: 1000000
globalDefault: false
preemptionPolicy: PreemptLowerPriority
description: "Servicios críticos de negocio."
EOF
El YAML, explicado con preguntas y respuestas
¿Una PriorityClass tiene namespace?
No. Es un objeto de clúster, como las StorageClasses, las IngressClasses o los nodos. Tiene sentido: la prioridad es una escala común, y no serviría de nada que cada equipo tuviera la suya.
¿Qué es el value y qué escala se usa?
Un número entero, y cuanto más alto, más importante. La escala te la inventas tú, pero hay un tramo reservado: por encima de mil millones (1000000000) están las clases del sistema, system-cluster-critical y system-node-critical, que usan los componentes del propio Kubernetes. Míralas, ya existen en tu clúster:
kubectl get priorityclass
¿Qué pasa si pongo globalDefault: true?
Que todos los Pods que no declaren prioridad heredan esa clase. Solo puede haber una en todo el clúster, y es una decisión seria: convertir "crítico" en el valor por defecto es exactamente igual de inútil que no tener prioridades. Si no hay ninguna clase por defecto, la prioridad de un Pod sin clase es cero.
¿Y preemptionPolicy?
Decide si esta clase expulsa o solo adelanta en la cola. Con PreemptLowerPriority (el valor por defecto), un Pod que no cabe puede echar a otros. Con Never, el Pod es prioritario para elegir sitio cuando lo haya, pero no desaloja a nadie: se pone el primero de la cola y espera. Esa segunda opción es muy útil para cargas importantes pero no urgentes, y es la que evita sustos.
kubectl apply -f prioridades.yaml
kubectl get priorityclass
Paso 2: Llenar el nodo con lo prescindible
Edita /home/laborant/manifests/reports.yaml y añade una sola línea dentro de su spec:
priorityClassName: baja
Y aplícalo:
kubectl apply -f manifests/reports.yaml
kubectl get pod reports -o wide
Está en node-01, tan tranquilo, ocupando el 70 % de la CPU del nodo. Nadie lo molesta.
Paso 3: Llega el importante
Ahora edita /home/laborant/manifests/api.yaml con la otra clase:
priorityClassName: alta
Y prepárate para mirar dos cosas a la vez. Aplica y observa:
kubectl apply -f manifests/api.yaml
kubectl get pods -o wide --watch
En unos segundos: el Pod reports desaparece y el Pod api pasa de Pending a Running en node-01.
Lo que acaba de ocurrir, paso a paso, y merece la pena entenderlo bien:
- El scheduler intenta colocar a
api. No cabe ennode-01(el único que sunodeSelectorpermite) porquereportstiene la CPU reservada. - En vez de rendirse, evalúa la expulsión: busca qué Pods de menor prioridad tendría que echar de ese nodo para hacer sitio.
- Elige a
reportscomo Pod a desalojar y lo desaloja, respetando su período de gracia (y, en la medida de lo posible, sus PodDisruptionBudgets). - Con el hueco libre, programa a
api.
Léelo en los eventos, que es donde queda la confesión:
kubectl get events --sort-by=.lastTimestamp | tail -10
kubectl describe pod api | grep -A5 Events
Verás el motivo Preempted y el nombre del Pod desalojado.
La letra pequeña que hay que conocer
El Pod expulsado no vuelve. reports era un Pod suelto, así que ha desaparecido para siempre. Si hubiera sido parte de un Deployment, el ReplicaSet crearía un sustituto que iría a otro nodo, o se quedaría en Pending esperando sitio. Moraleja: la preemption no reprograma al Pod desalojado, solo lo expulsa; quien lo recrea, si acaso, es su controlador.
La prioridad también ordena la cola. No es solo para expulsar: entre varios Pods pendientes, el scheduler atiende primero a los de mayor prioridad, aunque no haya que echar a nadie.
Cuidado con la prioridad como arma. En un clúster compartido, si cada equipo puede crear PriorityClasses y usarlas libremente, todos acabarán siendo críticos y habrás vuelto al punto de partida (con la desventaja de haber añadido complejidad). Por eso las PriorityClasses se gobiernan: se restringe su creación con RBAC y se limita su uso con ResourceQuotas y scopeSelector, que permiten decir "en este namespace solo puedes tener 2 Pods de prioridad alta".
No confundas prioridad con QoS. Son dos jerarquías distintas y actúan en momentos distintos: la prioridad la usa el scheduler para decidir a quién coloca y a quién echa; la QoS Class la usa el kubelet para decidir a quién desaloja cuando el nodo se queda sin memoria. Un Pod puede ser de prioridad alta y BestEffort a la vez, y ser el primero en caer cuando al nodo le falte memoria.
Resumen
- Una PriorityClass es un objeto de clúster con un
valueentero: cuanto más alto, más importante. - La preemption expulsa Pods de menor prioridad para hacer sitio a uno mayor.
preemptionPolicy: Neverprioriza sin expulsar. - El Pod desalojado no se reprograma solo: lo recrea su controlador, o no lo recrea nadie.
globalDefaultsolo puede haber uno, y sin él la prioridad por defecto es cero.- Prioridad (scheduler, dónde) y QoS (kubelet, a quién matar) son escalas distintas. No se mezclan.
- Previous lesson
- Challenge: programa los Pods pendientes
- Next lesson
- PodDisruptionBudget y drenado de nodos