Kubernetes: Warum ist es so wichtig, das Ressourcenmanagement des Systems einzurichten?

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?

Kubernetes: Warum ist es so wichtig, das Ressourcenmanagement des Systems einzurichten?

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: 200m

    d.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: 2Gi

    d.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: 4Gi

    d.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: 1Gi

    d.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:

  1. Filtern
  2. 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 binden

Hier 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:

  1. 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
  2. burstable — wenn mindestens ein Container im Pod einen Request und ein Limit hat, wobei der Request < Limit ist
  3. 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:

  1. Die aktuellen Werte der beobachteten Ressource (currentMetricValue) werden bestimmt.
  2. Die gewünschten Werte für die Ressource (desiredMetricValue), die für Systemressourcen über Requests festgelegt werden, werden definiert.
  3. Die aktuelle Anzahl der Replikate (currentReplicas) wird bestimmt.
  4. 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-status

    Das 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: 30

    Das 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:
    1. Er erhält die absoluten Werte der Metriken der Pods vom Metric-Server, d.h. 101m und 4m
    2. Er berechnet den durchschnittlichen absoluten Wert, d.h. (101m + 4m) / 2 = 53m
    3. Er erhält den absoluten Wert für den gewünschten Ressourcenverbrauch (dazu werden die Requests aller Container summiert) 60m + 30m = 90m
    4. 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:

  1. Um die Genauigkeit des Schedulers bei der Lastverteilung zwischen den k8s-Nodes zu verbessern
  2. Um die Wahrscheinlichkeit eines Ereignisses „Pod-Eviction“ zu verringern
  3. Für das horizontale automatisierte Skalieren von Anwendungs-Pods (HPA)
  4. Für das horizontale automatisierte Skalieren von Nodes (Cluster Autoscaling) bei Cloud-Anbietern

Lesen Sie auch andere Artikel in unserem Blog:

Quelle: habr.com

60GB SSD 8Gb DDR4