Préparation à la CKA - 14 - Taints & Tolerations

Publié le 19/11/2024 par Ali Sanhaji

Taints and tolerations

Dans l’article précédent, nous avons parlé de Daemonset, et à la fin nous avons vu que les pods du daemonset arrivaient à se scheduler sur les nœuds worker, mais pas sur le control plane. Nous avons dit que c’était un problème lié aux taints du nœud de control plane. Nous allons expliquer dans cet article les notions de taints, et de tolerations qui lui sont associées.

La taint est une sorte de label spécial qui est mis sur certains nœuds pour dire qu’ils ont un caractère spécial ET donc qu’il ne faut pas que des pods puissent se scheduler dessus. Le mot taint en anglais peut être traduit entre autres par infecté. C’est comme si on dit que le nœud est infecté, ce qui repousse les pods d’être schedulés dessus. La seule manière pour un pod d’être schedulé sur un nœud avec une taint, c’est qu’il ait la toleration qui correspond à la taint, donc qu’il supporte cette taint.

Nous allons voir un exemple sur notre cluster l’effet qu’a une taint. Exemple Nous allons mettre une taint sur les noeuds workers de notre cluster :

kubectl taint node k8s-node-1 type=special:NoSchedule
kubectl taint node k8s-node-2 type=special:NoSchedule

Ici, nous avons dit que les noeuds vont porter une taint avec la clé “type” et la valeur “special”, et que ça va avoir pour effet NoSchedule, c’est-à-dire que ça va empêcher le scheduling des pods, qui ne portent pas la toleration qui correspond, sur ces noeuds. La clé et la valeur sont complètement arbitraires, “type” et “special” ne sont pas des mot-clés.

Voici le pod que nous allons lancé, qui n’a donc pas de toleration :

pod-no-toleration.yaml:

apiVersion: v1
kind: Pod
metadata:
  labels:
    run: podtol
  name: podtol
spec:
  containers:
  - image: busybox:latest
    name: podtol
    args:
      - sleep
      - "3600"

Nous lançons le pod :

kubectl apply -f pod-no-toleration.yaml

Quand on voit l’état du pod, on voit qu’il est en pending, donc pas encore schedulé :

kubectl get pods
NAME     READY   STATUS    RESTARTS   AGE
podtol   0/1     Pending   0          10s

Si on regarde de plus près ce qui se passe :

kubectl describe pods

Events:
  Type     Reason            Age   From               Message
  ----     ------            ----  ----               -------
  Warning  FailedScheduling  7s    default-scheduler  0/3 nodes are available: 1 node(s) had untolerated taint {node-role.kubernetes.io/control-plane: }, 2 node(s) had untolerated taint {type: special}. preemption: 0/3 nodes are available: 3 Preemption is not helpful for scheduling..

On voit que le scheduler ne trouve pas de noeud à assigner au pod, car tous les noeuds ont une taint : celle qu’on a ajouté aux deux noeuds worker, et celle qui existait déjà sur le control plane.

Pour remédier à ça, il faut donc ajouter la toleration qui va bien au pod.

pod-toleration.yaml:

apiVersion: v1
kind: Pod
metadata:
  labels:
    run: podtol
  name: podtol
spec:
  containers:
  - image: busybox:latest
    name: podtol
    args:
      - sleep
      - "3600"
  tolerations:
  - key: "type"
    operator: "Equal"
    value: "special"
    effect: "NoSchedule"

Dans la section tolerations à la fin de la description du pod, on peut lire qu’on dit que ce pod est capable de tolérer les taints qui ont pour clé “type” qui est égale à la valeur “special” et qui a pour effet NoSchedule.

Si on relance le pod avec la nouvelle toleration et qu’on regarde son état :

kubectl apply -f pod-toleration.yaml

kubectl get pods -o wide
NAME     READY   STATUS    RESTARTS   AGE   IP           NODE
podtol   1/1     Running   0          49s   10.244.2.5   k8s-node-2

Donc il a bien été schedulé sur un des noeuds worker, même si ce dernier a une taint.

Revenons maintenant à notre daemonset de l’article dernier, mais d’abord un peu de rangement.

On supprime le pod, et on enlève la taint des noeuds workers (comme pour ajouter mais en mettant un “-” à la fin de la taint) :

kubectl delete -f pod-toleration.yaml
pod "podtol" deleted

kubectl taint node k8s-node-1 type=special:NoSchedule-
node/k8s-node-1 untainted

kubectl taint node k8s-node-2 type=special:NoSchedule-
node/k8s-node-2 untainted

Daemonset

Notre daemonset la dernière fois ressemblait à ça :

daemonset-no-toleration.yaml:

apiVersion: apps/v1
kind: DaemonSet
metadata:
  labels:
    type: daemon
  name: daemontest
spec:
  selector:
    matchLabels:
      run: daemon
  template:
    metadata:
      labels:
        run: daemon
      name: daemonpod
    spec:
      containers:
      - image: busybox:latest
        name: daemonpod
        args:
          - sleep
          - "3600"

On va le lancer :

kubectl apply -f daemonset-no-toleration.yaml

kubectl get pods -o wide

NAME               READY   STATUS    RESTARTS   AGE   IP           NODE
daemontest-hmm8j   1/1     Running   0          61s   10.244.1.4   k8s-node-1
daemontest-nmcjr   1/1     Running   0          64s   10.244.2.3   k8s-node-2

Encore une fois, le daemonset est lancé sur les noeuds worker mais pas sur le noeud de control plane. Hors pour les daemonset, nous avons parfois besoin de les avoir sur tous les noeuds, comme par exemple pour un daemonset qui récolte les métriques ou les logs sur tous les noeuds.

Nous savons maintenant que ça doit être un souci avec les taints. Cherchons donc la taint qui bloque au niveau du noeud de control plane :

kubectl get nodes k8s-controlplane -o json | jq '.spec.taints'                                                                                     [
  {
    "effect": "NoSchedule",
    "key": "node-role.kubernetes.io/control-plane"
  }
]

Ici, on voit que le noeud du control plane a une taint qui a pour clé “node-role.kubernetes.io/control-plane” et qui n’a pas de valeur. En fait, les taints n’ont pas besoin de valeur pour être effective. On dit que la taint “node-role.kubernetes.io/control-plane” tout simplement existe.

Nous allons donc ajouter une toleration au daemonset pour qu’il se schedule sur le pod :

daemonset-toleration.yaml:

apiVersion: apps/v1
kind: DaemonSet
metadata:
  labels:
    type: daemon
  name: daemontest
spec:
  selector:
    matchLabels:
      run: daemon
  template:
    metadata:
      labels:
        run: daemon
      name: daemonpod
    spec:
      containers:
      - image: busybox:latest
        name: daemonpod
        args:
          - sleep
          - "3600"
      tolerations:
      - key: "node-role.kubernetes.io/control-plane"
        operator: "Exists"
        effect: "NoSchedule"

Ici donc on dit que les pods du daemonset tolèrent les noeuds qui ont une taint avec la clé “node-role.kubernetes.io/control-plane” et qui ont pour effet NoSchedule.

Quand on lance le daemonset :

kubectl apply -f daemonset-toleration.yaml             
daemonset.apps/daemontest configured


kubectl get pods -o wide                                                                                                                  NAME               READY   STATUS        RESTARTS   AGE     IP           NODE
daemontest-2klsg   1/1     Running       0          3m10s      10.244.0.6   k8s-controlplane
daemontest-hmm8j   1/1     Running       0          3m16s   10.244.1.5   k8s-node-1
daemontest-nmcjr   1/1     Running   0          3m13s   10.244.2.4   k8s-node-2

On voit bien que le daemonset a aussi été schedulé sur le noeud de control plane.

Conclusion

Voilà donc pour les taints et tolerations, qui sont très utiles si par exemple on veut avoir une certains nombre de noeuds qui ne sont dédiés qu’à une application spécifique qui aura la bonne toleration, afin d’empêcher les autres applications d’être schedulées sur les mêmes noeuds.

Nous allons voir dans les prochains articles d’autres moyens de jouer avec le scheduling dans kubernetes, avec les notions de node selector et d’affinity en général.