ProHoster > Blog > Verwaltung > Erstellen eines zusätzlichen Kube-Schedulers mit einem benutzerdefinierten Satz von Planungsregeln
Erstellen eines zusätzlichen Kube-Schedulers mit einem benutzerdefinierten Satz von Planungsregeln
Kube-Scheduler ist eine integrale Komponente von Kubernetes, die für die knotenübergreifende Planung von Pods gemäß festgelegten Richtlinien verantwortlich ist. Während des Betriebs eines Kubernetes-Clusters müssen wir oft nicht darüber nachdenken, welche Richtlinien zum Planen von Pods verwendet werden, da der Richtliniensatz des Standard-Kube-Schedulers für die meisten alltäglichen Aufgaben geeignet ist. Es gibt jedoch Situationen, in denen es für uns wichtig ist, den Prozess der Pod-Zuweisung zu optimieren, und es gibt zwei Möglichkeiten, diese Aufgabe zu erfüllen:
Erstellen Sie einen Kube-Scheduler mit einem benutzerdefinierten Regelsatz
Schreiben Sie Ihren eigenen Scheduler und bringen Sie ihm bei, mit API-Serveranfragen zu arbeiten
In diesem Artikel beschreibe ich die Umsetzung des ersten Punktes zur Lösung des Problems der ungleichmäßigen Planung von Feuerstellen bei einem unserer Projekte.
Eine kurze Einführung in die Funktionsweise von Kube-Scheduler
Besonders hervorzuheben ist die Tatsache, dass kube-scheduler nicht für die direkte Planung von Pods verantwortlich ist, sondern nur für die Bestimmung des Knotens, auf dem der Pod platziert werden soll. Mit anderen Worten: Das Ergebnis der Arbeit von kube-scheduler ist der Name des Knotens, den er für eine Planungsanforderung an den API-Server zurückgibt, und dort endet seine Arbeit.
Zunächst erstellt kube-scheduler eine Liste von Knoten, auf denen der Pod gemäß den Prädikatenrichtlinien geplant werden kann. Als nächstes erhält jeder Knoten aus dieser Liste eine bestimmte Anzahl von Punkten gemäß den Prioritätsrichtlinien. Als Ergebnis wird der Knoten mit der maximalen Anzahl an Punkten ausgewählt. Wenn es Knoten gibt, die die gleiche maximale Punktzahl haben, wird ein zufälliger Knoten ausgewählt. Eine Liste und Beschreibung der Richtlinien für Prädikate (Filterung) und Prioritäten (Bewertung) finden Sie in Dokumentation.
Beschreibung des Problemkörpers
Trotz der großen Anzahl verschiedener Kubernetes-Cluster, die bei Nixys verwaltet werden, stießen wir erst kürzlich auf das Problem der Pod-Planung, als eines unserer Projekte eine große Anzahl periodischer Aufgaben (ca. 100 CronJob-Entitäten) ausführen musste. Um die Problembeschreibung möglichst einfach zu gestalten, nehmen wir als Beispiel einen Microservice, bei dem einmal pro Minute eine Cron-Task gestartet wird, die die CPU etwas belastet. Zur Ausführung der Cron-Task wurden drei Knoten mit absolut identischen Eigenschaften zugewiesen (jeweils 24 vCPUs).
Gleichzeitig lässt sich nicht genau sagen, wie lange die Ausführung des CronJob dauern wird, da sich die Menge der Eingabedaten ständig ändert. Während des normalen Betriebs von kube-scheduler führt jeder Knoten durchschnittlich 3–4 Jobinstanzen aus, die etwa 20–30 % der CPU-Last jedes Knotens verursachen:
Das Problem selbst besteht darin, dass Cron-Task-Pods manchmal nicht mehr auf einem der drei Knoten geplant wurden. Das heißt, zu einem bestimmten Zeitpunkt war für einen der Knoten kein einziger Pod geplant, während auf den anderen beiden Knoten 6–8 Kopien der Aufgabe liefen, was ~40–60 % der CPU-Last verursachte:
Das Problem trat mit völlig zufälliger Häufigkeit erneut auf und hing gelegentlich mit dem Zeitpunkt zusammen, an dem eine neue Version des Codes eingeführt wurde.
Durch die Erhöhung der Kube-Scheduler-Protokollierungsstufe auf Stufe 10 (-v=10) begannen wir aufzuzeichnen, wie viele Punkte jeder Knoten während des Bewertungsprozesses gewonnen hat. Während des normalen Planungsbetriebs konnten in den Protokollen folgende Informationen angezeigt werden:
Diese. Nach den aus den Protokollen erhaltenen Informationen zu urteilen, erzielte jeder Knoten die gleiche Anzahl an Endpunkten und ein zufälliger Knoten wurde für die Planung ausgewählt. Zum Zeitpunkt der problematischen Planung sahen die Protokolle so aus:
Daraus ist ersichtlich, dass einer der Knoten weniger Endpunkte erzielte als die anderen und die Planung daher nur für die beiden Knoten durchgeführt wurde, die die maximale Punktzahl erreichten. Daher waren wir definitiv davon überzeugt, dass das Problem genau in der Planung der Pods liegt.
Der weitere Algorithmus zur Lösung des Problems war für uns offensichtlich: Analysieren Sie die Protokolle, verstehen Sie, mit welcher Priorität der Knoten keine Punkte erzielt hat, und passen Sie gegebenenfalls die Richtlinien des Standard-Kube-Schedulers an. Allerdings stehen wir hier vor zwei wesentlichen Schwierigkeiten:
Bei der maximalen Protokollierungsstufe (10) werden nur für einige Prioritäten gewonnene Punkte berücksichtigt. Im obigen Auszug aus den Protokollen können Sie sehen, dass Knoten für alle in den Protokollen wiedergegebenen Prioritäten bei der normalen Planung und bei der Problemplanung die gleiche Punktzahl erzielen, das Endergebnis bei der Problemplanung jedoch unterschiedlich ist. Daraus können wir schließen, dass die Bewertung bei einigen Prioritäten „hinter den Kulissen“ erfolgt und wir keine Möglichkeit haben zu verstehen, für welche Priorität der Knoten keine Punkte erhalten hat. Wir haben dieses Problem ausführlich beschrieben Problem Kubernetes-Repository auf Github. Zum Zeitpunkt des Verfassens dieses Artikels haben die Entwickler geantwortet, dass in den Kubernetes-Updates v1.15,1.16, 1.17 und XNUMX Protokollierungsunterstützung hinzugefügt wird.
Es gibt keine einfache Möglichkeit zu verstehen, mit welchen spezifischen Richtlinien kube-scheduler derzeit arbeitet. Ja in Dokumentation Diese Liste wird aufgeführt, enthält jedoch keine Informationen darüber, welche spezifischen Gewichtungen den einzelnen Prioritätsrichtlinien zugewiesen werden. Sie können die Gewichtungen nur in sehen oder die Richtlinien des Standard-Kube-Schedulers bearbeiten Quellcodes.
Es ist erwähnenswert, dass wir einmal feststellen konnten, dass ein Knoten keine Punkte gemäß der ImageLocalityPriority-Richtlinie erhalten hat, die einem Knoten Punkte vergibt, wenn er bereits über das zum Ausführen der Anwendung erforderliche Image verfügt. Das heißt, als eine neue Version der Anwendung eingeführt wurde, gelang es der Cron-Task, auf zwei Knoten zu laufen und ein neues Image aus der Docker-Registrierung auf sie herunterzuladen, sodass zwei Knoten im Vergleich zum dritten Knoten eine höhere Endpunktzahl erhielten .
Wie ich oben geschrieben habe, sehen wir in den Protokollen keine Informationen über die Auswertung der ImageLocalityPriority-Richtlinie. Um unsere Annahme zu überprüfen, haben wir das Image mit der neuen Version der Anwendung auf dem dritten Knoten abgelegt, woraufhin die Planung korrekt funktionierte . Gerade aufgrund der ImageLocalityPriority-Richtlinie wurde das Planungsproblem recht selten beobachtet; häufiger war es mit etwas anderem verbunden. Aufgrund der Tatsache, dass wir nicht alle Richtlinien in der Prioritätenliste des Standard-Kube-Schedulers vollständig debuggen konnten, bestand ein Bedarf an einer flexiblen Verwaltung der Pod-Planungsrichtlinien.
Formulierung des Problems
Wir wollten, dass die Lösung des Problems so spezifisch wie möglich ist, das heißt, die Hauptentitäten von Kubernetes (hier meinen wir den Standard-Kube-Scheduler) sollten unverändert bleiben. Wir wollten ein Problem nicht an einem Ort lösen und es an einem anderen schaffen. So kamen wir zu zwei Möglichkeiten zur Lösung des Problems, die in der Einleitung des Artikels angekündigt wurden – das Erstellen eines zusätzlichen Schedulers oder das Schreiben eines eigenen. Die Hauptanforderung für die Planung von Cron-Aufgaben besteht darin, die Last gleichmäßig auf drei Knoten zu verteilen. Diese Anforderung kann durch bestehende Kube-Scheduler-Richtlinien erfüllt werden. Um unser Problem zu lösen, macht es also keinen Sinn, einen eigenen Scheduler zu schreiben.
Anweisungen zum Erstellen und Bereitstellen eines zusätzlichen Kube-Schedulers finden Sie in Dokumentation. Allerdings schien es uns, dass die Deployment-Entität nicht ausreichte, um Fehlertoleranz beim Betrieb eines so wichtigen Dienstes wie Kube-Scheduler zu gewährleisten. Daher entschieden wir uns, einen neuen Kube-Scheduler als statischen Pod bereitzustellen, der direkt überwacht werden sollte von Kubelet. Somit haben wir folgende Anforderungen an den neuen Kube-Scheduler:
Der Dienst muss als statischer Pod auf allen Cluster-Mastern bereitgestellt werden
Für den Fall, dass der aktive Pod mit Kube-Scheduler nicht verfügbar ist, muss Fehlertoleranz bereitgestellt werden
Die Hauptpriorität bei der Planung sollte die Anzahl der verfügbaren Ressourcen auf dem Knoten sein (LeastRequestedPriority).
Implementierungslösungen
Es ist sofort erwähnenswert, dass wir alle Arbeiten in Kubernetes v1.14.7 ausführen werden, denn Dies ist die Version, die im Projekt verwendet wurde. Beginnen wir damit, ein Manifest für unseren neuen Kube-Scheduler zu schreiben. Nehmen wir das Standardmanifest (/etc/kubernetes/manifests/kube-scheduler.yaml) als Grundlage und bringen es in die folgende Form:
Der Name des Pods und Containers wurde in kube-scheduler-cron geändert
Als Option wurde die Verwendung der Ports 10151 und 10159 angegeben hostNetwork: true und wir können nicht dieselben Ports wie der Standard-Kube-Scheduler (10251 und 10259) verwenden.
Mit dem Parameter --config haben wir die Konfigurationsdatei angegeben, mit der der Dienst gestartet werden soll
Konfigurierte Bereitstellung der Konfigurationsdatei (scheduler-custom.conf) und der Planungsrichtliniendatei (scheduler-custom-policy-config.json) vom Host
Vergessen Sie nicht, dass unser Kube-Scheduler ähnliche Rechte wie der Standard benötigt. Bearbeiten Sie die Clusterrolle:
Lassen Sie uns nun darüber sprechen, was in der Konfigurationsdatei und der Planungsrichtliniendatei enthalten sein sollte:
Konfigurationsdatei (scheduler-custom.conf)
Um die standardmäßige Kube-Scheduler-Konfiguration zu erhalten, müssen Sie den Parameter verwenden --write-config-to von Dokumentation. Die resultierende Konfiguration werden wir in der Datei /etc/kubernetes/scheduler-custom.conf ablegen und auf die folgende Form reduzieren:
Wir setzen „schedulerName“ auf den Namen unseres kube-scheduler-cron-Dienstes.
Im Parameter lockObjectName Sie müssen auch den Namen unseres Dienstes festlegen und sicherstellen, dass der Parameter leaderElect auf true setzen (wenn Sie einen Masterknoten haben, können Sie ihn auf false setzen).
Geben Sie den Pfad zur Datei mit einer Beschreibung der Planungsrichtlinien im Parameter an algorithmSource.
Es lohnt sich, einen genaueren Blick auf den zweiten Punkt zu werfen, in dem wir die Parameter für die Taste bearbeiten leaderElection. Um Fehlertoleranz zu gewährleisten, haben wir (leaderElect) der Prozess der Auswahl eines Anführers (Masters) zwischen den Pods unseres Kube-Schedulers unter Verwendung eines einzigen Endpunkts für sie (resourceLock) mit dem Namen kube-scheduler-cron (lockObjectName) im Kube-System-Namespace (lockObjectNamespace). Wie Kubernetes eine hohe Verfügbarkeit der Hauptkomponenten (einschließlich Kube-Scheduler) gewährleistet, finden Sie in Artikel.
Planungsrichtliniendatei (scheduler-custom-policy-config.json)
Wie ich bereits geschrieben habe, können wir nur durch die Analyse seines Codes herausfinden, mit welchen spezifischen Richtlinien der Standard-Kube-Scheduler arbeitet. Das heißt, wir können eine Datei mit Planungsrichtlinien für den Standard-Kube-Scheduler nicht auf die gleiche Weise wie eine Konfigurationsdatei erhalten. Beschreiben wir die für uns interessanten Planungsrichtlinien in der Datei /etc/kubernetes/scheduler-custom-policy-config.json wie folgt:
Daher erstellt kube-scheduler zunächst eine Liste von Knoten, für die ein Pod gemäß der GeneralPredicates-Richtlinie geplant werden kann (die eine Reihe von PodFitsResources-, PodFitsHostPorts-, HostName- und MatchNodeSelector-Richtlinien umfasst). Und dann wird jeder Knoten gemäß den Richtlinien im Prioritäten-Array bewertet. Um die Bedingungen unserer Aufgabe zu erfüllen, waren wir der Ansicht, dass eine solche Reihe von Richtlinien die optimale Lösung wäre. Ich möchte Sie daran erinnern, dass eine Reihe von Richtlinien mit ihren detaillierten Beschreibungen in verfügbar sind Dokumentation. Um Ihre Aufgabe zu erfüllen, können Sie einfach die verwendeten Richtlinien ändern und ihnen entsprechende Gewichtungen zuweisen.
Nennen wir das Manifest des neuen Kube-Schedulers, den wir zu Beginn des Kapitels erstellt haben, kube-scheduler-custom.yaml und platzieren es im folgenden Pfad /etc/kubernetes/manifests auf drei Masterknoten. Wenn alles richtig gemacht wurde, startet Kubelet einen Pod auf jedem Knoten und in den Protokollen unseres neuen Kube-Schedulers sehen wir die Information, dass unsere Richtliniendatei erfolgreich angewendet wurde:
Creating scheduler from configuration: {{ } [{GeneralPredicates <nil>}] [{ServiceSpreadingPriority 1 <nil>} {EqualPriority 1 <nil>} {LeastRequestedPriority 1 <nil>} {NodePreferAvoidPodsPriority 10000 <nil>} {NodeAffinityPriority 1 <nil>}] [] 10 false}
Registering predicate: GeneralPredicates
Predicate type GeneralPredicates already registered, reusing.
Registering priority: ServiceSpreadingPriority
Priority type ServiceSpreadingPriority already registered, reusing.
Registering priority: EqualPriority
Priority type EqualPriority already registered, reusing.
Registering priority: LeastRequestedPriority
Priority type LeastRequestedPriority already registered, reusing.
Registering priority: NodePreferAvoidPodsPriority
Priority type NodePreferAvoidPodsPriority already registered, reusing.
Registering priority: NodeAffinityPriority
Priority type NodeAffinityPriority already registered, reusing.
Creating scheduler with fit predicates 'map[GeneralPredicates:{}]' and priority functions 'map[EqualPriority:{} LeastRequestedPriority:{} NodeAffinityPriority:{} NodePreferAvoidPodsPriority:{} ServiceSpreadingPriority:{}]'
Jetzt müssen wir nur noch in der Spezifikation unseres CronJob angeben, dass alle Anfragen zur Planung seiner Pods von unserem neuen Kube-Scheduler verarbeitet werden sollen:
Letztendlich haben wir einen zusätzlichen Kube-Scheduler mit einem einzigartigen Satz an Planungsrichtlinien erhalten, dessen Arbeit direkt vom Kubelet überwacht wird. Darüber hinaus haben wir die Wahl eines neuen Anführers zwischen den Pods unseres Kube-Schedulers eingerichtet, für den Fall, dass der alte Anführer aus irgendeinem Grund nicht verfügbar ist.
Reguläre Anwendungen und Dienste werden weiterhin über den Standard-Kube-Scheduler geplant und alle Cron-Aufgaben wurden vollständig auf den neuen übertragen. Die durch Cron-Tasks verursachte Last wird nun gleichmäßig auf alle Knoten verteilt. Wenn man bedenkt, dass die meisten Cron-Aufgaben auf denselben Knoten ausgeführt werden wie die Hauptanwendungen des Projekts, hat dies das Risiko, Pods aufgrund fehlender Ressourcen zu verschieben, erheblich reduziert. Nach der Einführung des zusätzlichen Kube-Schedulers traten keine Probleme mehr mit ungleichmäßiger Planung von Cron-Aufgaben auf.