Aucun manifeste Kubernetes ne déclare explicitement une classe de qualité de service : elle se déduit automatiquement de la façon dont requests et limits sont posés, ou pas posés du tout. Cette déduction silencieuse a une conséquence concrète et souvent découverte au pire moment : elle détermine l’ordre exact dans lequel le kubelet évince des pods quand un nœud manque de mémoire.

Trois classes, aucune déclarée directement

Guaranteed s’obtient quand chaque conteneur du pod fixe requests strictement égal à limits, pour le CPU et la mémoire à la fois : la protection maximale, le dernier candidat à l’éviction. Burstable couvre tout le reste dès qu’au moins une request ou une limit existe sans remplir les conditions de Guaranteed : c’est la classe la plus fréquente en pratique, celle où atterrit la grande majorité des workloads configurés sans y penser explicitement. BestEffort s’applique quand aucune request ni limit n’est définie nulle part : le premier candidat à l’éviction, sans considération pour l’importance réelle du service.

# Guaranteed : requests égal à limits sur les deux ressources,
# la seule combinaison qui obtient cette classe
resources:
  requests: {cpu: "500m", memory: "512Mi"}
  limits: {cpu: "500m", memory: "512Mi"}
# BestEffort : rien de défini, la classe la plus vulnérable,
# souvent le résultat d'un oubli plutôt que d'un choix
resources: {}

Ce qui déclenche réellement l’éviction

L’ordre d’éviction ne s’active que sous pression mémoire réelle du nœud (MemoryPressure), pas en fonctionnement normal : tant que le nœud a de la marge, les trois classes cohabitent sans différence visible. C’est précisément ce qui rend le problème silencieux jusqu’à l’incident : un service critique en BestEffort tourne parfaitement bien pendant des mois, jusqu’au jour où un pic de charge sur le même nœud déclenche une pression mémoire, et où le kubelet évince ce service en premier, avant tout autre pod moins critique mais mieux classé.

# La classe QoS effective d'un pod, visible a posteriori,
# jamais déclarée explicitement dans le manifeste
kubectl get pod my-app -o jsonpath='{.status.qosClass}'

QoS et PriorityClass : deux mécanismes, pas un seul

Une PriorityClass décide qui est préempté au moment du scheduling, quand un nouveau pod plus prioritaire réclame une place. La classe QoS décide autre chose : qui est évincé après coup, sous pression mémoire, une fois les pods déjà en cours d’exécution. Les deux mécanismes s’appliquent en cascade : à priorité égale, c’est la classe QoS qui départage l’ordre d’éviction, ce qui signifie qu’un pod critique mal dimensionné (BestEffort) reste vulnérable même avec une PriorityClass élevée, si personne n’a également posé ses requests et limits correctement.

Le piège du BestEffort non intentionnel

BestEffort n’est presque jamais un choix délibéré : c’est le résultat d’un manifeste écrit rapidement, sans resources du tout, souvent pour un composant considéré secondaire au moment de l’écrire, qui devient ensuite critique sans que personne ne revienne corriger sa configuration. Un audit régulier des classes QoS effectives des workloads qui comptent (kubectl get pods -o custom-columns=NAME:.metadata.name,QOS:.status.qosClass) rattrape ce qu’aucun déploiement normal ne révèle avant l’incident.

À retenir

La classe QoS d’un pod se déduit silencieusement de ses requests et limits, jamais d’un choix explicite, et ne devient visible qu’au moment précis où un nœud manque de mémoire. BestEffort (aucune ressource définie) est le premier candidat à l’éviction, indépendamment de l’importance réelle du service ; Guaranteed (requests égal à limits) offre la meilleure protection au prix d’un dimensionnement plus rigide. Ce mécanisme complète, sans le remplacer, celui de la PriorityClass : les deux comptent, un des réglages fins qui distinguent un cluster qui tient sous pression d’un cluster qui découvre ses priorités réelles pendant l’incident, un souci central d’une migration Kubernetes bien menée.