Zum Hauptinhalt springen

Wie Sie Pod-Speicher effizienten Stresstests unterziehen können

· 10 Minuten Lesezeit
Yinghao Wang
Contributor of Chaos Mesh
Inoffizielle Beta-Übersetzung

Diese Seite wurde von PageTurner AI übersetzt (Beta). Nicht offiziell vom Projekt unterstützt. Fehler gefunden? Problem melden →

Banner
Banner

Chaos Mesh enthält das Tool StressChaos, mit dem Sie CPU- und Speicherlast in Ihren Pod injizieren können. Dies ist besonders nützlich, wenn Sie CPU- oder speichersensitive Programme testen oder benchmarken möchten und deren Verhalten unter Last analysieren wollen.

Bei unseren Tests mit StressChaos stießen wir jedoch auf einige Usability- und Performance-Probleme. Beispielsweise: Warum verbraucht StressChaos deutlich weniger Speicher als konfiguriert? Um diese Probleme zu beheben, entwickelten wir neue Testmethoden. In diesem Artikel zeige ich, wie wir die Probleme analysiert und gelöst haben – damit Sie StressChaos optimal nutzen können.

Bevor Sie fortfahren, installieren Sie bitte Chaos Mesh in Ihrem Cluster. Detaillierte Anleitungen finden Sie auf unserer Website.

Last in einen Ziel-Pod injizieren

Ich demonstriere die Injektion von StressChaos am Beispiel von hello-kubernetes, das per Helm Charts verwaltet wird. Klonen Sie zunächst das hello-kubernetes-Repo und passen Sie das Chart an, um ein Ressourcenlimit zu setzen.

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

Suchen Sie die Resources-Zeile und ändern Sie sie wie folgt:

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

Bevor wir Last injizieren, prüfen wir den aktuellen Speicherverbrauch des Ziel-Pods. Starten Sie eine Shell im Pod und führen Sie folgenden Befehl aus (ersetzen Sie den Pod-Namen):

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

Zeigen Sie eine Speicherverbrauchsübersicht an mit:

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

Wie unten zu sehen, verbraucht der Pod 4.269 MB Speicher.

/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

Das erscheint inkonsistent: Wir haben den Speicherverbrauch auf 500 MiB begrenzt, doch der Pod scheint mehrere GB zu nutzen. Die Summe der Prozessspeichernutzung erreicht nicht 500 MiB – immerhin zeigen top und free ähnliche Werte.

Wir werden nun StressChaos auf den Pod anwenden. Hier das verwendete YAML:

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'

Speichern Sie das YAML in einer Datei (z.B. memory.yaml). Wenden Sie den Chaos-Versuch an mit:

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

Prüfen wir nun erneut den Speicherverbrauch.

              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

Man erkennt, dass stress-ng-Instanzen in den Pod injiziert wurden. Der Speicherverbrauch stieg um 60 MiB – unerwartet, da die Dokumentation 200 MiB (4 × 50 MiB) prognostiziert.

Erhöhen wir die Last von 50 MiB auf 3.000 MiB, um das Pod-Limit zu überschreiten. Ich lösche den Chaos-Versuch, passe die Größe an und wende ihn erneut an.

Und – Boom! Die Shell beendet mit Code 137. Nach dem erneuten Verbinden ist der Speicherverbrauch normalisiert. Keine stress-ng-Instanzen mehr! Was ist passiert?

Warum verschwindet StressChaos?

Kubernetes begrenzt Containerspeicher via cgroup-Mechanismus. Um das 500-MiB-Limit im Pod zu prüfen, führen Sie im Container aus:

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

Die Ausgabe in Bytes entspricht 500 * 1024 * 1024.

Requests werden nur für die Planung verwendet, wo der Pod platziert werden soll. Der Pod selbst hat kein Memory-Limit oder -Request, aber man kann ihn als Summe aller seiner Container betrachten.

Wir haben von Anfang an einen Fehler gemacht. free und top sind nicht "cgrouped". Sie stützen sich auf /proc/meminfo (procfs) für Daten. Leider ist /proc/meminfo so alt, dass es sogar vor cgroup existierte. Es liefert Ihnen Host-Speicherinformationen statt der Ihres Containers. Fangen wir nochmal von vorne an und sehen, welche Speichernutzung wir diesmal erhalten.

Um die cgroup-basierte Speichernutzung abzurufen, geben Sie ein:

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

Bei Anwendung des 50 MiB StressChaos ergibt sich folgendes:

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

Das sind etwa 51 MiB mehr Speichernutzung als ohne StressChaos.

Warum ist unsere Shell beendet worden? Exit-Code 137 bedeutet "Fehler, da Container SIGKILL erhalten hat". Das führt uns zur Überprüfung des Pods. Achten Sie auf den Pod-Status und die Events.

~ 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
......

Die Events verraten uns, warum die Shell abgestürzt ist. hello-kubernetes hat einen Liveness Probe, und wenn der Containerspeicher das Limit erreicht, beginnt die Anwendung zu versagen, und Kubernetes beschließt, sie zu beenden und neu zu starten. Wenn der Pod neu startet, endet StressChaos. In diesem Fall können Sie sagen, dass das Chaos einwandfrei funktioniert. Es findet eine Schwachstelle in Ihrem Pod. Sie könnten sie jetzt beheben und das Chaos erneut anwenden. Alles scheint perfekt - bis auf eine Sache. Warum verursachen vier 50-MiB-vm-Worker insgesamt 51 MiB? Die Antwort offenbart sich erst, wenn wir in den stress-ng-Quellcode hier schauen:

vm_bytes /= args->num_instances;

Hoppla! Die Dokumentation ist also falsch. Die mehreren vm-Worker belegen die angegebene Gesamtgröße, anstatt pro Worker so viel Speicher per mmap zu reservieren. Jetzt haben wir endlich für alles eine Erklärung. In den folgenden Abschnitten besprechen wir weitere Szenarien zur Speicherbelastung.

Was passiert ohne Liveness Probe?

Löschen wir die Probes und versuchen es erneut. Suchen Sie folgende Zeilen in deploy/helm/hello-kubernetes/templates/deployment.yaml und entfernen Sie sie.

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

Danach führen Sie ein Upgrade der Deployment durch.

Interessant in diesem Szenario ist, dass die Speichernutzung kontinuierlich steigt, dann abrupt fällt; das wiederholt sich. Was passiert hier? Sehen wir uns den Kernel-Log an. Achten Sie auf die letzten beiden Zeilen.

/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

Aus der Ausgabe geht klar hervor, dass die stress-ng-vm-Prozesse wegen Out-of-Memory-Fehlern (OOM) beendet werden.

Wenn Prozesse nicht den gewünschten Speicher erhalten, wird es heikel. Sie werden höchstwahrscheinlich fehlschlagen. Statt auf Abstürze zu warten, ist es besser, einige Prozesse zu beenden, um mehr Speicher zu gewinnen. Der OOM-Killer beendet Prozesse nach einer Reihenfolge und versucht, möglichst viel Speicher zurückzugewinnen, während er möglichst wenig Probleme verursacht. Details dazu finden Sie in dieser Einführung zum OOM-Killer.

Betrachten wir die obige Ausgabe: node, unser Anwendungsprozess, der nie beendet werden sollte, hat einen oom_score_adj von 988. Das ist riskant, denn er ist der Prozess mit der höchsten Wahrscheinlichkeit, gekillt zu werden. Aber es gibt eine einfache Methode, um zu verhindern, dass der OOM-Killer einen bestimmten Prozess beendet. Wenn Sie einen Pod erstellen, erhält er eine Quality of Service (QoS)-Klasse. Näheres dazu unter Quality of Service für Pods konfigurieren.

Grundsätzlich wird ein Pod mit präzise spezifizierten Ressourcenanforderungen als Guaranteed-Pod klassifiziert. Der OOM-Killer beendet keine Container in einem Guaranteed-Pod, solange andere Ziele verfügbar sind. Dazu gehören nicht-Guaranteed-Pods und stress-ng-Worker. Ein Pod ohne Ressourcenanforderungen wird als BestEffort markiert und vom OOM-Killer priorisiert beendet.

Damit schließen wir unseren Überblick ab. Wir empfehlen, free und top nicht zur Speicherbewertung in Containern zu verwenden. Achten Sie bei der Vergabe von Ressourcenlimits an Ihre Pods auf die richtige QoS-Klasse. In Zukunft werden wir ein detaillierteres StressChaos-Dokument bereitstellen.

Tieferer Einblick in die Kubernetes-Speicherverwaltung

Kubernetes versucht, Pods zu entfernen, die übermäßig viel Speicher verbrauchen (ohne ihre Limits zu überschreiten). Die Pod-Speichernutzung wird von Kubernetes aus /sys/fs/cgroup/memory/memory.usage_in_bytes bezogen und durch den total_inactive_file-Wert in memory.stat reduziert.

Beachten Sie, dass Kubernetes keinen Swap-Speicher unterstützt. Selbst bei aktiviertem Swap auf einem Node erstellt Kubernetes Container mit swappiness=0, was de facto Swap deaktiviert. Dies erfolgt primär aus Leistungsgründen.

memory.usage_in_bytes entspricht resident set plus cache, während total_inactive_file zwischengespeicherten Speicher bezeichnet, den das Betriebssystem bei Knappheit freigeben kann. memory.usage_in_bytes - total_inactive_file wird als working_set bezeichnet. Diesen working_set-Wert erhalten Sie durch kubectl top pod <your pod> --containers. Kubernetes nutzt diesen Wert für Entscheidungen zur Pod-Entfernung.

Kubernetes überprüft regelmäßig die Speichernutzung. Bei zu schnellem Speicheranstieg oder nicht entfernbarem Container wird der OOM-Killer aktiviert. Kubernetes schützt systemeigene Prozesse und wählt daher stets den Container. Ein beendeter Container wird abhängig von Ihrer Restart-Richtlinie neu gestartet. Bei Beendigung zeigt kubectl describe pod <your pod> einen Neustart mit Grund OOMKilled.

Erwähnenswert ist der Kernel-Speicher. Seit v1.9 ist Kubernetes-Kernel-Speicherunterstützung standardmäßig aktiviert – ein Feature der cgroup-Speichersubsysteme. Die Begrenzung des Kernel-Speicherverbrauchs kann jedoch bis Kernel v4.2 zu cgroup-Leaks führen. Abhilfe schafft ein Upgrade auf v4.3 oder Deaktivierung des Features.

Unsere Implementierung von StressChaos

StressChaos testet Containerverhalten bei Speicherknappheit durch gezielte Allokation. Dabei nutzt es das Tool stress-ng zur Speicherbelegung und kontinuierlichen Beschreibung. Da Container-Speicherlimits an cgroups gebunden sind, muss stress-ng in einer spezifischen cgroup laufen. Mit ausreichenden Berechtigungen lässt sich dies durch Schreiben in /sys/fs/cgroup/-Dateien realisieren.

Falls Sie Chaos Mesh spannend finden und uns verbessern helfen möchten, besuchen Sie gern unseren Slack-Channel (#project-chaos-mesh)! Oder reichen Sie Pull Requests bzw. Issues in unserem GitHub-Repository ein.