
Cluster-Ressourcenmanagement ist immer ein komplexes Thema. Wie kann die Notwendigkeit erklärt werden, Ressourcen für den Benutzer zu konfigurieren, der seine Anwendungen im Cluster bereitstellt? Vielleicht ist es einfacher, dies zu automatisieren?
Problembeschreibung
Das Ressourcenmanagement ist eine wichtige Aufgabe im Kontext der Kubernetes-Clusterverwaltung. Aber warum ist es wichtig, wenn Kubernetes die ganze harte Arbeit für Sie erledigt? Weil es nicht so ist. Kubernetes bietet Ihnen praktische Tools zur Lösung vieler Probleme ... wenn Sie diese Tools verwenden. Für jeden Pod in Ihrem Cluster können Sie die für seine Container erforderlichen Ressourcen angeben. Und Kubernetes verwendet diese Informationen, um Instanzen Ihrer Anwendung auf Clusterknoten zu verteilen.
Nur wenige Menschen nehmen das Ressourcenmanagement bei Kubernetes ernst. Dies ist normal für einen leicht geladenen Cluster mit einigen statischen Anwendungen. Aber was ist, wenn Sie einen sehr dynamischen Cluster haben? Woher kommen und gehen Anwendungen, wo wird der Namespace ständig erstellt und gelöscht? Ein Cluster mit einer großen Anzahl von Benutzern, die ihren eigenen Namespace erstellen und Anwendungen bereitstellen können? In diesem Fall kommt es anstelle einer stabilen und vorhersehbaren Orchestrierung zu einer Reihe zufälliger Abstürze in Anwendungen und manchmal sogar in Komponenten von Kubernetes selbst!
Hier ist ein Beispiel für einen solchen Cluster:

Sie sehen 3 Herde im Status "Beenden". Dies ist jedoch nicht die übliche Entfernung von Herden - sie stecken in diesem Zustand fest, weil der Containerd-Daemon auf ihrem Knoten von etwas sehr ressourcenhungrigem getroffen wurde.
Solche Probleme können gelöst werden, indem der Mangel an Ressourcen richtig behandelt wird. Dies ist jedoch nicht das Thema dieses Artikels (es gibt einen guten Artikel ) sowie keine Silberkugel, um alle Probleme mit Ressourcen zu lösen.
Der Hauptgrund für solche Probleme ist falsch oder mangelnde Ressourcenverwaltung im Cluster. Und wenn diese Art von Problem für Bereitstellungen keine Katastrophe darstellt, da sie leicht ein neues Problem erstellen, unter dem sie arbeiten, sind solche Einfrierungen für Entitäten wie DaemonSet oder noch mehr für StatefulSet fatal und erfordern manuelle Eingriffe.
Sie können einen riesigen Cluster mit viel CPU und Speicher haben. Wenn Sie viele Anwendungen ohne die richtigen Ressourceneinstellungen ausführen, besteht die Möglichkeit, dass alle ressourcenintensiven Pods auf demselben Knoten platziert werden. Sie kämpfen um Ressourcen, auch wenn die verbleibenden Knoten des Clusters praktisch frei bleiben.
Sie können auch häufig weniger kritische Fälle sehen, in denen einige der Anwendungen von ihren Nachbarn betroffen sind. Selbst wenn die Ressourcen dieser "unschuldigen" Anwendungen korrekt konfiguriert wurden, kann ein Wandern unter sie kommen und sie töten. Ein Beispiel für ein solches Szenario:
- Ihre Anwendung fordert 4 GB Speicher an, benötigt jedoch zunächst nur 1 GB.
- Ein Wandern unter ohne Ressourcenkonfiguration wird demselben Knoten zugewiesen.
- Das Wandern unter verbraucht den gesamten verfügbaren Speicher.
- Ihre Anwendung versucht, mehr Speicher zuzuweisen, und stürzt ab, weil nicht mehr vorhanden ist.
Ein weiterer recht beliebter Fall ist die Neubewertung. Einige Entwickler stellen große Anfragen in Manifesten "nur für den Fall" und verwenden diese Ressourcen nie. Das Ergebnis ist eine Geldverschwendung.
Entscheidungstheorie
Horror! Richtig?
Glücklicherweise bietet Kubernetes eine Möglichkeit, Pods einige Einschränkungen aufzuerlegen, indem Standardressourcenkonfigurationen sowie Mindest- und Höchstwerte angegeben werden. Dies wird mit dem LimitRange-Objekt implementiert. LimitRange ist ein sehr praktisches Tool, wenn Sie nur über eine begrenzte Anzahl von Namespaces verfügen oder die vollständige Kontrolle über deren Erstellung haben. Auch ohne die richtige Konfiguration der Ressourcen sind Ihre Anwendungen in ihrer Verwendung eingeschränkt. "Unschuldige", richtig abgestimmte Herde sind sicher und vor schädlichen Nachbarn geschützt. Wenn jemand eine gierige Anwendung ohne Ressourcenkonfiguration bereitstellt, erhält diese Anwendung Standardwerte und stürzt wahrscheinlich ab. Und das ist alles! Die Anwendung zieht niemanden mehr mit sich.
Somit haben wir ein Werkzeug, um die Konfiguration von Ressourcen für Herde zu steuern und zu erzwingen. Jetzt scheinen wir sicher zu sein. Also? Nicht wirklich. Tatsache ist, dass, wie bereits beschrieben, unsere Namespaces von Benutzern erstellt werden können und LimitRange daher möglicherweise nicht in solchen Namespaces vorhanden ist, da sie in jedem Namespace separat erstellt werden müssen. Daher brauchen wir etwas nicht nur auf Namespace-Ebene, sondern auch auf Cluster-Ebene. Eine solche Funktion gibt es in Kubernetes jedoch noch nicht.
Deshalb habe ich beschlossen, meine Lösung für dieses Problem zu schreiben. Lassen Sie mich Ihnen vorstellen - Limit Operator. Dies ist ein Operator, der auf der Grundlage des Operator SDK- Frameworks erstellt wurde, das die benutzerdefinierte ClusterLimit-Ressource verwendet und dabei hilft, alle "unschuldigen" Anwendungen im Cluster zu sichern. Mit diesem Operator können Sie die Standardwerte und Ressourcenlimits für alle Namespaces mit minimalem Konfigurationsaufwand steuern. Außerdem können Sie mithilfe von NamespaceSelector genau auswählen, wo die Konfiguration angewendet werden soll.
Beispiel für ClusterLimitapiVersion: limit.myafq.com/v1alpha1 kind: ClusterLimit metadata: name: default-limit spec: namespaceSelector: matchLabels: limit: "limited" limitRange: limits: - type: Container max: cpu: "800m" memory: "1Gi" min: cpu: "100m" memory: "99Mi" default: cpu: "700m" memory: "900Mi" defaultRequest: cpu: "110m" memory: "111Mi" - type: Pod max: cpu: "2" memory: "2Gi"
Mit dieser Konfiguration erstellt der Operator einen LimitRange nur im Namespace mit der Bezeichnung limit: limited
. Dies ist nützlich, um strengere Einschränkungen für eine bestimmte Gruppe von Namespaces bereitzustellen. Wenn NamespaceSelector nicht angegeben ist, wendet der Operator einen LimitRange auf alle Namespaces an. Wenn Sie LimitRange manuell für einen bestimmten Namespace konfigurieren möchten, können Sie die Anmerkung "limit.myafq.com/unlimited": true
wird der Bediener angewiesen, diesen Namespace zu überspringen und LimitRange nicht automatisch zu erstellen.
Beispielskript zur Verwendung des Operators:
- Erstellen Sie Standard-ClusterLimit mit liberalen Einschränkungen und ohne NamespaceSelector - es wird überall angewendet.
- Erstellen Sie für eine Reihe von Namespaces mit kompakten Anwendungen ein zusätzliches, strengeres ClusterLimit mit NamespaceSelector. Fügen Sie diesen Namespaces entsprechende Beschriftungen hinzu.
- Fügen Sie in einem Namespace mit sehr ressourcenintensiven Anwendungen die Anmerkung "limit.myafq.com/unlimited": true ein und konfigurieren Sie LimitRange manuell mit viel größeren Grenzwerten als im Standard-ClusteLimit angegeben.
Das Wichtigste, was Sie über mehrere LimitRange in einem Namespace wissen sollten:
Wenn ein Sub in einem Namespace mit mehreren LimitRange erstellt wird, werden die größten Standardeinstellungen zum Konfigurieren seiner Ressourcen verwendet. Die Maximal- und Minimalwerte werden jedoch gemäß den strengsten Grenzwerten überprüft.
Praktisches Beispiel
Der Bediener verfolgt alle Änderungen in allen Namespace-, ClusterLimits- und untergeordneten LimitRanges und initiiert die Koordination des Status des Clusters bei jeder Änderung der überwachten Objekte. Mal sehen, wie es in der Praxis funktioniert.
Erstellen Sie zunächst unter ohne Einschränkungen:
kubectl run / get output ❯() kubectl run --generator=run-pod/v1 --image=bash bash pod/bash created ❯() kubectl get pod bash -o yaml apiVersion: v1 kind: Pod metadata: labels: run: bash name: bash namespace: default spec: containers: - image: bash name: bash resources: {}
Hinweis: Ein Teil der Befehlsausgabe wurde weggelassen, um das Beispiel zu vereinfachen.
Wie Sie sehen können, ist das Feld "Ressourcen" leer, was bedeutet, dass dieses Sub überall gestartet werden kann.
Jetzt erstellen wir das Standard-ClusterLimit für den gesamten Cluster mit ziemlich liberalen Werten:
default-limit.yaml apiVersion: limit.myafq.com/v1alpha1 kind: ClusterLimit metadata: name: default-limit spec: limitRange: limits: - type: Container max: cpu: "4" memory: "5Gi" default: cpu: "700m" memory: "900Mi" defaultRequest: cpu: "500m" memory: "512Mi"
Und auch strenger für eine Teilmenge von Namespaces:
restriktive-limit.yaml apiVersion: limit.myafq.com/v1alpha1 kind: ClusterLimit metadata: name: restrictive-limit spec: namespaceSelector: matchLabels: limit: "restrictive" limitRange: limits: - type: Container max: cpu: "800m" memory: "1Gi" default: cpu: "100m" memory: "128Mi" defaultRequest: cpu: "50m" memory: "64Mi" - type: Pod max: cpu: "2" memory: "2Gi"
Erstellen Sie dann die Namespaces und Pods, um zu sehen, wie es funktioniert.
Normaler Namespace mit Standardeinschränkung:
apiVersion: v1 kind: Namespace metadata: name: regular
Und ein etwas eingeschränkterer Namespace, der Legende nach - für leichte Anwendungen:
apiVersion: v1 kind: Namespace metadata: labels: limit: "restrictive" name: lightweight
Wenn Sie sich die Protokolle des Bedieners unmittelbar nach dem Erstellen des Namespace ansehen, finden Sie unter dem Spoiler Folgendes:
Bedienerprotokolle {...,"msg":"Reconciling ClusterLimit","Triggered by":"/regular"} {...,"msg":"Creating new namespace LimitRange.","Namespace":"regular","LimitRange":"default-limit"} {...,"msg":"Updating namespace LimitRange.","Namespace":"regular","Name":"default-limit"} {...,"msg":"Reconciling ClusterLimit","Triggered by":"/lightweight"} {...,"msg":"Creating new namespace LimitRange.","Namespace":"lightweight","LimitRange":"default-limit"} {...,"msg":"Updating namespace LimitRange.","Namespace":"lightweight","Name":"default-limit"} {...,"msg":"Creating new namespace LimitRange.","Namespace":"lightweight","LimitRange":"restrictive-limit"} {...,"msg":"Updating namespace LimitRange.","Namespace":"lightweight","Name":"restrictive-limit"}
Der fehlende Teil des Protokolls enthält 3 weitere Felder, die derzeit nicht relevant sind
Wie Sie sehen können, wurde bei der Erstellung jedes Namespace die Erstellung eines neuen LimitRange gestartet. Ein eingeschränkterer Namespace hat zwei LimitRange - Standard und strenger.
Versuchen wir nun, ein Paar Herde in diesen Namespaces zu erstellen.
kubectl run / get output ❯() kubectl run --generator=run-pod/v1 --image=bash bash -n regular pod/bash created ❯() kubectl get pod bash -o yaml -n regular apiVersion: v1 kind: Pod metadata: annotations: kubernetes.io/limit-ranger: 'LimitRanger plugin set: cpu, memory request for container bash; cpu, memory limit for container bash' labels: run: bash name: bash namespace: regular spec: containers: - image: bash name: bash resources: limits: cpu: 700m memory: 900Mi requests: cpu: 500m memory: 512Mi
Wie Sie sehen, ist das Ressourcenfeld jetzt gefüllt, obwohl wir die Art und Weise, wie der Pod erstellt wird, nicht geändert haben. Möglicherweise bemerken Sie auch die von LimitRanger automatisch erstellte Anmerkung.
Erstellen Sie jetzt unter in einem leichten Namespace:
kubectl run / get output ❯() kubectl run --generator=run-pod/v1 --image=bash bash -n lightweight pod/bash created ❯() kubectl get pods -n lightweight bash -o yaml apiVersion: v1 kind: Pod metadata: annotations: kubernetes.io/limit-ranger: 'LimitRanger plugin set: cpu, memory request for container bash; cpu, memory limit for container bash' labels: run: bash name: bash namespace: lightweight spec: containers: - image: bash name: bash resources: limits: cpu: 700m memory: 900Mi requests: cpu: 500m memory: 512Mi
Bitte beachten Sie, dass die Ressourcen im Kamin dieselben sind wie im vorherigen Beispiel. Dies liegt daran, dass bei mehreren LimitRange beim Erstellen von Pods weniger strenge Standardwerte verwendet werden. Aber warum brauchen wir dann einen begrenzteren LimitRange? Es wird verwendet, um die Maximal- und Minimalwerte von Ressourcen zu überprüfen. Um dies zu demonstrieren, werden wir unser begrenztes ClusterLimit noch eingeschränkter gestalten:
restriktive-limit.yaml apiVersion: limit.myafq.com/v1alpha1 kind: ClusterLimit metadata: name: restrictive-limit spec: namespaceSelector: matchLabels: limit: "restrictive" limitRange: limits: - type: Container max: cpu: "200m" memory: "250Mi" default: cpu: "100m" memory: "128Mi" defaultRequest: cpu: "50m" memory: "64Mi" - type: Pod max: cpu: "2" memory: "2Gi"
Beachten Sie den Abschnitt:
- type: Container max: cpu: "200m" memory: "250Mi"
Jetzt haben wir maximal 200m CPU und 250Mi Speicher für den Container im Kamin eingestellt. Und jetzt versuchen Sie noch einmal zu erstellen unter:
❯() kubectl run --generator=run-pod/v1 --image=bash bash -n lightweight Error from server (Forbidden): pods "bash" is forbidden: [maximum cpu usage per Container is 200m, but limit is 700m., maximum memory usage per Container is 250Mi, but limit is 900Mi.]
Unser Sub hat große Werte, die vom Standard-LimitRange festgelegt wurden, und konnte nicht gestartet werden, da es die maximal zulässige Ressourcenprüfung nicht bestanden hat.
Dies war ein Beispiel für die Verwendung des Limit-Operators. Probieren Sie es selbst aus und spielen Sie mit ClusterLimit in Ihrer lokalen Kubernetes-Instanz.
Im GitHub Limit Operator-Repository finden Sie das Manifest für die Bereitstellung des Operators sowie den Quellcode. Wenn Sie die Funktionalität des Bedieners erweitern möchten, sind Pull-Quests und Feature-Quests willkommen!
Fazit
Das Ressourcenmanagement bei Kubernetes ist entscheidend für die Stabilität und Zuverlässigkeit Ihrer Anwendungen. Passen Sie Ihre Herdressourcen nach Möglichkeit an. Und verwenden Sie LimitRange, um sich gegen Fälle zu versichern, in denen dies nicht möglich ist. Automatisieren Sie die Erstellung von LimitRange mit dem Limit Operator.
Befolgen Sie diese Tipps, und Ihr Cluster ist immer vor dem ressourcenlosen Chaos streunender Herde geschützt.