Kubernetes, pool, node et pods

Ainsi qu'une autre petite question

a marqué ce sujet comme résolu.

Bonjour ! :)

En ce moment, je suis à fond sur Kubernetes. Et j’aime bien. Mais il y à une chose que je ne comprend pas.

Les différents acteurs cloud propose de faire de "l’auto-scalling" si il y a une charge importante qui arrive (par exemple, suite à une publicité, des jours de fête…).

Cependant, imaginons que j’ai 2 pools, de 2 nodes de 2 pods. J’ai donc au total 2 pools, 4 nodes et 8 pods.

Dans ce cas, lors d’un pic de visite, qu’est ce qui sera augmenté ? Le nombre de node ? De pod ?

Aussi, je ne comprend pas à quoi sert d’avoir plusieurs pools. Un grand nombre de node sur un pool ne suffit pas ?

Sinon, j’ai une autre question bonus, dans le cas ou j’ai un Ingress Controller NGINX, quand je créé mes déploiements et mes services pour mes applications, je dois mettre quoi en type de service ? ClusterIP ? NodePort ? LoadBalancer ?

Parce que LoadBalancer créé un nouveau LoadBalancer sur mon interface DigitalOcean ou encore AWS, et c’est pas gratuit. Je me demande donc si c’est utile d’avoir plusieurs LoadBalancer, ou si le Ingress suffit à transférer correctement les différentes requêtes a travers les nodes (et pool, je sais pas).

Merci ! :)

EDIT : Ok, je viens de comprendre un truc.

Si j’ai 1 pool de 2 nodes de 2 pods, en fait j’aurais que 2 pods, et pas 4. Les pods sont répartis équitablement au sein des nodes disponibles. Donc en fait, augmenter le nombre de nodes permet de les distribuer sur les nodes pour diminuer la charge de travail. En revanche, est-ce utile d’avoir 4 replicas si je n’ai que 2 nodes au total ? Ou est-ce bien d’avoir 4 replicas si j’ai 2 nodes + 2 nodes en reserve (auto-scalling) en cas de forte visites ?

+0 -0

Salut !

Pour commencer je pense qu’il faut légèrement corriger ton intuition :

Cependant, imaginons que j’ai 2 pools, de 2 nodes de 2 pods.

Formulé comme ça, tu sembles penser que :

  • les nodes "appartiennent" à des pools (ce qui est vrai),
  • les pods "appartiennent" à des nodes (ce qui est… piégeux).

Il faut casser cette image dans ton esprit. Dans kubernetes tu as :

  • D’un côté, M nodes, les machines immédiatement disponibles dans ton cluster, qui peuvent être (ou non) organisés en plusieurs pools ;
  • De l’autre côté, N pods, les "unités de charge de travail" à faire tourner dans le cluster.

Les pods représentent le travail à faire, les nodes sont des ressources (RAM et CPU) pour faire ce travail, mais il n’y a pas de relation d’appartenance entre les deux. Kubernetes fait exécuter les pods aux nodes : si un node tombe, le travail qu’il exécutait n’est pas spécifiquement "le sien", et se retrouve simplement redistribué aux autres nodes.

Dans ce cas, lors d’un pic de visite, qu’est ce qui sera augmenté ? Le nombre de node ? De pod ?

Si tu utilises un HorizontalPodAutoscaler, c’est uniquement le nombre de pods qui sera modifié en cas de pic (ou creux) de charge.

Par contre, suivant ton fournisseur de cloud, tu peux aussi configurer de l’autoscaling sur des nodes. En général ça se configure au niveau du pool. Ça te permet de spawner ou détruire des machines automatiquement en fonction de la charge que ton cluster est en train d’absorber.

Aussi, je ne comprend pas à quoi sert d’avoir plusieurs pools. Un grand nombre de node sur un pool ne suffit pas ?

Réponse courte : si ce sont toutes les mêmes machines que tu manipules et que tu n’as pas de besoin particulier d’utiliser des machines aux caractéristiques différentes, tu peux te contenter d’un seul pool.

Réponse longue : il peut y avoir plein de raisons différentes d’utiliser plusieurs pools. Par exemple :

  • S’ils regroupent chacun des modèles de machines aux caractéristiques différentes,
  • Si tu veux contrôler finement quelles workloads vont tourner en isolation les unes des autres, tu peux créer différents pools avec des labels différents.

Si je prends l’exemple du cluster que j’ai au boulot, celui-ci fait tourner des workloads très variées :

  • des serveurs de jeu Unity, qui requièrent jusqu’à 2 CPU et 2Go de RAM par instance,
  • des tas de services de backend, qui mangent 5% de CPU et 10 Mo de RAM par instance,
  • ma CI, qui lance ponctuellement une certaine quantité de jobs en "one shot" dans l’infrastructure,
  • etc.

Il est évident que pour faire tourner des serveurs de jeu, je vais utiliser des machines différentes que pour les services de backend. De même, pour la CI, je vais vouloir utiliser un pool le plus petit possible quitte à l’auto-scaler à la demande pour faire tourner un pipeline.

Dans ces conditions pour faire tourner ces charges de travail j’utilise 3 pools distincts :

  • Un pool de "grosses" machines dédiées (genre 4Go RAM et 4 CPU) avec son propre autoscaling pour faire tourner les serveurs de jeu,
  • Un pool de petites machines (genre 1CPU, 1Go de RAM) qui n’est pas scalé, mais sur lequel tourne au moins une instance de chacun des services de back,
  • Un pool de petites machines préemptibles (qui ne durent pas plus de 24h, peuvent disparaître à n’importe quel moment, et sont très peu chères), qui s’auto-scale, pour absorber les pics de charge côté CI ET backend.

Sinon, j’ai une autre question bonus, dans le cas ou j’ai un Ingress Controller NGINX, quand je créé mes déploiements et mes services pour mes applications, je dois mettre quoi en type de service ? ClusterIP ? NodePort ? LoadBalancer ?

Le rôle de l’Ingress est d’accepter les connexions sur un point d’accès (l’IP/Port) public et de les renvoyer aux services internes de ton cluster. Dans ces conditions, les services vers lesquels l’ingress va ventiler sont censés être internes au cluster et n’ont donc pas besoin d’être de type LoadBalancer. Par défaut, j’utilise plutôt des services de type ClusterIP, parce qu’ils ont le mérite de correspondre à des entrées DNS dans le cluster et permettent de faire facilement des choses assez sympa (comme des services headless qui permettent de découvrir automatiquement les IP de toutes les répliques actives à l’instant T…).

+5 -0

Woua, merci pour cette réponse détaillée ! :)

Formulé comme ça, tu sembles penser que :

les nodes "appartiennent" à des pools (ce qui est vrai), les pods "appartiennent" à des nodes (ce qui est… piégeux).

Et tu fais bien de faire la remarque. En effet, je pensais qu’en mettant 2 le nombre d’instance, ça allait mettre 2 pods par nœud.

Ton explication sur ce qu’est un nœud et un pod est vraiment très claire, et je comprends nettement mieux. Merci ! :)

Si tu utilises un HorizontalPodAutoscaler, c’est uniquement le nombre de pods qui sera modifié en cas de pic (ou creux) de charge.

Mais alors, j’ai une autre question : Si j’ai deux nœuds, avec une puissance A. augmenter le nombre de pod pour par exemple le passer de 2 à 8 ne changera rien, si ? Puisque la puissance A, elle, ne sera pas augmentée.

En fait, derrière cette question, se cache en réalité une incompréhension sur le système de pod. Avoir au total 2 pods quand on à 2 nœuds, je comprends. Mais avoir plus de pods, je ne vois pas l’intérêt, car ce n’est pas eux qui donnent de la puissance (ils en consomment, même).

Idem, si j’ai 2 pods qui sont répartis entre 2 noeud, et qu’un de ces derniers venait à ne plus être disponible, le pod qui lui était "associé" sera donc dirigé vers le nœud encore en vie. Mais dans ce cas, d’où l’intérêt ?

Aussi, petite question qui me bloque pas mal en ce moment : Chez mon fournisseur Cloud, je peux indiquer un nombre de noeud minimum et maximum (pour de l’autoscalling). Est-ce qu’il est recommandé de mettre un nombre de pods égal au nombre de noeud maximum que je peux avoir, ou dois-je calculer ça autrement ?

Enfin, j’aurais une dernière interrogation : dans le cas où j’ai deux pools, le premier avec deux nœuds (et maximum 4) d’une puissance moyenne, et l’autre avec deux nœuds (et maximum 4) d’une puissance bien plus élevée. En cas de pic de visite, quel pool sera augmenté en nœud ? Le plus puissant ? Le plus faible en premier, puis le plus puissant ? Ou est-ce en fonction du Cloud Provider, qui gère ça de son côté ?

En tout cas, merci encore pour ta réponse, elle m’a beaucoup éclairci ! :)

+0 -0

Mais alors, j’ai une autre question : Si j’ai deux nœuds, avec une puissance A. augmenter le nombre de pod pour par exemple le passer de 2 à 8 ne changera rien, si ? Puisque la puissance A, elle, ne sera pas augmentée.

En fait, derrière cette question, se cache en réalité une incompréhension sur le système de pod.

Je crois que ce qui coince, c’est que tu réfléchis en termes de "puissance".

Ce que les nodes fournissent, c’est du temps de calcul (CPU) et de la mémoire (RAM). Ce sont des ressources que consomment les pods pour tourner.

Autrement dit, si une machine a 2 fois plus de CPU et de RAM qu’une autre, cela ne veut pas dire qu’elle fera le même travail deux fois plus vite. Ça veut juste dire qu’elle peut faire tourner 2 fois plus de pods simultanément, ou bien des pods 2 fois plus consommateurs de ressources.

Ensuite, ça dépend vraiment des applications que tu fais tourner. Imaginons un pod qui consomme 10% CPU et 50Mo de RAM pour traiter 1000 requêtes par seconde, sur un node qui dispose de 1 CPU et 1 Go de RAM.

  • Si ce pod est tout seul à tourner sur son node, alors il traite 1000 requêtes par seconde et utilise 10% du CPU et 5% de la RAM disponibles sur le node.
  • Si l’on crée une seconde instance du pod, on va maintenant traiter 2000 requêtes par seconde en tout, et utiliser pour cela 20% du CPU et 10% de la RAM à disposition.

En doublant le nombre de répliques j’ai doublé le nombre de requêtes que je peux traiter par seconde. Soit exactement ce que je veux doubler pendant un pic de charge.

Je pense que cette explication doit déjà pas mal alimenter ta réflexion sur tes autres questions. N’hésite pas s’il y a toujours des points pas clairs.

+0 -0

Je précise ici ce que je disais par ailleurs à @FougereBle : le doublement ici fonctionne bien parce que tu as des contraintes fortes sur tout le reste, en particulier : le pod est une unité indivisible, et aura réellement les ressources qu’il demande (ce qu’est censé te garantir Kubernetes, je suppose).

L’informatique est bourrée de non-linéarités (cf ce tuto et celui-ci) et d’effets de seuils (caches, capacités physiques des CPU, mémoire, entrées/sortie disque et réseau…) ; donc d’une manière générale, gérer deux fois plus de connexions ne va pas prendre deux fois plus de ressources. Ça peut être plus, ou moins, selon les cas.

Pour reprendre l’exemple, en admettant « assez de ressources physiques », si un pod traite 1000 requêtes par seconde avec 10 % du CPU et 5 % de la RAM, alors deux pods traitent 2000 requêtes par seconde avec 20 % du CPU et 10 % de la RAM, ça c’est OK.

Par contre, ça n’implique pas que :

  • Un pod 2 fois plus gros (avec 20 % du CPU et 10 % de la RAM) traitera 2000 requêtes par seconde : tu peux avoir des caches partagés qui augmenteront les performances, ou au contraire des effets bizarre qui la diminuera.
  • Gérer 1500 requêtes par seconde nécessitera 15 % du CPU et 7,5 % de la RAM. Typiquement, tu peux avoir des objets qui restent en mémoire quelle que soit la charge.
  • Tu peux scaler à l’infini ; dans cet exemple précis, tu ne pourras pas mettre de onzième pod parce qu’il n’y a pas la puissance CPU disponible.

Et tu as toujours le problème des points de contrainte unique (par exemple ta BDD, qui s’étale assez mal sur plusieurs nœuds).

Cela dit, c’est clair que ce genre d’infrastructure aide beaucoup pour gérer l’augmentation de performances ; mais si tu as des contraintes fortes l’idéal reste de faire des tests de charges, qui sont compliqués à réaliser de manière réaliste.

Un point que je ne sais pas du tout, c’est si Kubernetes permet de définir des garanties sur les I/O et la bande passante (disque et réseau), ou si tu peux découvrir des surprise à cause de ça.

Pour synthétiser, je pense que le fond du problème, c’est qu’avant de penser "passage à l’échelle" sur une charge de travail, il faut connaître son "profil de performances" (Qu’est-ce qui limite la quantité de travail ? Quand ? Pourquoi ?).

Pour connaître le profil de performances d’un pod ou d’une fonctionnalité, il faut effectivement faire des tests de charge, et pour faire ces tests de charge, il faut d’abord mettre en place la couche d’observabilité du système.

Dit plus simplement : avant de penser à l’auto-scaling, une bonne idée serait que tu installes dans ton cluster un Prometheus et un Grafana qui te permettent d’avoir sous les yeux des graphes qui mettent en correspondance :

  • la charge de travail réalisé (nombre de requêtes traitées),
  • la consommation en CPU/RAM du pod,
  • la consommation de bande passante du pod.

C’est beaucoup plus facile à faire qu’à décrire ! Il suffit d’installer le chart Helm qui va bien (https://github.com/grafana/helm-charts) et de configurer une dashboard dans Grafana. Une fois que tu as ça, tu peux commencer à jouer, à simuler des pics de charge, à voir ce qui pète en premier, et donc à comprendre comment tes pods se comportent face à une montée de charge. Plus important encore, tu peux caractériser ce qui se passe pour de vrai et en temps réel sur ton infra avec un système qui tourne en utilisation normale.

Une fois que tu as ces connaissances à ta disposition, ça te permet de décider d’un tas de trucs que tu vas pouvoir paramétrer dans tes déploiements :

  • Les requêtes de ressources du Pod : quelle quantité de RAM et de CPU est-il supposé utiliser en situation normale ? Kubernetes se sert de ces requêtes pour affecter les pods à des noeuds qui disposent des ressources disponibles. Par défaut, Kubernetes tolère qu’un pod dépasse ses requêtes de ressources, c’est une soft limit. Par contre si un node se retrouve très chargé, Kubernetes va tuer (et réaffecter) en priorité les pods qui dépassent leur requête de ressources.
  • Les limites de ressources du Pod : à partir de quelle utilisation de RAM et de CPU on considère que le pod est dans un état "pathologique" ? Cette fois, c’est une hard limit : dès l’instant que le pod dépasse une limite, Kubernetes le tue et le re-schedule.
  • Combien de répliques tu veux avoir de ce pod "en fonctionnement normal" (pas spécialement en creux ou en pic de charge). Cela dépend de la charge que tu observes au quotidien sur le système, mais aussi des garanties de disponibilité que tu veux tenir (combien de répliques peuvent tomber à la fois sans que ça n’impacte significativement la qualité de service).
  • Si ça vaut le coup de configurer de l'autoscaling : certains pods s’y prêtent généralement bien (genre des services d’API) et augmenter les répliques augmente la capacité de travail, d’autres pas du tout et les scaler n’augmentera pas ta capacité de traitement : il n’y a pas de règle pour prendre une décision automatique.

Dans les questions qui restaient, il y avait celle-ci :

Idem, si j’ai 2 pods qui sont répartis entre 2 noeud, et qu’un de ces derniers venait à ne plus être disponible, le pod qui lui était "associé" sera donc dirigé vers le nœud encore en vie. Mais dans ce cas, d’où l’intérêt ?

Là on entre dans les configurations avancées, mais tu peux parfaitement ajouter des hints à Kubernetes pour lui demander d’éviter de scheduler un pod sur un noeud si celui-ci fait déjà tourner une réplique du même deployment. Si tu n’as que 2 machines, ça n’est pas immédiatement apparent, mais si tu as plus de 2 machines (disons 3 : A, B et C) et 2 instances (une sur A et une sur B), alors quand B tombe, kubernetes sera "incité" à redistribuer le travail vers C plutôt que A qui fait déjà tourner une instance.

Il y a plein de petits mécanismes de ce style que tu peux utiliser pour contrôler finement ce qui se passe, mais à mon avis tu n’en es pas encore là. Pareil pour l’auto-scaling.

Je pense qu’avant d’arriver à tuner ce genre de détails, tu devrais déjà te forger une bonne compréhension des mécaniques de base de Kubernetes, et du profil de perfs de ton application. Il sera toujours temps de découvrir les affinities, anti-affinities, taints et tolerations quand tu auras un problème concret à résoudre.

Un point que je ne sais pas du tout, c’est si Kubernetes permet de définir des garanties sur les I/O et la bande passante (disque et réseau), ou si tu peux découvrir des surprise à cause de ça.

Les seules ressources qui sont "comprises" (surveillées explicitement) par K8s pour le moment sont la RAM et le CPU, c’est seulement sur ces ressources que l’on peut configurer les requêtes/limites des pods, et les critères pour leur auto-scaling. Autrement dit, seules les consommations de RAM et de CPU sont utilisées pour partager les ressources des nodes entre les pods comme on se partage un gâteau.

Ça semble sacrément difficile de généraliser cette approche aux IO, notamment parce qu’il ne s’agit pas d’une ressource "unique" où on pourrait se dire "ce pod pioche 5MB/s de bande passante alors qu’il en a 2GB/s disponible".

Dans tous les cas, ce genre de choses se surveille de la même manière et avec les mêmes outils que pour configurer les ressources et limites de CPU/RAM : en installant un système d’observabilité dans le cluster, et en analysant les réactions des pods dans diverses conditions.

+1 -0
Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte