Notes de lecture de K8s in Action——[14]Sécurisation des nœuds de cluster et du réseau

Notes de lecture de K8s in Action——[14]Sécurisation des nœuds de cluster et du réseau

Jusqu'à présent, les pods étaient créés quelle que soit la quantité de CPU et de mémoire qu'ils étaient autorisés à consommer. Cependant, comme vous le verrez dans ce chapitre, la définition de la quantité maximale qu'un Pod est censé consommer et est autorisé à consommer est une partie importante de toute définition de Pod. La définition de ces deux ensembles de paramètres garantit que les pods n'occupent qu'une part des ressources fournies par le cluster Kubernetes et affecte également la manière dont les pods sont planifiés dans le cluster.

14.1 Demander des ressources pour les conteneurs d'un pod

Lorsque vous créez un pod, vous spécifiez la quantité de CPU et de mémoire dont chaque conteneur a besoin (appelée requests), ainsi qu'une limite stricte sur ce qu'il est autorisé à consommer (appelée limits). Ces paramètres sont spécifiés individuellement pour chaque conteneur, et non pour l'ensemble du Pod. Les demandes et limites de ressources d'un Pod sont la somme des demandes et des limites de tous ses conteneurs .

14.1.1 Création de pods avec des demandes de ressources

Examinons un exemple de fichier manifeste de Pod dans lequel le processeur et la mémoire d'un seul conteneur requestsont été spécifiés, comme ceci :

# requests-pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: requests-pod
spec:
  containers:
  - image: busybox
    command: ["dd", "if=/dev/zero", "of=/dev/null"]
    name: main
    resources:
      requests:
        cpu: 200m # 容器请求 200 毫核心(即一个单独的 CPU 核心时间的五分之一)。
        memory: 10Mi

Dans le manifeste du pod, votre conteneur unique nécessite un cinquième des cœurs de processeur (200 millicores) pour fonctionner correctement. Cinq de ces pods/conteneurs peuvent être exécutés sur un seul cœur de processeur.

Dans la spécification du Pod, vous avez également demandé 10 Mo de mémoire pour le conteneur. Lorsqu'un pod démarre, vous pouvez afficher rapidement la consommation CPU du processus en exécutant la commande top dans le conteneur, comme indiqué ci-dessous :

$ kubectl exec -it requests-pod -- top
Mem: 7830880K used, 321904K free, 5344K shrd, 271216K buff, 5278144K cached
CPU: 17.3% usr 15.6% sys  0.0% nic 64.3% idle  0.0% io  0.0% irq  2.6% sirq
Load average: 5.02 2.29 1.26 3/1075 12
  PID  PPID USER     STAT   VSZ %VSZ CPU %CPU COMMAND
    1     0 root     R     1312  0.0   1 24.6 dd if /dev/zero of /dev/null
    7     0 root     R     1320  0.0   0  0.0 top

Voyons comment la spécification des demandes de ressources au sein d'un pod affecte la planification du pod.

14.1.2 Comprendre comment les demandes de ressources affectent la planification

En spécifiant une demande de ressources, vous spécifiez la quantité minimale de ressources dont le pod a besoin. Il s'agit d'informations utilisées par le planificateur lors de la planification des pods sur les nœuds. Chaque nœud dispose d'une certaine quantité de processeur et de mémoire qui peut être allouée aux pods. Lors de la planification des pods, le planificateur prend uniquement en compte les nœuds disposant de suffisamment de ressources non allouées pour répondre aux besoins en ressources du pod . Si la quantité de CPU ou de mémoire non allouée est inférieure à la quantité demandée par le pod, Kubernetes ne planifiera pas le pod sur le nœud car le nœud ne peut pas fournir le nombre minimum requis par le pod.

Comprendre comment le planificateur détermine si un pod peut être placé sur un nœud

Comme le montre la figure 14.1. Trois pods sont déployés sur le nœud. Ensemble, ils sollicitent 80 % du CPU du nœud et 60 % de sa mémoire. Le pod D, illustré dans le coin inférieur droit de la figure, ne peut pas être planifié sur le nœud car il demande 25 % du processeur, ce qui est supérieur aux 20 % non alloués du processeur. En fait, ces trois Pods n’utilisent que 70 % du CPU, mais cela ne l’affecte pas.

image-20230611201350173

Comprendre comment le planificateur utilise les requêtes de pod lors de la sélection du meilleur nœud pour un pod

Vous vous souvenez peut-être du chapitre 11 que le planificateur filtre d'abord la liste des nœuds pour exclure les nœuds qui ne peuvent pas accueillir de pods, puis donne la priorité aux nœuds restants en fonction de la fonction de priorité configurée. Celles-ci incluent deux fonctions prioritaires qui classent les nœuds en fonction de la quantité de ressources demandées : LeastRequestedPriorityet MostRequestedPriority. Le premier préfère les nœuds qui demandent moins de ressources (avec plus de ressources non allouées), tandis que le second fait le contraire : il préfère les nœuds avec les ressources les plus demandées (moins de CPU et de mémoire non allouées). Ils considèrent tous la quantité de ressources demandée plutôt que la quantité réelle de ressources utilisées.

Le planificateur est configuré pour utiliser une seule de ces fonctions. Vous vous demandez peut-être pourquoi quelqu'un utiliserait la fonctionnalité MostRequestedPriority. Après tout, si vous disposez d’un ensemble de nœuds, vous souhaitez généralement répartir la charge du processeur de manière égale entre eux. Cependant, lors de l'exécution sur une infrastructure cloud, en gardant les pods étroitement emballés, certains nœuds seront vides et pourront être supprimés, ce qui permet d'économiser des frais généraux.

Vérifier la capacité du nœud

Voyons le planificateur en action. Vous déployerez un autre Pod et demanderez quatre fois plus de ressources qu'auparavant. Mais avant de faire cela, examinons la capacité du nœud. Étant donné que le planificateur a besoin de connaître la quantité de CPU et de mémoire dont dispose chaque nœud, le Kubelet rapporte ces données au serveur API et les rend disponibles via les ressources du nœud. Vous pouvez le visualiser à l'aide de la commande kubectl décrire comme suit :

$ kubectl describe nodes
......
Addresses:
  InternalIP:  33.33.33.110
  Hostname:    yjq-k8s1
Capacity:
  cpu:                4
  ephemeral-storage:  41019616Ki
  hugepages-2Mi:      0
  memory:             8152788Ki
  pods:               110
Allocatable:
  cpu:                4
  ephemeral-storage:  37803678044
  hugepages-2Mi:      0
  memory:             8050388Ki
  pods:               110
  ......

La sortie affiche deux ensembles de quantités liées aux ressources disponibles sur le nœud : la capacité du nœud et les ressources allouables. La capacité représente les ressources totales d'un nœud, qui peuvent ne pas être toutes disponibles pour les pods. Certaines ressources peuvent être réservées à Kubernetes et/ou aux composants système. Les décisions du planificateur sont basées uniquement sur la quantité de ressources pouvant être allouées.

Lorsque la quantité de ressources demandées par un certain pod est supérieure à la quantité de ressources pouvant être allouées par n'importe quel nœud, le pod ne peut pas être planifié .

14.1.3 Comprendre comment les requêtes CPU affectent le partage du temps CPU

Supposons que deux pods soient en cours d'exécution (les pods système peuvent être ignorés pour l'instant car ils sont inactifs la plupart du temps). L’un d’eux a demandé 200 millicards de CPU, tandis que l’autre en a demandé cinq fois plus. Au début de ce chapitre, nous avons mentionné que Kubernetes fait la différence entre les ressources requestset les limits. Puisque vous n’en avez pas encore défini limits, ces deux Pods n’ont aucune limite en termes d’utilisation du CPU. Si les processus de chaque pod utilisent le temps CPU maximum, quel sera le temps CPU de chaque pod ?

Les requêtes CPU affectent non seulement la planification, mais déterminent également la façon dont le temps CPU inutilisé est réparti entre les pods. Étant donné que votre premier Pod a demandé 200 milliCal de CPU et l'autre 1 000 milliCal, le temps CPU inutilisé est divisé entre les deux Pods dans un rapport de 1:5 (voir Figure 14.2). Si les deux pods utilisent leur temps CPU maximum, le premier pod obtiendra 1/6, soit 16,7 %, du temps CPU, et l'autre pod obtiendra les 5/6 restants, soit 83,3 %.

image-20230611202730290

limitsL'utilisation maximale du processeur d'un pod peut être contrôlée via les paramètres .

14.1.4 Définir et demander des ressources personnalisées

Kubernetes vous permet également d'ajouter des ressources personnalisées aux nœuds et de les demander dans les demandes de ressources de pod. Initialement appelé Opaque Integer Resources, mais changé en Kubernetes version 1.8 Extended Resources.

Tout d'abord, vous devez ajouter votre ressource personnalisée à Kubernetes en l'ajoutant au champ de capacité de l'objet Node. Cela peut être PATCH HTTPfait en exécutant une requête. Le nom de la ressource peut être n'importe quoi, par exemple example.org/myresource, à condition qu'il ne commence pas par le domaine kubernetes.io. La quantité doit être un nombre entier (par exemple, vous ne pouvez pas la définir sur 100 m car 0,1 n'est pas un nombre entier ; mais vous pouvez la définir sur 1 000 m ou 2 000 m ou 1 ou 2). La valeur est automatiquement copiée de la capacité vers le champ attribuable.

Ensuite, lors de la création du pod, vous spécifiez le même nom de ressource et le même numéro de demande dans le champ resources.requests de la spécification du conteneur, ou lors de l'utilisation de kubectl run --requests. Le planificateur garantira que le pod est déployé uniquement sur les nœuds disposant du nombre requis de ressources personnalisées. Chaque Pod déployé réduira évidemment la quantité de cette ressource pouvant être allouée.

Un exemple de ressource personnalisée pourrait être le nombre de cartes GPU disponibles sur un nœud . Les pods qui nécessitent l'utilisation d'un GPU spécifient le nombre de cartes GPU dans leur nombre de requêtes.

14.2 Limitation des ressources disponibles pour un conteneur

14.2.1 Définition d'une limite stricte pour la quantité de ressources qu'un conteneur peut utiliser

Les processeurs sont des ressources compressibles, ce qui signifie que vous pouvez limiter la quantité de processeur utilisée par un conteneur pour évoluer sans affecter négativement les processus exécutés dans le conteneur. La mémoire est évidemment différente : elle est incompressible. Une fois qu'un processus obtient un morceau de mémoire, cette mémoire ne peut pas être retirée du processus jusqu'à ce que le processus lui-même la libère. C'est pourquoi vous devez limiter la quantité maximale de mémoire qu'un conteneur peut obtenir.

Sans contraintes de mémoire, un conteneur (ou pod) exécuté sur un nœud de travail peut consommer toute la mémoire disponible et affecter tous les autres pods du nœud ainsi que tous les nouveaux pods planifiés pour être planifiés sur ce nœud (rappelez-vous, les nouveaux pods sont planifiés sur le nœud). nœud basé sur les demandes de mémoire plutôt que sur l'utilisation réelle). Un seul pod défectueux ou malveillant peut rendre un nœud entier inutilisable.

Pour éviter que cela ne se produise, Kubernetes vous permet de spécifier des ressources pour chaque conteneur limits. L'exemple ci-dessous montre un pod avec des limites de ressources :

# limited-pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: limited-pod
spec:
  containers:
  - image: busybox
    command: ["dd", "if=/dev/zero", "of=/dev/null"]
    name: main
    resources:
      limits:
        cpu: 1
        memory: 20Mi

Le conteneur de ce pod a configuré des limites de ressources pour le processeur et la mémoire. Un processus exécuté dans un conteneur ne peut pas consommer plus d'un cœur de processeur et 20 Mo de mémoire.

Comme aucune ressource n'est spécifiée requests, elles seront définies sur limitsla même valeur que la ressource.

Contrairement aux demandes de ressources, les limites de ressources ne sont pas limitées par la quantité de ressources qu'un nœud peut allouer. La somme de toutes les limites de tous les pods d'un nœud peut dépasser 100 % de la capacité du nœud (Figure 14.3). En d’autres termes, les limites des ressources peuvent être dépassées. Cela a une conséquence importante : lorsque les ressources d'un nœud sont utilisées à 100 %, certains conteneurs devront être supprimés .

image-20230611204408239

14.2.2 Dépassement de la limite

Que se passe-t-il lorsqu'un processus exécuté dans un conteneur tente d'utiliser plus de ressources que ce qui est autorisé ?

Vous avez appris que le processeur est une ressource compressible et qu'il est naturel qu'un processus veuille consommer tout le temps processeur lorsqu'il n'attend pas d'opérations d'E/S. Cependant, comme vous l'avez appris, l'utilisation du processeur d'un processus est limitée. Par conséquent, lors de la définition d'une limite de processeur pour un conteneur, le processus n'obtient pas plus de temps processeur que la limite configurée.

C'est différent pour la mémoire. Lorsqu'un processus tente d'allouer de la mémoire au-delà de sa limite, le processus est tué (on l'appelle OOMKilled, où MOO signifie Out Of Memory). Si la politique de redémarrage du pod est définie sur Always ou OnFailure, le processus est redémarré immédiatement, vous ne remarquerez donc peut-être même pas sa suppression. Cependant, s'il continue de dépasser la limite de mémoire et est arrêté, Kubernetes commencera à ajouter des délais entre les redémarrages. Dans ce cas, vous verrez CrashLoopBackOffle statut :

L'état CrashLoopBackOff ne signifie pas que le Kubelet a abandonné. Cela signifie qu'après chaque crash, Kubelet augmentera l'intervalle de temps avant de redémarrer le conteneur . Après le premier crash, il redémarre immédiatement le conteneur, puis s'il plante à nouveau, il attend 10 secondes avant de redémarrer. Lors des crashs suivants, ce délai augmente de façon exponentielle jusqu'à 20, 40, 80 et 160 secondes, pour finalement être limité à 300 secondes. Une fois que l'intervalle atteint la limite de 300 secondes, le Kubelet redémarrera le conteneur indéfiniment toutes les cinq minutes jusqu'à ce que le Pod cesse de planter ou soit supprimé.

Pour vérifier la cause d'un crash de conteneur, vous pouvez vérifier les journaux du pod et/ou utiliser la commande kubectl décrire pod, comme indiqué dans la liste suivante.

image-20230611205014256

Le statut OOMKilled vous indique que le conteneur a été supprimé en raison d'une mémoire insuffisante. Si vous ne souhaitez pas que le conteneur soit tué, il est important de ne pas définir la limite de mémoire trop basse. Cependant, les conteneurs peuvent être OOMKilled même s'ils ne dépassent pas leurs limites. .

14.2.3 Comprendre comment les applications dans des conteneurs voient les limites

Pod avant création :

$ kubectl create -f limit-pod.yaml 
pod/limited-pod created

Utilisez topla commande pour afficher l'utilisation des ressources du processus :

$ kubectl exec -it limited-pod top
Mem: 8005788K used, 147000K free, 5468K shrd, 381848K buff, 5631300K cached
CPU: 15.6% usr 17.2% sys  0.0% nic 65.7% idle  0.0% io  0.0% irq  1.3% sirq
Load average: 1.47 0.82 0.56 7/1100 19
  PID  PPID USER     STAT   VSZ %VSZ CPU %CPU COMMAND
    1     0 root     R     1312  0.0   1 25.0 dd if /dev/zero of /dev/null
   14     0 root     R     1320  0.0   0  0.0 top

La limite de CPU du Pod est définie sur 1 cœur et la limite de mémoire est définie sur 20 Mio. En regardant de près le résultat de la commande top, il est complètement différent des 20 Mio définis pour le conteneur. De même, la limite du CPU est fixée à un cœur et le processus principal semble n'utiliser que 25 % du temps CPU disponible, même si la commande dd utilisée utiliserait normalement tout son temps CPU disponible.

Ce que le conteneur voit, c'est la mémoire du nœud

La commande top affiche la quantité de mémoire sur l'ensemble du nœud sur lequel le conteneur est exécuté. Même si une limite est définie sur la quantité de mémoire disponible pour le conteneur, le conteneur n'a pas connaissance de cette limite.

Le conteneur peut voir le nombre de cœurs de processeur de tous les nœuds

Comme pour la mémoire, le conteneur voit également tous les processeurs du nœud, quelle que soit la limite de processeur configurée pour le conteneur. Définir la limite du processeur sur un seul cœur n’expose pas un seul cœur de processeur au conteneur. La limitation du processeur ne fait que limiter la quantité de temps CPU qu'un conteneur peut utiliser .

Un conteneur avec une limite de CPU à un cœur fonctionnant sur un CPU avec 64 CPU obtiendra 1/64 du temps CPU total. Même si sa limite est fixée à un cœur, le processus du conteneur ne s'exécutera pas sur un seul cœur. À différents moments, son code peut être exécuté sur différents cœurs.

Certaines applications examinent le nombre de processeurs sur le système pour décider du nombre de threads de travail à exécuter. Encore une fois, une telle application fonctionne bien sur un ordinateur portable de développement, mais lorsqu'elle est déployée sur un nœud avec un grand nombre de cœurs, elle lancera trop de threads, tous en compétition pour le temps CPU (éventuellement) limité. De plus, chaque thread nécessite de la mémoire supplémentaire, ce qui fait monter en flèche l'utilisation de la mémoire par l'application.

Vous pouvez directement utiliser le système cgroups pour obtenir la limite CPU configurée en lisant le fichier suivant :

  • /sys/fs/cgroup/cpu/cpu.cfs_quota_us
  • /sys/fs/cgroup/cpu/cpu.cfs_period_us

14.3 Comprendre les classes de QoS des pods

Nous avons déjà mentionné que les limites de ressources peuvent être surutilisées et qu'un nœud peut ne pas être en mesure de fournir à tous ses pods la quantité de ressources spécifiée dans la limite de ressources.

Imaginez que vous ayez deux pods, dans lesquels le pod A utilise 90 % de la mémoire du nœud, et le pod B a soudainement besoin de plus de mémoire qu'il n'en a utilisé jusqu'à présent, et le nœud ne peut pas fournir la mémoire requise. Quel conteneur faut-il tuer ? Doit-il s'agir du Pod B, puisque sa demande de mémoire ne peut pas être satisfaite, ou le Pod A doit-il être supprimé pour libérer de la mémoire afin qu'elle puisse être donnée au Pod B ?

Évidemment, cela dépend des circonstances. Kubernetes ne peut pas prendre la bonne décision de manière indépendante et a besoin d'un moyen de spécifier les pods prioritaires dans cette situation. Kubernetes y parvient en classant les pods en trois catégories de qualité de service (QoS) :

  • BestEffort (priorité la plus basse)
  • Éclatable
  • Garanti (priorité la plus élevée)

14.3.1 Définition de la classe QoS pour un pod

Les catégories de QoS sont dérivées d'une combinaison de demandes et de limites de ressources du conteneur Pod. Voici comment catégoriser :

Attribuer un pod de la classe BestEffort

La classe QoS la plus basse priorité est la classe BestEffort. Il est attribué aux pods pour lesquels aucune demande ni limite n'est définie (dans n'importe quel conteneur). Il s'agit de la classe QoS attribuée à tous les pods créés dans les chapitres précédents. Les conteneurs exécutés dans ces pods n'ont aucune garantie de ressources. Dans le pire des cas, ils n'obtiendront peut-être pas du tout de temps CPU et seront les premiers à être arrêtés lorsque de la mémoire devra être libérée pour d'autres Pods . Cependant, comme BestEffort Pod ne définit pas de limite de mémoire, son conteneur peut utiliser n'importe quelle quantité de mémoire si suffisamment de mémoire est disponible.

Attribuez un pod de classe Garanti :

La classe QoS garantie s'applique aux pods où les requêtes du conteneur sont égales à la limite . Pour qu’un Pod soit classé comme Garanti, les trois conditions suivantes doivent être remplies :

  • Les requêtes et les limites doivent être définies pour le processeur et la mémoire.
  • Les demandes et les limites doivent être définies pour chaque conteneur.
  • Ils doivent être égaux (la limite doit correspondre aux demandes pour chaque ressource dans chaque conteneur).

Étant donné que les demandes de ressources d'un conteneur (si elles ne sont pas définies explicitement) sont par défaut égales à la limite, il suffit de spécifier des limites pour toutes les ressources (pour chaque conteneur d'un pod) pour que ce pod soit garanti . Les conteneurs de ces pods obtiennent la quantité de ressources demandée, mais ne peuvent pas utiliser de ressources supplémentaires (car leur limite n'est pas supérieure à leur demande).

Attribuer une classe QoS extensible au pod

Entre BestEffort et Garanti se trouve la catégorie Burstable QoS, à laquelle appartiennent tous les autres Pods. Cela inclut les pods à conteneur unique où la limite de conteneur ne correspond pas à la demande et tous les pods dans lesquels au moins un conteneur spécifie la demande de ressources mais ne définit pas la limite. Inclut également les pods dans lesquels la requête d'un conteneur correspond à la limite, mais l'autre conteneur n'a aucune requête ni limite spécifiée. Les pods extensibles obtiennent la quantité de ressources demandée, mais sont autorisés à utiliser des ressources supplémentaires si nécessaire .

Comment la relation entre les demandes et les limites définit les catégories de QoS.

Pour résumer, on peut tracer le schéma suivant :

image-20230611211842577

Déterminer la catégorie QoS du conteneur

Le tableau 14.1 présente les catégories de QoS en fonction de la façon dont les demandes de ressources et les limites sont définies sur un seul conteneur. Pour les pods à conteneur unique, les catégories QoS s'appliquent également au pod.

image-20230611212223241

  • Si la demande est définie mais qu'aucune limite n'est définie, reportez-vous à la ligne du tableau où la demande est inférieure à la limite. Si seule la limite est définie, la demande par défaut est la limite, reportez-vous donc à la ligne où la demande est égale à la limite.
Déterminer la catégorie QoS d'un pod avec plusieurs conteneurs

Pour un Pod multi-conteneurs, si tous les conteneurs ont la même classe QoS, la classe QoS du Pod est également la même. Si au moins un conteneur a une classe différente, la classe QoS du Pod est Burstable quelle que soit la classe du conteneur . Le tableau 14.2 montre la relation entre la catégorie QoS d'un pod à double conteneur et les catégories de ses deux conteneurs. Cela peut être facilement étendu aux Pods avec plusieurs conteneurs.

image-20230611212525005

14.3.2 Comprendre quel processus est tué lorsque la mémoire est faible

Lorsque le système est sursouscrit, la catégorie QoS détermine quel conteneur est supprimé en premier afin que les ressources libérées puissent être mises à la disposition des pods de priorité plus élevée. La première chose dans la file d'attente qui doit être tuée est le Pod de la catégorie BestEffort, suivi du Pod de la catégorie Burstable, et enfin du Pod de la catégorie Garanti.

Comprenez comment les catégories de QoS sont organisées :

Regardons l'exemple montré dans la figure 14.5. Supposons qu'il existe deux pods à conteneur unique, le premier ayant la catégorie BestEffort QoS et le second la catégorie Burstable. Lorsque la mémoire entière d'un nœud a été saturée et qu'un processus sur le nœud tente d'allouer plus de mémoire, le système devra tuer un processus (peut-être même le processus essayant d'allouer de la mémoire supplémentaire) pour satisfaire la demande d'allocation. Dans ce cas, tuez toujours d'abord le processus en cours d'exécution dans le pod BestEffort, et non le processus en cours d'exécution dans le pod Burstable.

image-20230611212953642

Évidemment, avant que l'un des processus du pod garanti ne soit tué, le processus du pod BestEffort sera également tué. De même, avant que le processus du pod garanti ne soit tué, le processus du pod Burstable sera également tué. Mais que se passe-t-il s’il n’y a que deux Burstable Pods ? De toute évidence, le processus de sélection doit donner la priorité à l’un par rapport à l’autre.

Comment gérer les conteneurs avec la même catégorie QoS

Chaque processus en cours d'exécution a un score OutOfMemory (OOM) . Le système sélectionne un processus à tuer en comparant les scores MOO de tous les processus en cours d'exécution. Lorsque la mémoire doit être libérée, le processus ayant le score le plus élevé est tué.

Le score MOO est calculé à partir de deux aspects : le pourcentage de mémoire disponible utilisé par le processus et un ajustement du score MOO fixe basé sur la catégorie QoS du pod et la mémoire demandée du conteneur. Lorsqu'il y a deux pods à conteneur unique, tous deux appartenant à la catégorie Burstable, le système tuera le pod qui utilise un pourcentage de mémoire demandée plus élevé que l'autre . C'est pourquoi, dans la figure 14.5, le pod B, qui utilisait 90 % de la mémoire demandée, serait tué plus tôt que le pod C, qui en utilisait 70 %, même si le pod C utilisait plus de mémoire que le pod B.

14.4 Définition des requêtes et des limites par défaut pour les pods par espace de noms

14.4.1 Présentation de la ressource LimitRange

La ressource LimitRange permet de spécifier pour chaque espace de noms les limites minimales et maximales pouvant être définies sur chaque ressource, ainsi que la ressource demandée par défaut pour le conteneur sans la spécifier explicitement, comme le montre la figure 14.6.

image-20230612160203604

La ressource LimitRange est utilisée par le plug-in LimitRanger Admission Control. Le plugin LimitRanger valide la spécification du Pod lorsque la configuration du Pod est publiée sur le serveur API. Si la validation échoue, la configuration sera immédiatement rejetée. Par conséquent, un bon cas d'utilisation de l'objet LimitRange consiste à empêcher les utilisateurs de créer des pods plus grands que n'importe quel nœud du cluster . Sans un tel LimitRange, le serveur API acceptera le Pod mais ne le planifiera jamais.

La limite spécifiée dans la ressource LimitRange s'applique à chaque pod/conteneur ou autre objet créé avec l'objet LimitRange dans le même espace de noms. Ils ne limitent pas la quantité totale de ressources de tous les pods dans l'espace de noms.

14.4.2 Création d'un objet LimitRange

Un exemple de fichier YAML créant un LimitRange est le suivant :

# limits.yaml
apiVersion: v1
kind: LimitRange
metadata:
  name: example
spec:
  limits:
  #  Pod级别的限制
  - type: Pod 
    min:
      cpu: 50m
      memory: 5Mi
    max:
      cpu: 1
      memory: 1Gi
  # 容器级别的限制
  - type: Container
    # 默认请求
    defaultRequest:
      cpu: 100m
      memory: 10Mi
    # 默认限制
    default:
      cpu: 200m
      memory: 100Mi
    # 最大/最小的请求/限制
    min:
      cpu: 50m
      memory: 5Mi
    max:
      cpu: 1
      memory: 1Gi
    # 每种资源的限制和请求之间的最大比率
    maxLimitRequestRatio:
      cpu: 4
      memory: 10
  # 还可以设置PVC可以请求的最小和最大存储量。
  - type: PersistentVolumeClaim
    min:
      storage: 1Gi
    max:
      storage: 10Gi

En dessous du niveau du conteneur, vous pouvez non seulement définir des limites minimales et maximales, mais vous pouvez également définir une demande de ressources par défaut (defaultRequest) et une limite par défaut (default), qui s'appliqueront à chaque conteneur qui ne les spécifie pas explicitement.

En plus des valeurs minimales, maximales et par défaut, vous pouvez même définir un rapport limite/demande maximale. L'exemple précédent définit le CPU maxLimitRequestRatio sur 4, ce qui signifie que la limite de CPU du conteneur ne pourra pas dépasser quatre fois sa demande de CPU. Si un conteneur demande 200 millicores, il ne sera pas accepté si sa limite de CPU est définie sur 801 millicores ou plus. Pour mémoire, le ratio maximum est fixé à 10.

Au chapitre 6, nous avons introduit les PersistentVolumeClaims (PVC), qui permettent de revendiquer une certaine quantité de stockage persistant comme le conteneur d'un Pod. Tout comme il existe des limites sur le processeur minimum et maximum qu'un conteneur peut demander, il devrait également y avoir des limites sur la quantité de stockage qu'un seul PVC peut demander. Les objets LimitRange le permettent également, comme indiqué au bas de l'exemple.

L'exemple montre un seul objet LimitRange qui contient toutes les limites, mais vous pouvez également les diviser en plusieurs objets si vous souhaitez les regrouper par type (par exemple, un pour les limites des pods, un autre pour les limites des conteneurs et un autre avec le PVC). Les limites de plusieurs objets LimitRange sont combinées lors de la validation d'un pod ou d'un PVC.

Étant donné que la validation (et les valeurs par défaut) configurée dans l'objet LimitRange est effectuée par le serveur d'API lorsqu'il reçoit un nouveau manifeste de pod ou de PVC, les pods et les PVC existants ne seront pas revalidés si la limite est modifiée ultérieurement. .

14.4.3 Application des limites

Avec la limite que vous avez définie, vous pouvez maintenant essayer de créer un pod qui demande plus de CPU que ce que LimitRange autorise :

# limits-pod-too-big.yaml
apiVersion: v1
kind: Pod
metadata:
  name: too-big
spec:
  containers:
  - image: busybox
    args: ["sleep", "9999999"]
    name: main
    resources:
      requests:
        cpu: 2

Le conteneur unique de ce pod nécessitait deux processeurs, ce qui est supérieur au maximum précédemment défini dans LimitRange. La création de ce pod produira les résultats suivants :

$ kubectl create -f limits-pod-too-big.yaml 
The Pod "too-big" is invalid: spec.containers[0].resources.requests: Invalid value: "2": must be less than or equal to cpu limit

On peut constater que la demande a été rejetée car la demande CPU était supérieure à l'ensemble 1.

14.4.4 Application des demandes et des limites de ressources par défaut

Voyons maintenant comment définir les valeurs par défaut sur les conteneurs qui ne spécifient pas de demandes ni de limites de ressources par défaut. Déployez à nouveau le pod kubia-manual du chapitre 3 :

# kubia-manual.yaml 
apiVersion: v1
kind: Pod
metadata:
  name: kubia-manual
spec:
  containers:
    - image: yijunquan/kubia
      name: kubia
      ports:
        - containerPort: 8080
          protocol: TCP
$ kubectl create -f kubia-manual.yaml 
pod/kubia-manual created

Avant de définir l'objet LimitRange, aucune demande de ressources ni limite n'était définie pour tous les pods, mais désormais, les valeurs par défaut sont automatiquement appliquées lors de la création des pods. Cela peut être confirmé en décrivant le Pod kubia-manual :

$ kubectl describe po kubia-manual
......
    Limits:
      cpu:     200m
      memory:  100Mi
    Requests:
      cpu:        100m
      memory:     10Mi
    Environment:  <none>

Les demandes et limites du conteneur correspondent à celles que vous spécifiez dans l'objet LimitRange. Si vous utilisez une spécification LimitRange différente dans un autre espace de noms, les pods créés dans cet espace de noms auront évidemment des exigences et des limites différentes. Cela permet aux administrateurs de configurer les ressources par défaut, minimales et maximales d'un pod par espace de noms.

Mais gardez à l’esprit que les limites configurées dans LimitRange s’appliquent uniquement à chaque pod/conteneur individuel. Il est toujours possible de créer de nombreux Pods et de consommer toutes les ressources disponibles dans le cluster. Les LimitRanges n'offrent aucune protection. Les objets ResourceQuota fournissent cette protection. Vous en apprendrez plus à leur sujet dans la section suivante.

14.5 Limitation du total des ressources disponibles dans un espace de noms

14.5.1 Présentation de l'objet ResourceQuota

Le plugin ResourceQuota Admission Control vérifie si le pod en cours de création entraînerait un dépassement du ResourceQuota configuré. Si tel est le cas, la création du Pod est refusée. Étant donné que les quotas de ressources sont appliqués lors de la création des pods, les objets ResourceQuota n'affectent que les pods créés après la création de l'objet ResourceQuota et n'ont aucun effet sur les pods existants.

ResourceQuota limite la quantité de ressources informatiques que les pods de l'espace de noms peuvent consommer et la quantité de stockage PersistentVolumeClaims . Cela peut également limiter le nombre de pods, de revendications et d'autres objets API qu'un utilisateur est autorisé à créer dans l'espace de noms. Puisque nous avons principalement traité jusqu'à présent du processeur et de la mémoire, commençons par voir comment spécifier des quotas pour ceux-ci.

Créer ResourceQuota pour le processeur et la mémoire

Le total de CPU et de mémoire pouvant être consommés par tous les pods de l'espace de noms est défini en créant un objet ResourceQuota , comme indiqué dans l'exemple suivant :

# quota-cpu-memory.yaml
apiVersion: v1
kind: ResourceQuota
metadata:
  name: cpu-and-mem
spec:
  hard:
    requests.cpu: 400m
    requests.memory: 200Mi
    limits.cpu: 600m
    limits.memory: 500Mi

Ce ResourceQuota définit la quantité maximale de CPU qu'un pod dans l'espace de noms peut demander à 400 millicores. Le nombre total maximum de limites de processeur dans l'espace de noms est défini sur 600 millicores. Pour la mémoire, la taille totale maximale des requêtes est fixée à 200 Mio, tandis que la limite est fixée à 500 Mio.

Un objet ResourceQuota s'applique à l'espace de noms dans lequel il est créé, tout comme LimitRange, mais il s'applique à la somme des demandes de ressources et des limites pour tous les pods, plutôt qu'à chaque pod ou conteneur individuel, comme le montre la figure 14.7.

image-20230612163756795

Afficher le quota et l'utilisation du quota
$ kubectl create -f quota-cpu-memory.yaml 
resourcequota/cpu-and-mem created

$ kubectl describe quota
Name:            cpu-and-mem
Namespace:       default
Resource         Used   Hard
--------         ----   ----
limits.cpu       1200m  600m
limits.memory    120Mi  500Mi
requests.cpu     1300m  400m
requests.memory  40Mi   200Mi

Une chose à noter lors de la création de ResourceQuota est que vous devez également créer un objet LimitRange en même temps.. Dans ce cas, vous disposez déjà d'un LimitRange basé sur la configuration précédente, mais sans lui, vous ne pouvez pas exécuter le pod kubia-manual car il ne spécifie aucune demande ou limite de ressources.

14.5.2 Spécification d'un quota pour le stockage persistant

Les objets ResourceQuota peuvent également limiter la quantité de stockage persistant pouvant être déclarée dans un espace de noms, comme suit :

# quota-storage.yaml
apiVersion: v1
kind: ResourceQuota
metadata:
  name: storage
spec:
  hard:
    requests.storage: 500Gi
    ssd.storageclass.storage.k8s.io/requests.storage: 300Gi
    standard.storageclass.storage.k8s.io/requests.storage: 1Ti

Dans cet exemple, la quantité de stockage que PersistentVolumeClaims peut demander dans tous les espaces de noms est limitée à 500 Gio (spécifiée par l'entrée request.storage dans l'objet ResourceQuota). Limitez le stockage SSD à 300 Gio (spécifié par ssd StorageClass). Le stockage sur disque dur aux performances inférieures (norme StorageClass) est limité à 1 Tio.

14.5.3 Limitation du nombre d'objets pouvant être créés

ResourceQuota peut également être configuré pour limiter le nombre de pods, de ReplicationControllers, de services et d'autres objets dans un seul espace de noms.

L'exemple suivant montre un exemple d'objet ResourceQuota qui limite le nombre d'objets :

# quota-object-count.yaml
apiVersion: v1
kind: ResourceQuota
metadata:
  name: objects
spec:
  hard:
    pods: 10
    replicationcontrollers: 5
    secrets: 10
    configmaps: 10
    persistentvolumeclaims: 5
    services: 5
    services.loadbalancers: 1
    services.nodeports: 2
    ssd.storageclass.storage.k8s.io/persistentvolumeclaims: 2

Le ResourceQuota dans cet exemple permet aux utilisateurs de créer jusqu'à 10 pods dans l'espace de noms, qu'ils soient créés manuellement ou par un ReplicationController, ReplicaSet, DaemonSet, Job, etc. Cela limite également le nombre de ReplicationControllers à 5. Vous pouvez créer jusqu'à 5 services, parmi lesquels il ne peut y avoir qu'un seul service de type LoadBalancer et seulement deux services de type NodePort. Semblable à la quantité maximale de stockage demandée pouvant être spécifiée par StorageClass, il est également possible de limiter le nombre de PersistentVolumeClaims par StorageClass.

Des quotas de nombre d'objets peuvent être définis pour les objets suivants :

  • cartes de configuration
  • réclamations de volume persistant
  • gousses
  • contrôleurs de réplication
  • quotas de ressources
  • prestations de service
  • services.loadbalancers
  • services.nodeports
  • secrets

14.5.4 Spécification de quotas pour des états de pod spécifiques et/ou des classes de QoS

Les quotas créés jusqu'à présent s'appliquent à tous les pods, quel que soit leur statut actuel et leur catégorie de QoS. Toutefois, les quotas peuvent également être limités à un ensemble de plages de quotas. Il existe actuellement quatre gammes disponibles, à savoir BestEffort , NotBestEffort, Terminating et NotTerminating .

Les plages BestEffort et NotBestEffort déterminent si les quotas s'appliquent aux pods avec la catégorie QoS BestEffort ou l'une des deux autres catégories (c'est-à-dire Burstable et Garanti).

Les deux autres étendues (Terminating et NotTerminating), contrairement à ce que leurs noms suggèrent, ne s'appliquent pas à l'arrêt (ou au non-arrêt) des pods. Vous pouvez spécifier la durée pendant laquelle chaque pod est autorisé à s'exécuter avant d'être arrêté et marqué comme ayant échoué. Ceci peut être réalisé en définissant le champ activeDeadlineSeconds dans la spécification du Pod. Cette propriété définit le nombre de secondes pendant lesquelles un pod est autorisé à s'exécuter sur un nœud par rapport à l'heure de démarrage avant que le pod ne soit marqué comme ayant échoué et terminé. La plage de quotas de fin s'applique aux pods pour lesquels activeDeadlineSeconds est définie, tandis que NotTerminating s'applique aux pods pour lesquels ce n'est pas le cas .

Lors de la création d'un ResourceQuota, vous pouvez spécifier la portée à laquelle il s'applique. Les pods doivent être conformes à toutes les plages spécifiées auxquelles le quota s'applique. De plus, ce qu'un quota peut limiter dépend de la portée du quota. La plage BestEffort ne peut limiter que le nombre de pods, tandis que les trois autres plages peuvent limiter le nombre de pods, les demandes de CPU/mémoire et les limites de CPU/mémoire .

Par exemple, si vous souhaitez appliquer un quota uniquement aux pods BestEffort et NotTerminating, vous pouvez créer l'objet ResourceQuota suivant :

# quota-scoped.yaml
apiVersion: v1
kind: ResourceQuota
metadata:
  name: besteffort-notterminating-pods
spec:
  scopes:
  - BestEffort
  - NotTerminating
  hard:
    pods: 4

Ce quota garantit qu'il y a au plus activeDeadlineSecondsquatre pods avec la catégorie BestEffort QoS et aucun. Si le quota concerne les pods NotBestEffort, vous pouvez également spécifier requêtes.cpu, demandes.mémoire, limites.cpu et limites.mémoire.

Guess you like

Origin blog.csdn.net/weixin_47692652/article/details/131172948