Préparation à la CKA - 17 - Requests & Limits

Publié le 10/12/2024 par Ali Sanhaji

Requests et limits

Kubernetes ne se contente pas de décider où placer les Pods : il doit également s’assurer que les ressources du cluster sont utilisées de manière contrôlée, prévisible et équitable.

Lorsqu’un Pod est planifié, le scheduler prend en compte les ressources disponibles sur les nœuds du cluster, principalement le CPU et la mémoire (RAM). Chaque nœud met à disposition une quantité de ressources dite allocatable, c’est-à-dire la part réellement utilisable par les Pods, après déduction des ressources réservées au système d’exploitation et aux composants Kubernetes (comme le kubelet).

Dans Kubernetes, l’allocation des ressources se fait container par container, et repose sur deux notions fondamentales :

  • Requests : la quantité minimale de ressources garantie au container, utilisée par le scheduler pour décider du placement.
  • Limits : la quantité maximale de ressources que le container est autorisé à consommer.

La définition correcte des requests et des limits est essentielle pour garantir la stabilité du cluster, éviter la contention de ressources et assurer un comportement prévisible des applications en cas de charge élevée.

Exemple Prenons l’exemple du pod à déployer suivant:

podquota.yaml:

apiVersion: v1
kind: Pod
metadata:
  labels:
    run: podquota
  name: podquota
spec:
  containers:
  - image: busybox:latest
    name: podquota
    args:
      - sleep
      - "3600"
    resources:
      requests:
        memory: "1G"
        cpu: "500m"
      limits:
        memory: "2G"
        cpu: "1"
kubectl apply -f podquota.yaml

kubectl describe pod podquota
...
    Limits:
      cpu:     1
      memory:  2G
    Requests:
      cpu:        500m
      memory:     1G
...

Requests

Les requests sont la réservation des ressources en CPU et RAM, on bloque ces ressources sur le nœud sélectionné. Si par exemple on a des nœuds avec 2 CPU et 8GB RAM allouables, alors quand on met des requests de 500mCPU et 1G de RAM, il ne reste plus que 1,5CPU et 7GB de RAM sur le nœud.

Le CPU est exprimé en millicores (mCPU). 1000m correspond à 1 CPU.

Une request de 500m signifie que le container demande l’équivalent de 0,5 CPU garanti pour le scheduling.

Contrairement à la mémoire, le CPU est une ressource compressible : si le container dépasse sa limite CPU, il sera simplement throttled (ralenti), mais ne sera pas tué.

Pour la mémoire, il y a deux types d’unités, les G,M,K et les Gi,Mi,Ki, les premières étant des puissances de 10 tandis que les secondes dans des puissances de 2. Donc 1G veut dire qu’on veut allouer 1x103M Bytes de mémoire, alors que 1Gi veut dire qu’on veut allouer 1x210Mi Bytes de mémoire: 1G Bytes = 1x103M Bytes = 1x106K Bytes = 1x109 Bytes = 1000000000 Bytes 1Gi Bytes = 1x210Mi Bytes = 1x220Ki Bytes = 1x230 Bytes = 1073741824 Bytes

Pour notre pod, le scheduler va essayer de trouver un nœud du cluster où il y a au moins 500mCPU et 1G de mémoire. S’il n’en trouve pas, il ne pourra pas scheduler le pod et lancera une erreur.

Limits

En ce qui concerne les limits, ce sont les valeurs que le container ne doit pas dépasser. Notre pod a une limit de CPU de 1. C’est-à-dire qu’il a droit au moins à un demi CPU (500mCPU), mais si le CPU n’est pas occupé par un autre process, notre container peut occuper le CPU entièrement. En revanche, il ne dépassera pas l’utilisation d’1 CPU, le système va l’en empêcher. C’est-à-dire qu’il ne pourra pas utiliser 2 CPUs, même si le deuxième est libre. D’ailleurs, c’est une des raisons pour lesquelles il est d’usage de ne pas mettre de limit sur le CPU pour la plupart des applications (il y a des raisons d’en mettre pour certaines). Ne pas en mettre permet au container d’occuper les CPUs disponibles tant qu’ils sont vides. S’ils commencent à être occupés, il consommera uniquement ses 500mCPU qui lui sont réservés.

Pour la mémoire, ici nous avons mis 2G en limit. Cela veut dire que le container occupera 1G de mémoire de base avec ses requests, mais pourra occuper jusqu’à 2G s’il y a de la place en mémoire. Contrairement au CPU, la mémoire est une ressource non compressible : si un container dépasse sa limit mémoire, il sera immédiatement terminé avec un statut OOMKilled. En revanche, s’il dépasse la limit à cause d’une mémoire mal gérée par l’application, il risque de se faire OOMKilled, c’est-à-dire que le pod sera détruit et reschedulé. En plus, s’il occupe plus d’1G de mémoire, et qu’il faut lui enlever des pages mémoire pour les allouer à un autre process, cela peut être un peu brutal pour l’application de perdre des éléments qu’elle utilisait en mémoire. Certaines applications ne fonctionnent pas très bien dans ce mode-là. La plupart du temps, par prudence, le mieux est de ne pas mettre de surprovisionnement en mémoire et de se contenter de requests=limits en ce qui concerne l’allocation des ressources en mémoire.

QoS class

La combinaison de requests et limits des containers d’un pod détermine dans quelle classe de qualité de service (QoS).

S’il n’a ni request ni limit, un pod sera dans la classe Best Effort. Il va tourner sur n’importe quel nœud quelques soient les ressources disponibles, mais ce sera le premier à se faire limiter ou détruire s’il y a conflit sur les ressources.

S’il a des requests mais pas de limit, il sera dans la classe Burstable, qui a plus de chance de survivre que la classe Best Effort en cas de conflit sur les ressources.

Si tous les containers du pod ont des requests=limits, le pod sera dans la classe Guaranteed, et c’est le dernier à se faire limiter ou détruire par rapport aux deux autres classes.

Pour qu’un Pod soit dans la classe Guaranteed, tous ses containers doivent définir à la fois des requests et des limits pour le CPU et la mémoire, et ces valeurs doivent être strictement identiques.

Conclusion

Les requests et les limits jouent un rôle fondamental dans la stabilité et le dimensionnement d’un cluster Kubernetes.

  • Les requests déterminent le placement des Pods.
  • Les limits contrôlent la consommation maximale des ressources.
  • La combinaison des deux influence la classe QoS et le comportement en cas de pression sur les ressources.

Un mauvais dimensionnement peut entraîner :

  • du gaspillage de ressources (requests trop élevées),
  • des conflits et des OOMKilled (requests trop faibles ou limits inadaptées).

La définition des ressources doit donc être basée sur l’observation, les métriques et des tests de charge, et évoluer avec le comportement réel de l’application.