El artículo de hoy presenta principalmente las etapas del proceso de programación donde las estrategias de Predicates y Priorities entran en juego.
Predicates
Primero, echemos un vistazo a los Predicates. El rol de los Predicates en el proceso de programación se puede entender como un Filtro, es decir: “filtra” una serie de nodos que cumplen las condiciones de todos los nodos en el clúster actual según la política de programación. Estos nodos son todos los hosts potenciales para el Pod que está esperando ser programado. En Kubernetes, hay cuatro políticas de programación predeterminadas.
El primer tipo se llama GeneralPredicates.
Como su nombre indica, este conjunto de reglas de filtrado es responsable de las políticas de programación más básicas. Por ejemplo, PodFitsResources calcula si los recursos de CPU y memoria del host son suficientes. Por supuesto, como mencioné antes, PodFitsResources solo verifica el campo requests del Pod. Cabe señalar que el programador de Kubernetes no define tipos de recursos específicos para recursos de hardware como GPUs, sino que utiliza un formato Key-Value llamado Extended Resource para describirlos. Por ejemplo:
apiVersion: v1
kind: Pod
metadata:
name: extended-resource-demo
spec:
containers:
- name: extended-resource-demo-ctr
image: nginx
resources:
requests:
alpha.kubernetes.io/nvidia-gpu: "2"
limits:
alpha.kubernetes.io/nvidia-gpu: "2"
Se puede ver que nuestro Pod declara el uso de dos GPUs NVIDIA utilizando la definición alpha.kubernetes.io/nvidia-gpu=2. En PodFitsResources, el programador no sabe realmente que el significado de este campo Key es GPU, sino que utiliza directamente el siguiente Value para el cálculo. Por supuesto, en el campo Capacity del Node, también debes agregar el número total de GPUs en este host, por ejemplo: alpha.kubernetes.io/nvidia-gpu=4. Explicaré estos procesos en detalle más adelante al explicar Device Plugin. PodFitsHost verifica si el nombre del host coincide con el spec.nodeName del Pod. PodFitsHostPorts verifica si los puertos del host (spec.nodePort) solicitados por el Pod entran en conflicto con los puertos que ya están en uso. PodMatchNodeSelector verifica si los nodos especificados por nodeSelector o nodeAffinity del Pod coinciden con el nodo bajo examen, etc. Se puede ver que este conjunto de GeneralPredicates es la condición de filtrado más básica para que Kubernetes examine si un Pod puede ejecutarse en un Node. Por lo tanto, GeneralPredicates también son llamados directamente por otros componentes (como kubelet).
El segundo tipo son las reglas de filtrado relacionadas con Volumes.
Este conjunto de reglas de filtrado es responsable de la política de programación relacionada con los Volumes persistentes del contenedor. Entre ellos, NoDiskConflict verifica si hay conflicto entre los Volumes persistentes declarados por múltiples Pods. Por ejemplo, los Volumes de tipo AWS EBS no permiten que dos Pods los utilicen al mismo tiempo. Por lo tanto, cuando un Volume EBS llamado A ya está montado en un nodo determinado, otro Pod que también declara el uso de este Volume A no puede ser programado en ese nodo. MaxPDVolumeCountPredicate verifica si un cierto tipo de Volume persistente en un nodo ha excedido un número determinado. Si es así, el Pod que declara el uso de ese tipo de Volume persistente ya no puede ser programado en ese nodo. VolumeZonePredicate verifica si la etiqueta Zone (dominio de alta disponibilidad) del Volume persistente coincide con la etiqueta Zone del nodo bajo examen. Además, hay una regla llamada VolumeBindingPredicate. Es responsable de verificar si el campo nodeAffinity del PV correspondiente al Pod coincide con la etiqueta de algún nodo. Los Local Persistent Volumes (volúmenes persistentes locales) deben usar nodeAffinity para enlazarse a un nodo específico. Esto significa que durante la fase de Predicates, Kubernetes debe poder programar en función de los atributos de Volume del Pod. Además, si el PVC del Pod aún no se ha enlazado a un PV específico, el programador también es responsable de verificar todos los PVs a enlazar. Cuando hay un PV disponible y el nodeAffinity de este PV es consistente con el nodo bajo examen, esta regla devolverá “éxito”. Por ejemplo:
apiVersion: v1
kind: PersistentVolume
metadata:
name: example-local-pv
spec:
capacity:
storage: 500Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
storageClassName: local-storage
local:
path: /mnt/disks/vol1
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- my-node
Se puede ver que el directorio persistente correspondiente a este PV solo aparecerá en el host llamado my-node. Por lo tanto, cualquier Pod que use este PV a través de un PVC debe ser programado en my-node para funcionar correctamente. VolumeBindingPredicate es exactamente donde el programador toma esta decisión.
El tercer tipo son las reglas de filtrado relacionadas con el host.
Este conjunto de reglas examina principalmente si el Pod a programar cumple ciertas condiciones del propio Node. Por ejemplo, PodToleratesNodeTaints verifica el mecanismo de “taint” del Node que usamos a menudo. Solo cuando el campo Toleration del Pod coincide con el campo Taint del Node, este Pod puede ser programado en el Node. NodeMemoryPressurePredicate verifica si la memoria del Node actual ya no es suficiente. Si es así, el Pod que espera ser programado no puede ser programado en este Node.
El cuarto tipo son las reglas de filtrado relacionadas con Pods.
Este conjunto de reglas se superpone con la mayoría de GeneralPredicates. Lo especial es PodAffinityPredicate. El rol de esta regla es verificar las relaciones de afinidad y anti-afinidad entre el Pod a programar y los Pods existentes en el Node. Por ejemplo:
apiVersion: v1
kind: Pod
metadata:
name: with-pod-antiaffinity
spec:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchExpressions:
- key: security
operator: In
values:
- S2
topologyKey: kubernetes.io/hostname
containers:
- name: with-pod-affinity
image: docker.io/ocpqe/hello-pod
En este ejemplo, la regla podAntiAffinity especifica que este Pod no quiere estar en el mismo Node que cualquier Pod que lleve la etiqueta security=S2. Cabe señalar que PodAffinityPredicate tiene un alcance, como la regla anterior, que solo es válida para Nodes que llevan la etiqueta Key kubernetes.io/hostname. Este es el rol de la palabra clave topologyKey.
Estos cuatro tipos de Predicates constituyen la estrategia básica para que el programador determine si un Node puede ejecutar el Pod a programar. Durante la ejecución, cuando se programa un Pod, el programador de Kubernetes inicia simultáneamente 16 Goroutines para calcular concurrentemente Predicates para todos los Nodes en el clúster, y finalmente devuelve la lista de hosts que pueden ejecutar este Pod. Cabe señalar que al ejecutar Predicates para cada Node, el programador verifica en un orden fijo. Este orden se determina según el significado de los propios Predicates. Por ejemplo, los Predicates relacionados con el host se verificarán antes. De lo contrario, calcular PodAffinityPredicate en un host con recursos gravemente insuficientes no tiene sentido.
Priorities
Después del “filtrado” de nodos en la fase de Predicates, el trabajo de la fase de Priorities es puntuar estos nodos. El rango de puntuación aquí es de 0 a 10 puntos, y el nodo con la puntuación más alta es el mejor nodo al que finalmente se enlazará el Pod. La regla de puntuación más utilizada en Priorities es LeastRequestedPriority. Este algoritmo en realidad selecciona el host con los recursos más libres (CPU y Memoria). Además, hay otros tres Priorities: NodeAffinityPriority, TaintTolerationPriority e InterPodAffinityPriority. Como sus nombres indican, son similares en significado y método de cálculo a los tres Predicates anteriores: PodMatchNodeSelector, PodToleratesNodeTaints y PodAffinityPredicate. Sin embargo, como Priority, cuanto más cumplan los campos de un Node con las reglas anteriores, mayor será su puntuación. En los Priorities predeterminados, también hay una estrategia llamada ImageLocalityPriority. Es una nueva regla de programación habilitada en Kubernetes v1.12, que es: si la imagen que necesita el Pod a programar es grande y ya existe en algunos Nodes, entonces estos Nodes tendrán una puntuación más alta. Por supuesto, para evitar que el algoritmo cause acumulación de programación, el programador también optimizará según la distribución de la imagen al calcular la puntuación, es decir: si el número de nodos donde se distribuye la imagen grande es muy pequeño, entonces el peso de estos nodos se reducirá, “compensando” así el riesgo de causar acumulación de programación. En resumen, este es el principio de funcionamiento principal de las reglas de programación predeterminadas en el programador de Kubernetes.
En el proceso de ejecución real, la información sobre el clúster y los Pods en el programador ya está en caché, por lo que el proceso de ejecución de estos algoritmos es relativamente rápido. Además, para algoritmos de programación más complejos, como PodAffinityPredicate, no solo prestan atención al Pod a programar y al Node bajo examen al calcular, sino que también necesitan prestar atención a la información de todo el clúster, como recorrer todos los nodos y leer sus Labels. En este momento, el programador de Kubernetes primero calculará la información del clúster necesaria para el algoritmo por adelantado antes de ejecutar el algoritmo de programación para cada Pod a programar, y luego la almacenará en caché. De esta manera, al ejecutar realmente el algoritmo, el programador solo necesita leer la información en caché para calcular, evitando así la necesidad de recuperar y calcular repetidamente la información de todo el clúster para cada Node al calcular Predicates.
Resumen
En resumen, este artículo ha discutido los principales algoritmos de programación dentro del programador predeterminado de Kubernetes. Es importante tener en cuenta que, además de las reglas cubiertas en este artículo, en realidad hay algunas estrategias dentro del programador de Kubernetes que no están habilitadas por defecto. Puedes especificar un archivo de configuración para kube-scheduler o crear un ConfigMap para configurar qué reglas habilitar o deshabilitar. Además, puedes controlar el comportamiento del programador estableciendo pesos para los Priorities. Esto concluye la explicación de los mecanismos de programación predeterminados en el programador de Kubernetes. Vale la pena mencionar que, aunque el programador funciona rápidamente debido al almacenamiento en caché de la información del clúster y los Pods, para algoritmos más complejos, el programador debe tener en cuenta todo el estado del clúster, no solo el Pod y el Node en cuestión. Esto incluye cálculos preliminares y almacenamiento en caché de la información necesaria del clúster antes de la ejecución real del algoritmo de programación, asegurando eficiencia y precisión en el proceso de programación.
Novita AI es la plataforma integral en la nube que impulsa tus ambiciones de IA. Con API integradas sin problemas, computación serverless y aceleración de GPU, proporcionamos las herramientas rentables que necesitas para construir y escalar rápidamente tu negocio impulsado por IA. Elimina los problemas de infraestructura y comienza gratis – Novita AI hace realidad tus sueños de IA.
Lectura recomendada:
