In der Regel besteht immer die Notwendigkeit, einem bestimmten Anwendung einen dedizierten Pool von Ressourcen zur Verfügung zu stellen, um deren korrekte und stabile Funktion zu gewährleisten. Aber was passiert, wenn mehrere Anwendungen auf denselben Ressourcen laufen? Wie stellt man sicher, dass jede von ihnen mit den minimal notwendigen Ressourcen ausgestattet ist? Wie kann man den Ressourcenverbrauch einschränken? Wie verteilt man die Last sinnvoll zwischen den Knoten? Wie gewährleistet man die Funktionsfähigkeit des horizontalen Skalierungssystems bei steigender Last auf den Anwendungen?

Beginnen wir mit den grundlegenden Ressourcentypen im System — hierzu zählen natürlich die CPU-Zeit und der Arbeitsspeicher. In den k8s-Mannifests werden diese Ressourcentypen in folgenden Einheiten gemessen:
- CPU — in Kernen
- RAM — in Bytes
Für jede Ressource gibt es zudem die Möglichkeit, zwei Arten von Anforderungen zu definieren — requests und Limits. Requests — beschreibt die minimalen Anforderungen an die verfügbaren Ressourcen des Knotens für den Start des Containers (und des Pods insgesamt), während Limits eine feste Obergrenze für die verfügbare Ressourcenmenge des Containers festlegt.
Es ist wichtig zu verstehen, dass im Manifest nicht unbedingt beide Typen eindeutig definiert werden müssen; das Verhalten wird wie folgt sein:
- Wenn nur die Limits der Ressource explizit angegeben sind, nimmt die Requests für diese Ressource automatisch den Wert der Limits an (dies kann überprüft werden, indem die Entität beschreiben wird). Das heißt, die tatsächliche Nutzung des Containers wird durch die Menge der Ressourcen begrenzt, die er für seinen Start benötigt.
- Wenn für die Ressource nur die Requests explizit angegeben sind, werden keine oberen Begrenzungen für diese Ressource festgelegt – das heißt, der Container ist nur durch die Ressourcen des Knotens begrenzt.
Es besteht auch die Möglichkeit, das Ressourcenmanagement nicht nur auf der Ebene eines bestimmten Containers, sondern auch auf der Ebene des Namespace mit Hilfe der folgenden Entitäten zu konfigurieren:
- LimitRange – beschreibt die Begrenzungspolitik auf der Container-/Pod-Ebene im Namespace und ist notwendig, um die Standardgrenzen für Container/Pods sowie zur Verhinderung der Erstellung übermäßig großer Container/Pods (oder umgekehrt) zu beschreiben, die Anzahl zu begrenzen und mögliche Unterschiede in den Werten von Limits und Requests zu definieren.
- ResourceQuotas — beschreibt die Einschränkungspolitik für alle Container im Namespace und dient in der Regel der Ressourcenzuweisung innerhalb von Umgebungen (nützlich, wenn die Umgebungen auf Knotenebene nicht strikt getrennt sind).
Nachfolgend sind Beispiele für Manifestdateien aufgeführt, in denen Ressourcengrenzen festgelegt werden:
Auf der Ebene eines bestimmten Containers:
containers: - name: app-nginx image: nginx resources: requests: memory: 1Gi limits: cpu: 200mDas bedeutet, dass beim Starten eines Containers mit nginx mindestens 1 GB RAM und 0,2 CPU-Kapazität auf dem Knoten verfügbar sein müssen; der Container kann maximal 0,2 CPU und den gesamten verfügbaren RAM auf dem Knoten nutzen.
Auf der Ebene des gesamten Namespace:
apiVersion: v1 kind: ResourceQuota metadata: name: nxs-test spec: hard: requests.cpu: 300m requests.memory: 1Gi limits.cpu: 700m limits.memory: 2GiDas heißt, die Summe aller Anforderungen der Container im Standard-Namespace darf 300m für CPU und 1G für RAM nicht überschreiten, die Summe aller Limits beträgt 700m für CPU und 2G für RAM.
Standardbeschränkungen für Container im Namespace:
apiVersion: v1 kind: LimitRange metadata: name: nxs-limit-per-container spec: limits: - type: Container defaultRequest: cpu: 100m memory: 1Gi default: cpu: 1 memory: 2Gi min: cpu: 50m memory: 500Mi max: cpu: 2 memory: 4GiDas bedeutet, dass im Standard-Namespace für alle Container standardmäßig eine Anforderung von 100m für die CPU und 1G für den RAM sowie ein Limit von 1 CPU und 2G festgelegt werden. Zudem gibt es Beschränkungen für mögliche Werte in den Anforderungs- und Grenzwerten für CPU (50m < x < 2) und RAM (500M < x < 4G).
Einschränkungen auf Pod-Ebene ns:
apiVersion: v1 kind: LimitRange metadata: name: nxs-limit-pod spec: limits: - type: Pod max: cpu: 4 memory: 1GiDas bedeutet, dass für jeden Pod im Standard-Namespace eine Einschränkung von 4 vCPU und 1G festgelegt wird.
Nun möchte ich erläutern, welche Vorteile die Festlegung dieser Grenzen für uns haben kann.
Der Mechanismus zur Lastverteilung zwischen den Knoten
Wie bekannt ist, ist für die Verteilung der Pods auf die Knoten ein K8s-Komponent zuständig, der Scheduler, der nach einem bestimmten Algorithmus arbeitet. Dieser Algorithmus durchläuft bei der Auswahl des optimalen Knotens zur Ausführung zwei Phasen:
- Filterung
- Rankings
Das bedeutet, dass gemäß der beschriebenen Politik zunächst Knoten ausgewählt werden, auf denen die Ausführung des Pods möglich ist, basierend auf einem Satz von Prädikaten (darunter wird auch geprüft, ob der Knoten genügend Ressourcen für die Ausführung des Pods hat — PodFitsResources), und dann werden für jeden dieser Knoten gemäß den Prioritäten Es werden Punkte vergeben (unter anderem, je mehr freie Ressourcen ein Knoten hat, desto mehr Punkte werden ihm zugewiesen – LeastResourceAllocation/LeastRequestedPriority/BalancedResourceAllocation) und erfolgt der Start auf dem Knoten mit der höchsten Punktzahl (sofern mehrere Knoten diese Bedingung erfüllen, wird einer von ihnen zufällig ausgewählt).
Es ist wichtig zu verstehen, dass der Scheduler bei der Bewertung der verfügbaren Ressourcen des Knotens auf die in etcd gespeicherten Daten zurückgreift – d.h. auf die Summe von requested/limit der Ressourcen jedes Pods, der auf diesem Knoten läuft, jedoch nicht auf den tatsächlichen Ressourcenverbrauch. Diese Informationen können durch den Befehl abgerufen werden: kubectl describe node $NODE, zum Beispiel:
# kubectl describe nodes nxs-k8s-s1
..
Non-terminated Pods: (9 in total)
Namespace Name CPU Requests CPU Limits Memory Requests Memory Limits AGE
--------- ---- ------------ ---------- --------------- ------------- ---
ingress-nginx nginx-ingress-controller-754b85bf44-qkt2t 0 (0%) 0 (0%) 0 (0%) 0 (0%) 233d
kube-system kube-flannel-26bl4 150m (0%) 300m (1%) 64M (0%) 500M (1%) 233d
kube-system kube-proxy-exporter-cb629 0 (0%) 0 (0%) 0 (0%) 0 (0%) 233d
kube-system kube-proxy-x9fsc 0 (0%) 0 (0%) 0 (0%) 0 (0%) 233d
kube-system nginx-proxy-k8s-worker-s1 25m (0%) 300m (1%) 32M (0%) 512M (1%) 233d
nxs-monitoring alertmanager-main-1 100m (0%) 100m (0%) 425Mi (1%) 25Mi (0%) 233d
nxs-logging filebeat-lmsmp 100m (0%) 0 (0%) 100Mi (0%) 200Mi (0%) 233d
nxs-monitoring node-exporter-v4gdq 112m (0%) 122m (0%) 200Mi (0%) 220Mi (0%) 233d
Allocated resources:
(Total limits may be over 100 percent, i.e., overcommitted.)
Resource Requests Limits
-------- -------- ------
cpu 487m (3%) 822m (5%)
memory 15856217600 (2%) 749976320 (3%)
ephemeral-storage 0 (0%) 0 (0%)Hier sehen wir alle Pods, die auf einem bestimmten Knoten ausgeführt werden, sowie die Ressourcen, die jeder Pod anfordert. So sehen die Logs des Schedulers beim Starten des Pods cronjob-cron-events-1573793820-xt6q9 aus (diese Informationen erscheinen im Log des Schedulers, wenn das Logging-Niveau auf 10 in den Startparametern gesetzt wird –v=10):
Log
I1115 07:57:21.637791 1 scheduling_queue.go:908] Versuch, Pod nxs-stage/cronjob-cron-events-1573793820-xt6q9 zu planen
I1115 07:57:21.637804 1 scheduler.go:453] Versuch, Pod zu planen: nxs-stage/cronjob-cron-events-1573793820-xt6q9
I1115 07:57:21.638285 1 predicates.go:829] Planung des Pods nxs-stage/cronjob-cron-events-1573793820-xt6q9 auf Node nxs-k8s-s5 ist erlaubt, Node läuft nur 16 von 110 Pods.
I1115 07:57:21.638300 1 predicates.go:829] Planung des Pods nxs-stage/cronjob-cron-events-1573793820-xt6q9 auf Node nxs-k8s-s6 ist erlaubt, Node läuft nur 20 von 110 Pods.
I1115 07:57:21.638322 1 predicates.go:829] Planung des Pods nxs-stage/cronjob-cron-events-1573793820-xt6q9 auf Node nxs-k8s-s3 ist erlaubt, Node läuft nur 20 von 110 Pods.
I1115 07:57:21.638322 1 predicates.go:829] Planung des Pods nxs-stage/cronjob-cron-events-1573793820-xt6q9 auf Node nxs-k8s-s4 ist erlaubt, Node läuft nur 17 von 110 Pods.
I1115 07:57:21.638334 1 predicates.go:829] Planung des Pods nxs-stage/cronjob-cron-events-1573793820-xt6q9 auf Node nxs-k8s-s10 ist erlaubt, Node läuft nur 16 von 110 Pods.
I1115 07:57:21.638365 1 predicates.go:829] Planung des Pods nxs-stage/cronjob-cron-events-1573793820-xt6q9 auf Node nxs-k8s-s12 ist erlaubt, Node läuft nur 9 von 110 Pods.
I1115 07:57:21.638334 1 predicates.go:829] Planung des Pods nxs-stage/cronjob-cron-events-1573793820-xt6q9 auf Node nxs-k8s-s11 ist erlaubt, Node läuft nur 11 von 110 Pods.
I1115 07:57:21.638385 1 predicates.go:829] Planung des Pods nxs-stage/cronjob-cron-events-1573793820-xt6q9 auf Node nxs-k8s-s1 ist erlaubt, Node läuft nur 19 von 110 Pods.
I1115 07:57:21.638402 1 predicates.go:829] Planung des Pods nxs-stage/cronjob-cron-events-1573793820-xt6q9 auf Node nxs-k8s-s2 ist erlaubt, Node läuft nur 21 von 110 Pods.
I1115 07:57:21.638383 1 predicates.go:829] Planung des Pods nxs-stage/cronjob-cron-events-1573793820-xt6q9 auf Node nxs-k8s-s9 ist erlaubt, Node läuft nur 16 von 110 Pods.
I1115 07:57:21.638335 1 predicates.go:829] Planung des Pods nxs-stage/cronjob-cron-events-1573793820-xt6q9 auf Node nxs-k8s-s8 ist erlaubt, Node läuft nur 18 von 110 Pods.
I1115 07:57:21.638408 1 predicates.go:829] Planung des Pods nxs-stage/cronjob-cron-events-1573793820-xt6q9 auf Node nxs-k8s-s13 ist erlaubt, Node läuft nur 8 von 110 Pods.
I1115 07:57:21.638478 1 predicates.go:1369] Planung des Pods nxs-stage/cronjob-cron-events-1573793820-xt6q9 auf Node nxs-k8s-s10 ist erlaubt, die Antipod-affinitätsbedingungen sind erfüllt.
I1115 07:57:21.638505 1 predicates.go:1369] Planung des Pods nxs-stage/cronjob-cron-events-1573793820-xt6q9 auf Node nxs-k8s-s8 ist erlaubt, die Antipod-affinitätsbedingungen sind erfüllt.
I1115 07:57:21.638577 1 predicates.go:1369] Planung des Pods nxs-stage/cronjob-cron-events-1573793820-xt6q9 auf Node nxs-k8s-s9 ist erlaubt, die Antipod-affinitätsbedingungen sind erfüllt.
I1115 07:57:21.638583 1 predicates.go:829] Planung des Pods nxs-stage/cronjob-cron-events-1573793820-xt6q9 auf Node nxs-k8s-s7 ist erlaubt, Node läuft nur 25 von 110 Pods.
I1115 07:57:21.638932 1 resource_allocation.go:78] cronjob-cron-events-1573793820-xt6q9 -> nxs-k8s-s10: BalancedResourceAllocation, Kapazität 39900 millicores 66620178432 Speicherbytes, Gesamtanforderung 2343 millicores 9640186880 Speicherbytes, Punktestand 9
I1115 07:57:21.638946 1 resource_allocation.go:78] cronjob-cron-events-1573793820-xt6q9 -> nxs-k8s-s10: LeastResourceAllocation, Kapazität 39900 millicores 66620178432 Speicherbytes, Gesamtanforderung 2343 millicores 9640186880 Speicherbytes, Punktestand 8
I1115 07:57:21.638961 1 resource_allocation.go:78] cronjob-cron-events-1573793820-xt6q9 -> nxs-k8s-s9: BalancedResourceAllocation, Kapazität 39900 millicores 66620170240 Speicherbytes, Gesamtanforderung 4107 millicores 11307422720 Speicherbytes, Punktestand 9
I1115 07:57:21.638971 1 resource_allocation.go:78] cronjob-cron-events-1573793820-xt6q9 -> nxs-k8s-s8: BalancedResourceAllocation, Kapazität 39900 millicores 66620178432 Speicherbytes, Gesamtanforderung 5847 millicores 24333637120 Speicherbytes, Punktestand 7
I1115 07:57:21.638975 1 resource_allocation.go:78] cronjob-cron-events-1573793820-xt6q9 -> nxs-k8s-s9: LeastResourceAllocation, Kapazität 39900 millicores 66620170240 Speicherbytes, Gesamtanforderung 4107 millicores 11307422720 Speicherbytes, Punktestand 8
I1115 07:57:21.638990 1 resource_allocation.go:78] cronjob-cron-events-1573793820-xt6q9 -> nxs-k8s-s8: LeastResourceAllocation, Kapazität 39900 millicores 66620178432 Speicherbytes, Gesamtanforderung 5847 millicores 24333637120 Speicherbytes, Punktestand 7
I1115 07:57:21.639022 1 generic_scheduler.go:726] cronjob-cron-events-1573793820-xt6q9_nxs-stage -> nxs-k8s-s10: TaintTolerationPriority, Punktestand: (10)
I1115 07:57:21.639030 1 generic_scheduler.go:726] cronjob-cron-events-1573793820-xt6q9_nxs-stage -> nxs-k8s-s8: TaintTolerationPriority, Punktestand: (10)
I1115 07:57:21.639034 1 generic_scheduler.go:726] cronjob-cron-events-1573793820-xt6q9_nxs-stage -> nxs-k8s-s9: TaintTolerationPriority, Punktestand: (10)
I1115 07:57:21.639041 1 generic_scheduler.go:726] cronjob-cron-events-1573793820-xt6q9_nxs-stage -> nxs-k8s-s10: NodeAffinityPriority, Punktestand: (0)
I1115 07:57:21.639053 1 generic_scheduler.go:726] cronjob-cron-events-1573793820-xt6q9_nxs-stage -> nxs-k8s-s8: NodeAffinityPriority, Punktestand: (0)
I1115 07:57:21.639059 1 generic_scheduler.go:726] cronjob-cron-events-1573793820-xt6q9_nxs-stage -> nxs-k8s-s9: NodeAffinityPriority, Punktestand: (0)
I1115 07:57:21.639061 1 interpod_affinity.go:237] cronjob-cron-events-1573793820-xt6q9 -> nxs-k8s-s10: InterPodAffinityPriority, Punktestand: (0)
I1115 07:57:21.639063 1 selector_spreading.go:146] cronjob-cron-events-1573793820-xt6q9 -> nxs-k8s-s10: SelectorSpreadPriority, Punktestand: (10)
I1115 07:57:21.639073 1 interpod_affinity.go:237] cronjob-cron-events-1573793820-xt6q9 -> nxs-k8s-s8: InterPodAffinityPriority, Punktestand: (0)
I1115 07:57:21.639077 1 selector_spreading.go:146] cronjob-cron-events-1573793820-xt6q9 -> nxs-k8s-s8: SelectorSpreadPriority, Punktestand: (10)
I1115 07:57:21.639085 1 interpod_affinity.go:237] cronjob-cron-events-1573793820-xt6q9 -> nxs-k8s-s9: InterPodAffinityPriority, Punktestand: (0)
I1115 07:57:21.639088 1 selector_spreading.go:146] cronjob-cron-events-1573793820-xt6q9 -> nxs-k8s-s9: SelectorSpreadPriority, Punktestand: (10)
I1115 07:57:21.639103 1 generic_scheduler.go:726] cronjob-cron-events-1573793820-xt6q9_nxs-stage -> nxs-k8s-s10: SelectorSpreadPriority, Punktestand: (10)
I1115 07:57:21.639109 1 generic_scheduler.go:726] cronjob-cron-events-1573793820-xt6q9_nxs-stage -> nxs-k8s-s8: SelectorSpreadPriority, Punktestand: (10)
I1115 07:57:21.639114 1 generic_scheduler.go:726] cronjob-cron-events-1573793820-xt6q9_nxs-stage -> nxs-k8s-s9: SelectorSpreadPriority, Punktestand: (10)
I1115 07:57:21.639127 1 generic_scheduler.go:781] Host nxs-k8s-s10 => Punktestand 100037
I1115 07:57:21.639150 1 generic_scheduler.go:781] Host nxs-k8s-s8 => Punktestand 100034
I1115 07:57:21.639154 1 generic_scheduler.go:781] Host nxs-k8s-s9 => Punktestand 100037
I1115 07:57:21.639267 1 scheduler_binder.go:269] AssumePodVolumes für Pod "nxs-stage/cronjob-cron-events-1573793820-xt6q9", Node "nxs-k8s-s10"
I1115 07:57:21.639286 1 scheduler_binder.go:279] AssumePodVolumes für Pod "nxs-stage/cronjob-cron-events-1573793820-xt6q9", Node "nxs-k8s-s10": Alle PVCs gebunden und nichts zu tun
I1115 07:57:21.639333 1 factory.go:733] Versuch, cronjob-cron-events-1573793820-xt6q9 an nxs-k8s-s10 zu bindenHier sehen wir, dass der Scheduler zunächst die Nodes filtert und eine Liste von drei Nodes erstellt, auf denen eine Ausführung möglich ist (nxs-k8s-s8, nxs-k8s-s9, nxs-k8s-s10). Anschließend werden die Punkte nach verschiedenen Parametern (einschließlich BalancedResourceAllocation, LeastResourceAllocation) für jede dieser Nodes gezählt, um den am besten geeigneten Knoten zu bestimmen. Letztendlich wird der Plan auf dem Knoten mit der höchsten Punktzahl erstellt (hier haben gleich zwei Nodes die gleiche Punktzahl von 100037, daher wird zufällig einer von ihnen ausgewählt — nxs-k8s-s10).
Fazit: Wenn auf einem Knoten Pods laufen, für die keine Einschränkungen festgelegt sind, entspricht das aus Sicht der Ressourcennutzung in K8s dem Zustand, als wären solche Pods auf diesem Knoten überhaupt nicht vorhanden. Daher kann es, wenn Sie zum Beispiel einen Pod mit einer ressourcenintensiven Anwendung (wie wowza) haben und für diesen keine Einschränkungen definiert sind, zu einer Situation kommen, in der dieser Pod tatsächlich alle Ressourcen des Knotens verbraucht hat, aber K8s diesen Knoten als nicht überlastet betrachtet. In der Folge erhält dieser Knoten die gleiche Punktzahl bei der Bewertung (insbesondere in Bezug auf die verfügbaren Ressourcen) wie ein Knoten, auf dem keine aktiven Pods laufen, was letztlich zu einer ungleichmäßigen Lastverteilung zwischen den Knoten führen kann.
Pod kündigen
Wie bekannt ist, wird jedem Pod eine der 3 QoS-Klassen zugewiesen:
- guaranteed — wird zugewiesen, wenn für jeden Container im Pod sowohl Memory- als auch CPU-Requests und -Limits festgelegt sind, wobei diese Werte übereinstimmen müssen
- burstable — wenn mindestens ein Container im Pod Requests und Limits hat, wobei der Request < Limit ist
- best effort — wenn kein Container im Pod hinsichtlich der Ressourcen eingeschränkt ist
Wenn auf einem Node Ressourcenmangel (Speicherplatz, RAM) festgestellt wird, beginnt der Kubelet, Pods nach einem bestimmten Algorithmus zu priorisieren und auszusiedeln, der die Priorität des Pods und seine QoS-Klasse berücksichtigt. Zum Beispiel, wenn es um RAM geht, werden basierend auf der QoS-Klasse Punkte nach folgendem Prinzip vergeben:
- Guaranteed: -998
- BestEffort: 1000
- Burstable: min(max(2, 1000 - (1000 * memoryRequestBytes) / machineMemoryCapacityBytes), 999)
Das bedeutet, dass der Kubelet bei gleichrangiger Priorität zuerst die Pods mit der QoS-Klasse "Best Effort" aus dem Node ausgliedert.
Fazit: Wenn Sie die Wahrscheinlichkeit verringern möchten, dass ein benötigter Pod bei Ressourcenmangel vom Node entfernt wird, sollten Sie neben der Priorität auch dafür sorgen, dass request/limit gesetzt werden.
Der Mechanismus zur horizontalen Skalierung von Anwendungs-Pods (HPA)
Wenn die Aufgabe darin besteht, die Anzahl der Pods automatisch zu erhöhen oder zu verringern, abhängig von der Ressourcennutzung (systemisch - CPU/RAM oder benutzerspezifisch - rps), kann eine Entität wie HPA (Horizontal Pod Autoscaler) bei der Lösung helfen. Sein Algorithmus funktioniert wie folgt:
- Die aktuellen Werte der beobachteten Ressource (currentMetricValue) werden ermittelt.
- Die gewünschten Werte für die Ressource (desiredMetricValue) werden für Systemressourcen über eine Anfrage festgelegt.
- Es wird die aktuelle Anzahl von Replikaten (currentReplicas) bestimmt.
- Die gewünschte Anzahl der Replikate (desiredReplicas) wird nach folgender Formel berechnet:
desiredReplicas = [ currentReplicas * ( currentMetricValue / desiredMetricValue )]
Es findet keine Skalierung statt, wenn der Faktor (currentMetricValue / desiredMetricValue) nahe bei 1 liegt (die zulässige Abweichung kann von uns selbst festgelegt werden, sie beträgt standardmäßig 0,1).
Betrachten wir die Funktionsweise von hpa am Beispiel der Anwendung app-test (beschrieben als Deployment), bei der die Anzahl der Replikate je nach CPU-Verbrauch geändert werden muss:
Manifest der Anwendung
kind: Deployment apiVersion: apps/v1beta2 metadata: name: app-test spec: selector: matchLabels: app: app-test replicas: 2 template: metadata: labels: app: app-test spec: containers: - name: nginx image: registry.nixys.ru/generic-images/nginx imagePullPolicy: Always resources: requests: cpu: 60m ports: - name: http containerPort: 80 - name: nginx-exporter image: nginx/nginx-prometheus-exporter resources: requests: cpu: 30m ports: - name: nginx-exporter containerPort: 9113 args: - -nginx.scrape-uri - http://127.0.0.1:80/nginx-statusDas bedeutet, wir sehen, dass die Anwendung ursprünglich mit zwei Instanzen gestartet wird, von denen jede zwei Container für nginx und nginx-exporter enthält, für die jeweils festgelegt ist, requests für die CPU.
HPA-Manifest
apiVersion: autoscaling/v2beta2 kind: HorizontalPodAutoscaler metadata: name: app-test-hpa spec: maxReplicas: 10 minReplicas: 2 scaleTargetRef: apiVersion: extensions/v1beta1 kind: Deployment name: app-test metrics: - type: Resource resource: name: cpu target: type: Utilization averageUtilization: 30Das bedeutet, wir haben eine HPA erstellt, die den Deployment app-test überwacht und die Anzahl der Pods mit der Anwendung basierend auf dem CPU-Wert reguliert (wir erwarten, dass der Pod 30 % der ihm zugewiesenen CPU benötigt), wobei die Anzahl der Replikate zwischen 2 und 10 liegt.
Nun betrachten wir den Mechanismus der HPA, wenn wir eine Last auf einen der Pods anwenden:
# kubectl top pod NAME CPU(cores) MEMORY(bytes) app-test-78559f8f44-pgs58 101m 243Mi app-test-78559f8f44-cj4jz 4m 240Mi
Insgesamt haben wir Folgendes:
- Wünschenswerter Wert (desiredMetricValue) — gemäß den HPA-Einstellungen beträgt dieser 30 %
- Aktueller Wert (currentMetricValue) — zur Berechnung ermittelt der controller-manager den Durchschnittswert des Ressourcenverbrauchs in %, d.h., vereinfacht gesagt, er macht Folgendes:
- Er erhält die absoluten Werte der Metriken der Pods vom Metric-Server, d.h. 101m und 4m.
- Er berechnet den durchschnittlichen absoluten Wert, d.h. (101m + 4m) / 2 = 53m.
- Er erhält den Absolutwert für den gewünschten Ressourcenverbrauch (dabei werden die Requests aller Container summiert) 60m + 30m = 90m.
- Berechnet den durchschnittlichen Prozentsatz der CPU-Nutzung im Verhältnis zu den Pod-Requests, d.h. 53m / 90m * 100% = 59%.
Jetzt haben wir alles Notwendige, um zu bestimmen, ob die Anzahl der Replikate geändert werden muss; dazu berechnen wir den Faktor:
ratio = 59% / 30% = 1,96.
Das heißt, die Anzahl der Replikate sollte ungefähr verdoppelt werden und beträgt [2 * 1,96] = 4.
Fazit: Wie man sehen kann, ist eine Voraussetzung für das Funktionieren dieses Mechanismus, dass Anfragen für alle Container im beobachteten Pod vorhanden sind.
Mechanismus der horizontalen automatischen Skalierung von Knoten (Cluster Autoscaler).
Um die negativen Auswirkungen auf das System bei Lastspitzen zu minimieren, reicht die Einrichtung eines HPA möglicherweise nicht aus. Beispielsweise entscheidet der HPA-Controller-Manager gemäß den Einstellungen, dass die Anzahl der Replikate verdoppelt werden muss, jedoch sind auf den Knoten keine freien Ressourcen verfügbar, um so viele Pods zu starten (d.h. der Knoten kann die angeforderten Ressourcen der Pod-Requests nicht bereitstellen), und diese Pods wechseln in den Status Pending.
In diesem Fall, wenn der Anbieter über eine entsprechende IaaS/PaaS (z.B. GKE/GCE, AKS, EKS usw.) verfügt, kann uns ein solches Werkzeug wie Node Autoscalerhelfen. Er ermöglicht es, die maximale und minimale Anzahl von Knoten im Cluster festzulegen und die aktuelle Anzahl der Knoten automatisch zu regulieren (indem er über die API des Cloud-Anbieters Knoten anfordert oder entfernt), wenn ein Ressourcenmangel im Cluster festgestellt wird und die Pods nicht geplant werden können (im Status Pending sind).
Fazit: Für die Möglichkeit der automatischen Skalierung der Knoten müssen in den Containern der Pods Requests definiert werden, damit k8s die Auslastung der Knoten korrekt bewerten und entsprechend melden kann, dass für den Start eines weiteren Pods im Cluster nicht genügend Ressourcen vorhanden sind.
Fazit
Es ist zu beachten, dass die Festlegung von Ressourcenbeschränkungen für den Container keine zwingende Voraussetzung für den erfolgreichen Start der Anwendung ist, jedoch aus folgenden Gründen dennoch ratsam ist:
- Um eine genauere Arbeitsweise des Schedulers hinsichtlich der Lastverteilung zwischen den Knoten k8s zu ermöglichen
- Um die Wahrscheinlichkeit eines „Pod-Eviction“-Ereignisses zu reduzieren
- Um das horizontale Autoscaling der Anwendungs-Pods (HPA) zu ermöglichen
- Für das horizontale automatisierte Skalieren von Knoten (Cluster-Autoscaling) bei Cloud-Anbietern
Lesen Sie auch andere Artikel in unserem Blog:
Quelle: habr.com
