CPU-Limits und aggressive Drosselung in Kubernetes

Hinweis perev. : Diese warnende Geschichte von Omio, dem europäischen Reiseaggregator, führt die Leser von der Grundtheorie zu den faszinierenden praktischen Feinheiten der Kubernetes-Konfiguration. Die Vertrautheit mit solchen Fällen hilft nicht nur, den Horizont zu erweitern, sondern auch nicht triviale Probleme zu vermeiden.



Haben Sie jemals festgestellt, dass die Anwendung "festgefahren" ist, nicht mehr auf Anfragen zur Überprüfung des Zustands reagiert und Sie den Grund für dieses Verhalten nicht verstehen konnten? Eine mögliche Erklärung ist das Kontingentlimit für CPU-Ressourcen. Er wird in diesem Artikel besprochen.

TL; DR:
Wir empfehlen dringend, die CPU-Beschränkungen in Kubernetes zu deaktivieren (oder CFS-Kontingente in Kubelet zu deaktivieren), wenn Sie eine Linux-Kernelversion mit einem CFS-Kontingentfehler verwenden. Im Kern daEin schwerwiegender und bekannter Fehler, der zu übermäßiger Drosselung und Verzögerungen führt
.

Bei Omio wird die gesamte Infrastruktur von Kubernetes verwaltet . Alle unsere zustandsbehafteten und zustandslosen Lasten funktionieren ausschließlich auf Kubernetes (wir verwenden die Google Kubernetes Engine). In den letzten sechs Monaten haben wir zufällige Verlangsamungen beobachtet. Anwendungen frieren ein oder reagieren nicht mehr auf Integritätsprüfungen, verlieren ihre Verbindung zum Netzwerk usw. Ein solches Verhalten hat uns lange verwirrt, und schließlich haben wir beschlossen, das Problem genau anzugehen.

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 Drosselung der CPU;
  • Das Problem und die Nuancen lösen.

Ein paar Worte zu Containern und Kubernetes


Kubernetes ist in der Tat der moderne Standard in der Welt der Infrastruktur. Ihre Hauptaufgabe ist die Orchestrierung von Containern.

Behälter


In der Vergangenheit mussten wir Artefakte wie Java JARs / WARs, Python Eggs oder ausführbare Dateien für den späteren Start auf Servern erstellen. Damit sie jedoch funktionieren, mussten sie zusätzliche Arbeit leisten: Installieren Sie die Laufzeit (Java / Python), platzieren Sie die erforderlichen Dateien an den richtigen Stellen, stellen Sie die Kompatibilität mit einer bestimmten Version des Betriebssystems sicher usw. Mit anderen Worten, Sie mussten genau auf das Konfigurationsmanagement achten (was häufig zu Konflikten zwischen Entwicklern und Systemadministratoren führte).

Container haben alles verändert.Jetzt fungiert das Containerbild als Artefakt. Es kann als eine Art erweiterte ausführbare Datei dargestellt werden, die nicht nur ein Programm, sondern auch eine vollständige Laufzeit (Java / Python / ...) sowie die erforderlichen Dateien / Pakete enthält, die vorinstalliert und zur Ausführung bereit sind. 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 haben einen eigenen virtuellen Netzwerkadapter, ein eigenes Dateisystem mit eingeschränktem Zugriff, eine eigene Prozesshierarchie, eigene Einschränkungen für CPU und Speicher usw. All dies wird durch ein spezielles Subsystem des Linux-Kernels realisiert - Namespaces (Namespace).

Kubernetes


Wie bereits erwähnt, ist Kubernetes ein Containerorchester. Es funktioniert wie folgt: Sie stellen ihm einen Pool von Maschinen zur Verfügung und sagen dann: "Hey Kubernetes, starten Sie zehn Instanzen meines Containers mit jeweils 2 Prozessoren und 3 GB Speicher und halten Sie sie betriebsbereit!" Kubernetes kümmert sich um den Rest. Er findet freie Kapazitäten, startet Container und startet sie bei Bedarf neu, führt ein Update ein, wenn Versionen geändert werden usw. Mit Kubernetes können Sie von der Hardwarekomponente abstrahieren und die gesamte Systemvielfalt für die Bereitstellung und den Betrieb von Anwendungen geeignet machen.


Kubernetes aus der Sicht eines einfachen Laien

Was sind Anfrage und Limit in Kubernetes


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

Sie können eine Analogie mit einer Gemeinschaftswohnung ziehen. Ein geräumiger Raum wird genommen (Autos / Knoten) und an mehrere Mieter (Container) vermietet. Kubernetes fungiert als Makler. Es stellt sich die Frage, wie Mieter vor Konflikten geschützt werden können. Was ist, wenn einer von ihnen beschließt, einen halben Tag im Badezimmer zu verbringen?

Hier kommen Anfrage und Limit ins Spiel. Die CPU- Anforderung dient nur zu Planungszwecken. Dies ist so etwas wie die "Wunschliste" eines Containers und wird verwendet, um den am besten geeigneten Knoten auszuwählen. Gleichzeitig kann das CPU- Limit mit einem Leasing verglichen werden - sobald wir einen Knoten für den Container abholenwird nicht in der Lage sein , die festgelegten Grenzen zu überschreiten. Und hier entsteht ein Problem ...

Wie Anforderungen und Limits in Kubernetes implementiert werden


Kubernetes verwendet den Kernel-Throttling-Mechanismus (Skipping Clock), um CPU-Limits zu implementieren. Wenn die Anwendung den Grenzwert überschreitet, wird die Drosselung aktiviert (d. H. Sie empfängt weniger CPU-Zyklen). Speicheranforderungen und Speicherbeschränkungen sind unterschiedlich organisiert, sodass sie leichter zu erkennen sind. Dazu reicht es aus, den Status des letzten Pod-Neustarts zu überprüfen: ob es sich um "OOMKilled" handelt. Mit der Drosselung der CPU ist nicht alles so einfach, da K8s nur Metriken zur Verwendung und nicht für cgroups zur Verfügung stellt.

CPU-Anfrage



Implementierung der CPU-Anforderung

Betrachten wir der Einfachheit halber einen Prozess anhand eines Beispiels einer Maschine mit einer 4-Kern-CPU.

K8s verwendet den cgroups-Mechanismus, um die Zuweisung von Ressourcen (Speicher und Prozessor) zu steuern. Für ihn steht ein hierarchisches Modell zur Verfügung: Ein Nachkomme erbt die Grenzen der übergeordneten Gruppe. Verteilungsdetails werden im virtuellen Dateisystem ( /sys/fs/cgroup) gespeichert . Im Falle des Prozessors ist dies /sys/fs/cgroup/cpu,cpuacct/*.

K8s verwendet die Datei cpu.share, um Prozessorressourcen zuzuweisen. In unserem Fall erhält die Root-Kontrollgruppe 4096 Anteile an CPU-Ressourcen - 100% der verfügbaren Prozessorleistung (1 Kern = 1024; dies ist ein fester Wert). Die Stammgruppe verteilt die Ressourcen proportional in Abhängigkeit von den Anteilen der Nachkommen, die in vorgeschrieben sindcpu.shareund diese wiederum tun dasselbe mit ihren Nachkommen usw. Typischerweise Kubernetes Wurzelknoten Kontrollgruppe hat drei Kinder: system.slice, user.sliceund kubepods. Die ersten beiden Untergruppen werden verwendet, um Ressourcen zwischen kritischen Systemlasten und Benutzerprogrammen außerhalb von K8s zu verteilen. Das letzte - - kubepodswird von Kubernetes erstellt, um Ressourcen zwischen Pods zu verteilen.

Das obige Diagramm zeigt, dass die erste und die zweite Untergruppe 1024 Aktien erhalten haben, wobei 4096 Aktien der Kuberpod-Untergruppe zugeordnet sind . Wie ist das möglich? Schließlich stehen der Stammgruppe nur 4096 Aktien zur Verfügung, und die Summe der Aktien ihrer Nachkommen übersteigt diese Zahl erheblich ( 6144))? Tatsache ist, dass der Wert logisch sinnvoll ist, sodass der Linux Scheduler (CFS) ihn verwendet, um die CPU-Ressourcen proportional zuzuweisen. In unserem Fall erhalten die ersten beiden Gruppen 680 echte Aktien (16,6% von 4096) und Kubepod die restlichen 2736 Aktien. Im Falle von Ausfallzeiten verwenden die ersten beiden Gruppen die zugewiesenen Ressourcen nicht.

Glücklicherweise verfügt der Scheduler über einen Mechanismus, um den Verlust nicht verwendeter CPU-Ressourcen zu vermeiden. Es überträgt "Leerlauf" -Kapazitäten an den globalen Pool, von dem sie auf Gruppen verteilt werden, die zusätzliche Prozessorkapazitäten benötigen (die Übertragung erfolgt in Stapeln, um Rundungsverluste zu vermeiden). Eine ähnliche Methode gilt für alle Nachkommen von Nachkommen.

Dieser Mechanismus gewährleistet 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, unterscheidet sich ihre Implementierung grundlegend: Dies ist der irreführendste und am wenigsten dokumentierte Teil.

K8s verwendet den CFS-Quotenmechanismus , um Grenzwerte zu implementieren. Ihre Einstellungen werden in den Dateien cfs_period_usund cfs_quota_usim Verzeichnis cgroup angegeben (die Datei befindet sich auch dort cpu.share).

Im Gegensatz cpu.sharedazu basiert das Kontingent auf einem bestimmten Zeitraum und nicht auf der verfügbaren Prozessorleistung. cfs_period_usLegt die Dauer der Periode (Ära) fest - sie beträgt immer 100.000 μs (100 ms). K8s kann diesen Wert ändern, ist jedoch derzeit nur in der Alpha-Version verfügbar. Der Scheduler verwendet die Ära, um verwendete Kontingente neu zu starten. Zweite Dateicfs_quota_us, legt die verfügbare Zeit (Kontingent) in jeder Epoche fest. Bitte beachten Sie, dass dies auch in Mikrosekunden angezeigt wird. Die Quote kann die Dauer der Ära überschreiten; Mit anderen Worten kann es mehr als 100 ms sein.

Schauen wir uns zwei Szenarien auf 16-Core-Computern an (der häufigste Computertyp in Omio):


Szenario 1: 2-Threads und ein Limit von 200 ms. Ohne Drosselung


Szenario 2: 10 fließt und ein Limit von 200 ms. Die Drosselung beginnt nach 20 ms, der Zugriff auf die Prozessorressourcen wird nach weiteren 80 ms fortgesetzt.

Angenommen, Sie setzen das CPU-Limit auf 2 Kerne. Kubernetes übersetzt diesen Wert in 200 ms. Dies bedeutet, dass der Container maximal 200 ms CPU-Zeit ohne Drosselung verwenden kann.

Und hier beginnt der Spaß. Wie oben erwähnt, beträgt das verfügbare Kontingent 200 ms. Wenn auf einem 12-Core-Computer zehn Threads parallel ausgeführt werden (siehe Abbildung für Szenario 2), während alle anderen Pods inaktiv sind, ist das Kontingent in nur 20 ms (da 10 * 20 ms = 200 ms) und allen Threads erschöpft von diesem Pod wird für die nächsten 80 ms gedrosselt . Der bereits erwähnte Scheduler-Fehler verschlimmert die Situation , aufgrund derer eine übermäßige Drosselung auftritt und der Container das vorhandene Kontingent nicht einmal berechnen kann.

Wie wird die Drosselung in Pods bewertet?


Geh einfach zum Pod und renne cat /sys/fs/cgroup/cpu/cpu.stat.

  • nr_periods - die Gesamtzahl der Perioden des Planers;
  • nr_throttled- die Anzahl der gedrosselten Perioden in der Komposition nr_periods;
  • throttled_time - kumulative Drosselzeit in Nanosekunden.



Was ist wirklich los?


Infolgedessen erhalten wir in allen Anwendungen eine hohe Drosselung. Manchmal ist es eineinhalb Mal stärker als das berechnete!

Dies führt zu verschiedenen Fehlern - Bereitschaftsprüfungsfehler, Container-Hänge, Unterbrechungen der Netzwerkverbindung, Zeitüberschreitungen bei Serviceabrufen. Dies führt letztendlich zu einer erhöhten Latenz und erhöhten Fehlern.

Entscheidung und Konsequenzen


Hier ist alles einfach. Wir haben die CPU-Grenzwerte aufgegeben und damit begonnen, den Betriebssystemkern in Clustern auf die neueste Version zu aktualisieren, in der der Fehler behoben wurde. Die Anzahl der Fehler (HTTP 5xx) in unseren Diensten ist sofort erheblich gesunken:

HTTP-Fehler 5xx



HTTP 5xx-Fehler eines kritischen Dienstes

P95 Reaktionszeit



Verzögerung bei kritischen Serviceanfragen, 95. Perzentil

Betriebskosten



Anzahl der verbrachten Stunden

Was ist der Haken?


Wie am Anfang des Artikels angegeben:

Sie können eine Analogie mit einer Gemeinschaftswohnung ziehen ... Kubernetes fungiert als Makler. Aber wie können Mieter vor Konflikten geschützt werden? Was ist, wenn einer von ihnen beschließt, einen halben Tag im Badezimmer zu verbringen?

Das ist der Haken. Ein fahrlässiger Container kann alle verfügbaren Prozessorressourcen auf der Maschine aufnehmen. 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), kann die Situation außer Kontrolle geraten. Wir bei Omio haben grundlegende Docker-Dateien mit angemessenen Standardeinstellungen für den Stapel der Hauptsprachen automatisiert, sodass es kein solches Problem gab.

Wir empfehlen, dass Sie USE- Metriken (Nutzung, Sättigung und Fehler), API-Verzögerungen und Fehlerraten überwachen . Stellen Sie sicher, dass die Ergebnisse wie erwartet sind.

Verweise


Das ist unsere Geschichte. Die folgenden Materialien haben wesentlich dazu beigetragen, das Geschehen zu verstehen:


Kubernetes-Fehlerberichterstattung:


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

PS vom Übersetzer


Lesen Sie auch in unserem Blog:


All Articles