In der Regel besteht stets die Notwendigkeit, einen speziellen Pool von Ressourcen für eine Anwendung bereitzustellen, um deren ordnungsgemäße und stabile Funktion zu gewährleisten. Aber was, wenn mehrere Anwendungen auf denselben Ressourcen laufen? Wie stellt man sicher, dass jede von ihnen mit den minimal notwendigen Ressourcen versorgt wird? Wie kann man den Ressourcenverbrauch begrenzen? Wie verteilt man die Last sinnvoll zwischen den Knoten? Wie gewährleistet man das Funktionieren des Mechanismus der horizontalen Skalierung bei steigender Belastung der Anwendungen?

Zunächst muss geklärt werden, welche Haupttypen von Ressourcen im System existieren - das sind natürlich die CPU-Zeit und der Arbeitsspeicher. In den k8s-Manifests werden diese Ressourcenarten in folgenden Einheiten gemessen:
- CPU - in Kernen
- RAM - in Bytes
Dabei gibt es für jede Ressource die Möglichkeit, zwei Arten von Anforderungen festzulegen - 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 strenge Begrenzung der für den Container verfügbaren Ressourcen festlegt.
Es ist wichtig zu verstehen, dass im Manifest nicht unbedingt beide Arten von Anforderungen explizit definiert werden müssen; das Verhalten wird in diesem Fall Folgendes sein:
- Wenn nur die limits für eine Ressource explizit festgelegt sind, dann nimmt requests für diese Ressource automatisch den Wert von limits an (das kann bestätigt werden, indem die Entität mit describe aufgerufen wird). Das bedeutet, dass die Arbeit des Containers tatsächlich auf die Menge an Ressourcen beschränkt ist, die er für seinen Start benötigt.
- Wenn für die Ressource nur requests explizit festgelegt sind, gibt es keine oberen Begrenzungen für diese Ressource - das bedeutet, dass der Container nur durch die Ressourcen des Knotens selbst beschränkt ist.
Es besteht auch die Möglichkeit, das Ressourcenmanagement nicht nur auf der Ebene eines bestimmten Containers, sondern auch auf der Ebene des Namensraums mit Hilfe der folgenden Entitäten zu konfigurieren:
- LimitRange beschreibt die Beschränkungspolitik auf der Ebene von Container/Pod im ns und dient dazu, Standardbeschränkungen für Container/Pods zu definieren sowie die Erstellung von deutlich überdimensionierten Containern/Pods (oder umgekehrt) zu verhindern, ihre Anzahl zu begrenzen und mögliche Unterschiede in den Werten von limits und requests festzulegen.
- ResourceQuotas — beschreibt die Einschränkungspolitik insgesamt für alle Container in ns und wird in der Regel verwendet, um Ressourcen nach Umgebungen zu differenzieren (nützlich, wenn die Umgebungen nicht strikt auf Knotenebene getrennt sind)
Nachfolgend sind Beispiele für Manifeste aufgeführt, in denen Ressourcenschränkungen festgelegt werden:
Auf der Ebene eines bestimmten Containers:
containers: - name: app-nginx image: nginx resources: requests: memory: 1Gi limits: cpu: 200md.h. in diesem Fall wird zum Starten des Containers mit nginx mindestens ein freies 1G RAM und 0.2 CPU auf dem Knoten benötigt, wobei der Container maximal 0.2 CPU und gesamten verfügbaren RAM auf dem Knoten nutzen kann.
Auf der Ebene des gesamten ns:
apiVersion: v1 kind: ResourceQuota metadata: name: nxs-test spec: hard: requests.cpu: 300m requests.memory: 1Gi limits.cpu: 700m limits.memory: 2Gid.h. die Summe aller Requests der Container im Standard-ns darf 300m für CPU und 1G für RAM nicht überschreiten, und die Summe aller Limits — 700m für CPU und 2G für RAM.
Standardbeschränkungen für Container im ns:
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: 4Gid.h. im Standard-namespace wird für alle Container standardmäßig ein Request von 100m für CPU und 1G für RAM festgelegt, das Limit — 1 CPU und 2G. Gleichzeitig wurde auch eine Grenze für die möglichen Werte in Request/Limits für CPU (50m < x < 2) und RAM (500M < x < 4G) festgelegt.
Einschränkungen auf der Ebene der Pods im ns:
apiVersion: v1 kind: LimitRange metadata: name: nxs-limit-pod spec: limits: - type: Pod max: cpu: 4 memory: 1Gid.h. für jeden Pod im Standard-ns wird ein Limit von 4 vCPU und 1G festgelegt.
Nun möchte ich erzählen, welche Vorteile uns die Festlegung dieser Beschränkungen bieten kann.
Der Mechanismus zur Lastverteilung zwischen Knoten
Wie bekannt ist, ist ein solcher Bestandteil von k8s verantwortlich für die Verteilung von Pods auf Knoten, nämlich scheduler, der nach einem bestimmten Algorithmus arbeitet. Dieser Algorithmus durchläuft zwei Phasen bei der Auswahl des optimalen Knotens für den Start:
- Filtern
- Bewerten
Das heißt, gemäß der beschriebenen Politik werden zunächst Knoten ausgewählt, auf denen ein Pod basierend auf einer Menge von Predicates möglicherweise gestartet werden kann (es wird auch überprüft, ob genügend Ressourcen auf dem Knoten für den Start des Pods vorhanden sind — PodFitsResources), und dann wird für jeden dieser Knoten gemäß Prioritäten Punkte werden gutgeschrieben (wobei mehr freie Ressourcen bei einem Knoten zu mehr Punkten führen – LeastResourceAllocation/LeastRequestedPriority/BalancedResourceAllocation) und die Ausführung erfolgt auf dem Knoten mit der höchsten Punktzahl (wenn mehrere Knoten dieses Kriterium erfüllen, wird zufällig einer von ihnen ausgewählt).
Dabei ist zu beachten, dass der Scheduler bei der Bewertung der verfügbaren Ressourcen eines Knotens auf die Daten zurückgreift, die in etcd gespeichert sind – das heißt, auf die Summe der angeforderten/Limit-Ressourcen jedes Pods, der auf diesem Knoten ausgeführt wird, aber nicht auf den tatsächlichen Ressourcenverbrauch. Diese Informationen können im Ausgabe der Befehlszeile 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 dieser Pods anfordert. So sieht das Protokoll des Schedulers beim Starten des Pods cronjob-cron-events-1573793820-xt6q9 aus (diese Informationen erscheinen im Protokoll des Schedulers, wenn das Log-Level in den Argumenten des Startbefehls auf -v=10 eingestellt wird):
Protokoll
I1115 07:57:21.637791 1 scheduling_queue.go:908] Wird versucht, Pod nxs-stage/cronjob-cron-events-1573793820-xt6q9 zu planen
I1115 07:57:21.637804 1 scheduler.go:453] Versuche, 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 führt nur 16 von 110 Pods aus.
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 führt nur 20 von 110 Pods aus.
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 führt nur 20 von 110 Pods aus.
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 führt nur 17 von 110 Pods aus.
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 führt nur 16 von 110 Pods aus.
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 führt nur 9 von 110 Pods aus.
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 führt nur 11 von 110 Pods aus.
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 führt nur 19 von 110 Pods aus.
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 führt nur 21 von 110 Pods aus.
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 führt nur 16 von 110 Pods aus.
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 führt nur 18 von 110 Pods aus.
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 führt nur 8 von 110 Pods aus.
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, bestehende Pods Antisymmetrie-Bedingungen 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, bestehende Pods Antisymmetrie-Bedingungen 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, bestehende Pods Antisymmetrie-Bedingungen 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 führt nur 25 von 110 Pods aus.
I1115 07:57:21.638932 1 resource_allocation.go:78] cronjob-cron-events-1573793820-xt6q9 -> nxs-k8s-s10: Ausgewogene Ressourcenallokation, Kapazität 39900 millicores 66620178432 Bytes Arbeitsspeicher, gesamte Anfrage 2343 millicores 9640186880 Bytes Arbeitsspeicher, Punktzahl 9
I1115 07:57:21.638946 1 resource_allocation.go:78] cronjob-cron-events-1573793820-xt6q9 -> nxs-k8s-s10: Minimale Ressourcenallokation, Kapazität 39900 millicores 66620178432 Bytes Arbeitsspeicher, gesamte Anfrage 2343 millicores 9640186880 Bytes Arbeitsspeicher, Punktzahl 8
I1115 07:57:21.638961 1 resource_allocation.go:78] cronjob-cron-events-1573793820-xt6q9 -> nxs-k8s-s9: Ausgewogene Ressourcenallokation, Kapazität 39900 millicores 66620170240 Bytes Arbeitsspeicher, gesamte Anfrage 4107 millicores 11307422720 Bytes Arbeitsspeicher, Punktzahl 9
I1115 07:57:21.638971 1 resource_allocation.go:78] cronjob-cron-events-1573793820-xt6q9 -> nxs-k8s-s8: Ausgewogene Ressourcenallokation, Kapazität 39900 millicores 66620178432 Bytes Arbeitsspeicher, gesamte Anfrage 5847 millicores 24333637120 Bytes Arbeitsspeicher, Punktzahl 7
I1115 07:57:21.638975 1 resource_allocation.go:78] cronjob-cron-events-1573793820-xt6q9 -> nxs-k8s-s9: Minimale Ressourcenallokation, Kapazität 39900 millicores 66620170240 Bytes Arbeitsspeicher, gesamte Anfrage 4107 millicores 11307422720 Bytes Arbeitsspeicher, Punktzahl 8
I1115 07:57:21.638990 1 resource_allocation.go:78] cronjob-cron-events-1573793820-xt6q9 -> nxs-k8s-s8: Minimale Ressourcenallokation, Kapazität 39900 millicores 66620178432 Bytes Arbeitsspeicher, gesamte Anfrage 5847 millicores 24333637120 Bytes Arbeitsspeicher, Punktzahl 7
I1115 07:57:21.639022 1 generic_scheduler.go:726] cronjob-cron-events-1573793820-xt6q9_nxs-stage -> nxs-k8s-s10: TaintTolerationPriority, Punktzahl: (10)
I1115 07:57:21.639030 1 generic_scheduler.go:726] cronjob-cron-events-1573793820-xt6q9_nxs-stage -> nxs-k8s-s8: TaintTolerationPriority, Punktzahl: (10)
I1115 07:57:21.639034 1 generic_scheduler.go:726] cronjob-cron-events-1573793820-xt6q9_nxs-stage -> nxs-k8s-s9: TaintTolerationPriority, Punktzahl: (10)
I1115 07:57:21.639041 1 generic_scheduler.go:726] cronjob-cron-events-1573793820-xt6q9_nxs-stage -> nxs-k8s-s10: NodeAffinityPriority, Punktzahl: (0)
I1115 07:57:21.639053 1 generic_scheduler.go:726] cronjob-cron-events-1573793820-xt6q9_nxs-stage -> nxs-k8s-s8: NodeAffinityPriority, Punktzahl: (0)
I1115 07:57:21.639059 1 generic_scheduler.go:726] cronjob-cron-events-1573793820-xt6q9_nxs-stage -> nxs-k8s-s9: NodeAffinityPriority, Punktzahl: (0)
I1115 07:57:21.639061 1 interpod_affinity.go:237] cronjob-cron-events-1573793820-xt6q9 -> nxs-k8s-s10: InterPodAffinityPriority, Punktzahl: (0)
I1115 07:57:21.639063 1 selector_spreading.go:146] cronjob-cron-events-1573793820-xt6q9 -> nxs-k8s-s10: SelectorSpreadPriority, Punktzahl: (10)
I1115 07:57:21.639073 1 interpod_affinity.go:237] cronjob-cron-events-1573793820-xt6q9 -> nxs-k8s-s8: InterPodAffinityPriority, Punktzahl: (0)
I1115 07:57:21.639077 1 selector_spreading.go:146] cronjob-cron-events-1573793820-xt6q9 -> nxs-k8s-s8: SelectorSpreadPriority, Punktzahl: (10)
I1115 07:57:21.639085 1 interpod_affinity.go:237] cronjob-cron-events-1573793820-xt6q9 -> nxs-k8s-s9: InterPodAffinityPriority, Punktzahl: (0)
I1115 07:57:21.639088 1 selector_spreading.go:146] cronjob-cron-events-1573793820-xt6q9 -> nxs-k8s-s9: SelectorSpreadPriority, Punktzahl: (10)
I1115 07:57:21.639103 1 generic_scheduler.go:726] cronjob-cron-events-1573793820-xt6q9_nxs-stage -> nxs-k8s-s10: SelectorSpreadPriority, Punktzahl: (10)
I1115 07:57:21.639109 1 generic_scheduler.go:726] cronjob-cron-events-1573793820-xt6q9_nxs-stage -> nxs-k8s-s8: SelectorSpreadPriority, Punktzahl: (10)
I1115 07:57:21.639114 1 generic_scheduler.go:726] cronjob-cron-events-1573793820-xt6q9_nxs-stage -> nxs-k8s-s9: SelectorSpreadPriority, Punktzahl: (10)
I1115 07:57:21.639127 1 generic_scheduler.go:781] Host nxs-k8s-s10 => Punktzahl 100037
I1115 07:57:21.639150 1 generic_scheduler.go:781] Host nxs-k8s-s8 => Punktzahl 100034
I1115 07:57:21.639154 1 generic_scheduler.go:781] Host nxs-k8s-s9 => Punktzahl 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] Versuche, cronjob-cron-events-1573793820-xt6q9 an nxs-k8s-s10 zu bindenHier sehen wir, dass der Scheduler zunächst eine Filterung durchführt und eine Liste von 3 Knoten erstellt, auf denen ein Start möglich ist (nxs-k8s-s8, nxs-k8s-s9, nxs-k8s-s10). Anschließend zählt er die Punkte basierend auf mehreren Parametern (einschließlich BalancedResourceAllocation, LeastResourceAllocation) für jeden dieser Knoten, um den am besten geeigneten Knoten zu bestimmen. Letztendlich ist der Plan für den Knoten mit der höchsten Punktzahl (hier haben zwei Knoten sofort die gleiche Punktzahl von 100037, daher wird einer von ihnen zufällig ausgewählt — nxs-k8s-s10).
Ausgabe: Wenn auf einem Knoten Pods laufen, für die keine Einschränkungen festgelegt sind, entspricht dies für k8s (hinsichtlich des Ressourcenverbrauchs) dem Zustand, als wären solche Pods auf diesem Knoten überhaupt nicht vorhanden. Daher kann es passieren, dass ein Pod mit einem ressourcenintensiven Prozess (zum Beispiel wowza) alle Ressourcen des Knotens beansprucht, ohne dass k8s diesen Knoten als ausgelastet betrachtet und ihm bei der Bewertung (insbesondere bei den Punkten zur Bewertung der verfügbaren Ressourcen) die gleiche Punktzahl wie einem Knoten zugeordnet wird, auf dem keine aktiven Pods laufen, was letztlich zu einer ungleichen Lastverteilung zwischen den Knoten führen kann.
Pody kündigen
Wie bekannt ist, erhält jeder Pod eine der 3 QoS-Klassen:
- guaranteed — wird vergeben, wenn für jeden Container im Pod sowohl ein Request als auch ein Limit für memory und cpu festgelegt sind, wobei diese Werte übereinstimmen müssen
- burstable — wenn mindestens ein Container im Pod einen Request und ein Limit hat, wobei der Request < Limit ist
- best effort — wenn kein Container im Pod in Bezug auf Ressourcen eingeschränkt ist
Wenn auf dem Knoten ein Mangel an Ressourcen (Speicher, RAM) festgestellt wird, beginnt kubelet, Pods nach einem bestimmten Algorithmus zu bewerten und zu kündigen, der die Priorität des Pods und seine QoS-Klasse berücksichtigt. Zum Beispiel, wenn es um RAM geht, werden die Punkte basierend auf der QoS-Klasse nach folgendem Prinzip vergeben:
- Guaranteed: -998
- BestEffort: 1000
- Burstable: min(max(2, 1000 — (1000 * memoryRequestBytes) / machineMemoryCapacityBytes), 999)
Das heißt, bei gleicher Priorität wird kubelet zunächst die Pods mit der QoS-Klasse best effort vom Knoten kündigen.
Ausgabe: Wenn Sie die Wahrscheinlichkeit verringern möchten, dass ein erforderlicher Pod bei Ressourcenengpässen vom Knoten gekündigt wird, sollten Sie neben der Priorität auch dafür sorgen, dass für ihn ein Request/Limit festgelegt wird.
Horizontal Pod Autoscaling (HPA) Mechanismus
Wenn die Aufgabe besteht, die Anzahl der Pods automatisch je nach Ressourcennutzung (Systemressourcen — CPU/RAM oder benutzerdefinierte — rps) zu erhöhen oder zu verringern, kann eine solche K8s-Einheit wie HPA (Horizontal Pod Autoscaler) dabei helfen. Der Algorithmus besteht aus Folgendem:
- Die aktuellen Werte der beobachteten Ressource (currentMetricValue) werden bestimmt.
- Die gewünschten Werte für die Ressource (desiredMetricValue), die für Systemressourcen über Requests festgelegt werden, werden definiert.
- Die aktuelle Anzahl der Replikate (currentReplicas) wird bestimmt.
- Die gewünschte Anzahl der Replikate (desiredReplicas) wird nach der folgenden Formel berechnet:
desiredReplicas = [ currentReplicas * ( currentMetricValue / desiredMetricValue )]
In diesem Fall findet keine Skalierung statt, wenn der Quotient (currentMetricValue / desiredMetricValue) nahe 1 liegt (wir können die zulässige Toleranz selbst festlegen, standardmäßig beträgt sie 0,1).
Betrachten wir die Funktionsweise von HPA am Beispiel der Anwendung app-test (beschrieben als Deployment), bei der die Anzahl der Replikate basierend auf der CPU-Nutzung angepasst 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 heißt, wir sehen, dass der Pod mit der Anwendung anfangs in zwei Instanzen gestartet wird, von denen jede zwei Container, nginx und nginx-exporter, enthält, für die jeweils requests für CPU festgelegt ist.
Manifest HPA
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 heißt, wir haben ein HPA erstellt, das das Deployment app-test überwachen und die Anzahl der Pods mit der Anwendung basierend auf dem CPU-Wert regulieren wird (wir erwarten, dass der Pod 30 % des angeforderten CPU benötigt), wobei die Anzahl der Replikate im Bereich von 2 bis 10 liegt.
Jetzt betrachten wir den Mechanismus der HPA-Arbeit, 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 erhalten wir Folgendes:
- Der gewünschte Wert (desiredMetricValue) – entsprechend den HPA-Einstellungen beträgt er 30%
- Der aktuelle Wert (currentMetricValue) – zur Berechnung berechnet der Controller-Manager den Durchschnittswert des Ressourcenverbrauchs in %, d.h. er macht im Wesentlichen 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 absoluten Wert für den gewünschten Ressourcenverbrauch (dazu werden die Requests aller Container summiert) 60m + 30m = 90m
- Er berechnet den durchschnittlichen CPU-Verbrauchsprozentsatz im Verhältnis zu den Pod-Requests, d.h. 53m / 90m * 100% = 59%
Jetzt haben wir alles, was wir benötigen, um zu bestimmen, ob die Anzahl der Replikate geändert werden muss. Dafür berechnen wir den Faktor:
ratio = 59% / 30% = 1.96
Das bedeutet, die Anzahl der Replikate sollte etwa verdoppelt werden, also [2 * 1.96] = 4.
Ausgabe: Wie zu sehen ist, ist eine Voraussetzung für das Funktionieren dieses Mechanismus, dass Requests für alle Container im beobachteten Pod vorhanden sind.
Der Mechanismus der horizontalen automatischen Skalierung von Nodes (Cluster Autoscaler)
Um die negativen Auswirkungen auf das System bei Lastspitzen abzumildern, reicht es oft nicht aus, einen konfigurierten HPA zu haben. Beispielsweise trifft der HPA-Controller-Manager auf der Grundlage seiner Einstellungen die Entscheidung, dass die Anzahl der Replikate verdoppelt werden muss, jedoch stehen auf den Nodes keine freien Ressourcen zur Verfügung, um so viele Pods zu starten (d.h. die Node kann die angeforderten Ressourcen für die Pod-Requests nicht bereitstellen) und diese Pods wechseln in den Status Pending.
In diesem Fall kann uns, wenn der Anbieter einen entsprechenden IaaS/PaaS (zum Beispiel GKE/GCE, AKS, EKS usw.) hat, ein Werkzeug wie Node Autoscalerhelfen. Er ermöglicht es, die maximale und die minimale Anzahl von Nodes im Cluster festzulegen und die aktuelle Anzahl der Nodes automatisch zu regulieren (indem er die API des Cloud-Anbieters aufruft, um eine Node zu bestellen/zu löschen), wenn Ressourcendefizite im Cluster festgestellt werden und Pods nicht geplant werden können (in dem Status Pending sind).
Ausgabe: Für die Möglichkeit der automatischen Skalierung von Nodes müssen in den Containern der Pods Requests festgelegt werden, damit k8s die Belastung der Nodes korrekt einschätzen und entsprechend melden kann, dass im Cluster nicht genügend Ressourcen vorhanden sind, um einen weiteren Pod zu starten.
Fazit
Es ist wichtig zu beachten, dass die Festlegung von Ressourcenbeschränkungen für Container keine zwingende Voraussetzung für den erfolgreichen Start einer Anwendung ist, es jedoch aus folgenden Gründen dennoch ratsam ist:
- Um die Genauigkeit des Schedulers bei der Lastverteilung zwischen den k8s-Nodes zu verbessern
- Um die Wahrscheinlichkeit eines Ereignisses „Pod-Eviction“ zu verringern
- Für das horizontale automatisierte Skalieren von Anwendungs-Pods (HPA)
- Für das horizontale automatisierte Skalieren von Nodes (Cluster Autoscaling) bei Cloud-Anbietern
Lesen Sie auch andere Artikel in unserem Blog:
Quelle: habr.com
