Assurer les ressources à Cuba

image


La gestion des ressources de cluster est toujours un sujet complexe. Comment expliquer la nécessité de configurer des ressources pour l'utilisateur qui déploie ses applications sur le cluster? Peut-être qu'il est plus facile d'automatiser cela?


Description du problème


La gestion des ressources est une tâche importante dans le contexte de l'administration du cluster Kubernetes. Mais pourquoi est-ce important si Kubernetes fait tout le travail dur pour vous? Parce que non. Kubernetes vous fournit des outils pratiques pour résoudre de nombreux problèmes ... si vous utilisez ces outils. Pour chaque module de votre cluster, vous pouvez spécifier les ressources nécessaires pour ses conteneurs. Et Kubernetes utilisera ces informations pour distribuer les instances de votre application sur les nœuds de cluster.


Peu de gens prennent au sérieux la gestion des ressources chez Kubernetes. Ceci est normal pour un cluster légèrement chargé avec quelques applications statiques. Mais que faire si vous avez un cluster très dynamique? Où vont et viennent les applications, où l'espace de noms est-il créé et supprimé tout le temps? Un cluster avec un grand nombre d'utilisateurs qui peuvent créer leur propre espace de noms et déployer des applications? Eh bien, dans ce cas, au lieu d'une orchestration stable et prévisible, vous aurez un tas de plantages aléatoires dans les applications, et parfois même dans les composants de Kubernetes lui-même!


Voici un exemple d'un tel cluster:


image


Vous voyez 3 foyers à l'état "Terminé". Mais ce n'est pas la suppression habituelle des foyers - ils sont bloqués dans cet état parce que le démon containerd sur leur nœud a été touché par quelque chose de très gourmand en ressources.


De tels problèmes peuvent être résolus en gérant correctement le manque de ressources, mais ce n'est pas le sujet de cet article (il y a un bon article ), ainsi qu'une solution miracle pour résoudre tous les problèmes de ressources.


La principale raison de ces problèmes est incorrecte ou le manque de gestion des ressources dans le cluster. Et si ce type de problème n'est pas un désastre pour les déploiements, car ils en créeront facilement un nouveau, alors pour des entités comme DaemonSet, ou plus encore pour StatefulSet, de tels gels seront fatals et nécessiteront une intervention manuelle.


Vous pouvez avoir un énorme cluster avec beaucoup de CPU et de mémoire. Lorsque vous exécutez de nombreuses applications dessus sans paramètres de ressources appropriés, il est possible que tous les pods gourmands en ressources soient placés sur le même nœud. Ils se battront pour des ressources, même si les nœuds restants du cluster restent pratiquement libres.


Vous pouvez également souvent voir des cas moins critiques où certaines applications sont affectées par leurs voisins. Même si les ressources de ces applications "innocentes" étaient correctement configurées, un sous-marin errant peut venir les tuer. Un exemple d'un tel scénario:


  1. Votre application demande 4 Go de mémoire, mais ne prend initialement que 1 Go.
  2. Une errance sous, sans configuration de ressource, est affectée au même noeud.
  3. L'errance sous consomme toute la mémoire disponible.
  4. Votre application tente d'allouer plus de mémoire et se bloque car il n'y en a plus.

La réévaluation est un autre cas assez populaire. Certains développeurs font d'énormes demandes dans des manifestes «au cas où» et n'utilisent jamais ces ressources. Le résultat est un gaspillage d'argent.


Théorie de la décision


Horreur! Non?
Heureusement, Kubernetes offre un moyen d'imposer certaines restrictions aux pods en spécifiant les configurations de ressources par défaut ainsi que les valeurs minimales et maximales. Ceci est implémenté à l'aide de l' objet LimitRange . LimitRange est un outil très pratique lorsque vous avez un nombre limité d'espaces de noms ou un contrôle total sur le processus de création. Même sans la configuration appropriée des ressources, vos applications seront limitées dans leur utilisation. Les foyers «innocents» correctement réglés seront sûrs et protégés des voisins nuisibles. Si quelqu'un déploie une application gourmande sans configuration de ressources, cette application recevra les valeurs par défaut et se bloquera probablement. Et c'est tout! L'application ne traînera plus personne.


Ainsi, nous avons un outil pour contrôler et forcer la configuration des ressources pour les foyers, maintenant il semble que nous soyons en sécurité. Alors? Pas vraiment. Le fait est que, comme nous l'avons décrit précédemment, nos espaces de noms peuvent être créés par les utilisateurs, et par conséquent, LimitRange peut ne pas être présent dans ces espaces de noms, car il doit être créé séparément dans chaque espace de noms. Par conséquent, nous avons besoin de quelque chose non seulement au niveau de l'espace de noms, mais aussi au niveau du cluster. Mais il n'y a pas encore de telle fonction dans Kubernetes.


C'est pourquoi j'ai décidé d'écrire ma solution à ce problème. Permettez-moi de vous présenter - Opérateur de limite. Il s'agit d'un opérateur créé sur la base du framework Operator SDK , qui utilise la ressource personnalisée ClusterLimit et permet de sécuriser toutes les applications "innocentes" du cluster. En utilisant cet opérateur, vous pouvez contrôler les valeurs par défaut et les limites de ressources pour tous les espaces de noms en utilisant la quantité minimale de configuration. Il vous permet également de choisir exactement où appliquer la configuration à l'aide de namespaceSelector.


Exemple de ClusterLimit
apiVersion: limit.myafq.com/v1alpha1 kind: ClusterLimit metadata: name: default-limit spec: namespaceSelector: matchLabels: limit: "limited" limitRange: limits: - type: Container max: cpu: "800m" memory: "1Gi" min: cpu: "100m" memory: "99Mi" default: cpu: "700m" memory: "900Mi" defaultRequest: cpu: "110m" memory: "111Mi" - type: Pod max: cpu: "2" memory: "2Gi" 

Avec cette configuration, l'opérateur créera un LimitRange uniquement dans l'espace de noms avec le label limit: limited . Cela sera utile pour fournir des restrictions plus strictes dans un groupe spécifique d'espaces de noms. Si namespaceSelector n'est pas spécifié, l'opérateur appliquera un LimitRange à tous les espaces de noms. Si vous souhaitez configurer LimitRange manuellement pour un espace de noms spécifique, vous pouvez utiliser l'annotation "limit.myafq.com/unlimited": true cela indiquera à l'opérateur de sauter cet espace de noms et de ne pas créer LimitRange automatiquement.


Exemple de script pour utiliser l'opérateur:


  • Créez ClusterLimit par défaut avec des restrictions libérales et sans namespaceSelector - il sera appliqué partout.
  • Pour un ensemble d'espaces de noms avec des applications légères, créez un ClusterLimit supplémentaire et plus rigoureux avec namespaceSelector. Mettez des étiquettes sur ces espaces de noms en conséquence.
  • Sur un espace de noms avec des applications très gourmandes en ressources, placez l'annotation "limit.myafq.com/unlimited": true et configurez LimitRange manuellement avec des limites beaucoup plus larges que celles spécifiées dans le ClusteLimit par défaut.

La chose importante à savoir sur plusieurs LimitRange dans un même espace de noms:
Lorsqu'un sous est créé dans un espace de noms avec plusieurs LimitRange, les valeurs par défaut les plus importantes seront prises pour configurer ses ressources. Mais les valeurs maximales et minimales seront vérifiées selon la plus stricte de LimitRange.

Exemple pratique


L'opérateur suivra toutes les modifications dans tous les espaces de noms, ClusterLimits, enfants LimitRanges et initiera la coordination de l'état du cluster avec toute modification des objets surveillés. Voyons comment cela fonctionne dans la pratique.


Pour commencer, créez sous sans aucune restriction:


kubectl exécuter / obtenir la sortie
 ❯() kubectl run --generator=run-pod/v1 --image=bash bash pod/bash created ❯() kubectl get pod bash -o yaml apiVersion: v1 kind: Pod metadata: labels: run: bash name: bash namespace: default spec: containers: - image: bash name: bash resources: {} 

Remarque: une partie de la sortie de la commande a été omise pour simplifier l'exemple.


Comme vous pouvez le voir, le champ «ressources» est vide, ce qui signifie que ce sous peut être lancé n'importe où.
Nous allons maintenant créer le ClusterLimit par défaut pour l'ensemble du cluster avec des valeurs assez libérales:


default-limit.yaml
 apiVersion: limit.myafq.com/v1alpha1 kind: ClusterLimit metadata: name: default-limit spec: limitRange: limits: - type: Container max: cpu: "4" memory: "5Gi" default: cpu: "700m" memory: "900Mi" defaultRequest: cpu: "500m" memory: "512Mi" 

Et aussi plus rigoureux pour un sous-ensemble d'espaces de noms:


restrictive-limit.yaml
 apiVersion: limit.myafq.com/v1alpha1 kind: ClusterLimit metadata: name: restrictive-limit spec: namespaceSelector: matchLabels: limit: "restrictive" limitRange: limits: - type: Container max: cpu: "800m" memory: "1Gi" default: cpu: "100m" memory: "128Mi" defaultRequest: cpu: "50m" memory: "64Mi" - type: Pod max: cpu: "2" memory: "2Gi" 

Créez ensuite les espaces de noms et les pods pour voir comment cela fonctionne.
Espace de noms normal avec restriction par défaut:


 apiVersion: v1 kind: Namespace metadata: name: regular 

Et un espace de noms légèrement plus limité, selon la légende - pour les applications légères:


 apiVersion: v1 kind: Namespace metadata: labels: limit: "restrictive" name: lightweight 

Si vous regardez les journaux de l'opérateur immédiatement après avoir créé l'espace de noms, vous pouvez trouver quelque chose comme ça sous le spoiler:


journaux d'opérateur
 {...,"msg":"Reconciling ClusterLimit","Triggered by":"/regular"} {...,"msg":"Creating new namespace LimitRange.","Namespace":"regular","LimitRange":"default-limit"} {...,"msg":"Updating namespace LimitRange.","Namespace":"regular","Name":"default-limit"} {...,"msg":"Reconciling ClusterLimit","Triggered by":"/lightweight"} {...,"msg":"Creating new namespace LimitRange.","Namespace":"lightweight","LimitRange":"default-limit"} {...,"msg":"Updating namespace LimitRange.","Namespace":"lightweight","Name":"default-limit"} {...,"msg":"Creating new namespace LimitRange.","Namespace":"lightweight","LimitRange":"restrictive-limit"} {...,"msg":"Updating namespace LimitRange.","Namespace":"lightweight","Name":"restrictive-limit"} 

La partie manquante du journal contient 3 autres champs qui ne sont pas pertinents pour le moment


Comme vous pouvez le voir, la création de chaque espace de noms a commencé la création de nouveaux LimitRange. Un espace de noms plus limité a obtenu deux LimitRange - par défaut et plus strict.


Essayons maintenant de créer une paire de foyers dans ces espaces de noms.


kubectl exécuter / obtenir la sortie
 ❯() kubectl run --generator=run-pod/v1 --image=bash bash -n regular pod/bash created ❯() kubectl get pod bash -o yaml -n regular apiVersion: v1 kind: Pod metadata: annotations: kubernetes.io/limit-ranger: 'LimitRanger plugin set: cpu, memory request for container bash; cpu, memory limit for container bash' labels: run: bash name: bash namespace: regular spec: containers: - image: bash name: bash resources: limits: cpu: 700m memory: 900Mi requests: cpu: 500m memory: 512Mi 

Comme vous pouvez le voir, malgré le fait que nous n'avons pas changé la façon dont le pod est créé, le champ de ressource est maintenant rempli. Vous pouvez également remarquer l'annotation créée automatiquement par LimitRanger.


Maintenant, créez sous dans un espace de noms léger:


kubectl exécuter / obtenir la sortie
 ❯() kubectl run --generator=run-pod/v1 --image=bash bash -n lightweight pod/bash created ❯() kubectl get pods -n lightweight bash -o yaml apiVersion: v1 kind: Pod metadata: annotations: kubernetes.io/limit-ranger: 'LimitRanger plugin set: cpu, memory request for container bash; cpu, memory limit for container bash' labels: run: bash name: bash namespace: lightweight spec: containers: - image: bash name: bash resources: limits: cpu: 700m memory: 900Mi requests: cpu: 500m memory: 512Mi 

Veuillez noter que les ressources dans l'âtre sont les mêmes que dans l'exemple précédent. En effet, dans le cas de plusieurs LimitRange, des valeurs par défaut moins strictes seront utilisées lors de la création de pods. Mais alors pourquoi avons-nous besoin d'une LimitRange plus limitée? Il sera utilisé pour vérifier les valeurs maximales et minimales des ressources. Pour démontrer, nous allons rendre notre ClusterLimit limité encore plus limité:


restrictive-limit.yaml
 apiVersion: limit.myafq.com/v1alpha1 kind: ClusterLimit metadata: name: restrictive-limit spec: namespaceSelector: matchLabels: limit: "restrictive" limitRange: limits: - type: Container max: cpu: "200m" memory: "250Mi" default: cpu: "100m" memory: "128Mi" defaultRequest: cpu: "50m" memory: "64Mi" - type: Pod max: cpu: "2" memory: "2Gi" 

Faites attention à la section:


 - type: Container max: cpu: "200m" memory: "250Mi" 

Maintenant, nous avons défini un processeur de 200 m et 250 Mo de mémoire comme maximum pour le conteneur dans l'âtre. Et maintenant, essayez de créer sous:


 ❯() kubectl run --generator=run-pod/v1 --image=bash bash -n lightweight Error from server (Forbidden): pods "bash" is forbidden: [maximum cpu usage per Container is 200m, but limit is 700m., maximum memory usage per Container is 250Mi, but limit is 900Mi.] 

Notre sous-marin a de grandes valeurs définies par le LimitRange par défaut et il n'a pas pu démarrer car il n'a pas réussi la vérification des ressources maximales autorisées.




C'était un exemple d'utilisation de l'opérateur de limite. Essayez-le vous-même et jouez avec ClusterLimit dans votre instance Kubernetes locale.


Dans le référentiel d'opérateur GitHub Limit, vous pouvez trouver le manifeste pour le déploiement de l'opérateur, ainsi que le code source. Si vous souhaitez étendre les fonctionnalités de l'opérateur, les quêtes de tirage et les quêtes de fonctionnalité sont les bienvenues!


Conclusion


La gestion des ressources chez Kubernetes est essentielle à la stabilité et à la fiabilité de vos applications. Personnalisez vos ressources de foyer chaque fois que possible. Et utilisez LimitRange pour vous assurer contre les cas où cela n'est pas possible. Automatisez la création de LimitRange à l'aide de l'opérateur de limite.


Suivez ces conseils et votre cluster sera toujours à l'abri du chaos sans ressources des foyers errants.

Source: https://habr.com/ru/post/fr473908/


All Articles