Mastering Kubernetes Resource Requests and Limits für Spitzenleistung

Erfahren Sie die entscheidenden Unterschiede zwischen Kubernetes Resource Requests und Limits für CPU und Arbeitsspeicher. Dieser Leitfaden erklärt, wie diese Einstellungen die Quality of Service (QoS)-Klassen (Guaranteed, Burstable, BestEffort) bestimmen, Knoteninstabilität verhindern und die Effizienz der Cluster-Scheduling optimieren. Enthält praktische YAML-Beispiele und Best Practices für die Leistungsoptimierung.

Mastering Kubernetes Resource Requests und Limits für Spitzenleistung

Kubernetes Resource Requests und Limits sehen in YAML einfach aus, aber sie prägen fast jedes Day-two-Verhalten in einem Cluster: wo Pods landen, welche Workloads zuerst evakuiert werden, ob eine App unter Last CPU-gedrosselt wird und wie viel freie Kapazität Sie vermeintlich haben. Eine schlechte Einstellung kann eine gesunde Anwendung kaputt aussehen lassen. Eine fehlende Einstellung kann dazu führen, dass der Scheduler Pods auf einen Knoten packt, bis der erste Traffic-Spike zu einem Noisy-Neighbor-Vorfall wird.

Der Teil, der Teams überrascht, ist, dass Requests und Limits von verschiedenen Systemen verwendet werden. Requests sind hauptsächlich ein Scheduling-Versprechen. Limits sind eine Durchsetzungsgrenze. Sie als dasselbe zu behandeln, führt zu seltsamen Ergebnissen, insbesondere bei CPU.

Grundlegendes Verständnis der Kernkonzepte: Requests vs. Limits

In Kubernetes kann ein Container den erwarteten Ressourcenverbrauch mit resources.requests und resources.limits definieren. Sie sind technisch nicht obligatorisch, es sei denn, Ihr Cluster verwendet Richtlinien wie LimitRange oder Admission Controls, aber Produktions-Workloads sollten normalerweise zumindest Requests für CPU und Arbeitsspeicher definieren. Ohne Requests hat der Scheduler wenig nützliche Informationen und der Pod fällt in eine schwächere Quality-of-Service-Position.

1. Resource Requests (requests)

Requests repräsentieren die Menge an Ressourcen, die einem Container garantiert bei der Planung zur Verfügung gestellt wird. Dies ist die Mindestmenge an Ressourcen, die der kube-scheduler verwendet, um zu entscheiden, auf welchen Knoten ein Pod platziert wird.

  • Scheduling: Ein Knoten muss genügend verfügbare allokierbare Ressourcen haben, die die Summe aller Pod-Requests erfüllen, bevor ein neuer Pod dort geplant werden kann.
  • Laufzeitpriorität: Requests beeinflussen CPU-Anteile und Entscheidungen zur Speicher-Evakuierung. Sie sind keine magische Reservierung, die jede Verlangsamung verhindert, aber sie geben Kubernetes und dem Kernel bessere Informationen bei Konflikten.

2. Resource Limits (limits)

Limits definieren die maximale Menge an Ressourcen, die ein Container verbrauchen darf. Das Überschreiten dieser Limits führt zu spezifischen, definierten Verhaltensweisen für CPU und Arbeitsspeicher.

  • CPU-Limits: Wenn ein Container versucht, mehr CPU als sein Limit zu nutzen, wird die cgroups des Linux-Kernels seine Nutzung drosseln und ihn daran hindern, weitere Zyklen zu verbrauchen.
  • Speicher-Limits: Wenn ein Container sein Speicherlimit überschreitet, kann der Kernel einen Prozess im Container beenden. Kubernetes meldet dies als OOMKilled, wenn dies der aufgezeichnete Beendigungsgrund ist.

CPU vs. Speicherverhalten

Es ist entscheidend, den qualitativen Unterschied zu verstehen, wie Kubernetes CPU- versus Speichergrenzen durchsetzt:

Ressource Verhalten bei Überschreitung des Limits Durchsetzungsmechanismus
CPU Gedrosselt (verlangsamt) cgroups (CPU-Bandbreitensteuerung)
Speicher Beendet (OOMKill) Kernel OOM Killer

Praktische Vorsicht: CPU-Limits können einen Knoten vor einem außer Kontrolle geratenen Prozess schützen, können aber auch Latenzprobleme verursachen, wenn sie zu niedrig eingestellt sind. Viele Plattform-Teams setzen Speicherlimits konsistent und sind bei CPU-Limits für latenzempfindliche Dienste selektiver, abhängig von ihrer Risikobereitschaft und Cluster-Richtlinie.

Definieren von Ressourcen in Pod-Spezifikationen

Ressourcen werden im Block spec.containers[*].resources definiert. Mengen werden mit standardmäßigen Kubernetes-Suffixen angegeben (z.B. m für Milli-CPU, Mi für Mebibytes).

CPU-Einheitendefinitionen

  • 1 CPU-Einheit entspricht 1 vollständigen Kern (oder vCPU bei Cloud-Anbietern).
  • 1000m (Millicores) entspricht 1 CPU-Einheit.

Speicher-Einheitendefinitionen

  • Mi (Mebibytes) oder Gi (Gibibytes) sind üblich.
  • 1024Mi = 1Gi.

Beispiel-YAML-Konfiguration

Betrachten Sie einen Container, der ein garantiertes Minimum von 500m CPU und 256Mi Speicher benötigt, aber niemals 1 CPU und 512Mi überschreiten sollte:

resources:
  requests:
    memory: "256Mi"
    cpu: "500m"
  limits:
    memory: "512Mi"
    cpu: "1"

Die Zahlen sollten aus beobachtetem Verhalten stammen, nicht aus Vermutungen. Für eine kleine HTTP-API könnte ein anfänglicher Request auf der normalen p50- oder p90-Nutzung während des Geschäftsverkehrs basieren, dann nach einem Lasttest angepasst werden. Für einen JVM-Dienst muss der Speicher Heap, Metaspace, nativen Speicher, Thread-Stacks, direkte Puffer und Sidecar-Overhead umfassen. Für einen Batch-Job kann der Spitzenspeicher während der größten Eingabe wichtiger sein als der durchschnittliche Speicher.

Quality of Service (QoS)-Klassen

Die Beziehung zwischen Requests und Limits bestimmt die Quality of Service (QoS)-Klasse, die einem Pod zugewiesen wird. Diese Klasse bestimmt die Priorität des Pods, wenn Ressourcen knapp werden und der Knoten Speicher zurückgewinnen muss (Evakuierung).

Kubernetes definiert drei QoS-Klassen:

1. Guaranteed

Definition: Alle Container im Pod müssen identische, nicht-null Requests und Limits für beide CPU und Speicher haben.

  • Vorteil: Diese Pods werden als letzte bei Ressourcendruck evakuiert, was maximale Stabilität gewährleistet.
  • Anwendungsfall: Kritische Systemkomponenten oder Datenbanken, die strenge Leistungsisolierung erfordern.

2. Burstable

Definition: Mindestens ein Container im Pod hat definierte Requests, aber entweder sind Requests und Limits nicht für alle Container gleich, oder einige Ressourcen sind nicht limitiert (obwohl das Setzen von Limits dringend empfohlen wird).

  • Vorteil: Erlaubt Containern, über ihre Requests hinauszugehen und ungenutzte Kapazität auf dem Knoten bis zu ihren definierten Limits zu nutzen.
  • Evakuierungspriorität: Vor BestEffort-Pods, aber nach Guaranteed-Pods evakuiert.
  • Anwendungsfall: Die meisten standardmäßigen zustandslosen Anwendungen, bei denen leichte Abweichungen in der Latenz akzeptabel sind.

3. BestEffort

Definition: Der Pod hat keine Requests oder Limits für irgendeinen Container definiert.

  • Vorteil: Keiner, außer Einfachheit.
  • Risiko: Diese Pods sind die ersten Kandidaten für die Evakuierung, wenn der Knoten unter Speicherdruck steht. Sie können auch schlecht um CPU konkurrieren, da kein Request deklariert wurde.
  • Anwendungsfall: Nicht-kritische Batch-Jobs oder Logging-Agenten, die leicht neu gestartet werden können.

Praktische Optimierungsstrategien

Effektives Ressourcenmanagement erfordert Messung, Iteration und sorgfältige Planung.

Strategie 1: Messen und Setzen von Requests genau

Requests sollten die Menge an Ressourcen widerspiegeln, die die Anwendung benötigt, um die meiste Zeit akzeptabel zu laufen. Wenn Sie Requests zu hoch setzen, verschwenden Sie Cluster-Kapazität, da der Scheduler diese Kapazität als bereits vergeben betrachtet. Wenn Sie sie zu niedrig setzen, kann der Scheduler zu viele Pods auf einen Knoten platzieren, und der Workload kann bei Konflikten eher leiden.

Verwenden Sie Überwachungswerkzeuge wie Prometheus und Grafana, um Request-Werte mit der tatsächlichen Nutzung zu vergleichen. Ein üblicher Ausgangspunkt ist, mehrere Tage normalen Verkehrs zu betrachten, offensichtliche einmalige Vorfälle zu ignorieren und Requests nahe einem anhaltenden Perzentil zu setzen, anstatt dem einzelnen höchsten Spike. Das genaue Perzentil ist eine politische Entscheidung; der Hauptpunkt ist, Daten zu verwenden und sie zu überprüfen.

Wenn ein Dienst beispielsweise normalerweise 180m CPU verwendet, während des Deploy-Warm-ups auf etwa 450m ansteigt und seltene Spitzen auf 900m während einer bekannten Batch-Aufgabe hat, kann das Setzen eines 900m-Requests den ganzen Tag Kapazität verschwenden. Das Setzen eines 50m-Requests kann den Pod billig zu planen machen, aber instabil unter Konflikten. Ein Request im normalen anhaltenden Bereich, plus separate Handhabung für den Batch-Pfad, ist oft eine bessere Diskussion.

Strategie 2: Definieren konservativer Limits

Limits wirken als Sicherheitsgrenze, sind aber nicht kostenlos. Für Speicher kann ein Limit knapp über dem gemessenen Spitzenverbrauch verhindern, dass ein Container den Knoten verbraucht. Für CPU verhindert ein Limit, dass ein außer Kontrolle geratener Prozess unbegrenzt CPU verwendet, aber aggressive Limits können einen Dienst drosseln, selbst wenn der Knoten freie Kerne hat.

Warnung zu CPU-Limits: Das Setzen von CPU-Limits unterhalb der tatsächlichen Nachfrage kann sichtbare Latenz durch Drosselung verursachen. Burstable QoS ist eine vernünftige Wahl für viele zustandslose Dienste, während Guaranteed QoS besser für Workloads reserviert ist, bei denen der Isolationskompromiss beabsichtigt ist.

Strategie 3: Nutzung des Vertical Pod Autoscaler (VPA)

Manuelles Optimieren von Ressourcen ist schwierig und zeitaufwändig. Der Vertical Pod Autoscaler (VPA) überwacht die Laufzeitnutzung und kann Ressourcen-Requests empfehlen oder aktualisieren, abhängig von seinem Modus. In vielen Setups wird VPA zunächst im Empfehlungsmodus verwendet, damit Teams vorgeschlagene Requests überprüfen können, bevor automatische Aktualisierungen erlaubt werden.

Seien Sie vorsichtig, wenn Sie VPA mit dem Horizontal Pod Autoscaler kombinieren. HPA skaliert oft basierend auf der Auslastung relativ zu Requests, sodass das Ändern von Requests das Skalierungsverhalten ändern kann. Es kann gut funktionieren, sollte aber bewusst getestet werden.

Strategie 4: Resource Quotas für Namespaces

Um Ressourcen-Hogging über Teams oder Umgebungen hinweg zu verhindern, sollten Administratoren Resource Quotas auf Namespace-Ebene verwenden. Eine ResourceQuota erzwingt aggregierte Grenzen für die Gesamtmenge an CPU/Speicher-Requests und -Limits, die in diesem Namespace existieren können, und gewährleistet so Fairness.

Beispiel-Namespace-Quota

apiVersion: v1
kind: ResourceQuota
metadata:
  name: compute-quota
  namespace: development
spec:
  hard:
    requests.cpu: "10"
    limits.memory: "20Gi"

Dies stellt sicher, dass die gesamte angeforderte CPU über alle Pods im development-Namespace 10 Kerne nicht überschreiten kann und die gesamten Speicherlimits 20Gi nicht überschreiten können.

Wie sich schlechte Einstellungen in echten Clustern zeigen

Ressourcenfehler kündigen sich selten als "Ressourcenfehler" an. Sie sehen normalerweise aus wie Anwendungsvorfälle.

Wenn CPU-Limits zu niedrig sind, können Sie hohe Anforderungslatenz sehen, während CPU-Nutzungsdiagramme gedeckelt erscheinen. Der Container möchte mehr CPU, aber die cgroup-Drosselung hält ihn zurück. In Prometheus-basierten Setups können Metriken wie container_cpu_cfs_throttled_periods_total und container_cpu_cfs_periods_total helfen zu zeigen, ob Drosselung Teil der Geschichte ist.

Wenn Speicherlimits zu niedrig sind, kann der Pod mit Reason: OOMKilled neu starten. Die Anwendungsprotokolle können abrupt enden, weil der Prozess kein ordentliches Herunterfahren hatte. kubectl describe pod sagt in diesem Fall normalerweise schneller die Wahrheit als das App-Protokoll.

Wenn Requests zu hoch sind, können Pods in Pending bleiben, obwohl Dashboards eine niedrige durchschnittliche Knotenauslastung zeigen. Der Scheduler platziert Pods nicht basierend auf der durchschnittlichen tatsächlichen Nutzung; er vergleicht angeforderte Ressourcen mit allokierbaren Ressourcen. Deshalb kann ein Cluster unterausgelastet aussehen und dennoch einen neuen Pod ablehnen.

Wenn Requests fehlen, kann der Workload in ruhigen Perioden gut aussehen und dann das Erste sein, das eingeschränkt wird, wenn der Knoten ausgelastet ist. Das kann für Wegwerf-Jobs akzeptabel sein, ist aber eine schlechte Standardeinstellung für benutzerorientierte Dienste.

Ein sicherer Optimierungs-Workflow

Ein praktischer Optimierungszyklus sieht so aus:

  1. Beginnen Sie mit expliziten CPU- und Speicher-Requests für jeden Produktionscontainer, einschließlich Sidecars.
  2. Setzen Sie Speicherlimits basierend auf beobachtetem Spitzenwert plus Spielraum, dann achten Sie auf OOMKilled-Neustarts.
  3. Entscheiden Sie, ob CPU-Limits durch Richtlinien oder Workload-Risiko erforderlich sind. Wenn Sie sie verwenden, überwachen Sie die Drosselung.
  4. Vergleichen Sie angeforderte CPU und Speicher wöchentlich oder monatlich mit der tatsächlichen Nutzung, insbesondere nach großen Releases.
  5. Behandeln Sie die richtige Dimensionierung als Änderungsmanagement. Ein niedrigerer Request kann die Bin-Packing-Dichte erhöhen, aber auch das Ausfallverhalten bei Knotendruck ändern.

Sidecars verdienen Aufmerksamkeit. Ein Service-Mesh-Proxy, Log-Shipper oder Sicherheitsagent kann genug CPU oder Speicher verbrauchen, um den tatsächlichen Fußabdruck des Pods zu ändern. Wenn nur der Haupt-App-Container optimiert wird, kann der Pod dem Scheduler immer noch falsch dargestellt werden.

Beispiel: Behebung eines durch CPU-Drosselung verursachten Latenzanstiegs

Stellen Sie sich einen API-Container mit dieser Konfiguration vor:

resources:
  requests:
    cpu: "100m"
    memory: "256Mi"
  limits:
    cpu: "200m"
    memory: "512Mi"

Während eines Verkaufsevents steigt die Latenz. Der Knoten hat noch freie CPU, aber der Container ist auf 200m gedeckelt. Das Erhöhen der Replikate kann helfen, aber jeder Pod ist immer noch einzeln gedrosselt. Eine bessere Lösung könnte sein, das CPU-Limit zu erhöhen oder zu entfernen, den Request zu erhöhen, um der normalen anhaltenden Nachfrage zu entsprechen, und HPA zu verwenden, damit der Dienst skaliert, bevor die Latenz hässlich wird.

Die wichtige Lektion ist, dass eine niedrige CPU-Nutzung im Diagramm nicht immer bedeutet, dass die App im Leerlauf ist. Es kann bedeuten, dass die App nicht mehr verwenden darf.

Abschließende Überprüfung

Requests sagen Kubernetes, wie der Pod platziert und priorisiert werden soll. Limits sagen dem Kernel, wo er ihn stoppen soll. Gute Werte stammen aus echten Metriken, Lasttests und einer klaren Entscheidung darüber, was für jeden Workload wichtiger ist: Dichte, Isolierung, Latenz oder Kosten. Überprüfen Sie die Werte nach Verkehrsänderungen, Abhängigkeitsänderungen und Laufzeit-Upgrades. Veraltete Ressourceneinstellungen sind einer der leisesten Wege, wie ein Cluster in schlechte Leistung abdriftet.