Scheduler asignar pods a nodos
Dentro de kubernetes es uno de los procesos mas importantes, se encarga de determinar a que nodo se envia cada POD.
El kube-scheduler
es uno de los componentes centrales en Kubernetes que se encarga de asignar los Pods a los nodos del clúster. Cuando un Pod es creado pero aún no tiene un nodo asignado, el scheduler decide en cuál nodo se ejecutará el Pod basándose en varios factores como la capacidad disponible, las reglas de afinidad y anti-afinidad, entre otros. Los subprocesos clave que realiza el kube-scheduler
incluyen:
1. Recepción de Pods sin nodo asignado
Cuando se crea un Pod, este pasa a estar en estado Pending hasta que el scheduler le asigne un nodo. El kube-scheduler observa constantemente el API server buscando Pods en este estado.
2. Filtrado de nodos
Una vez que el scheduler detecta un Pod sin asignar, realiza un filtrado inicial de los nodos disponibles en el clúster para descartar aquellos que no pueden ejecutar el Pod. Este filtrado se realiza en base a varios criterios, incluyendo:
- Capacidad de recursos: CPU, memoria y almacenamiento.
- Taints y tolerations: Algunos nodos pueden tener restricciones que limitan qué Pods pueden ejecutarse en ellos.
- Node Selectors y Node Affinity: Si el Pod tiene restricciones sobre en qué tipo de nodos debe ejecutarse (por ejemplo, en nodos con ciertas etiquetas).
Este proceso produce un subconjunto de nodos viables.
3. Ranking de nodos (priorización)
Luego de filtrar los nodos viables, el scheduler aplica un conjunto de reglas de priorización para clasificar los nodos en función de qué tan apropiado es cada uno para ejecutar el Pod. Algunos de los criterios de priorización incluyen:
- Distribución equilibrada de la carga: El scheduler tiende a preferir nodos que no estén sobrecargados.
- Localidad de datos: Si el Pod accede a un volumen de almacenamiento persistente, se pueden preferir nodos que estén más cerca del almacenamiento para minimizar la latencia.
- Afinidad de red: Se pueden priorizar nodos en base a la proximidad a otros Pods con los que el nuevo Pod deba interactuar.
4. Asignación del nodo
Una vez que el scheduler evalúa y ordena los nodos según los criterios anteriores, selecciona el nodo más adecuado y asigna el Pod a ese nodo. Esta asignación se realiza a través del API server de Kubernetes, que actualiza el estado del Pod indicando en qué nodo debe ejecutarse.
5. Monitoreo y reprogramación (Opcional)
En algunos casos, el scheduler puede estar involucrado en la reprogramación de Pods si un nodo falla o si se aplican políticas que requieren redistribuir los Pods en otros nodos. Sin embargo, generalmente son los controladores como el kube-controller-manager los que toman decisiones sobre el reaprovisionamiento de Pods en estas situaciones.
Ejecución de Plugins
Kubernetes permite extender el comportamiento del scheduler a través de plugins que se ejecutan en diferentes fases del proceso de programación. Algunos ejemplos de plugins incluyen:
- Extender el filtrado: Los plugins pueden agregar criterios adicionales para descartar nodos.
- Modificaciones en la priorización: Se pueden implementar reglas personalizadas para modificar cómo se priorizan los nodos.
- Pre-reservación y validaciones post-reservación: Permiten ejecutar lógica adicional antes y después de asignar el Pod a un nodo.
Estos subprocesos permiten que el kube-scheduler
administre de manera eficiente la asignación de cargas de trabajo en los nodos del clúster, optimizando el uso de recursos y garantizando el cumplimiento de las políticas definidas por los usuarios o administradores del clúster.
Asignando un pod a un nodo de manera manual
Esta no es una buena practica, ya que estamos forzando a un pod hacia cierto NODO sin pasar por los filtros avanzados que tendria por default. Pero lo haremos con fines educativos, para que veamos como podemos forzarlo y que pasa si ese nodo no existe.
my-pods.yaml
apiVersion: v1
kind: Pod
metadata:
name: nginx
labels:
zone: prod
version: v1
spec:
containers:
- name: nginx
image: nginx
nodeName: worker-01
Aplicamos el archivo, kubectl apply -f my-pods.yaml
Si hacemos kubectl get pod -o wide
veremos que no logra encontrar
el nombre del nodo por lo que terminara elimnando al pod.
Hagamos en el control-plane un kubectl get nodes
para ver los
nombres y cambiamos nodeName de nuestro fichero yaml por
alguno del los nodos que si tenemos disponibles.
NAME STATUS ROLES AGE VERSION
debiank8s-node1 Ready <none> 19h v1.31.1
debiank8s-node2 Ready <none> 37m v1.31.1
debiank8s1 Ready control-plane 21h v1.31.1
apiVersion: v1
kind: Pod
metadata:
name: nginx
labels:
zone: prod
version: v1
spec:
containers:
- name: nginx
image: nginx
nodeName: debiank8s-node1
Node selector
Otra manera de determinar a que nodo se le va a asignar un pod es usar el selector de nodos.
Busca en una serie de nodos en base a una condicion de labels.
apiVersion: v1
kind: Pod
metadata:
name: nginx
labels:
zone: prod
version: v1
spec:
containers:
- name: nginx
image: nginx
nodeSelector:
entorno: desarrollo
Podemos revisar que labels tiene nuestros nodos con
kubectl get nodes --show-labels
Una vez mas hacemos hacemos el apply kubectl apply -f my-pod.yml
y vemos kubectl get pod -o wide
veremos que queda en PENDING
ya que no hay ningun nodo que cumpla esa condicion.
Quedara en estado PENDING hasta que haya un nodo donde posicionarse.
Agreguemos ese label a un nodo:
kubectl label node debiank8s-node1 entorno=desarrollo
Ahora si volemos a kubectl get pod -o wide
veremos que
ya lo asigno.
Node Affinity
Es parecido al node selector pero con mas reglas y condiciones, es mas granular.
Node Affinity (afinidad de nodos) es una característica de Kubernetes que te permite controlar en qué nodos se deben programar tus Pods. Es una extensión de las etiquetas y selectores, pero ofrece más flexibilidad y control. Básicamente, te permite especificar preferencias sobre qué nodos deberían ejecutar tus Pods, basándote en las etiquetas que tenga el nodo.
¿Por qué usar Node Affinity?
El Node Affinity es útil cuando deseas que un Pod se ejecute en nodos específicos que tienen ciertas características, como nodos con hardware especial (por ejemplo, GPU), diferentes zonas de disponibilidad, o nodos en diferentes ubicaciones geográficas.
Tipos de Node Affinity
Existen dos tipos principales de afinidad de nodos en Kubernetes:
-
requiredDuringSchedulingIgnoredDuringExecution (obligatorio durante la programación, ignorado durante la ejecución):
- Este tipo de afinidad especifica que el Pod debe ser programado solo en los nodos que coinciden con las reglas que has definido. Si ningún nodo coincide con las condiciones especificadas, el Pod no será programado.
- Es obligatorio durante la fase de programación, pero si el nodo deja de coincidir (por ejemplo, se cambian las etiquetas del nodo después de que el Pod ya esté ejecutándose), el Pod sigue ejecutándose.
Ejemplo de uso:
affinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: - matchExpressions: - key: disktype operator: In values: - ssd
En este ejemplo, el Pod solo será programado en nodos que tengan la etiqueta
disktype=ssd
. -
preferredDuringSchedulingIgnoredDuringExecution (preferido durante la programación, ignorado durante la ejecución):
- Este tipo de afinidad es una preferencia, no un requerimiento. Kubernetes intentará programar el Pod en nodos que coincidan con la afinidad, pero si no encuentra un nodo que cumpla con la preferencia, el Pod será programado en cualquier otro nodo disponible.
Ejemplo de uso:
affinity: nodeAffinity: preferredDuringSchedulingIgnoredDuringExecution: - weight: 1 preference: matchExpressions: - key: region operator: In values: - us-west-1
En este caso, Kubernetes preferirá programar el Pod en un nodo con la etiqueta
region=us-west-1
, pero si no hay ninguno disponible, el Pod se ejecutará en cualquier otro nodo.
Elementos clave en Node Affinity:
- nodeSelectorTerms: Define los términos que deben cumplir los nodos para que los Pods sean programados en ellos.
- matchExpressions: Permite definir las condiciones con operadores como
In
,NotIn
,Exists
, y más, para especificar qué etiquetas deben estar presentes en los nodos. - operator: Define la relación entre la clave y los valores. Puede ser:
In
: El valor de la etiqueta debe coincidir con uno de los valores especificados.NotIn
: El valor de la etiqueta no debe coincidir con ninguno de los valores especificados.Exists
: El nodo debe tener esa etiqueta, independientemente del valor.DoesNotExist
: El nodo no debe tener esa etiqueta.
Ejemplo completo de Node Affinity en un Pod:
apiVersion: v1
kind: Pod
metadata:
name: nginx-pod
spec:
containers:
- name: nginx
image: nginx
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: zone
operator: In
values:
- us-central1-a
- us-central1-b
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 1
preference:
matchExpressions:
- key: disktype
operator: In
values:
- ssd
En este ejemplo:
- El Pod solo puede ejecutarse en nodos que tengan la etiqueta
zone
con los valoresus-central1-a
ous-central1-b
(esto es obligatorio). - Kubernetes preferirá programar el Pod en nodos con la etiqueta
disktype=ssd
(esto es opcional).
Diferencia con NodeSelector
La principal diferencia entre Node Affinity
y NodeSelector
es que Node Affinity
ofrece más flexibilidad. Mientras que NodeSelector
es una herramienta más simple y rígida (solo permite requerimientos obligatorios), Node Affinity
permite definir preferencias además de requerimientos estrictos. Además, con Node Affinity
, puedes usar operadores como In
, NotIn
, y otros, mientras que NodeSelector
solo permite una correspondencia exacta.
Resumen
- Node Affinity te permite controlar en qué nodos se ejecutan tus Pods usando reglas basadas en etiquetas de los nodos.
- Tiene dos tipos principales: reglas obligatorias (
requiredDuringSchedulingIgnoredDuringExecution
) y reglas preferidas (preferredDuringSchedulingIgnoredDuringExecution
). - Ofrece más flexibilidad que
NodeSelector
, lo que te permite definir tanto preferencias como requerimientos estrictos para la colocación de tus Pods.
Esto es especialmente útil para asegurar que ciertos Pods se ejecuten en nodos específicos que cumplen con los requisitos de hardware o ubicación geográfica.
Taints y Tolerations
Los Taints y Tolerations en Kubernetes son mecanismos que permiten controlar qué Pods pueden ser programados en determinados nodos. Funcionan juntos para evitar que ciertos Pods se ejecuten en ciertos nodos, o para permitir que solo Pods específicos se ejecuten en esos nodos. Veamos más en detalle cómo funcionan cada uno:
1. Taints (Manchas)
Un Taint es una “marca” que puedes aplicar a un nodo en Kubernetes para indicar que ese nodo tiene una restricción o condición especial. El propósito de un Taint es evitar que los Pods sean programados en ese nodo a menos que esos Pods tengan una Toleration correspondiente que les permita hacerlo.
Sintaxis de un Taint:
Un Taint tiene tres partes clave:
- Clave (Key): Una etiqueta descriptiva que indica qué tipo de condición o restricción tiene el nodo.
- Valor (Value): Un valor opcional asociado a la clave que describe más a fondo la condición.
- Efecto (Effect): Qué ocurre cuando un Pod no tiene la tolerancia necesaria. Los efectos posibles son:
NoSchedule
: No permite que Pods se programen en el nodo.PreferNoSchedule
: Kubernetes preferirá no programar Pods en el nodo, pero no es obligatorio.NoExecute
: Expulsa (desprograma) cualquier Pod existente en el nodo si no tiene la Toleration adecuada.
Ejemplo de Taint:
kubectl taint nodes nodename key=value:NoSchedule
Este comando aplicará un Taint al nodo nodename
, impidiendo que los Pods sin la tolerancia adecuada se programen en él. En este caso, el Taint tiene:
- Clave:
key
- Valor:
value
- Efecto:
NoSchedule
Ejemplo práctico de un Taint:
kubectl taint nodes worker1 disktype=ssd:NoSchedule
Este comando añade un Taint al nodo worker1
, diciendo que solo los Pods que tienen tolerancia al Taint disktype=ssd
pueden ser programados en ese nodo. Otros Pods no se programarán en este nodo.
2. Tolerations (Tolerancias)
Una Toleration se agrega a los Pods y permite que un Pod “tolere” o ignore un Taint aplicado a un nodo. Si un Pod tiene la tolerancia adecuada para el Taint de un nodo, Kubernetes permitirá que el Pod se ejecute en ese nodo.
Sintaxis de una Toleration:
Las Tolerations también se expresan como clave-valor, pero se aplican en la configuración de los Pods.
Ejemplo de una Toleration en un manifiesto de un Pod:
tolerations:
- key: "disktype"
operator: "Equal"
value: "ssd"
effect: "NoSchedule"
En este ejemplo:
- Key:
disktype
— El Taint que está tolerando el Pod. - Operator:
Equal
— El operador que compara el valor del Taint. - Value:
ssd
— El valor que debe coincidir con el Taint aplicado al nodo. - Effect:
NoSchedule
— Indica que el Pod podrá ser programado en nodos que tengan el Taintdisktype=ssd
con el efectoNoSchedule
.
Ejemplo completo de Pod con Toleration:
apiVersion: v1
kind: Pod
metadata:
name: my-pod
spec:
containers:
- name: nginx
image: nginx
tolerations:
- key: "disktype"
operator: "Equal"
value: "ssd"
effect: "NoSchedule"
En este caso, el Pod my-pod
tiene una tolerancia para el Taint disktype=ssd:NoSchedule
. Por lo tanto, puede ejecutarse en nodos con ese Taint.
Comportamiento de los Efectos:
- NoSchedule: Evita que Pods sin la tolerancia adecuada sean programados en el nodo. Los Pods con la tolerancia pueden ejecutarse.
- PreferNoSchedule: Kubernetes intentará evitar programar Pods sin la tolerancia, pero no es estrictamente obligatorio.
- NoExecute: Además de evitar que nuevos Pods sin tolerancia sean programados en el nodo, expulsa los Pods existentes que no tienen la tolerancia adecuada.
Ejemplo de NoExecute:
Si aplicas el siguiente Taint a un nodo:
kubectl taint nodes worker1 special=true:NoExecute
- Los Pods que ya estén ejecutándose en ese nodo y que no tengan una tolerancia para el Taint
special=true
serán desprogramados (expulsados). - Los nuevos Pods que no tengan la tolerancia no podrán ejecutarse en ese nodo.
Ejemplo de Toleration para NoExecute:
tolerations:
- key: "special"
operator: "Equal"
value: "true"
effect: "NoExecute"
Esto permite que el Pod tolere el Taint special=true:NoExecute
y siga ejecutándose en el nodo sin ser expulsado.
Relación entre Taints y Tolerations:
- Taints: Se aplican a los nodos y controlan qué Pods pueden ejecutarse en ellos.
- Tolerations: Se aplican a los Pods y les permiten ignorar los Taints en los nodos, permitiendo que se ejecuten en nodos que normalmente restringirían otros Pods.
Ejemplo final:
Supón que tienes un nodo que solo debe ejecutar Pods de alta prioridad que estén configurados para tolerar ciertas condiciones. Puedes aplicar un Taint a ese nodo:
kubectl taint nodes high-priority-node priority=high:NoSchedule
Luego, los Pods que pueden ejecutarse en ese nodo tendrían que declarar la siguiente Toleration en su especificación:
tolerations:
- key: "priority"
operator: "Equal"
value: "high"
effect: "NoSchedule"
Con esta configuración, solo los Pods que tengan esta tolerancia podrán ejecutarse en ese nodo.
Resumen:
- Taints: Aplicados a los nodos para evitar que ciertos Pods se ejecuten en ellos.
- Tolerations: Aplicadas a los Pods para permitir que se ejecuten en nodos con Taints específicos.
- Los Taints y Tolerations trabajan juntos para definir políticas más detalladas de programación de Pods y permiten evitar que se ejecuten en nodos que no están destinados a ellos o que solo lo hagan en condiciones específicas.
Estos mecanismos son esenciales para lograr una programación efectiva y flexible en clústeres grandes o heterogéneos.