Aller au contenu principal

Comment effectuer un test de stress mémoire de Pod efficacement

· 12 minutes de lecture
Yinghao Wang
Contributor of Chaos Mesh
Traduction Bêta Non Officielle

Cette page a été traduite par PageTurner AI (bêta). Non approuvée officiellement par le projet. Vous avez trouvé une erreur ? Signaler un problème →

bannière
bannière

Chaos Mesh inclut l'outil StressChaos qui vous permet d'injecter du stress CPU et mémoire dans vos Pods. Cet outil s'avère très utile lorsque vous testez ou évaluez un programme sensible au CPU ou à la mémoire, et que vous souhaitez connaître son comportement sous pression.

Cependant, lors de nos tests et de notre utilisation de StressChaos, nous avons rencontré certains problèmes d'ergonomie et de performance. Par exemple, pourquoi StressChaos utilise-t-il beaucoup moins de mémoire que ce que nous avons configuré ? Pour résoudre ces problèmes, nous avons développé une nouvelle série de tests. Dans cet article, je vais vous expliquer comment nous avons diagnostiqué et corrigé ces problèmes. Ces informations vous permettront de tirer le meilleur parti de StressChaos.

Avant de poursuivre, vous devez installer Chaos Mesh dans votre cluster. Vous trouverez des instructions détaillées sur notre site web.

Injecter du stress dans une cible

Je vais vous montrer comment injecter StressChaos dans une cible. Dans cet exemple, j'utiliserai hello-kubernetes, géré par des helm charts. La première étape consiste à cloner le dépôt hello-kubernetes et à modifier le chart pour lui appliquer une limite de ressources.

git clone https://github.com/paulbouwer/hello-kubernetes.git
code deploy/helm/hello-kubernetes/values.yaml # or whichever editor you prefer

Recherchez la ligne des ressources et modifiez-la comme suit :

resources:
requests:
memory: '200Mi'
limits:
memory: '500Mi'

Cependant, avant d'injecter quoi que ce soit, vérifions la consommation mémoire de la cible. Connectez-vous au Pod et lancez un shell. Entrez la commande suivante, en remplaçant le nom de votre Pod par celui de l'exemple :

kubectl exec -it -n hello-kubernetes hello-kubernetes-hello-world-b55bfcf68-8mln6 -- /bin/sh

Affichez un résumé de l'utilisation mémoire. Entrez :

/usr/src/app $ free -m
/usr/src/app $ top

Comme vous pouvez le voir dans la sortie ci-dessous, le Pod consomme 4 269 Mo de mémoire.

/usr/src/app $ free -m
used
Mem: 4269
Swap: 0

/usr/src/app $ top
Mem: 12742432K used
PID PPID USER STAT VSZ %VSZ CPU %CPU COMMAND
1 0 node S 285m 2% 0 0% npm start
18 1 node S 284m 2% 3 0% node server.js
29 0 node S 1636 0% 2 0% /bin/sh
36 29 node R 1568 0% 3 0% top

Cela ne semble pas cohérent. Nous avons limité son utilisation mémoire à 500 Mio, mais le Pod semble utiliser plusieurs Go de mémoire. Si nous additionnons la mémoire utilisée par les processus, le total n'atteint pas 500 Mio. Cependant, les commandes top et free donnent au moins des résultats similaires.

Nous allons exécuter un StressChaos sur le Pod et observer les résultats. Voici le fichier yaml que nous utiliserons :

apiVersion: chaos-mesh.org/v1alpha1
kind: StressChaos
metadata:
name: mem-stress
namespace: chaos-mesh
spec:
mode: all
selector:
namespaces:
- hello-kubernetes
stressors:
memory:
workers: 4
size: 50MiB
options: ['']
duration: '1h'

Enregistrez le yaml dans un fichier. Je l'ai nommé memory.yaml. Pour appliquer le chaos, exécutez :

~ kubectl apply -f memory.yaml
stresschaos.chaos-mesh.org/mem-stress created

Vérifions à nouveau l'utilisation mémoire.

              used
Mem: 4332
Swap: 0

Mem: 12805568K used
PID PPID USER STAT VSZ %VSZ CPU %CPU COMMAND
54 50 root R 53252 0% 1 24% {stress-ng-vm} stress-ng --vm 4 --vm-keep --vm-bytes 50000000
57 52 root R 53252 0% 0 22% {stress-ng-vm} stress-ng --vm 4 --vm-keep --vm-bytes 50000000
55 53 root R 53252 0% 2 21% {stress-ng-vm} stress-ng --vm 4 --vm-keep --vm-bytes 50000000
56 51 root R 53252 0% 3 21% {stress-ng-vm} stress-ng --vm 4 --vm-keep --vm-bytes 50000000
18 1 node S 289m 2% 2 0% node server.js
1 0 node S 285m 2% 0 0% npm start
51 49 root S 41048 0% 0 0% {stress-ng-vm} stress-ng --vm 4 --vm-keep --vm-bytes 50000000
50 49 root S 41048 0% 2 0% {stress-ng-vm} stress-ng --vm 4 --vm-keep --vm-bytes 50000000
52 49 root S 41048 0% 0 0% {stress-ng-vm} stress-ng --vm 4 --vm-keep --vm-bytes 50000000
53 49 root S 41048 0% 3 0% {stress-ng-vm} stress-ng --vm 4 --vm-keep --vm-bytes 50000000
49 0 root S 41044 0% 0 0% stress-ng --vm 4 --vm-keep --vm-bytes 50000000
29 0 node S 1636 0% 3 0% /bin/sh
48 29 node R 1568 0% 1 0% top

Vous pouvez voir que des instances de stress-ng sont injectées dans le Pod. On observe une augmentation de 60 Mio dans le Pod, ce que nous n'attendions pas. La documentation indique que l'augmentation devrait être de 200 Mio (4 * 50 Mio).

Augmentons le stress en passant la charge mémoire de 50 Mio à 3 000 Mio. Cela devrait dépasser la limite mémoire du Pod. Je vais supprimer le chaos, modifier la taille et le réappliquer.

Et puis, boum ! Le shell se termine avec le code 137. Un moment plus tard, je me reconnecte au conteneur, et l'utilisation mémoire revient à la normale. Aucune instance de stress-ng n'est trouvée ! Que s'est-il passé ?

Pourquoi StressChaos disparaît-il ?

Kubernetes limite l'utilisation mémoire de votre conteneur via un mécanisme nommé cgroup. Pour voir la limite de 500 Mio dans notre Pod, accédez au conteneur et entrez :

/usr/src/app $ cat /sys/fs/cgroup/memory/memory.limit_in_bytes
524288000

La sortie est affichée en octets et correspond à 500 * 1024 * 1024.

Les requêtes ne sont utilisées que pour la planification du placement du Pod. Le Pod lui-même n'a pas de limite ou de requête mémoire, mais il peut être considéré comme la somme de tous ses conteneurs.

Nous faisons une erreur depuis le début : free et top ne sont pas "cgroupés". Ils s'appuient sur /proc/meminfo (procfs) pour les données. Malheureusement, /proc/meminfo est ancien - si ancien qu'il précède cgroup. Il vous fournira les informations mémoire de l'hôte plutôt que de votre conteneur. Repartons de zéro pour voir l'utilisation mémoire cette fois.

Pour obtenir l'utilisation mémoire cgroupée, saisissez :

/usr/src/app $ cat /sys/fs/cgroup/memory/memory.usage_in_bytes
39821312

L'application du StressChaos de 50 MiB donne ceci :

/usr/src/app $ cat /sys/fs/cgroup/memory/memory.usage_in_bytes
93577216

Cela représente environ 51 MiB d'utilisation mémoire supplémentaire par rapport à sans StressChaos.

Maintenant, pourquoi notre shell s'est-il arrêté ? Le code de sortie 137 indique "échec dû à un SIGKILL reçu par le conteneur". Cela nous amène à vérifier le Pod. Portez attention à l'état du Pod et à ses événements.

~ kubectl describe pods -n hello-kubernetes
......
Last State: Terminated
Reason: Error
Exit Code: 1
......
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
......
Warning Unhealthy 10m (x4 over 16m) kubelet Readiness probe failed: Get "http://10.244.1.19:8080/": context deadline exceeded (Client.Timeout exceeded while awaiting headers)
Normal Killing 10m (x2 over 16m) kubelet Container hello-kubernetes failed liveness probe, will be restarted
......

Les événements nous disent pourquoi le shell a planté. hello-kubernetes a une sonde de liveness, et lorsque la mémoire du conteneur approche de la limite, l'application commence à échouer, ce qui amène Kubernetes à le terminer et le redémarrer. Quand le Pod redémarre, StressChaos s'arrête. Dans ce cas, on peut dire que le chaos fonctionne correctement : il a révélé une vulnérabilité dans votre Pod. Vous pouvez maintenant la corriger et réappliquer le chaos. Tout semble parfait - sauf une chose. Pourquoi quatre workers vm de 50 MiB totalisent-ils 51 MiB ? La réponse ne se révèle qu'en examinant le code source de stress-ng ici :

vm_bytes /= args->num_instances;

Oups ! La documentation est donc erronée. Les multiples workers vm occuperont la taille totale spécifiée, plutôt que de mmap cette quantité par worker. Nous avons enfin toutes les réponses. Dans les sections suivantes, nous aborderons d'autres scénarios de stress mémoire.

Que se passe-t-il sans sonde de liveness ?

Supprimons les sondes et réessayons. Trouvez ces lignes dans deploy/helm/hello-kubernetes/templates/deployment.yaml et supprimez-les.

livenessProbe:
httpGet:
path: /
port: http
readinessProbe:
httpGet:
path: /
port: http

Mettez ensuite à jour le déploiement.

Ce qui est intéressant ici, c'est que l'utilisation mémoire monte continuellement puis chute brutalement, dans un mouvement de va-et-vient. Que se passe-t-il ? Vérifions les logs du kernel. Notez les deux dernières lignes.

/usr/src/app $ dmesg
......
[189937.362908] [ pid ] uid tgid total_vm rss nr_ptes swapents oom_score_adj name
[189937.363092] [441060] 1000 441060 63955 3791 80 3030 988 node
[189937.363110] [441688] 0 441688 193367 2136 372 181097 1000 stress-ng-vm
......
[189937.363148] Memory cgroup out of memory: Kill process 443160 (stress-ng-vm) score 1272 or sacrifice child
[189937.363186] Killed process 443160 (stress-ng-vm), UID 0, total-vm:773468kB, anon-rss:152704kB, file-rss:164kB, shmem-rss:0kB

Il est clair que les processus stress-ng-vm sont tués à cause d'erreurs de mémoire insuffisante (OOM).

Quand les processus ne peuvent obtenir la mémoire nécessaire, la situation devient délicate. Ils risquent fort d'échouer. Plutôt que d'attendre leur crash, mieux vaut en tuer certains pour libérer de la mémoire. Le tueur OOM arrête les processus par ordre de priorité, cherchant à récupérer un maximum de mémoire en causant le moins de problèmes possible. Pour plus de détails, consultez cette présentation du tueur OOM.

Dans la sortie ci-dessus, notez que node - notre processus applicatif qui ne devrait jamais être terminé - a un oom_score_adj de 988. C'est risqué car c'est le processus le plus susceptible d'être tué. Mais il existe un moyen simple d'empêcher le tueur OOM de tuer un processus spécifique. Lorsque vous créez un Pod, une classe de qualité de service (QoS) lui est attribuée. Pour plus de détails, consultez Configurer la qualité de service pour les Pods.

En règle générale, si vous créez un Pod avec des requêtes de ressources précisément spécifiées, il est classé comme Pod Guaranteed. L'OOM killer n'arrête pas les conteneurs d'un Pod Guaranteed s'il existe d'autres entités à éliminer. Ces entités incluent les Pods non-Guaranteed et les workers stress-ng. Un Pod sans requêtes de ressources est marqué BestEffort, et l'OOM killer l'arrête en priorité.

Notre tour d'horizon s'achève ici. Nous vous conseillons de ne pas utiliser free et top pour évaluer la mémoire dans les conteneurs. Soyez vigilant lorsque vous attribuez des limites de ressources à votre Pod et choisissez la QoS appropriée. À l'avenir, nous publierons une documentation plus détaillée sur StressChaos.

Plongée approfondie dans la gestion mémoire de Kubernetes

Kubernetes tente d'expulser les Pods qui utilisent trop de mémoire (sans dépasser leurs limites). Kubernetes récupère l'utilisation mémoire de votre Pod via /sys/fs/cgroup/memory/memory.usage_in_bytes et la soustrait de la ligne total_inactive_file dans memory.stat.

Notez que Kubernetes ne prend pas en charge le swap. Même sur un nœud avec swap activé, Kubernetes crée des conteneurs avec swappiness=0, ce qui désactive finalement le swap. Cela répond principalement à des préoccupations de performance.

memory.usage_in_bytes équivaut au resident set plus le cache, tandis que total_inactive_file représente la mémoire cache que le système d'exploitation peut récupérer en cas de pénurie. memory.usage_in_bytes - total_inactive_file est appelé working_set. Vous obtenez cette valeur working_set via kubectl top pod <your pod> --containers. Kubernetes utilise cette valeur pour décider d'expulser ou non vos Pods.

Kubernetes inspecte périodiquement l'utilisation mémoire. Si la consommation mémoire d'un conteneur augmente trop rapidement ou si le conteneur ne peut être expulsé, l'OOM killer est invoqué. Kubernetes protège ses propres processus, il sélectionne donc toujours le conteneur. Lorsqu'un conteneur est arrêté, il peut être redémarré ou non selon votre politique de redémarrage. S'il est tué, l'exécution de kubectl describe pod <your pod> montrera un redémarrage avec la raison OOMKilled.

Autre point notable : la mémoire noyau. Depuis la v1.9, la gestion de la mémoire noyau est activée par défaut dans Kubernetes. C'est également une fonctionnalité des sous-systèmes mémoire cgroup. Vous pouvez limiter l'utilisation de la mémoire noyau par les conteneurs. Malheureusement, cela provoque une fuite cgroup sur les noyaux jusqu'à la v4.2. Vous pouvez soit mettre à niveau votre noyau vers la v4.3, soit désactiver cette fonctionnalité.

Fonctionnement interne de StressChaos

StressChaos offre un moyen simple de tester le comportement de votre conteneur en situation de pénurie mémoire. Il utilise l'outil puissant stress-ng pour allouer de la mémoire et continuer à écrire dans la mémoire allouée. Comme les conteneurs ont des limites mémoire liées à un cgroup, nous devons exécuter stress-ng dans un cgroup spécifique. Heureusement, cette opération est simple. Avec des privilèges suffisants, nous pouvons assigner tout processus à n'importe quel cgroup en écrivant dans les fichiers de /sys/fs/cgroup/.

Si Chaos Mesh vous intéresse et que vous souhaitez nous aider à l'améliorer, rejoignez notre canal Slack (#project-chaos-mesh) ! Vous pouvez aussi soumettre vos pull requests ou issues sur notre dépôt GitHub.