Am 27. April wurde auf der
Strike-2019- Konferenz im Rahmen der DevOps-Sektion ein Bericht zum Thema „Automatische Skalierung und Ressourcenverwaltung in Kubernetes“ erstellt. Es wird erläutert, wie K8s verwendet werden, um eine hohe Verfügbarkeit von Anwendungen und deren maximale Leistung sicherzustellen.

Aus Tradition freuen wir uns, ein
Video mit einem Bericht (44 Minuten, viel informativer als der Artikel) und dem Hauptdruck in Textform zu präsentieren. Lass uns gehen!
Wir werden das Thema des Berichts anhand der Wörter analysieren und am Ende beginnen.
Kubernetes
Lassen Sie uns Docker-Container auf dem Host haben. Warum? Um Wiederholbarkeit und Isolation zu gewährleisten, was wiederum eine einfache und gute Bereitstellung ermöglicht, CI / CD. Wir haben viele solcher Maschinen mit Containern.
Was gibt Kubernetes in diesem Fall?
- Wir hören auf, an diese Maschinen zu denken, und beginnen mit der „Cloud“ zu arbeiten, einer Gruppe von Containern oder Pods (Gruppen von Containern).
- Darüber hinaus denken wir nicht einmal an einzelne Pods, sondern verwalten größere Gruppen. Mit solchen übergeordneten Grundelementen können wir sagen, dass es eine Vorlage zum Starten einer bestimmten Arbeitslast gibt, jedoch die erforderliche Anzahl von Instanzen für deren Start. Wenn wir die Vorlage anschließend ändern, ändern sich auch alle Instanzen.
- Mithilfe der deklarativen API beschreiben wir das von Kubernetes erstellte „Weltgerät“ (in YAML) , anstatt eine Folge bestimmter Befehle auszuführen. Und noch einmal: Wenn sich die Beschreibung ändert, ändert sich auch die tatsächliche Anzeige.
Ressourcenmanagement
CPU
Lassen Sie uns nginx, php-fpm und mysql auf dem Server ausführen. Diese Dienste werden sogar noch mehr laufende Prozesse haben, für die jeweils Rechenressourcen erforderlich sind:
(Die Zahlen auf der Folie sind "Papageien", das abstrakte Bedürfnis jedes Prozesses nach Rechenleistung)Um die Arbeit damit zu vereinfachen, ist es logisch, Prozesse zu Gruppen zusammenzufassen (z. B. alle Nginx-Prozesse zu einer „Nginx“ -Gruppe). Eine einfache und offensichtliche Möglichkeit, dies zu tun, besteht darin, jede Gruppe in einen Container zu legen:

Um fortzufahren, müssen Sie sich merken, was ein Container ist (unter Linux). Ihr Erscheinungsbild wurde durch drei wichtige Funktionen im Kernel ermöglicht, die seit langem implementiert sind:
Funktionen ,
Namespaces und
cgroups . Andere Technologien (einschließlich praktischer "Shells" wie Docker) trugen zur Weiterentwicklung bei:

Im Kontext des Berichts sind wir nur an
cgroups interessiert, da Kontrollgruppen Teil der Funktionalität von Containern (Docker usw.) sind, die das Ressourcenmanagement implementieren. Die Prozesse, die, wie wir wollten, in Gruppen zusammengefasst sind, sind die Kontrollgruppen.
Kehren wir zu den CPU-Anforderungen für diese Prozesse und jetzt für die Prozessgruppen zurück:
(Ich wiederhole, dass alle Zahlen ein abstrakter Ausdruck der Ressourcenanforderungen sind.)Gleichzeitig verfügt die CPU selbst über eine bestimmte endgültige Ressource
(im Beispiel 1000) , die möglicherweise nicht für alle ausreicht (die Summe der Anforderungen aller Gruppen beträgt 150 + 850 + 460 = 1460). Was wird in diesem Fall passieren?
Der Kernel beginnt, Ressourcen zu verteilen und tut dies „ehrlich“, indem er jeder Gruppe die gleiche Menge an Ressourcen zur Verfügung stellt. Im ersten Fall gibt es jedoch mehr als nötig (333> 150), sodass der Überschuss (333-150 = 183) in der Reserve verbleibt, die ebenfalls gleichmäßig auf zwei andere Container verteilt ist:

Als Ergebnis: Der erste Container hatte genug Ressourcen, der zweite - war nicht genug, der dritte - war nicht genug. Dies ist das Ergebnis des
"ehrlichen" Schedulers in Linux -
CFS . Seine Arbeit kann reguliert werden, indem jedem Behälter ein
Gewicht zugewiesen wird. Zum Beispiel so:

Schauen wir uns den Fall eines Ressourcenmangels im zweiten Container (php-fpm) an. Alle Containerressourcen werden gleichmäßig auf die Prozesse verteilt. Infolgedessen funktioniert der Master-Prozess gut, und alle Mitarbeiter werden langsamer, da sie weniger als die Hälfte des benötigten Bedarfs erhalten haben:

So funktioniert der CFS-Scheduler. Die Gewichte, die wir den Containern zuweisen, werden in Zukunft als
Anfragen bezeichnet . Warum so - siehe unten.
Schauen wir uns die ganze Situation von der anderen Seite an. Wie Sie wissen, führen alle Wege nach Rom und im Falle eines Computers zur CPU. Eine CPU, viele Aufgaben - Sie brauchen eine Ampel. Der einfachste Weg, Ressourcen zu verwalten, ist die Ampel: Sie geben einem Prozess eine feste Zugriffszeit auf die CPU, dann dem nächsten usw.

Dieser Ansatz wird als
harte Begrenzung bezeichnet . Denken Sie daran, nur als
Grenzen . Wenn Sie jedoch Grenzwerte auf alle Container verteilen, tritt ein Problem auf: MySQL fuhr die Straße entlang und irgendwann endete sein Bedarf an einer CPU, aber alle anderen Prozesse mussten warten, während die CPU im
Leerlauf war .

Kehren wir zum Linux-Kernel und seiner Interaktion mit der CPU zurück - das Gesamtbild sieht wie folgt aus:

Cgroup verfügt über zwei Einstellungen. Dies sind zwei einfache „Wendungen“, mit denen Sie Folgendes bestimmen können:
- Gewicht für den Container (Anfrage) ist Aktien ;
- Ein Prozentsatz der gesamten CPU-Zeit für die Bearbeitung von Containeraufgaben (Limits) ist das Kontingent .
Wie messe ich die CPU?
Es gibt verschiedene Möglichkeiten:
- Was Papageien sind , weiß niemand - jedes Mal, wenn Sie zustimmen müssen.
- Das Interesse ist klarer, aber relativ: 50% eines Servers mit 4 Kernen und 20 Kernen sind völlig verschiedene Dinge.
- Sie können die bereits erwähnten Gewichte verwenden , die Linux kennt, aber sie sind auch relativ.
- Die am besten geeignete Option besteht darin, die Rechenressourcen in Sekunden zu messen. Das heißt, in Sekunden Prozessorzeit im Verhältnis zu Sekunden Echtzeit: Sie gaben 1 Sekunde Prozessorzeit in 1 realen Sekunde aus - dies ist ein ganzer CPU-Kern.
Um es noch einfacher zu sagen, begannen sie direkt in den
Kernen zu messen, dh die CPU-Zeit relativ zur realen. Da Linux eher Gewichte als Prozessorzeit / -kerne versteht, war ein Übersetzungsmechanismus von einem zum anderen erforderlich.
Stellen Sie sich ein einfaches Beispiel mit einem Server mit 3 CPU-Kernen vor, bei dem drei Pods Gewichte (500, 1000 und 1500) auswählen, die leicht in die entsprechenden Teile der ihnen zugewiesenen Kerne (0,5, 1 und 1,5) konvertiert werden können.

Wenn Sie einen zweiten Server mit doppelt so vielen Kernen (6) verwenden und dort dieselben Pods platzieren, kann die Verteilung der Kerne einfach durch einfaches Multiplizieren mit 2 (1, 2 bzw. 3) berechnet werden. Der wichtige Punkt tritt jedoch auf, wenn der vierte Pod auf diesem Server angezeigt wird, dessen Gewicht der Einfachheit halber 3000 beträgt. Dadurch werden einige CPU-Ressourcen (die Hälfte der Kerne) weggenommen, und der Rest der Pods gibt sie wieder (Hälfte):

Kubernetes und CPU-Ressourcen
In Kubernetes werden CPU-Ressourcen normalerweise in
Millikernen gemessen, d.h. Als Grundgewicht werden 0,001 Kerne verwendet.
(Dasselbe gilt in der Linux / cgroups-Terminologie als CPU-Freigabe, genauer gesagt 1000 CPU = 1024 CPU-Freigaben.) K8s stellt sicher, dass nicht mehr Pods auf dem Server platziert werden, als CPU-Ressourcen für die Summe der Gewichte vorhanden sind alle Hülsen.
Wie läuft das Wenn ein Server zu einem Kubernetes-Cluster hinzugefügt wird, wird gemeldet, wie viele CPU-Kerne für ihn verfügbar sind. Beim Erstellen eines neuen Pods weiß der Kubernetes-Scheduler, wie viele Kerne dieser Pod benötigt. Somit wird der Pod auf dem Server definiert, auf dem genügend Kerne vorhanden sind.
Was passiert, wenn
keine Anforderung angegeben wird (d. H. Der Pod bestimmt nicht die Anzahl der benötigten Kernel)? Mal sehen, wie Kubernetes im Allgemeinen Ressourcen zählt.
Der Pod kann sowohl Anforderungen (CFS-Scheduler) als auch Grenzwerte angeben (erinnern Sie sich an die Ampel?):
- Wenn sie gleich sind, wird die garantierte QoS-Klasse dem Pod zugewiesen. Eine solche Menge an Kerneln, die ihm immer zur Verfügung stehen, ist garantiert.
- Wenn die Anforderung unter dem Grenzwert liegt, kann die QoS-Klasse geplatzt werden . Das heißt, Wir erwarten, dass Pod beispielsweise immer 1 Kern verwendet, aber dieser Wert ist keine Einschränkung dafür: Manchmal kann Pod mehr verwenden (wenn dafür freie Ressourcen auf dem Server vorhanden sind).
- Es gibt auch die QoS-Klasse mit dem besten Aufwand - diejenigen Pods, für die keine Anforderung angegeben ist, gehören dazu. Ressourcen werden ihnen zuletzt gegeben.
Die Erinnerung
Ähnlich verhält es sich mit dem Gedächtnis, aber ein wenig anders - schließlich ist die Art dieser Ressourcen anders. Im Allgemeinen lautet die Analogie wie folgt:

Mal sehen, wie Anfragen im Speicher implementiert werden. Lassen Sie Pods auf dem Server leben und ändern Sie den verbrauchten Speicher, bis einer von ihnen so groß wird, dass der Speicher knapp wird. In diesem Fall erscheint der OOM-Killer und beendet den größten Prozess:

Dies passt nicht immer zu uns, daher ist es möglich zu regeln, welche Prozesse für uns wichtig sind und nicht getötet werden sollten. Verwenden Sie dazu den Parameter
oom_score_adj .
Kehren wir zu den CPU-QoS-Klassen zurück und ziehen eine Analogie zu den oom_score_adj-Werten, die die Speicherverbrauchsprioritäten für Pods bestimmen:
- Der niedrigste oom_score_adj-Wert eines Pods ist -998, was bedeutet, dass ein solcher Pod an letzter Stelle getötet werden sollte. Dies ist garantiert .
- Die höchste - 1000 - ist die beste Anstrengung , solche Schoten werden vor allen anderen getötet.
- Um den Rest der Werte ( Burstable ) zu berechnen, gibt es eine Formel, deren Essenz darauf hinausläuft , dass je mehr Pod Ressourcen angefordert hat, desto geringer ist die Wahrscheinlichkeit, dass sie getötet werden.

Die zweite "Drehung" -
limit_in_bytes - für Limits. Damit ist alles einfacher: Wir weisen einfach die maximale Speichermenge zu, die ausgegeben werden soll, und hier (im Gegensatz zur CPU) steht außer Frage, worin sie (Speicher) gemessen wird.
Insgesamt
Anforderungen und
limits
werden für jeden Pod in Kubernetes festgelegt - sowohl Parameter für die CPU als auch für den Speicher:
- Basierend auf Anforderungen funktioniert der Kubernetes-Scheduler, der Pods auf mehrere Server verteilt.
- Basierend auf allen Parametern wird die QodS-Klasse des Pods bestimmt.
- Relative Gewichte werden basierend auf CPU-Anforderungen berechnet.
- Basierend auf CPU-Anforderungen wird ein CFS-Scheduler konfiguriert.
- Basierend auf Speicheranforderungen wird OOM Killer konfiguriert.
- Basierend auf den CPU-Grenzwerten wird eine „Ampel“ konfiguriert.
- Basierend auf Speicherlimits wird ein Limit für cgroup festgelegt.

Im Allgemeinen beantwortet dieses Bild alle Fragen, wie der Hauptteil des Ressourcenmanagements in Kubernetes stattfindet.
Autoskalierung
K8s Cluster-Autoscaler
Stellen Sie sich vor, der gesamte Cluster ist bereits belegt und es muss ein neuer Pod erstellt werden. Der Pod kann zwar nicht angezeigt werden, hängt jedoch im Status "
Ausstehend ". Damit es so aussieht, können wir einen neuen Server mit dem Cluster verbinden oder ... Cluster-Autoscaler einsetzen, der dies für uns erledigt: Bestellen Sie eine virtuelle Maschine beim Cloud-Anbieter (auf API-Anfrage) und verbinden Sie sie mit dem Cluster. Anschließend wird der Pod hinzugefügt .

Dies ist die automatische Skalierung des Kubernetes-Clusters, die (nach unserer Erfahrung) hervorragend funktioniert. Wie auch hier gibt es hier einige Nuancen ...
Während wir die Clustergröße vergrößerten, war alles in Ordnung, aber was passiert, wenn der Cluster
freigegeben wird ? Das Problem ist, dass die Migration von Pods (auf freie Hosts) technisch sehr schwierig und ressourcenintensiv ist. Kubernetes verfolgt einen völlig anderen Ansatz.
Stellen Sie sich einen Cluster von 3 Servern vor, auf denen eine Bereitstellung stattfindet. Er hat 6 Pods: Jetzt sind es 2 für jeden Server. Aus irgendeinem Grund wollten wir einen der Server ausschalten. Verwenden Sie dazu den Befehl
kubectl drain
, der:
- verbietet das Senden neuer Pods an diesen Server;
- Entfernen Sie vorhandene Pods auf dem Server.
Da Kubernetes die Wartung der Anzahl der Pods (6) überwacht, werden sie einfach auf anderen Knoten neu erstellt, jedoch nicht auf dem nicht verbundenen Knoten, da sie bereits als nicht zugänglich für das Platzieren neuer Pods markiert sind. Dies ist die grundlegende Mechanik für Kubernetes.

Hier gibt es jedoch eine Nuance. In einer ähnlichen Situation für StatefulSet (anstelle von Deployment) sind die Aktionen unterschiedlich. Jetzt haben wir bereits eine Stateful-Anwendung - zum Beispiel drei Pods mit MongoDB, von denen einer ein Problem hatte (die Daten wurden beschädigt oder ein anderer Fehler verhinderte, dass der Pod richtig gestartet wurde). Und wieder beschließen wir, einen Server zu trennen. Was wird passieren?

MongoDB
könnte sterben, weil es ein Quorum benötigt: Für einen Cluster von drei Installationen müssen mindestens zwei funktionieren. Dies
geschieht jedoch nicht - dank des
PodDisruptionBudget . Dieser Parameter bestimmt die minimal erforderliche Anzahl von Arbeitskapseln. Wenn Sie wissen, dass einer der Pods mit MongoDB nicht mehr funktioniert, und wenn minAvailable in
minAvailable: 2
für MongoDB festgelegt ist, können Sie den Pod mit Kubernetes nicht entfernen.
Fazit: Um Pods bei der Freigabe des Clusters korrekt zu verschieben (und tatsächlich neu zu erstellen), müssen Sie PodDisruptionBudget konfigurieren.
Horizontale Skalierung
Betrachten Sie eine andere Situation. In Kubernetes wird eine Anwendung als Bereitstellung ausgeführt. Der Benutzerverkehr kommt zu seinen Pods (zum Beispiel gibt es drei davon), und wir messen einen bestimmten Indikator in ihnen (z. B. CPU-Auslastung). Wenn die Last zunimmt, korrigieren wir sie im Zeitplan und erhöhen die Anzahl der Pods, um Anforderungen zu verteilen.
In Kubernetes müssen Sie dies heute nicht mehr manuell tun: Sie können die Anzahl der Pods abhängig von den Werten der gemessenen Lastindikatoren automatisch erhöhen / verringern.

Die Hauptfragen hier sind,
was genau zu messen ist und
wie die erhaltenen Werte
zu interpretieren sind (um eine Entscheidung über die Änderung der Anzahl der Pods zu treffen). Sie können viel messen:

Wie es technisch geht - Metriken sammeln usw. - Ich habe im Bericht über
Überwachung und Kubernetes ausführlich gesprochen. Und der wichtigste Rat für die Auswahl der optimalen Parameter ist das
Experimentieren !
Es gibt
eine USE-Methode (Utilization Saturation and Errors ), deren Bedeutung wie folgt lautet. Auf welcher Basis ist es sinnvoll, beispielsweise php-fpm zu skalieren? Basierend auf der Tatsache, dass die Arbeiter enden, ist es die
Nutzung . Und wenn die Arbeiter vorbei sind und neue Verbindungen nicht akzeptiert werden, ist das
Sättigung . Beide Parameter müssen gemessen werden, und abhängig von den Werten sollte eine Skalierung durchgeführt werden.
Anstelle einer Schlussfolgerung
Der Bericht enthält eine Fortsetzung: Informationen zur vertikalen Skalierung und zur Auswahl der richtigen Ressourcen. Ich werde in zukünftigen Videos auf
unserem YouTube darüber sprechen - abonnieren, um es nicht zu verpassen!
Videos und Folien
Video von der Aufführung (44 Minuten):
Präsentation des Berichts:
PS
Andere Kubernetes-Berichte in unserem Blog: