Limites CPU et throttling agressif dans Kubernetes

Note de traduction.: cette histoire instructive d'Omio — un agrĂ©gateur de voyages europĂ©en — guide les lecteurs de la thĂ©orie de base aux aspects pratiques captivants de la configuration de Kubernetes. DĂ©couvrir de tels cas aide non seulement Ă  Ă©largir ses horizons, mais aussi Ă  prĂ©venir des problĂšmes non triviaux.

Limites CPU et throttling agressif dans Kubernetes

Avez-vous déjà été confronté au fait qu'une application « se bloquait » sur place, cessait de répondre aux demandes de vérification de l'état (health checks) et que vous ne pouviez pas comprendre la raison d'un tel comportement ? L'une des explications possibles est liée à la limite de quota sur les ressources CPU. C'est ce dont nous allons parler dans cet article.

TL;DR :
Nous recommandons vivement d'éviter les limites de CPU dans Kubernetes (ou de désactiver les quotas CFS dans Kubelet), si vous utilisez une version du noyau Linux comportant un bogue de quotas CFS. Dans le noyau un support pour XWayland; il existe un bogue sérieux et bien connu qui entraßne un throttling excessif et des retards
.

Chez Omio toute notre infrastructure est gérée par Kubernetes. Tous nos workloads stateful et stateless fonctionnent exclusivement sur Kubernetes (nous utilisons Google Kubernetes Engine). Au cours des six derniers mois, nous avons commencé à observer des lenteurs aléatoires. Les applications se bloquent ou cessent de répondre aux health checks, perdent la connexion au réseau, etc. Ce comportement nous a longtemps mis dans l'impasse, et finalement, nous avons décidé de nous pencher sérieusement sur le problÚme.

Résumé de l'article :

  • Quelques mots sur les conteneurs et Kubernetes ;
  • Comment sont implĂ©mentĂ©es les demandes et les limites de CPU ;
  • Comment la limite de CPU fonctionne dans les environnements Ă  plusieurs cƓurs ;
  • Comment surveiller le throttling de CPU ;
  • Solution du problĂšme et nuances.

Quelques mots sur les conteneurs et Kubernetes

Kubernetes est essentiellement la norme moderne dans le monde de l'infrastructure. Sa tĂąche principale est l'orchestration des conteneurs.

Conteneurs

Par le passĂ©, nous devions crĂ©er des artefacts tels que des JAR/WAR Java, des Egg Python ou des exĂ©cutables pour les lancer ultĂ©rieurement sur des serveurs. Cependant, pour les faire fonctionner, il fallait effectuer un travail supplĂ©mentaire : installer l'environnement d'exĂ©cution (Java/Python), placer les fichiers nĂ©cessaires au bon endroit, assurer la compatibilitĂ© avec une version spĂ©cifique du systĂšme d'exploitation, etc. En d'autres termes, il fallait prĂȘter une attention particuliĂšre Ă  la gestion des configurations (ce qui Ă©tait souvent la source de conflits entre dĂ©veloppeurs et administrateurs systĂšmes).

Les conteneurs ont tout changĂ©. Maintenant, un artefact prend la forme d'une image de conteneur. On peut le considĂ©rer comme un fichier exĂ©cutable Ă©tendu, contenant non seulement le programme mais Ă©galement un environnement d'exĂ©cution complet (Java/Python/etc.), ainsi que les fichiers/packages nĂ©cessaires, prĂ©installĂ©s et prĂȘts Ă  ĂȘtre lancĂ©s. Les conteneurs peuvent ĂȘtre dĂ©ployĂ©s et exĂ©cutĂ©s sur diffĂ©rents serveurs sans aucune action supplĂ©mentaire.

De plus, les conteneurs fonctionnent dans leur propre environnement isolĂ©. Ils disposent de leur propre adaptateur rĂ©seau virtuel, d'un systĂšme de fichiers avec un accĂšs restreint, d'une hiĂ©rarchie de processus, de limitations sur le CPU et la mĂ©moire, etc. Tout cela est rĂ©alisĂ© grĂące Ă  une sous-systĂšme particulier du noyau Linux — les namespaces.

Kubernetes

Comme mentionné précédemment, Kubernetes est un orchestrateur de conteneurs. Son fonctionnement est le suivant : vous lui fournissez un pool de machines, puis vous dites : « Hé, Kubernetes, lance dix instances de mon conteneur avec 2 processeurs et 3 Go de mémoire chacune, et maintiens-les en état de marche ! ». Kubernetes s'occupe de tout le reste. Il trouvera les ressources disponibles, lancera les conteneurs et les redémarrera si nécessaire, appliquera des mises à jour lors des changements de version, etc. En gros, Kubernetes permet d'abstraire le matériel et rend la diversité des systÚmes utilisable pour le déploiement et le fonctionnement des applications.

Limites CPU et throttling agressif dans Kubernetes
Kubernetes du point de vue d'un simple citoyen

Que sont les requĂȘtes et les limites dans Kubernetes

D'accord, nous avons compris les conteneurs et Kubernetes. Nous savons également que plusieurs conteneurs peuvent se trouver sur une seule machine.

On peut faire une analogie avec un appartement communautaire. On prend un grand espace (machines/nƓuds) et on le loue Ă  plusieurs locataires (conteneurs). Kubernetes joue le rĂŽle d'agent immobilier. La question se pose : comment empĂȘcher les locataires de se disputer ? Que se passe-t-il si l'un d'eux, disons, dĂ©cide d'occuper la salle de bains pendant une demi-journĂ©e ?

C'est ici qu'entrent en jeu les requĂȘtes et les limites du CPU. Request Le CPU est uniquement utilisĂ© pour la planification. C'est un peu comme une « liste de souhaits » du conteneur, utilisĂ©e pour sĂ©lectionner le nƓud le plus adaptĂ©. En mĂȘme temps, le CPU Limite peut ĂȘtre comparĂ© Ă  un contrat de location — une fois que nous avons trouvĂ© un nƓud pour le conteneur, celui-ci ne pourra pas dĂ©passer les limites Ă©tablies. Et c'est lĂ  que le problĂšme se pose...

Comment les demandes et les limites sont-elles mises en Ɠuvre dans Kubernetes

Kubernetes utilise un mécanisme de throttling intégré au noyau pour réaliser les limites de CPU. Si une application dépasse la limite, le throttling se met en place (c'est-à-dire qu'elle reçoit moins de cycles CPU). Les demandes et limites pour la mémoire sont organisées différemment, ce qui les rend plus faciles à détecter. Il suffit de vérifier le dernier statut de redémarrage du pod : n'est-il pas « OOMKilled ». Avec le throttling CPU, c'est un peu plus complexe, car K8s ne rend disponibles que les métriques d'utilisation, et non celles des cgroups.

Demande de CPU

Limites CPU et throttling agressif dans Kubernetes
Comment est mise en Ɠuvre la demande CPU

Pour simplifier, prenons un exemple avec une machine à CPU 4 cƓurs.

K8s utilise le mécanisme des groupes de contrÎle (cgroups) pour gérer la distribution des ressources (mémoire et processeur). Il existe un modÚle hiérarchique : un enfant hérite des limites du groupe parent. Les détails de la distribution sont stockés dans un systÚme de fichiers virtuel (/sys/fs/cgroup). Dans le cas du processeur, cela /sys/fs/cgroup/cpu,cpuacct/*.

K8s utilise le fichier cpu.share pour distribuer les ressources processeur. Dans notre cas, le groupe de contrĂŽle racine reçoit 4096 parts de ressources CPU — 100% de la puissance CPU disponible (1 cƓur = 1024 ; c'est une valeur fixe). Le groupe racine distribue les ressources de maniĂšre proportionnelle en fonction des parts des enfants, inscrites dans cpu.share, et ceux-ci, Ă  leur tour, agissent de mĂȘme avec leurs enfants, etc. Dans un nƓud Kubernetes typique, le groupe de contrĂŽle racine a trois enfants : system.slice, user.slice et kubepods. Les deux premiers sous-groupes sont utilisĂ©s pour distribuer les ressources entre les charges systĂšme critiques et les programmes utilisateur en dehors de K8s. Le dernier — kubepods — est créé par Kubernetes pour distribuer les ressources entre les pods.

Dans le schéma ci-dessus, on peut voir que le premier et le deuxiÚme sous-groupe ont reçu chacun 1024 parts, tandis que le sous-groupe kubepod a été alloué 4096 parts. Comment cela est-il possible ? AprÚs tout, le groupe racine a accÚs à un total de 4096 parts, et la somme des parts de ses enfants dépasse considérablement ce chiffre (6144) ? La raison en est que la valeur a un sens logique, et le planificateur Linux (CFS) l'utilise pour la distribution proportionnelle des ressources CPU. Dans notre cas, les deux premiers groupes reçoivent chacun 680 parts réels (16,6% de 4096), tandis que kubepod reçoit les parts restantes. 2736 En cas d'inactivité, les deux premiers groupes n'utiliseront pas les ressources allouées.

Heureusement, le planificateur dispose d'un mécanisme permettant d'éviter la perte de ressources CPU inutilisées. Il transfÚre les capacités « inactives » dans un pool global, à partir duquel elles sont réparties aux groupes nécessitant des ressources supplémentaires du processeur (le transfert se fait par lots pour éviter des pertes dues à l'arrondi). Une méthode similaire est appliquée à tous les descendants des descendants.

Ce mécanisme assure une répartition équitable des capacités du processeur et veille à ce qu'aucun processus ne « vole » de ressources aux autres.

Limite CPU

Bien que les configurations de limites et de demandes dans K8s semblent similaires, leur mise en Ɠuvre diffĂšre radicalement : c'est la partie la plus trompeuse et la moins documentĂ©e.

K8s utilise un mĂ©canisme de quotas CFS pour mettre en Ɠuvre les limites. Leurs paramĂštres sont dĂ©finis dans les fichiers cfs_period_us et cfs_quota_us dans le rĂ©pertoire cgroup (le fichier se trouve Ă©galement lĂ ) cpu.share).

Contrairement Ă  cpu.share, la quota est basĂ©e sur une pĂ©riode de temps, et non sur la puissance du processeur disponible. cfs_period_us dĂ©finit la durĂ©e de la pĂ©riode (Ă©poque) — c’est toujours 100000 ÎŒs (100 ms). Dans K8s, il est possible de changer cette valeur, mais cette option est encore disponible uniquement en version alpha. Le planificateur utilise l'Ă©poque pour redĂ©marrer les quotas utilisĂ©s. Le deuxiĂšme fichier, cfs_quota_us, dĂ©finit le temps disponible (quota) Ă  chaque Ă©poque. Notez qu'il est Ă©galement indiquĂ© en microsecondes. La quota peut dĂ©passer la durĂ©e de l'Ă©poque ; en d'autres termes, elle peut ĂȘtre supĂ©rieure Ă  100 ms.

Examinons deux scĂ©narios sur des machines Ă  16 cƓurs (le type d'ordinateur le plus courant chez nous chez Omio) :

Limites CPU et throttling agressif dans Kubernetes
Scénario 1 : 2 flux et une limite de 200 ms. Sans throttling

Limites CPU et throttling agressif dans Kubernetes
Scénario 2 : 10 flux et une limite de 200 ms. Le throttling commence aprÚs 20 ms, l'accÚs aux ressources du processeur est rétabli encore aprÚs 80 ms.

Supposons que vous ayez fixĂ© une limite CPU Ă  2 cƓurs ; Kubernetes convertira cette valeur en 200 ms. Cela signifie que le conteneur peut utiliser au maximum 200 ms de temps processeur sans throttling.

Et c'est lĂ  que ça devient intĂ©ressant. Comme mentionnĂ© ci-dessus, le quota disponible est de 200 ms. Si vous avez dix flux en parallĂšle sur une machine Ă  12 cƓurs (voir l'illustration du scĂ©nario 2), tant que tous les autres pods sont inactifs, le quota sera Ă©puisĂ© en seulement 20 ms (puisque 10 * 20 ms = 200 ms), et tous les flux de ce pod seront « throttlĂ©s » (throttle) (limiter) sur les 80 ms suivants. La situation est aggravĂ©e par le dĂ©jĂ  mentionnĂ© bug du planificateur, qui provoque un throttling excessif et rend le conteneur incapable de consommer mĂȘme sa propre quota.

Comment évaluer le throttling dans les pods ?

Il suffit d'entrer dans le pod et d'exécuter cat /sys/fs/cgroup/cpu/cpu.stat.

  • nr_periods — le nombre total de pĂ©riodes du planificateur ;
  • nr_throttled — le nombre de pĂ©riodes throttlĂ©es parmi nr_periods;
  • throttled_time — le temps total throttlĂ© en nanosecondes.

Limites CPU et throttling agressif dans Kubernetes

Que se passe-t-il réellement ?

En fin de compte, nous avons un haut niveau de throttling dans toutes les applications. Parfois, il est une fois et demie plus fort que prévu !

Cela entraĂźne diverses erreurs — des Ă©checs de vĂ©rification de disponibilitĂ© (readiness), des blocages de conteneurs, des interruptions de connexions rĂ©seau, des dĂ©lais d'attente dans les appels de services. Au final, cela se traduit par une augmentation de la latence et du nombre d'erreurs.

Solutions et conséquences

C'est assez simple. Nous avons abandonné les limites de CPU et nous avons mis à jour le noyau du systÚme d'exploitation dans nos clusters vers la version la plus récente, dans laquelle le bug a été corrigé. Le nombre d'erreurs (HTTP 5xx) dans nos services a immédiatement considérablement diminué :

Erreurs HTTP 5xx

Limites CPU et throttling agressif dans Kubernetes
Erreurs HTTP 5xx d'un service critique

Temps de réponse p95

Limites CPU et throttling agressif dans Kubernetes
Latence des requĂȘtes d'un service critique, 95e percentile

Coûts d'exploitation

Limites CPU et throttling agressif dans Kubernetes
Nombre d'heures d'instance dépensées

OĂč est le piĂšge ?

Comme mentionné au début de l'article :

On peut faire une analogie avec un appartement en colocation
 Kubernetes joue le rĂŽle de l'agent immobilier. Mais comment empĂȘcher les colocataires de se disputer ? Que se passe-t-il si l'un d'eux dĂ©cide, disons, d'occuper la salle de bain pendant une demi-journĂ©e ?

Voilà le piÚge. Un conteneur malveillant peut consommer toutes les ressources CPU disponibles sur la machine. Si vous avez une architecture applicative bien conçue (par exemple, avec des JVM, Go, Node VM correctement configurés), alors ce n'est pas un problÚme : on peut travailler dans ces conditions pendant une longue période. Mais si les applications sont mal optimisées ou pas du tout optimisées (FROM java:latest), la situation peut échapper à tout contrÎle. Chez Omio, nous avons des Dockerfiles de base automatisés avec des configurations raisonnables par défaut pour les principaux langages, donc ce problÚme n'existait pas.

Nous recommandons de surveiller les métriques Golden Signals (utilisation, saturation et erreurs), délais API et fréquence des erreurs. Assurez-vous que les résultats correspondent à vos attentes.

Liens

Voici notre histoire. Les documents suivants ont grandement aidé à comprendre ce qui se passe :

Rapports d'erreurs Kubernetes :

Avez-vous rencontré des problÚmes similaires dans votre pratique ou avez-vous de l'expérience liée au throttling dans des environnements de production conteneurisés ? Partagez votre histoire dans les commentaires !

P.S. de l'auteur

Lisez aussi dans notre blog :

Source : habr.com

Acheter un hĂ©bergement fiable pour les sites avec protection DDoS, serveurs VPS VDS đŸ”„ Acheter un hĂ©bergement fiable pour les sites avec protection DDoS, serveurs VPS VDS | ProHoster