Entender Tolerations y Taints en Kubernetes
En anteriores entradas de refactorizando, vimos como podíamos asignar Pods a nodos por afinidad y por selector de nodos. En esta nueva entrada sobre entender Tolerations y Taints en Kubernetes, vamos a ver una funcionalidad más de kubernetes que nos va a permitir o evitar, añadir nuestros Pods a un nodo/s concreto.
Este artículo pretende demostrar el por qué hacer uso de esta funcionalidad de Kubernetes. Ya que es muy probable que te estes preguntando por qué no usar afinidad o selector de nodos. Pero taints y tolerations tiene una significativa diferencia, en lugar de aplicar el label al nodo, se va aplica un taint que dice al planificador si hay que repeler el Pod del nodo sino hace match. Hay que tener en cuenta que únicamente los Pods con toleration para ese taint se pueden planificar dentro del nodo con ese taint.
¿Por qué usar Taints y Tolerations en Kubernetes?
El uso de taints y tolerations, nos va a permitir mejorar y optimizar nuestro clúster de Kubernetes. Gracias a lo cual vamos a poder tener un mayor control sobre los Pods que desplegamos en nuestros nodos. Por ejemplo, nos va a permitir:
- Taints basadas en evictions: Si nuestro nodo experimenta algún problema, lo podemos planificar con una política de eviction para evitar su planificación.
- Nodos dedicados: Se puede usar una combinación entre afinidad de nodos y taints para lograr nodos dedicados.
- Nodos con características especiales de hardware: Podemos tener en ocasiones en las que queremos planificar Pods con unas necesidades de hardware especiales, en esos casos podemos hacer que los Pods sean repelidos por el nodo.
Configurando Taints y Tolerations
Añadir taints y tolerations a nuestros deployments, nodos y Pods es un proceso similar a como se hace con la afinidad de nodos. El primer paso que vamos a realizar es añadir la configuración necesaria al nodo para que no planifique un Pod en función de una etiqueta, vamos a ver como sería:
kubectl taint nodes dev-worker1 type=shared:NoSchedule node “dev-worker1” tainted
Con el comando anterior acabamos de definir un nuevo taint para el nodo dev-worker1. Este taint lo hemos definido con la clave special y un valor de true, y le asignamos el comportamiento que tendrá, en este caso NoSchedule, ten en cuenta que los comportamientos aceptados son NoSchedule, NoExecute y PreferNoSchedule.
La definición de un Taint tiene el formato <taintKey>=<taintValue>:<taintEffect>. El taint key y value será la clave y el valor con el que se defina ese nodo, y luego nos encontramos con el taintEffect, que será la planificación o comportamiento que va a tener ese nodo para reaccionar con un Pod.
Para entender mejor lo que taint va a hacer, por ejemplo, con el comando anterior un Pod no va a ser planificado en el dev-worker1 si en su deployment o en su Pod no tiene añadido el toleration. Es decir el nodo reacciona y aplica el efecto que hemos aplicado de NoSchedule.
Vamos a ver los diferentes tipos de planificación que se le pueden asignar al taint:
- NoSchedule: A menos que el Pod lleve la etiqueta igual que el nodo no se planificara en el nodo.
- NoExecute: En el momento en el que se aplica esta condición, el controlador desalojará inmediatamente todos los Pods que no hagan match con el taint definido en el nodo. Se le puede añadir la propiedad tolerationSeconds, en el que se indica los segundos antes de que sea desalojado del nodo.
- PreferNoSchedule: Si aplicamos este valor se «intentará» no añadir el Pod al nodo que no tolera el taint del nodo, pero no es requerido, podríamos decir que es la versión suave de NoSchedule.
Antes de crear un deploy o un Pod a veces es mejor saber si se ha definido un Taint sobre ese nodo:
kubectl describe node <host>
Name: dev-worker1 Roles: worker Labels: Name=shared-services beta.kubernetes.io/arch=amd64 beta.kubernetes.io/os=linux cattle.io/creator=norman kubernetes.io/arch=amd64 kubernetes.io/hostname=dev-worker1 kubernetes.io/os=linux node-role.kubernetes.io/worker=true nodegroup-type=shared-services Annotations: node.alpha.kubernetes.io/ttl: 0 projectcalico.org/IPv4Address: 10.0.0.7/32 projectcalico.org/IPv4IPIPTunnelAddr: 10.42.134.128 rke.cattle.io/external-ip: 10.10.1.3 rke.cattle.io/internal-ip: 10.10.1.3 volumes.kubernetes.io/controller-managed-attach-detach: true CreationTimestamp: Thu, 24 Jun 2021 15:57:12 +0200 Taints: type=shared:NoSchedule
En el ejemplo anterior podemos ver como nuestro nodo tiene un Taint con clave type, valor shared y de tipo NoSchedule.
Para eliminar un Taint aplicamos el siguiente comando:
kubectl taint nodes dev-worker1 type=shared:NoSchedule-
Añadir Toleration a un Pod
Una vez que hemos añadido un taint a nuestro Nodo, es momento de ver como hacer que un Pod haga match con ese taint, para que podamos establecerle un comporatmiento. Por lo que añadimos el campo toleration a la especificación del Pod o del Deployment:
tolerations: - key: "type" operator: "Equal" value: "shared" effect: "NoSchedule"
El fragmento anterior es el que tenemos que añadir a nuestros deployment, pod o statefulset para poder asignarlo al nodo. Vamos a verlo en un ejemplo completo:
apiVersion: v1 kind: Pod metadata: name: timetable spec: containers: - name: timetable image: timetable:latest tolerations: - key: "type" operator: "Equal" value: "shared" effect: "NoSchedule"
En el Pod anterior hemos añadido su tolerations, por lo que ahora ese Pod puede ser planificado en el nodo dev-worker1. Pero hay que tener en cuenta, que no significa que el Pod se planifique en ese nodo, solo que puede ser planificado. Para asegurarnos que ese Pod se asigna a ese nodo deberíamos de añadir afinidad de nodo y selector de nodo.
Diferentes configuraciones de Tolerations
La definición de Pod que hemos visto anteriormente, suele ser la clásica para que un Pod haga «match» con un Taint de un nodo, pero podemos a ver alguna configuración diferente.
No añadir value: Cuando no tenemos el valor de value, y el operador es Exists hará «match» con el taint si la key y el effect es el mismo del Taint del nodo.
Solo añadir operador Exists: Hará match con todos los keys, values y effects de un nodo.
Solo operador y key: Aplicará todos los efectos de la key de un nodo. Veamos el ejemplo abajo:
apiVersion: v1 kind: Pod metadata: name: timetable spec: containers: - name: timetable image: timetable:latest tolerations: - operator: "Exists" key: "type"
Configurar TaintNodesByCondition y Eviction
TaintNodesByCondition
TaintNodesByCondition nos va a permitir interactuar con la configuración del controlador del nodo para cambiar el comportamiento de la programación de los Pods.
Es decir, TaintNodesByCondition nos va a permitir en función de unas condiciones planificar o no un nodo, que que el único efecto es NoSchedule.
Las condiciones que encontramos para aplicar y sus taints son:
- OutOfDisk — Si hay insuficiente espacio libre para añadir un nuevo Pod el valor será True. El taint a añadir será
node.kubernetes.io/out-of-disk
. - Ready — Si el nodo no está healthy o no es alcanzable o no se sabe de ese nodo (Unknow) en el tiempo específicado, que por defecto son 40 segundos.
El
taintnode.kubernetes.io/not-ready
es añadido si la condición esFalse
y el taintnode.kubernetes.io/unreachable
si la condición es desconocido (unknow), es decir que no se sabe del estado del nodo. - MemoryPressure — Será valor
True
si la memoria del nodo es baja . El taint responsable de esta propiedad esnode.kubernetes.io/memory-pressure
y será añadido si el valor es True . - PIDPressure — Nos va a indicar si el nodo tiene muchos procesos
.
El taint es node.kubernetes.io/pid-pressure. - DiskPressure — Si la capacidad del disco es baja.
El taint es
node.kubernetes.io/disk-pressure
. - NetworkUnavailable — Si la red no es alcanzable o tiene problemas de configuración.
El
taint esnode.kubernetes.io/network-unavailable
.
Podemos activar o desactivar TaintNodesByCondition, de modo que cuando lo tengamos activado, podremos aplicar diferentes configuraciones y opciones de taints a los nodos.
Vamos a ver un ejemplo, en el que el Pod será planificado en el nodo si tiene el taint node.kubernetes.io/network-unavailable
y el taint node.kubernetes.io/notReady
y ambos pueden ser aplicados.
kind: Pod apiVersion: v1 metadata: name: example spec: tolerations: - key: node.kubernetes.io/network-unavailable operator: Exists effect: NoSchedule - key: node.kubernetes.io/notReady operator: Exists effect: NoSchedule containers: - name: nginx image: nginx
Taints basados en Evictions
El funcionamiento de TaintBasedEvictions es muy similar a como funciona el TaintNodesByCondition, nos va a permitir añadir taints a nodos basados en una serie de condiciones. Si esta opción esta activada, con efecto establecido NoExecute, podremos cambiar la planificación de eviction del Pod según la condición de Ready Node.
El tiempo que puede permanecer el Pod vinculado al nodo puede ser configurado, es decir, podemos establecer un tiempo de permanencia de ese Pod antes de que sea desalojado del Nodo.
Vamos a ver un ejemplo en el que permitimos un tiempo de 6000 segundos a un Pod mediante la key unrechable:
apiVersion: v1 kind: Pod metadata: name: timetable spec: containers: - name: timetable image: timetable:latest tolerations: - key: "node.kubernetes.io/unreachable" operator: "Exists" effect: "NoExecute" tolerationSeconds: 6000
Hay que tener en cuenta que si el desalojo (taint eviction) esta activado, se añade por defecto node.kubernetes.io/not-ready
con la propiedad tolerationSeconds=300
. Si es necesario este valor se puede cambiar.
Múltiples Taints y Tolerations
Cuando estamos realizando despliegues en nuestros nodos, puede darse la situación en que queramos planificar Pods en función de varios Taints. Para estas ocasiones Kubernetes nos va a permitir añadir múltiples taints y tolerations.
Nuestro sistema va a ignorar aquellos taints para los que existen tolerations y para los que no sean ignoradas se realizará el efecto específicado en la definición. Vamos a ver un ejemplo:
kubectl taint nodes dev-worker1 type1=shared1:NoSchedule kubectl taint nodes dev-worker1 type1=shared1:NoExecute kubectl taint nodes dev-worker1 type2=shared2:NoSchedule
Ahora en función de esos taints definidos en el nodo vamos a crear un Pod:
apiVersion: v1 kind: Pod metadata: name: timetable spec: containers: - name: timetable image: timetable:latest tolerations: - key: "type1" operator: "Equal" value: "shared1" effect: "NoSchedule" - key: "type1" operator: "Equal" value: "shared1" effect: "NoExecute"
Este Pod tolerará el primer y el segundo taint, pero con el tercer taint definido en el nodo va a tener el efecto de NoSchedule. Es decir, la definición de múltiples taints están actuando como filtros.
Incluso haciendo «match» con dos taint el Pod no va a ser planificado en el nodo. Si el Pod se ejecuto antes de que se añadiese el taint, seguirá ejecutá ya que tiene definido el efecto «NoExecute».
Conclusión
En este artículo sobre entender Tolerations y Taints en Kubernetes, hemos visto y entendido como podemos gestionar nuestros Pods para tener un mayor contro sobre ellos. Pudiendo desalojar en un tiempo, no planificar o no ejecutar en función de unas necesidades.
Esta funcionalidad, junto con selector y afinidad de nodos son imprescindibles para realizar una buena arquitectura o infraestructura en kubernetes y darnos un gran control sobre nuestros nodos.
Espero que después de leer este artículo se entiendan un poco mejor el uso de los taints y tolerations.
Si necesitas más información puedes escribirnos un comentario o un correo electrónico a refactorizando.web@gmail.com o también nos puedes contactar por nuestras redes sociales Facebook o twitter y te ayudaremos encantados!