Neuf conseils pour améliorer la performance de Kubernetes

Neuf conseils pour améliorer la performance de Kubernetes

Bonjour Ă  tous ! Je m'appelle Oleg Sidorenkov, je travaille chez DomClick en tant que responsable de l'Ă©quipe infrastructure. Nous exploitons « Kubik » en production depuis plus de trois ans, et durant ce temps, nous avons vĂ©cu de nombreux moments intĂ©ressants avec lui. Aujourd'hui, je vais vous expliquer comment, en adoptant la bonne approche, vous pouvez tirer encore plus de performance de votre cluster Kubernetes « vanille ». PrĂȘts, partez !

Vous savez tous trÚs bien que Kubernetes est un systÚme évolutif de code ouvert pour l'orchestration des conteneurs ; enfin, ou 5 binaires qui font de la magie en gérant le cycle de vie de vos microservices dans un environnement serveur. De plus, c'est un outil assez flexible que l'on peut assembler comme un ensemble Lego pour une personnalisation maximale selon différentes tùches.

Tout semble aller pour le mieux : mettez les serveurs dans le cluster comme du bois dans un four et ne vous inquiĂ©tez pas. Mais si vous pensez Ă  l'Ă©cologie, vous vous demanderez : « Comment puis-je entretenir le feu dans la cheminĂ©e tout en prĂ©servant la forĂȘt ? ». Autrement dit, comment trouver des moyens d'amĂ©liorer l'infrastructure tout en rĂ©duisant les coĂ»ts.

1. Surveillez les ressources des équipes et des applications

Neuf conseils pour améliorer la performance de Kubernetes

Une des méthodes les plus banales mais efficaces est l'introduction de requests/limits. Séparez les applications par namespaces, et les namespaces par équipes de développement. Attribuez aux applications, avant déploiement, des valeurs de consommation en termes de temps CPU, mémoire, et stockage éphémÚre.

resources:
   requests:
     memory: 2Gi
     cpu: 250m
   limits:
     memory: 4Gi
     cpu: 500m

Par expĂ©rience, nous avons constatĂ© qu'il ne faut pas gonfler les requests par rapport aux limits de plus du double. Le volume du cluster est calculĂ© en fonction des requests, et si vous attribuez aux applications une diffĂ©rence de ressources, par exemple, de 5 Ă  10 fois, imaginez ce qui arrivera Ă  votre nƓud lorsqu'il sera rempli de pods et recevra soudain une charge. Rien de bon. Au minimum, du throttling, et au maximum, vous direz adieu Ă  votre worker et aurez une charge cyclique sur les autres nƓuds aprĂšs que les pods aient commencĂ© Ă  migraient.

De plus, Ă  l'aide de limitranges vous pouvez dĂ©finir dĂšs le dĂ©part des valeurs de ressources pour le conteneur — minimales, maximales et par dĂ©faut :

➜  ~ kubectl describe limitranges --namespace ops
Name:       limit-range
Namespace:  ops
Type        Resource           Min   Max   Default Request  Default Limit  Max Limit/Request Ratio
----        --------           ---   ---   ---------------  -------------  -----------------------
Container   cpu                50m   10    100m             100m           2
Container   ephemeral-storage  12Mi  8Gi   128Mi            4Gi            -
Container   memory             64Mi  40Gi  128Mi            128Mi          2

N'oubliez pas de limiter les ressources du namespace pour qu'une équipe ne puisse pas prendre toutes les ressources du cluster :

➜  ~ kubectl describe resourcequotas --namespace ops
Name:                   resource-quota
Namespace:              ops
Resource                Used          Hard
--------                ----          ----
limits.cpu              77250m        80
limits.memory           124814367488  150Gi
pods                    31            45
requests.cpu            53850m        80
requests.memory         75613234944   150Gi
services                26            50
services.loadbalancers  0             0
services.nodeports      0             0

Comme on peut le voir dans la description resourcequotas, si l'équipe ops souhaite déployer des pods qui utiliseront encore 10 CPU, l'ordonnanceur ne le permettra pas et renverra une erreur :

Error creating: pods "nginx-proxy-9967d8d78-nh4fs" is forbidden: exceeded quota: resource-quota, requested: limits.cpu=5,requests.cpu=5, used: limits.cpu=77250m,requests.cpu=53850m, limited: limits.cpu=10,requests.cpu=10

Pour résoudre un tel problÚme, on peut écrire un outil, par exemple, comme celui-ci, capable de stocker et de commettre l'état des ressources des équipes.

2. Choisissez un stockage de fichiers optimal

Neuf conseils pour améliorer la performance de Kubernetes

Ici, je voudrais aborder le sujet des volumes persistants et du systĂšme de stockage des nƓuds de travail de Kubernetes. J'espĂšre que personne n'utilise de « Cube » sur HDD en production, mais parfois un SSD standard devient dĂ©jĂ  insuffisant. Nous avons Ă©tĂ© confrontĂ©s au problĂšme oĂč les journaux saturent le disque par les opĂ©rations d'entrĂ©e-sortie, et les options de solution ne sont pas trĂšs nombreuses :

  • Utiliser des SSD hautes performances ou passer Ă  NVMe (si vous gĂ©rez vous-mĂȘme votre matĂ©riel).

  • RĂ©duire le niveau de journalisation.

  • Effectuer un Ă©quilibre « intelligent » des pods qui saturent le disque (podAntiAffinity).

L'Ă©cran ci-dessus montre ce qui se passe avec le disque lors de l'utilisation du nginx-ingress-controller lorsque la journalisation access_logs est activĂ©e (~12 000 journaux/sec.). Cet Ă©tat peut Ă©videmment mener Ă  une dĂ©gradation de toutes les applications sur ce nƓud.

En ce qui concerne les PV, malheureusement, je n'ai pas testé tous les types Volumes persistants. Utilisez la meilleure option qui vous convient. Historiquement, une petite partie des services a besoin de volumes RWX, et pour cela, on utilise depuis longtemps le stockage NFS. C'est bon marché et... suffisant. Bien sûr, nous avons rencontré de nombreux problÚmes - mais nous avons appris à l'optimiser, et nous n'avons plus de soucis. Si possible, passez à un stockage d'objets S3.

3. Rassemblez des images optimisées

Neuf conseils pour améliorer la performance de Kubernetes

Il est préférable d'utiliser des images optimisées pour les conteneurs, afin que Kubernetes puisse les récupérer plus rapidement et les exécuter de maniÚre plus efficace. 

L'optimisation signifie que les images :

  • contiennent une seule application ou effectuent une seule fonction ;

  • sont de petite taille, car les grandes images sont plus mal transmises sur le rĂ©seau ;

  • ont des points de terminaison pour vĂ©rifier l'Ă©tat de santĂ© et la disponibilitĂ©, permettant Ă  Kubernetes d'agir en cas de pannes ;

  • utilisent des systĂšmes d'exploitation favorables aux conteneurs (comme Alpine ou CoreOS), qui sont plus rĂ©silients aux erreurs de configuration ;

  • utilisent des constructions multi-Ă©tapes, afin que vous puissiez dĂ©ployer uniquement des applications compilĂ©es, et non des sources connexes.

Il existe de nombreux outils et services permettant de vérifier et d'optimiser les images à la volée. Il est important de toujours les maintenir à jour et vérifiés en termes de sécurité. En fin de compte, vous obtenez :

  1. Une réduction de la charge réseau sur l'ensemble du cluster.

  2. Une diminution du temps de démarrage du conteneur.

  3. Un volume réduit de tout votre registre Docker.

4. Utilisez le cache DNS

Neuf conseils pour améliorer la performance de Kubernetes

En ce qui concerne les charges Ă©levĂ©es, il est assez difficile de vivre sans l'optimisation du systĂšme DNS du cluster. Il y a longtemps, les dĂ©veloppeurs de Kubernetes prenaient en charge leur solution kube-dns. Celle-ci a Ă©tĂ© mise en Ɠuvre chez nous, mais ce logiciel n'a pas Ă©tĂ© particuliĂšrement optimisĂ© et ne fournissait pas la performance requise, mĂȘme si la tĂąche semblait simple. Puis est arrivĂ© coredns, que nous avons adoptĂ© et qui nous a donnĂ© satisfaction ; il est devenu le service DNS par dĂ©faut dans K8s. À un moment donnĂ©, nous avons atteint 40 000 rps sur le systĂšme DNS, et cette solution est devenue insuffisante. Mais, par un heureux hasard, Nodelocaldns est sorti, Ă©galement connu sous le nom de cache local de nƓud, NodeLocal DNSCache.

Pourquoi utilisons-nous cela ? Il y a un bug dans le noyau Linux qui, lors d'un accĂšs multiple via conntrack NAT par UDP, conduit Ă  une condition de course pour l'Ă©criture dans les tables conntrack, et une partie du trafic Ă  travers le NAT est perdue (chaque appel Ă  travers le Service passe par le NAT). Nodelocaldns rĂ©sout ce problĂšme en Ă©liminant le NAT et en mettant Ă  niveau la connexion vers TCP pour les DNS en amont, ainsi qu'en mettant en cache localement les requĂȘtes DNS vers les sources amont (y compris un court cache nĂ©gatif de 5 secondes).

5. Scalez les pods horizontalement et verticalement de maniĂšre automatique

Neuf conseils pour améliorer la performance de Kubernetes

Pouvez-vous affirmer avec confiance que tous vos microservices sont prĂȘts pour une augmentation de charge de deux Ă  trois fois ? Comment allouer correctement les ressources Ă  vos applications ? Avoir quelques pods en plus de la charge de travail peut sembler redondant, et en garder juste assez vous expose au risque d'un arrĂȘt soudain en cas de pic de trafic sur le service. La voie du juste milieu est facilitĂ©e par des services tels que Horizontal Pod Autoscaler et Vertical Pod Autoscaler.

VPA permet d'augmenter automatiquement les requests/limits de vos conteneurs dans le pod en fonction de l'utilisation rĂ©elle. En quoi cela peut-il ĂȘtre utile ? Si vous avez des pods qui ne peuvent pas ĂȘtre mis Ă  l'Ă©chelle horizontalement pour une raison quelconque (ce qui n'est pas tout Ă  fait fiable), vous pouvez essayer de confier l'ajustement de ses ressources au VPA. Son atout rĂ©side dans un systĂšme de recommandations basĂ© sur des donnĂ©es historiques et actuelles provenant du metric-server, donc, si vous ne souhaitez pas changer automatiquement les requests/limits, vous pouvez simplement suivre les ressources recommandĂ©es pour vos conteneurs et optimiser les paramĂštres pour Ă©conomiser du CPU et de la mĂ©moire dans le cluster.

Neuf conseils pour améliorer la performance de KubernetesL'image est tirée de https://levelup.gitconnected.com/kubernetes-autoscaling-101-cluster-autoscaler-horizontal-pod-autoscaler-and-vertical-pod-2a441d9ad231

Le planificateur dans Kubernetes est toujours basĂ© sur les requests. Quelle que soit la valeur que vous y attribuez, le planificateur cherchera un nƓud appropriĂ© en fonction de celle-ci. Les valeurs des limits sont nĂ©cessaires au kubelet pour comprendre quand limiter ou tuer un pod. Et puisque le seul paramĂštre important est la valeur des requests, le VPA travaillera avec cela. Chaque fois que vous dĂ©finissez le dimensionnement vertical d'une application, vous dĂ©terminez quelles doivent ĂȘtre les requests. Et qu'en est-il des limits ? Ce paramĂštre sera Ă©galement redimensionnĂ© proportionnellement.

Par exemple, voici les configurations normales d'un pod :

ressources:
   demandes:
     mémoire: 250Mi
     cpu: 200m
   limites:
     mémoire: 500Mi
     cpu: 350m

Le mécanisme de recommandation détermine que votre application nécessite 300m de CPU et 500Mi pour fonctionner normalement. Vous obtiendrez ces paramÚtres :

ressources:
   demandes:
     mémoire: 500Mi
     cpu: 300m
   limites:
     mémoire: 1000Mi
     cpu: 525m

Comme mentionné précédemment, il s'agit d'une mise à l'échelle proportionnelle basée sur le ratio demandes/limites dans le manifeste :

  • CPU: 200m → 300m : ratio 1:1.75 ;

  • MĂ©moire: 250Mi → 500Mi : ratio 1:2.

Concernant HPA, ici le mécanisme est plus transparent. Des seuils de métriques sont définis, par exemple pour le CPU et la mémoire, et si la valeur moyenne de toutes les répliques dépasse le seuil, l'application est mise à l'échelle en ajoutant +1 pod jusqu'à ce que la valeur redescende en dessous du seuil ou jusqu'à atteindre le nombre maximal de répliques.

Neuf conseils pour améliorer la performance de KubernetesL'image est tirée de https://levelup.gitconnected.com/kubernetes-autoscaling-101-cluster-autoscaler-horizontal-pod-autoscaler-and-vertical-pod-2a441d9ad231

En plus des métriques habituelles, comme le CPU et la mémoire, vous pouvez configurer des seuils sur vos métriques personnalisées à partir de Prometheus et travailler avec elles si vous pensez que cela définit le mieux quand il faut mettre à l'échelle votre application. Une fois l'application stabilisée sous la limite de métrique spécifiée, le HPA commencera à réduire le nombre de pods jusqu'à atteindre le minimum de répliques ou jusqu'à ce que la charge soit satisfaisante par rapport au seuil donné.

6. N'oubliez pas le Node Affinity et le Pod Affinity

Neuf conseils pour améliorer la performance de Kubernetes

Tous les nƓuds ne fonctionnent pas sur le mĂȘme matĂ©riel, et tous les pods n'ont pas besoin d'exĂ©cuter des applications gourmandes en calculs. Kubernetes permet de spĂ©cifier une spĂ©cialisation des nƓuds et des pods Ă  l'aide de Node Affinity et Pod Affinity.

Si vous avez des nƓuds adaptĂ©s aux opĂ©rations gourmandes en calcul, il est prĂ©fĂ©rable de lier les applications Ă  ces nƓuds correspondants pour une efficacitĂ© maximale. Pour cela, utilisez nodeSelector avec l'Ă©tiquette du nƓud.

Supposons que vous ayez deux nƓuds : un avec CPUType=HIGHFREQ et un grand nombre de cƓurs rapides, l'autre avec MemoryType=HIGHMEMORY une grande quantitĂ© de mĂ©moire et un dĂ©bit plus rapide. Le plus simple est d'assigner le dĂ©ploiement du pod au nƓud HIGHFREQ, en ajoutant dans la section spec ce sĂ©lecteur :



nodeSelector:
	CPUType: HIGHFREQ

Un moyen plus coûteux et spécifique de faire cela est d'utiliser nodeAffinity dans le champ affinity de la section spec. Il existe deux options :

  • requiredDuringSchedulingIgnoredDuringExecution: configuration stricte (le planificateur ne dĂ©ploiera des pods que sur des nƓuds spĂ©cifiques (et nulle part ailleurs));

  • preferredDuringSchedulingIgnoredDuringExecution: configuration flexible (le planificateur essaiera de dĂ©ployer sur des nƓuds spĂ©cifiques, et s'il n'y parvient pas, il essaiera sur le prochain nƓud disponible).

Vous pouvez spĂ©cifier une syntaxe de gestion des Ă©tiquettes de nƓuds, par exemple, Dans, PasDans, Existe, N'existePas, Gt ou Lt. Cependant, gardez Ă  l'esprit que des mĂ©thodes complexes dans de longues listes d'Ă©tiquettes ralentiront la prise de dĂ©cision dans des situations critiques. En d'autres termes, ne compliquez pas.

Comme mentionnĂ© prĂ©cĂ©demment, Kubernetes permet de dĂ©finir la liaison des pods actuels. Cela signifie que vous pouvez faire en sorte que certains pods fonctionnent avec d'autres pods dans la mĂȘme zone de disponibilitĂ© (pertinent pour les clouds) ou nƓuds.

Dans podAffinity champs affinity de la section spec les mĂȘmes champs sont disponibles que dans le cas de nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution et preferredDuringSchedulingIgnoredDuringExecution. La seule diffĂ©rence est que matchExpressions lier les pods Ă  un nƓud sur lequel un pod avec cette Ă©tiquette est dĂ©jĂ  en cours d'exĂ©cution.

De plus, Kubernetes propose le champ podAntiAffinity, qui, au contraire, ne lie pas un pod à un nƓud avec certains pods.

Concernant les expressions nodeAffinity on peut donner le mĂȘme conseil : essayez de garder la simplicitĂ© et la logique des rĂšgles, ne tentez pas de surcharger la spĂ©cification des pods avec un ensemble complexe de rĂšgles. Il est trĂšs facile de crĂ©er une rĂšgle qui ne correspondra pas aux conditions du cluster, crĂ©ant une charge supplĂ©mentaire sur le planificateur et rĂ©duisant les performances globales.

7. Taints & Tolerations

Il existe Ă©galement un autre moyen de gĂ©rer le planificateur. Si vous avez un grand cluster avec des centaines de nƓuds et des milliers de microservices, il est trĂšs difficile de ne pas permettre Ă  certains pods de se dĂ©ployer sur certains nƓuds.

Le mĂ©canisme des taints aide Ă  cela — rĂšgles prohibitives. Par exemple, dans certaines situations, il peut ĂȘtre interdit Ă  certains nƓuds d'exĂ©cuter des pods. Pour appliquer un taint Ă  un nƓud spĂ©cifique, vous devez utiliser l'option taint dans kubectl. SpĂ©cifiez la clĂ© et la valeur, puis un taint comme NoSchedule ou NoExecute:

$ kubectl taint nodes node10 node-role.kubernetes.io/ingress=true:NoSchedule

Il convient également de noter que le mécanisme de taint prend en charge trois effets principaux : NoSchedule, NoExecute et PreferNoSchedule.

  • NoSchedule signifie que tant qu'il n'y a pas d'enregistrement correspondant dans la spĂ©cification du pod tolerations, il ne pourra pas ĂȘtre dĂ©ployĂ© sur le nƓud (dans cet exemple node10).

  • PreferNoSchedule — une version simplifiĂ©e NoSchedule. Dans ce cas, le planificateur tentera de ne pas rĂ©partir les pods qui n'ont pas d'enregistrement correspondant tolerations sur le nƓud, mais ce n'est pas une restriction stricte. Si le cluster ne dispose pas de ressources, les pods commenceront Ă  se dĂ©ployer sur ce nƓud.

  • NoExecute — cet effet dĂ©clenche une Ă©vacuation immĂ©diate des pods qui n'ont pas d'enregistrement correspondant tolerations.

Il est intĂ©ressant de noter que ce comportement peut ĂȘtre annulĂ© Ă  l'aide du mĂ©canisme des tolerations. Cela est pratique lorsqu'il existe un nƓud "interdit" et que vous souhaitez y placer uniquement des services d'infrastructure. Comment faire ? Autorisez uniquement les pods pour lesquels il existe une toleration appropriĂ©e.

Voici à quoi ressemblera la spécification du pod :

spec:
   tolerations:
     - key: "node-role.kubernetes.io/ingress"
        operator: "Equal"
        value: "true"
        effect: "NoSchedule"

Cela ne signifie pas qu'au prochain redeploiement, le pod se retrouvera nĂ©cessairement sur ce nƓud, ce n'est pas un mĂ©canisme d'affinitĂ© de nƓud et nodeSelector. Mais en combinant plusieurs fonctionnalitĂ©s, vous pouvez obtenir une configuration trĂšs flexible de l'ordonnanceur.

8. Configurez la priorité de déploiement des pods

Le fait que vous ayez configurĂ© la liaison des pods aux nƓuds ne signifie pas que tous les pods doivent ĂȘtre traitĂ©s avec la mĂȘme prioritĂ©. Par exemple, vous pouvez vouloir dĂ©ployer certains pods avant les autres.

Kubernetes propose différentes maniÚres de configurer la priorité des pods (Pod Priority and Preemption). La configuration se compose de plusieurs parties : l'objet PriorityClass et la description du champ priorityClassName dans la spécification du pod. Examinons un exemple :

apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
  name: high-priority
value: 99999
globalDefault: false
description: "Cette classe de prioritĂ© ne doit ĂȘtre utilisĂ©e que pour des pods trĂšs importants"

Nous crĂ©ons PriorityClass, lui attribuons un nom, une description et une valeur. Plus la value, plus la prioritĂ© est Ă©levĂ©e. La valeur peut ĂȘtre n'importe quel entier 32 bits, infĂ©rieur ou Ă©gal Ă  1 000 000 000. Des valeurs plus Ă©levĂ©es sont rĂ©servĂ©es pour des pods systĂšme critiques qui ne peuvent gĂ©nĂ©ralement pas ĂȘtre Ă©vacuĂ©s. L'Ă©vacuation n'aura lieu que si un pod de haute prioritĂ© n'a pas d'endroit oĂč se dĂ©ployer, alors une partie des pods d'un nƓud particulier sera Ă©vacuĂ©e. Si ce mĂ©canisme vous semble trop strict, vous pouvez ajouter l'option preemptionPolicy: Never, et alors il n'y aura pas d'Ă©vacuation, le pod sera placĂ© en tĂȘte de file et attendra que l'ordonnanceur trouve des ressources libres pour lui.

Ensuite, nous créons un pod dans lequel nous spécifions le nom priorityClassName:

apiVersion: v1
kind: Pod
metadata:
  name: static-web
  labels:
    role: myrole
 spec:
  containers:
    - name: web
      image: nginx
      ports:
        - name: web
          containerPort: 80
          protocol: TCP
  priorityClassName: high-priority
          

Vous pouvez créer autant de classes de priorité que vous le souhaitez, bien qu'il soit recommandé de ne pas en abuser (par exemple, se limiter aux priorités basse, moyenne et haute).

Ainsi, si nécessaire, vous pourrez améliorer l'efficacité du déploiement de services critiques, tels que nginx-ingress-controller, coredns, etc.

9. Optimisez le cluster ETCD

Neuf conseils pour améliorer la performance de Kubernetes

On peut considĂ©rer ETCD comme le cerveau de tout le cluster. Il est trĂšs important de maintenir cette base de donnĂ©es Ă  un niveau Ă©levĂ©, car c'est de lĂ  que dĂ©pend la vitesse des opĂ©rations dans le « Cube ». Il est assez standard, et en mĂȘme temps pas mal, de garder le cluster ETCD sur les nƓuds maĂźtres afin d'avoir une latence minimale avec le kube-apiserver. Si cela n'est pas possible, placez ETCD aussi prĂšs que possible, en ayant une bonne bande passante entre les participants. Faites Ă©galement attention au nombre de nƓuds d'ETCD qui peuvent ĂȘtre perdus sans nuire au cluster.

Neuf conseils pour améliorer la performance de Kubernetes

Gardez Ă  l'esprit qu'une augmentation excessive du nombre de participants dans le cluster peut amĂ©liorer la tolĂ©rance aux pannes au dĂ©triment de la performance, tout doit ĂȘtre Ă©quilibrĂ©.

En ce qui concerne la configuration du service, les recommandations sont rares :

  1. Ayez un bon matériel, en fonction des dimensions du cluster (vous pouvez lire ici).

  2. Ajustez quelques paramÚtres si vous avez réparti le cluster entre plusieurs centres de données ou si votre réseau et vos disques laissent à désirer (vous pouvez lire ici).

Conclusion

Cet article dĂ©crit des points que notre Ă©quipe s'efforce de respecter. Ce n'est pas un guide pas Ă  pas, mais des options qui peuvent ĂȘtre utiles pour optimiser les coĂ»ts de fonctionnement du cluster. Il est Ă©vident que chaque cluster est unique, et les solutions de configuration peuvent varier considĂ©rablement, c'est pourquoi il serait intĂ©ressant d'avoir votre retour : comment surveillez-vous votre cluster Kubernetes, avec quoi amĂ©liorez-vous son fonctionnement ? Partagez votre expĂ©rience dans les commentaires, nous serions ravis de la connaĂźtre.

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