Saltar a contenido

04 01 scheduling

Pod Priority y Preemption (PriorityClass)

¿Qué es una PriorityClass?

Es un objeto de nivel de clúster (no pertenece a un namespace) que define un mapeo entre un nombre de prioridad y un valor entero. Cuanto mayor sea el número, mayor será la prioridad del objeto.

Los Dos Superpoderes que Desbloquea:

  • Priority-based Scheduling (Planificación por Prioridad): Si el planificador (kube-scheduler) tiene una cola de Pods esperando ser asignados a un nodo, procesará primero los Pods con mayor prioridad.

  • Pod Preemption (Desalojo / Expulsión por Prioridad): Si un Pod de alta prioridad no puede ser planificado porque todos los nodos están llenos, el clúster expulsará (matará) Pods de menor prioridad de un nodo para liberar espacio y acomodar al Pod crítico.

Componentes Clave del Manifiesto YAML

A continuación, la estructura base optimizada de un objeto PriorityClass:

apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
  name: alta-prioridad-critical
value: 1000000
globalDefault: false
description: "Usar solo para servicios core que no pueden tolerar downtime (ej: API de pagos)."
preemptionPolicy: PreemptLowerPriority

Desglose de los Parámetros Críticos:

  • value: El valor máximo que puede tener una PriorityClass creada por un usuario es: 1,000,000,000.

  • globalDefault: Si se setea en true, cualquier Pod que se cree en el clúster sin especificar una prioridad explícita adoptará automáticamente este valor. ¡Cuidado! Solo puede haber una PriorityClass con globalDefault: true en todo el clúster.

  • preemptionPolicy: Controla el comportamiento frente a la falta de espacio. Tiene dos opciones:

  • PreemptLowerPriority (Por defecto): Si no hay espacio, expulsa a Pods menos importantes inmediatamente.

  • Never: El Pod se colocará al principio de la cola de espera de planificación (se asignará antes que otros), pero NUNCA matará a otros Pods para quitarles el lugar. Es ideal para trabajos de procesamiento Batch (Data Science, Backups masivos) que son importantes pero pueden esperar a que un nodo se libere de forma natural.

¿Cómo consume un Pod esta configuración?

Para que un Pod se beneficie de la prioridad, debes mapearlo explícitamente dentro de su definición en la sección spec usando el campo priorityClassName.

apiVersion: v1
kind: Pod
metadata:
  name: backend-pagos
  namespace: produccion
spec:
  priorityClassName: alta-prioridad-critical # <--- Enlace al PriorityClass
  containers:
  - name: app
    image: nginx
    resources:
      requests:
        memory: "1Gi"
        cpu: "500m"

El Flujo de "Preemption" bajo el capó 🕵️‍♂️

¿Qué pasa exactamente cuando un Pod prioritario desplaza a otro?

  1. El Pod prioritario ingresa a la cola de planificación.
  2. El kube-scheduler intenta buscar un nodo con suficiente CPU/Memoria libre (requests). No encuentra ninguno.
  3. El planificador busca un nodo donde, si elimina a uno o varios Pods de menor prioridad, el Pod prioritario pueda caber.
  4. Una vez detectado el nodo ideal, el planificador genera un evento y actualiza el campo status.nominatedNodeName en el Pod prioritario (básicamente le asigna su "trono reservado").
  5. Los Pods sacrificados de menor prioridad reciben una señal SIGTERM y pasan por su tiempo de gracia de terminación regular (terminationGracePeriodSeconds).
  6. En cuanto el nodo se vacía, el Pod prioritario es agendado y arranca.

Tips de Velocidad y Troubleshooting (Modo Examen / Producción)

  • ¿Cómo listar las prioridades existentes?
kubectl get priorityclasses
# o su versión corta:
kubectl get pc
  • Evita el comportamiento "Eviction Loop" (Bucle de Desalojos): Si creas una PriorityClass muy alta y se la pones a una aplicación inestable que entra en CrashLoopBackOff, Kubernetes podría estar matando constantemente Pods saludables de desarrollo para intentar revivir a un Pod que de todos modos va a fallar. Monitorea los eventos de preemption con:
kubectl get events --sort-by='.metadata.creationTimestamp'
  • Combinación de Oro (PriorityClass + ResourceQuotas): Si le das acceso a los desarrolladores para usar PriorityClass muy altas, podrían abusar de ellas para que sus Pods nunca mueran. En producción, la mejor práctica es limitar el uso de ciertas PriorityClasses por Namespace usando objetos ResourceQuota.

Resource Quotas en Kubernetes

¿Qué es una ResourceQuota?

Es un objeto de nivel de Namespace (vive restringido a un entorno lógico específico) que establece límites estrictos al consumo total de recursos agregados dentro de ese Namespace.

[!WARNING] ¡Si pones una cuota de CPU o Memoria en un Namespace, Kubernetes te obligará a definir requests y limits en TODOS los Pods que intentes crear allí! Si un desarrollador intenta desplegar un Pod sin configurar los recursos, el Admission Controller rechazará la creación inmediatamente con un error. Para solucionar esta fricción se suele emparejar con un objeto Link LimitRange.

Componentes Clave del Manifiesto YAML

A continuación, la estructura maestra de una ResourceQuota que cubre los tres frentes de control (Cómputo, Almacenamiento y Objetos):

apiVersion: v1
kind: ResourceQuota
metadata:
  name: cuota-infra-dev
  namespace: desarrollo # <- Aplica únicamente a este Namespace
spec:
  hard:
    # 1. Recursos de Cómputo (CPU y Memoria)
    requests.cpu: "4"
    requests.memory: 8Gi
    limits.cpu: "8"
    limits.memory: 16Gi

    # 2. Recursos de Almacenamiento
    requests.storage: 100Gi
    persistentvolumeclaims: "10"

    # 3. Cantidad Total de Objetos de la API
    pods: "20"
    services: "10"
    services.loadbalancers: "2"
    configmaps: "15"
    secrets: "20"

Tipos de Cuotas que Puedes Controlar

A. Recursos de Cómputo

Controlan la suma total de las solicitudes (requests) y límites (limits) de todos los contenedores activos en el namespace:

  • requests.cpu y requests.memory
  • limits.cpu y limits.memory

B. Recursos de Almacenamiento (Storage Quotas)

Controlan el consumo de almacenamiento persistente:

  • requests.storage: La suma de espacio en disco solicitado por todas las PVCs.
  • persistentvolumeclaims: El número máximo de PVCs permitidas.
  • <storage-class-name>.storage.k8s.io/requests.storage: Límita el espacio asignado a una StorageClass específica (ej. fast-disks.storage.k8s.io/requests.storage: 50Gi).

C. Cuotas de Conteo de Objetos (Object Count Quotas)

Ideales para evitar que el clúster sufra de saturación en su base de datos etcd debido a scripts automatizados erróneos:

  • pods, services, replicationcontrollers, deployments, statefulsets, secrets, configmaps, etc.

ResourceQuota combinada con Scopes

Puedes refinar una cuota para que afecte únicamente a ciertos tipos de Pods utilizando scopes.

Por ejemplo, si deseas crear una cuota que aplique solo a los Pods de alta prioridad (enganchados al tema de PriorityClass que estudiamos antes), puedes usarlo así:

apiVersion: v1
kind: ResourceQuota
metadata:
  name: cuota-servicios-criticos
  namespace: produccion
spec:
  hard:
    pods: "5"
  scopeSelector:
    matchExpressions:
    - scopeName: PriorityClass
      operator: In
      values: ["alta-prioridad-critical"]

Otros Scopes comunes: Terminating (Pods con un activeDeadlineSeconds), NotTerminating (Pods de larga duración sin deadline), BestEffort (Pods sin requests ni limits).

Tips de Velocidad y Troubleshooting

  • ¿Cómo ver el estado actual y uso de una cuota?
    El comando básico es descriptivo y te muestra una tabla clara con lo usado vs. el límite asignado:
kubectl describe quota cuota-infra-dev -n desarrollo
  • Creación ultra rápida en el Examen (Línea de Comandos Imperativa): No pierdas valiosos minutos estructurando el YAML si te piden una cuota simple:

kubectl create quota mi-cuota --hard=cpu=4,memory=8Gi,pods=10 -n desarrollo
* ¿Qué pasa si un Deployment supera la cuota?
Si tu cuota permite un máximo de 5 Pods y tu Deployment solicita escalar a 6 réplicas, el comando kubectl scale o la actualización del deployment se reportará como exitoso (Deployment creado/modificado). Sin embargo, el ReplicaSet en segundo plano fallará al intentar instanciar el sexto Pod.

  • ¿Dónde diagnosticarlo? Busca los eventos directamente en el ReplicaSet:

    kubectl describe replicaset <nombre-del-replicaset> -n <namespace>
    

LimitRange en Kubernetes

¿Qué es un LimitRange?

Es un objeto de nivel de Namespace que enumera las restricciones de min/max de consumo de recursos para los componentes individuales (Contenedores, Pods o PVCs) y permite inyectar configuraciones por defecto si el usuario las omite.

El Superpoder de Inyección Pasiva (Mutating Admission):

Si un Namespace tiene una ResourceQuota de CPU activa, cualquier Pod sin requests/limits será rechazado. Sin embargo, si creas un LimitRange en ese mismo Namespace, cuando un desarrollador envíe un Pod "desnudo" (sin configurar recursos), el LimitRange interceptará la petición e inyectará automáticamente los valores por defecto antes de que la cuota evalúe el Pod. ¡Adiós a los despliegues rechazados!

Componentes Clave del Manifiesto YAML

A continuación, el manifiesto definitivo de un LimitRange estructurado y comentado para entornos de producción y exámenes (CKA / CKAD):

apiVersion: v1
kind: LimitRange
metadata:
  name: limites-desarrollo
  namespace: desarrollo # <- Restringido a este Namespace
spec:
  limits:
  # --- 1. CONFIGURACIÓN PARA CONTENEDORES ---
  - type: Container
    default:              # Límite por defecto (limits) si el usuario no lo pone
      cpu: "500m"
      memory: 512Mi
    defaultRequest:       # Reserva por defecto (requests) si el usuario no la pone
      cpu: "200m"
      memory: 256Mi
    max:                  # Lo máximo que un contenedor puede llegar a pedir
      cpu: "1"
      memory: 1Gi
    min:                  # Lo mínimo indispensable que debe pedir
      cpu: "100m"
      memory: 128Mi

  # --- 2. CONFIGURACIÓN PARA EL POD COMPLETO ---
  - type: Pod
    max:                  # Suma máxima de todos los contenedores del Pod
      cpu: "2"
      memory: 2Gi

  # --- 3. CONFIGURACIÓN PARA ALMACENAMIENTO (PVCs) ---
  - type: PersistentVolumeClaim
    min: 1Gi              # No se pueden pedir discos menores a 1Gi
    max: 50Gi             # No se pueden pedir discos mayores a 50Gi

Desglose de Parámetros Críticos

  • default (Limits por defecto): Si el desarrollador crea un contenedor y no le pone la sección resources.limits, el clúster le asignará este valor de forma autoritaria.

  • defaultRequest (Requests por defecto): Si el contenedor no tiene configurada la sección resources.requests, el clúster le asignará este valor para que el kube-scheduler sepa dónde ubicarlo.

  • max y min: Si un desarrollador intenta aplicar un YAML donde los recursos superen el max o sean inferiores al min, el comando kubectl apply fallará en su consola inmediatamente arrojando un error del Admission Controller.

El Factor de Proporcionalidad (maxLimitRequestRatio)

Un parámetro avanzado pero sumamente útil en la arquitectura es el maxLimitRequestRatio:

  - type: Container
    maxLimitRequestRatio:
      cpu: "4"
      memory: "2"
  • ¿Qué hace? Controla el nivel de sobrecompromiso (oversubscription). Exige que el límite (limit) no sea ridículamente mayor que la reserva (request).

  • Ejemplo: Con un ratio de CPU de 4, si configuras un request.cpu de 100m, tu limit.cpu no puede ser mayor a 400m. Si le pones 500m, Kubernetes bloqueará el Pod.

Tips de Velocidad y Troubleshooting (Modo Examen / Producción)

  • ¿Cómo ver los límites activos en un Namespace de forma limpia?
    A diferencia de otros objetos, hacer un get no te dice mucho. El comando ideal para auditarlo es:
kubectl describe limitranges -n desarrollo
  • Comportamiento con múltiples contenedores: Ten mucho cuidado en los exámenes si te piden restricciones tipo type: Pod. Si tu LimitRange dice que el máximo por Pod es 2Gi de memoria, y creas un despliegue con un contenedor de aplicación de 1.5Gi y un sidecar de logs de 1Gi, la suma dará 2.5Gi. El Pod será rechazado por exceder el máximo global del Pod, aunque individualmente cada contenedor cumpla la regla de type: Container.

  • ¿Qué pasa con los Pods que ya estaban corriendo?
    Si editas o creas un LimitRange, los cambios no son retroactivos. Los Pods que ya se encuentran en ejecución seguirán trabajando con sus configuraciones anteriores sin inmutarse. Las reglas solo se aplican a los Pods recién nacidos.

Múltiples Planificadores y Perfiles en Kubernetes

Por defecto, Kubernetes utiliza el default-scheduler para asignar Pods a los nodos basándose en algoritmos estándar divididos en fases de filtrado y puntuación. Sin embargo, cuando las reglas nativas se quedan cortas, Kubernetes nos ofrece dos caminos arquitectónicos: crear un planificador secundario independiente o configurar múltiples perfiles dentro del mismo planificador.

Múltiples Planificadores (Multiple Schedulers)

Este enfoque clásico consiste en compilar o desplegar un proceso binario (o Pod) completamente independiente que corre en paralelo al planificador nativo del clúster.

  • ¿Cómo se configura?

  • Se define un nuevo archivo de configuración para el planificador secundario utilizando la API KubeSchedulerConfiguration.

  • Se le asigna un nombre único a este planificador en su propiedad de configuración (ej. mi-scheduler-personalizado).
  • Se despliega (normalmente como un Deployment o un Static Pod dentro del clúster).

  • ¿Cómo se usa en un Pod?

Para indicarle a Kubernetes que no use el planificador por defecto, debes añadir el campo explícito schedulerName dentro del spec del Pod:

apiVersion: v1
kind: Pod
metadata:
  name: mi-pod-especial
spec:
  schedulerName: mi-scheduler-personalizado  # <--- Aquí ocurre la magia
  containers:
  - name: app
    image: nginx
  • El gran desafío (Race Conditions): Cuando corres múltiples binarios de planificación independientes, ninguno sabe lo que el otro está haciendo en tiempo real. Si el default-scheduler y tu planificador secundario eligen el mismo nodo al mismo tiempo para Pods distintos, uno de los Pods fallará en la fase de asignación final (Binding), generando colisiones.

Perfiles del Planificador (Scheduler Profiles)

Para resolver los problemas de colisiones y evitar tener que gestionar múltiples binarios, Kubernetes introdujo el Scheduling Framework (a partir de la versión 1.18+). Este framework te permite configurar Múltiples Perfiles dentro del mismo y único proceso de kube-scheduler.

Cada perfil actúa, para el programador de aplicaciones, como si fuera un planificador independiente (tiene su propio schedulerName), pero internamente todo el procesamiento está coordinado centralizadamente, eliminando por completo las condiciones de carrera.

  • Los Plugins de Extensión:

El ciclo de vida de planificación se divide en múltiples etapas o "puntos de extensión" donde puedes habilitar (enable) o deshabilitar (disable) plugins nativos o personalizados:

  • QueueSort: Ordena la cola de Pods en espera.
  • Filter: (Antiguos Predicates) Filtra los nodos que no cumplen los requisitos básicos (ej. falta de CPU o taints).
  • Score: (Antiguos Priorities) Califica los nodos aptos para ver cuál es el óptimo.
  • Reserve y Permit: Reserva los recursos en el nodo antes del binding y puede retener un Pod en un estado de espera controlado.
  • Bind: Ejecuta la asignación final escribiendo en el kube-apiserver.

  • Ejemplo de Configuración de Perfiles (kube-scheduler.yaml):

apiVersion: kubescheduler.config.k8s.io/v1
kind: KubeSchedulerConfiguration
profiles:
  - schedulerName: default-scheduler  # Perfil estándar
  - schedulerName: no-score-scheduler # Perfil personalizado de alta velocidad
    plugins:
      score:
        disabled:
          - name: "*"  # Deshabilita todas las puntuaciones para asignar al primer nodo libre que pase el filtro

Tabla Comparativa: Múltiples Planificadores vs. PerfilesCaracterística

Característica Múltiples Planificadores (Múltiples Binarios) Perfiles del Planificador (Mismo Binario)
Arquitectura Varios procesos/Pods independientes corriendo en paralelo. Un único proceso/Pod coordinando múltiples configuraciones.
Riesgo de Colisión Alto: El Scheduler A y el B pueden elegir el mismo nodo a la vez, causando fallos en el Binding. Nulo: El framework coordina internamente y evita las condiciones de carrera.
Mantenimiento "Alto: Debes actualizar, parchear y monitorear múltiples despliegues de software." Bajo: Todo centralizado en un solo archivo de configuración del clúster.
Casos de Uso Ideales Cuando necesitas reescribir por completo el código fuente del algoritmo o usar herramientas externas muy complejas. Cuando necesitas modificar prioridades, comportamientos de filtrado o plugins nativos manteniendo la estabilidad del clúster."

Tips de Velocidad y Troubleshooting (Modo Examen CKA)

  • El Pod se queda en Pending: Si despliegas un Pod con un schedulerName personalizado y se queda eternamente en estado Pending, lo primero que debes revisar es si el planificador secundario (o el perfil correspondiente en el archivo de configuración) realmente está corriendo o bien configurado. Puedes verificarlo con:
kubectl get pods -n kube-system

Si el planificador no existe o su proceso está caído, ningún componente asignará el nodo al Pod y se quedará huérfano.

  • Inspección de Logs: Para ver por qué un perfil o un planificador secundario tomó una decisión errónea o falló, accede directamente a sus logs:
kubectl logs -n kube-system kube-scheduler-master  # (O el nombre de tu Pod personalizado)
  • Configuración de Static Pods: Recuerda que si modificas el archivo de configuración del planificador en un laboratorio basado en kubeadm, debes cerciorarte de que los volúmenes del archivo de configuración estén correctamente montados dentro del manifiesto del Static Pod del scheduler (/etc/kubernetes/manifests/kube-scheduler.yaml) para que el componente asimile correctamente los nuevos perfiles tras reiniciarse.