CPU-Limits und aggressive Drosselung in Kubernetes

Notiz. übersetzen: Diese aufschlussreiche Geschichte von Omio – einem europäischen Reiseaggregator – führt die Leser von der grundlegenden Theorie zu den faszinierenden praktischen Feinheiten der Kubernetes-Konfiguration. Die Kenntnis solcher Fälle hilft nicht nur, Ihren Horizont zu erweitern, sondern auch nicht triviale Probleme zu vermeiden.

CPU-Limits und aggressive Drosselung in Kubernetes

Haben Sie schon einmal erlebt, dass eine Anwendung nicht mehr funktioniert, auf Gesundheitschecks nicht mehr reagiert und Sie nicht herausfinden können, warum? Eine mögliche Erklärung hängt mit den Kontingentgrenzen für CPU-Ressourcen zusammen. Darüber werden wir in diesem Artikel sprechen.

TL; DR:
Wir empfehlen dringend, die CPU-Grenzwerte in Kubernetes (oder die CFS-Kontingente in Kubelet) zu deaktivieren, wenn Sie eine Version des Linux-Kernels mit einem CFS-Kontingentfehler verwenden. Im Kern имеется ernst und sehr bekannt ein Fehler, der zu übermäßiger Drosselung und Verzögerungen führt
.

In Omio Die gesamte Infrastruktur wird von Kubernetes verwaltet. Alle unsere zustandsbehafteten und zustandslosen Arbeitslasten laufen ausschließlich auf Kubernetes (wir verwenden Google Kubernetes Engine). In den letzten sechs Monaten begannen wir, zufällige Verlangsamungen zu beobachten. Anwendungen frieren ein oder reagieren nicht mehr auf Gesundheitsprüfungen, verlieren die Verbindung zum Netzwerk usw. Dieses Verhalten verwirrte uns lange und wir beschlossen schließlich, das Problem ernst zu nehmen.

Zusammenfassung des Artikels:

  • Ein paar Worte zu Containern und Kubernetes;
  • Wie CPU-Anforderungen und -Limits implementiert werden;
  • Funktionsweise des CPU-Limits in Multi-Core-Umgebungen;
  • So verfolgen Sie die CPU-Drosselung;
  • Problemlösung und Nuancen.

Ein paar Worte zu Containern und Kubernetes

Kubernetes ist im Wesentlichen der moderne Standard in der Infrastrukturwelt. Seine Hauptaufgabe ist die Container-Orchestrierung.

Container

In der Vergangenheit mussten wir Artefakte wie Java JARs/WARs, Python Eggs oder ausführbare Dateien erstellen, um sie auf Servern auszuführen. Damit sie jedoch funktionieren, mussten zusätzliche Arbeiten durchgeführt werden: Installation der Laufzeitumgebung (Java/Python), Platzierung der erforderlichen Dateien an den richtigen Stellen, Sicherstellung der Kompatibilität mit einer bestimmten Version des Betriebssystems usw. Mit anderen Worten: Dem Konfigurationsmanagement musste große Aufmerksamkeit geschenkt werden (was häufig zu Streitigkeiten zwischen Entwicklern und Systemadministratoren führte).

Container haben alles verändert. Jetzt ist das Artefakt ein Containerbild. Es kann als eine Art erweiterte ausführbare Datei dargestellt werden, die nicht nur das Programm, sondern auch eine vollwertige Ausführungsumgebung (Java/Python/...) sowie die erforderlichen Dateien/Pakete enthält, vorinstalliert und einsatzbereit laufen. Container können ohne zusätzliche Schritte auf verschiedenen Servern bereitgestellt und ausgeführt werden.

Darüber hinaus arbeiten Container in ihrer eigenen Sandbox-Umgebung. Sie verfügen über einen eigenen virtuellen Netzwerkadapter, ein eigenes Dateisystem mit eingeschränktem Zugriff, eine eigene Prozesshierarchie, eigene Einschränkungen bei CPU und Speicher usw. All dies wird dank eines speziellen Subsystems des Linux-Kernels – Namespaces – implementiert.

Kubernetes

Wie bereits erwähnt, ist Kubernetes ein Container-Orchestrator. Das funktioniert so: Sie geben ihm einen Maschinenpool und sagen dann: „Hey, Kubernetes, lass uns zehn Instanzen meines Containers mit jeweils 2 Prozessoren und 3 GB Speicher starten und sie am Laufen halten!“ Kubernetes kümmert sich um den Rest. Es findet freie Kapazitäten, startet Container und startet sie bei Bedarf neu, rollt bei Versionswechsel ein Update aus usw. Im Wesentlichen ermöglicht Ihnen Kubernetes die Abstrahierung der Hardwarekomponente und macht eine Vielzahl von Systemen für die Bereitstellung und Ausführung von Anwendungen geeignet.

CPU-Limits und aggressive Drosselung in Kubernetes
Kubernetes aus der Sicht des Laien

Was sind Anfragen und Limits in Kubernetes?

Okay, wir haben Container und Kubernetes behandelt. Wir wissen auch, dass sich mehrere Container auf derselben Maschine befinden können.

Eine Analogie lässt sich mit einer Gemeinschaftswohnung ziehen. Es werden großzügige Räumlichkeiten (Maschinen/Aggregate) übernommen und an mehrere Mieter (Container) vermietet. Kubernetes fungiert als Immobilienmakler. Es stellt sich die Frage, wie Mieter vor Konflikten untereinander bewahrt werden können. Was wäre, wenn einer von ihnen beispielsweise beschließt, das Badezimmer für den halben Tag auszuleihen?

Hier kommen Wünsche und Grenzen ins Spiel. CPU PREISANFRAGE (Request) werden ausschließlich zu Planungszwecken benötigt. Dies ist so etwas wie eine „Wunschliste“ des Containers und dient der Auswahl des am besten geeigneten Knotens. Gleichzeitig die CPU Begrenzen vergleichbar mit einem Mietvertrag – sobald wir eine Einheit für den Container auswählen, wird die wird nicht in der Lage sein über festgelegte Grenzen hinausgehen. Und hier entsteht das Problem...

Wie Anfragen und Limits in Kubernetes implementiert werden

Kubernetes verwendet einen im Kernel integrierten Drosselungsmechanismus (Überspringen von Taktzyklen), um CPU-Grenzwerte zu implementieren. Wenn eine Anwendung das Limit überschreitet, wird die Drosselung aktiviert (d. h. sie erhält weniger CPU-Zyklen). Anforderungen und Speichergrenzen sind anders organisiert, sodass sie leichter zu erkennen sind. Überprüfen Sie dazu einfach den letzten Neustartstatus des Pods: ob er „OOMKilled“ ist. Die CPU-Drosselung ist nicht so einfach, da K8s Metriken nur nach Nutzung und nicht nach Kontrollgruppen bereitstellt.

CPU-Anfrage

CPU-Limits und aggressive Drosselung in Kubernetes
Wie die CPU-Anfrage implementiert wird

Der Einfachheit halber betrachten wir den Vorgang am Beispiel einer Maschine mit einer 4-Kern-CPU.

K8s verwendet einen Kontrollgruppenmechanismus (cgroups), um die Zuweisung von Ressourcen (Speicher und Prozessor) zu steuern. Dafür steht ein hierarchisches Modell zur Verfügung: Das Kind erbt die Grenzen der Elterngruppe. Die Verteilungsdetails werden in einem virtuellen Dateisystem gespeichert (/sys/fs/cgroup). Im Falle eines Prozessors ist dies der Fall /sys/fs/cgroup/cpu,cpuacct/*.

K8s verwendet Datei cpu.share um Prozessorressourcen zuzuweisen. In unserem Fall erhält die Root-Cgroup 4096 Anteile an CPU-Ressourcen – 100 % der verfügbaren Prozessorleistung (1 Kern = 1024; dies ist ein fester Wert). Die Stammgruppe verteilt Ressourcen proportional zu den Anteilen der in registrierten Nachkommen cpu.share, und sie wiederum tun dasselbe mit ihren Nachkommen usw. Auf einem typischen Kubernetes-Knoten hat die Root-Cgroup drei untergeordnete Elemente: system.slice, user.slice и kubepods. Die ersten beiden Untergruppen werden verwendet, um Ressourcen zwischen kritischen Systemlasten und Benutzerprogrammen außerhalb von K8s zu verteilen. Letzter - kubepods – erstellt von Kubernetes, um Ressourcen zwischen Pods zu verteilen.

Das obige Diagramm zeigt, dass die erste und zweite Untergruppe jeweils erhalten haben 1024 Anteile, mit zugewiesener Kuberpod-Untergruppe 4096 Anteile Wie ist das möglich: Schließlich hat nur die Root-Gruppe Zugriff auf 4096 Anteile, und die Summe der Anteile ihrer Nachkommen übersteigt diese Zahl deutlich (6144)? Der Punkt ist, dass der Wert logisch sinnvoll ist, sodass der Linux-Scheduler (CFS) ihn verwendet, um CPU-Ressourcen proportional zuzuweisen. In unserem Fall erhalten die ersten beiden Gruppen 680 echte Anteile (16,6 % von 4096), und Kubepod erhält den Rest 2736 Anteile Im Falle einer Ausfallzeit nutzen die ersten beiden Gruppen die zugewiesenen Ressourcen nicht.

Glücklicherweise verfügt der Scheduler über einen Mechanismus, um die Verschwendung ungenutzter CPU-Ressourcen zu vermeiden. Es überträgt „ungenutzte“ Kapazität an einen globalen Pool, von wo aus sie an Gruppen verteilt wird, die zusätzliche Prozessorleistung benötigen (die Übertragung erfolgt stapelweise, um Rundungsverluste zu vermeiden). Eine ähnliche Methode wird auf alle Nachkommen von Nachkommen angewendet.

Dieser Mechanismus sorgt für eine gerechte Verteilung der Prozessorleistung und stellt sicher, dass kein Prozess anderen Ressourcen „stiehlt“.

CPU-Limit

Trotz der Tatsache, dass die Konfigurationen von Grenzwerten und Anforderungen in K8s ähnlich aussehen, ist ihre Implementierung radikal anders: Dies am irreführendsten und der am wenigsten dokumentierte Teil.

K8s greift ein CFS-Quotenmechanismus Grenzen umzusetzen. Ihre Einstellungen werden in Dateien festgelegt cfs_period_us и cfs_quota_us im cgroup-Verzeichnis (dort befindet sich auch die Datei). cpu.share).

Im Gegensatz zu cpu.share, auf der die Quote basiert Zeitraum, und nicht auf der verfügbaren Prozessorleistung. cfs_period_us Gibt die Dauer der Periode (Epoche) an – sie beträgt immer 100000 μs (100 ms). Es besteht die Möglichkeit, diesen Wert in K8s zu ändern, diese ist jedoch derzeit nur in der Alphaversion verfügbar. Der Scheduler verwendet die Epoche, um verbrauchte Kontingente neu zu starten. Zweite Datei cfs_quota_us, gibt die verfügbare Zeit (Kontingent) in jeder Epoche an. Beachten Sie, dass die Angabe auch in Mikrosekunden erfolgt. Das Kontingent kann die Epochenlänge überschreiten; mit anderen Worten, sie kann größer als 100 ms sein.

Schauen wir uns zwei Szenarien auf 16-Core-Rechnern an (dem häufigsten Computertyp, den wir bei Omio haben):

CPU-Limits und aggressive Drosselung in Kubernetes
Szenario 1: 2 Threads und ein 200-ms-Limit. Keine Drosselung

CPU-Limits und aggressive Drosselung in Kubernetes
Szenario 2: 10 Threads und 200 ms-Grenze. Die Drosselung beginnt nach 20 ms, der Zugriff auf Prozessorressourcen wird nach weiteren 80 ms wieder aufgenommen

Nehmen wir an, Sie legen das CPU-Limit auf fest 2 Kerne; Kubernetes übersetzt diesen Wert in 200 ms. Das bedeutet, dass der Container maximal 200 ms CPU-Zeit ohne Drosselung nutzen kann.

Und hier beginnt der Spaß. Wie oben erwähnt beträgt das verfügbare Kontingent 200 ms. Wenn Sie parallel arbeiten zehn Threads auf einer 12-Kern-Maschine (siehe Abbildung für Szenario 2), während alle anderen Pods im Leerlauf sind, ist das Kontingent in nur 20 ms erschöpft (da 10 * 20 ms = 200 ms) und alle Threads dieses Pods bleiben hängen » (drosseln) für die nächsten 80 ms. Das bereits erwähnte Scheduler-Fehler, wodurch es zu einer übermäßigen Drosselung kommt und der Container nicht einmal das vorhandene Kontingent erfüllen kann.

Wie kann die Drosselung in Pods bewertet werden?

Melden Sie sich einfach beim Pod an und führen Sie den Vorgang aus cat /sys/fs/cgroup/cpu/cpu.stat.

  • nr_periods — die Gesamtzahl der Zeitplanerperioden;
  • nr_throttled — Anzahl der gedrosselten Perioden in der Komposition nr_periods;
  • throttled_time – kumulative gedrosselte Zeit in Nanosekunden.

CPU-Limits und aggressive Drosselung in Kubernetes

Was ist wirklich los?

Dadurch kommt es bei allen Anwendungen zu einer hohen Drosselung. Manchmal ist er dabei eineinhalb Mal stärker als berechnet!

Dies führt zu verschiedenen Fehlern – Fehler bei der Bereitschaftsprüfung, Einfrieren von Containern, Unterbrechungen der Netzwerkverbindung, Zeitüberschreitungen bei Serviceaufrufen. Dies führt letztendlich zu einer erhöhten Latenz und höheren Fehlerraten.

Entscheidung und Konsequenzen

Hier ist alles einfach. Wir haben die CPU-Grenzwerte aufgegeben und mit der Aktualisierung des Betriebssystemkernels in Clustern auf die neueste Version begonnen, in der der Fehler behoben wurde. Die Anzahl der Fehler (HTTP 5xx) in unseren Diensten ist sofort deutlich gesunken:

HTTP 5xx-Fehler

CPU-Limits und aggressive Drosselung in Kubernetes
HTTP 5xx-Fehler für einen kritischen Dienst

Reaktionszeit S. 95

CPU-Limits und aggressive Drosselung in Kubernetes
Kritische Serviceanfragelatenz, 95. Perzentil

Betriebskosten

CPU-Limits und aggressive Drosselung in Kubernetes
Anzahl der aufgewendeten Instanzstunden

Was ist der Haken?

Wie am Anfang des Artikels angegeben:

Eine Analogie lässt sich zu einer Gemeinschaftswohnung ziehen. Kubernetes fungiert als Immobilienmakler. Doch wie kann man Mieter vor Konflikten untereinander bewahren? Was wäre, wenn einer von ihnen beispielsweise beschließt, das Badezimmer für den halben Tag auszuleihen?

Hier ist der Haken. Ein unachtsamer Container kann alle verfügbaren CPU-Ressourcen einer Maschine verschlingen. Wenn Sie über einen intelligenten Anwendungsstapel verfügen (z. B. JVM, Go, Node VM sind ordnungsgemäß konfiguriert), ist dies kein Problem: Sie können unter solchen Bedingungen lange arbeiten. Wenn Anwendungen jedoch schlecht oder gar nicht optimiert sind (FROM java:latest), könnte die Situation außer Kontrolle geraten. Bei Omio verfügen wir über automatisierte Docker-Basisdateien mit angemessenen Standardeinstellungen für den wichtigsten Sprachstapel, sodass dieses Problem nicht bestand.

Wir empfehlen, die Kennzahlen zu überwachen VERWENDUNG (Nutzung, Sättigung und Fehler), API-Verzögerungen und Fehlerraten. Stellen Sie sicher, dass die Ergebnisse den Erwartungen entsprechen.

Referenzen

Das ist unsere Geschichte. Die folgenden Materialien haben sehr geholfen, das Geschehen zu verstehen:

Kubernetes-Fehlerberichte:

Sind Sie in Ihrer Praxis auf ähnliche Probleme gestoßen oder haben Sie Erfahrungen mit der Drosselung in containerisierten Produktionsumgebungen? Teilen Sie Ihre Geschichte in den Kommentaren!

PS vom Übersetzer

Lesen Sie auch auf unserem Blog:

Source: habr.com

Kommentar hinzufügen