Kubernetes: perché è così importante configurare la gestione delle risorse del sistema?

In genere, c'è sempre la necessità di garantire un pool dedicato di risorse a un'applicazione per garantirne il corretto e stabile funzionamento. Ma cosa succede se più applicazioni operano sulla stessa infrastruttura? Come garantire a ciascuna di esse le risorse minime necessarie? Come limitare il consumo di risorse? In che modo è possibile distribuire il carico tra i nodi? Come garantire il funzionamento del meccanismo di scalabilità orizzontale in caso di aumento del carico sulle applicazioni?

Kubernetes: perché è così importante configurare la gestione delle risorse del sistema?

Dobbiamo iniziare identificando i principali tipi di risorse esistenti nel sistema: ovviamente, il tempo di CPU e la memoria RAM. Nelle manifestazioni di k8s, questi tipi di risorse sono misurati nelle seguenti unità:

  • CPU — in core
  • RAM — in byte

Per ciascuna risorsa, è possibile specificare due tipi di requisiti — requests e limits. Requests — descrive i requisiti minimi delle risorse libere del nodo per avviare un contenitore (e il pod in generale), mentre limits stabilisce un limite rigido sulle risorse disponibili per il contenitore.

È importante capire che nel manifesto non è necessario definire esplicitamente entrambi i tipi; in tal caso, il comportamento sarà il seguente:

  • Se viene specificato solo il limite delle risorse, le richieste per quella risorsa assumono automaticamente un valore pari al limite (è possibile verificarlo richiamando la descrizione dell'entità). Quindi, in pratica, il funzionamento del contenitore sarà limitato alla stessa quantità di risorse necessaria per il suo avvio.
  • Se per la risorsa è stata specificata solo la richiesta, non ci sono limiti superiori su tale risorsa; quindi, il contenitore è limitato solo dalle risorse del nodo stesso.

Esiste anche la possibilità di configurare la gestione delle risorse non solo a livello di singolo contenitore, ma anche a livello di namespace utilizzando le seguenti entità:

  • LimitRange — descrive la politica di limitazione a livello di contenitore/pod nel ns e serve a definire i limiti predefiniti per il contenitore/pod, oltre a prevenire la creazione di contenitori/pod eccessivamente grandi (o viceversa), limitare il loro numero e definire possibili differenze di valori tra limiti e richieste.
  • ResourceQuotas — descrivono la politica di limitazione in generale per tutti i contenitori all'interno di un namespace e viene utilizzata, di norma, per delimitare le risorse tra gli ambienti (utile quando gli ambienti non sono rigidamente separati a livello di nodi)

Di seguito sono riportati esempi di manifesti che definiscono le limitazioni delle risorse:

  • A livello di un singolo contenitore:

    containers:
    - name: app-nginx
      image: nginx
      resources:
        requests:
          memory: 1Gi
        limits:
          cpu: 200m

    Cioè, in questo caso per avviare un contenitore con nginx saranno necessarie almeno 1G di RAM libera e 0.2 CPU nel nodo, mentre il massimo che il contenitore può utilizzare è 0.2 CPU e tutta la RAM disponibile nel nodo.

  • A livello di un intero namespace:

    apiVersion: v1
    kind: ResourceQuota
    metadata:
      name: nxs-test
    spec:
      hard:
        requests.cpu: 300m
        requests.memory: 1Gi
        limits.cpu: 700m
        limits.memory: 2Gi

    Cioè, la somma di tutte le richieste dei contenitori nel namespace predefinito non può superare 300m per la CPU e 1G per la RAM, mentre la somma di tutti i limiti deve essere di 700m per la CPU e 2G per la RAM.

  • Limitazioni predefinite per i contenitori nel 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: 4Gi

    Cioè, nel namespace predefinito per tutti i container, verrà impostato un request di 100m per CPU e 1G per RAM, con un limit di 1 CPU e 2G. Inoltre, è stato impostato un limite sui valori possibili per request/limit di CPU (50m < x < 2) e RAM (500M < x < 4G).

  • Limitazioni a livello di pod ns:

    apiVersion: v1
    kind: LimitRange
    metadata:
     name: nxs-limit-pod
    spec:
     limits:
     - type: Pod
       max:
         cpu: 4
         memory: 1Gi

    Cioè, per ogni pod nel ns predefinito, sarà imposto un limite di 4 vCPU e 1G.

Ora vorrei discutere quali vantaggi può darci l'impostazione di queste limitazioni.

Meccanismo di bilanciamento del carico tra i nodi

Come è noto, la distribuzione dei pod tra i nodi è gestita da un componente k8s chiamato scheduler, che funziona secondo un algoritmo specifico. Questo algoritmo passa attraverso due fasi nella scelta del nodo ottimale per l'avvio:

  1. Filtraggio
  2. Classificazione

Cioè, secondo la politica descritta, inizialmente vengono selezionati i nodi sui quali è possibile avviare il pod in base a un insieme di predicates (tra cui viene controllato se il nodo dispone di risorse sufficienti per l'avvio del pod — PodFitsResources), e poi, per ciascuno di questi nodi, in base a priorities vengono assegnati punti (incluso che più risorse libere ha il nodo, più punti gli vengono assegnati - LeastResourceAllocation/LeastRequestedPriority/BalancedResourceAllocation) e si avvia sul nodo con il maggior numero di punti (se questo criterio è soddisfatto da più nodi, ne viene scelto uno a caso).

Inoltre, è importante capire che lo scheduler, quando valuta le risorse disponibili del nodo, si basa sui dati memorizzati in etcd - ossia sulla somma delle richieste/limiti delle risorse di ciascun pod avviato su quel nodo, ma non sul consumo effettivo delle risorse. Questa informazione può essere ottenuta dall'output del comando kubectl describe node $NODE, ad esempio:

# 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%)

Qui possiamo vedere tutti i pod avviati su un nodo specifico e le risorse richieste da ciascun pod. Ecco come appaiono i log dello scheduler quando si avvia il pod cronjob-cron-events-1573793820-xt6q9 (questa informazione apparirà nei log dello scheduler impostando il livello di logging a 10 nei parametri della riga di comando di avvio -v=10):

log

I1115 07:57:21.637791 1 scheduling_queue.go:908] Inizio tentativo di pianificare il pod nxs-stage/cronjob-cron-events-1573793820-xt6q9
I1115 07:57:21.637804 1 scheduler.go:453] Tentativo di pianificare il pod: nxs-stage/cronjob-cron-events-1573793820-xt6q9
I1115 07:57:21.638285 1 predicates.go:829] Pianificare il pod nxs-stage/cronjob-cron-events-1573793820-xt6q9 sul nodo nxs-k8s-s5 è consentito, il nodo sta eseguendo solo 16 pod su 110.
I1115 07:57:21.638300 1 predicates.go:829] Pianificare il pod nxs-stage/cronjob-cron-events-1573793820-xt6q9 sul nodo nxs-k8s-s6 è consentito, il nodo sta eseguendo solo 20 pod su 110.
I1115 07:57:21.638322 1 predicates.go:829] Pianificare il pod nxs-stage/cronjob-cron-events-1573793820-xt6q9 sul nodo nxs-k8s-s3 è consentito, il nodo sta eseguendo solo 20 pod su 110.
I1115 07:57:21.638322 1 predicates.go:829] Pianificare il pod nxs-stage/cronjob-cron-events-1573793820-xt6q9 sul nodo nxs-k8s-s4 è consentito, il nodo sta eseguendo solo 17 pod su 110.
I1115 07:57:21.638334 1 predicates.go:829] Pianificare il pod nxs-stage/cronjob-cron-events-1573793820-xt6q9 sul nodo nxs-k8s-s10 è consentito, il nodo sta eseguendo solo 16 pod su 110.
I1115 07:57:21.638365 1 predicates.go:829] Pianificare il pod nxs-stage/cronjob-cron-events-1573793820-xt6q9 sul nodo nxs-k8s-s12 è consentito, il nodo sta eseguendo solo 9 pod su 110.
I1115 07:57:21.638334 1 predicates.go:829] Pianificare il pod nxs-stage/cronjob-cron-events-1573793820-xt6q9 sul nodo nxs-k8s-s11 è consentito, il nodo sta eseguendo solo 11 pod su 110.
I1115 07:57:21.638385 1 predicates.go:829] Pianificare il pod nxs-stage/cronjob-cron-events-1573793820-xt6q9 sul nodo nxs-k8s-s1 è consentito, il nodo sta eseguendo solo 19 pod su 110.
I1115 07:57:21.638402 1 predicates.go:829] Pianificare il pod nxs-stage/cronjob-cron-events-1573793820-xt6q9 sul nodo nxs-k8s-s2 è consentito, il nodo sta eseguendo solo 21 pod su 110.
I1115 07:57:21.638383 1 predicates.go:829] Pianificare il pod nxs-stage/cronjob-cron-events-1573793820-xt6q9 sul nodo nxs-k8s-s9 è consentito, il nodo sta eseguendo solo 16 pod su 110.
I1115 07:57:21.638335 1 predicates.go:829] Pianificare il pod nxs-stage/cronjob-cron-events-1573793820-xt6q9 sul nodo nxs-k8s-s8 è consentito, il nodo sta eseguendo solo 18 pod su 110.
I1115 07:57:21.638408 1 predicates.go:829] Pianificare il pod nxs-stage/cronjob-cron-events-1573793820-xt6q9 sul nodo nxs-k8s-s13 è consentito, il nodo sta eseguendo solo 8 pod su 110.
I1115 07:57:21.638478 1 predicates.go:1369] Pianificare il pod nxs-stage/cronjob-cron-events-1573793820-xt6q9 sul nodo nxs-k8s-s10 è consentito, i termini di anti-affinità dei pod esistenti sono soddisfatti.
I1115 07:57:21.638505 1 predicates.go:1369] Pianificare il pod nxs-stage/cronjob-cron-events-1573793820-xt6q9 sul nodo nxs-k8s-s8 è consentito, i termini di anti-affinità dei pod esistenti sono soddisfatti.
I1115 07:57:21.638577 1 predicates.go:1369] Pianificare il pod nxs-stage/cronjob-cron-events-1573793820-xt6q9 sul nodo nxs-k8s-s9 è consentito, i termini di anti-affinità dei pod esistenti sono soddisfatti.
I1115 07:57:21.638583 1 predicates.go:829] Pianificare il pod nxs-stage/cronjob-cron-events-1573793820-xt6q9 sul nodo nxs-k8s-s7 è consentito, il nodo sta eseguendo solo 25 pod su 110.
I1115 07:57:21.638932 1 resource_allocation.go:78] cronjob-cron-events-1573793820-xt6q9 -> nxs-k8s-s10: Allocazione delle risorse bilanciata, capacità 39900 millicore 66620178432 byte di memoria, richiesta totale 2343 millicore 9640186880 byte di memoria, punteggio 9
I1115 07:57:21.638946 1 resource_allocation.go:78] cronjob-cron-events-1573793820-xt6q9 -> nxs-k8s-s10: Allocazione delle risorse minime, capacità 39900 millicore 66620178432 byte di memoria, richiesta totale 2343 millicore 9640186880 byte di memoria, punteggio 8
I1115 07:57:21.638961 1 resource_allocation.go:78] cronjob-cron-events-1573793820-xt6q9 -> nxs-k8s-s9: Allocazione delle risorse bilanciate, capacità 39900 millicore 66620170240 byte di memoria, richiesta totale 4107 millicore 11307422720 byte di memoria, punteggio 9
I1115 07:57:21.638971 1 resource_allocation.go:78] cronjob-cron-events-1573793820-xt6q9 -> nxs-k8s-s8: Allocazione delle risorse bilanciate, capacità 39900 millicore 66620178432 byte di memoria, richiesta totale 5847 millicore 24333637120 byte di memoria, punteggio 7
I1115 07:57:21.638975 1 resource_allocation.go:78] cronjob-cron-events-1573793820-xt6q9 -> nxs-k8s-s9: Allocazione delle risorse minime, capacità 39900 millicore 66620170240 byte di memoria, richiesta totale 4107 millicore 11307422720 byte di memoria, punteggio 8
I1115 07:57:21.638990 1 resource_allocation.go:78] cronjob-cron-events-1573793820-xt6q9 -> nxs-k8s-s8: Allocazione delle risorse minime, capacità 39900 millicore 66620178432 byte di memoria, richiesta totale 5847 millicore 24333637120 byte di memoria, punteggio 7
I1115 07:57:21.639022 1 generic_scheduler.go:726] cronjob-cron-events-1573793820-xt6q9_nxs-stage -> nxs-k8s-s10: Priorità di tolleranza a taint, punteggio: (10)
I1115 07:57:21.639030 1 generic_scheduler.go:726] cronjob-cron-events-1573793820-xt6q9_nxs-stage -> nxs-k8s-s8: Priorità di tolleranza a taint, punteggio: (10)
I1115 07:57:21.639034 1 generic_scheduler.go:726] cronjob-cron-events-1573793820-xt6q9_nxs-stage -> nxs-k8s-s9: Priorità di tolleranza a taint, punteggio: (10)
I1115 07:57:21.639041 1 generic_scheduler.go:726] cronjob-cron-events-1573793820-xt6q9_nxs-stage -> nxs-k8s-s10: Priorità di affinità del nodo, punteggio: (0)
I1115 07:57:21.639053 1 generic_scheduler.go:726] cronjob-cron-events-1573793820-xt6q9_nxs-stage -> nxs-k8s-s8: Priorità di affinità del nodo, punteggio: (0)
I1115 07:57:21.639059 1 generic_scheduler.go:726] cronjob-cron-events-1573793820-xt6q9_nxs-stage -> nxs-k8s-s9: Priorità di affinità del nodo, punteggio: (0)
I1115 07:57:21.639061 1 interpod_affinity.go:237] cronjob-cron-events-1573793820-xt6q9 -> nxs-k8s-s10: Priorità di affinità inter-pod, punteggio: (0)
I1115 07:57:21.639063 1 selector_spreading.go:146] cronjob-cron-events-1573793820-xt6q9 -> nxs-k8s-s10: Priorità di distribuzione selettiva, punteggio: (10)
I1115 07:57:21.639073 1 interpod_affinity.go:237] cronjob-cron-events-1573793820-xt6q9 -> nxs-k8s-s8: Priorità di affinità inter-pod, punteggio: (0)
I1115 07:57:21.639077 1 selector_spreading.go:146] cronjob-cron-events-1573793820-xt6q9 -> nxs-k8s-s8: Priorità di distribuzione selettiva, punteggio: (10)
I1115 07:57:21.639085 1 interpod_affinity.go:237] cronjob-cron-events-1573793820-xt6q9 -> nxs-k8s-s9: Priorità di affinità inter-pod, punteggio: (0)
I1115 07:57:21.639088 1 selector_spreading.go:146] cronjob-cron-events-1573793820-xt6q9 -> nxs-k8s-s9: Priorità di distribuzione selettiva, punteggio: (10)
I1115 07:57:21.639103 1 generic_scheduler.go:726] cronjob-cron-events-1573793820-xt6q9_nxs-stage -> nxs-k8s-s10: Priorità di distribuzione selettiva, punteggio: (10)
I1115 07:57:21.639109 1 generic_scheduler.go:726] cronjob-cron-events-1573793820-xt6q9_nxs-stage -> nxs-k8s-s8: Priorità di distribuzione selettiva, punteggio: (10)
I1115 07:57:21.639114 1 generic_scheduler.go:726] cronjob-cron-events-1573793820-xt6q9_nxs-stage -> nxs-k8s-s9: Priorità di distribuzione selettiva, punteggio: (10)
I1115 07:57:21.639127 1 generic_scheduler.go:781] Host nxs-k8s-s10 -> Punteggio 100037
I1115 07:57:21.639150 1 generic_scheduler.go:781] Host nxs-k8s-s8 -> Punteggio 100034
I1115 07:57:21.639154 1 generic_scheduler.go:781] Host nxs-k8s-s9 -> Punteggio 100037
I1115 07:57:21.639267 1 scheduler_binder.go:269] Supponiamo i volumi del pod per il pod "nxs-stage/cronjob-cron-events-1573793820-xt6q9", nodo "nxs-k8s-s10"
I1115 07:57:21.639286 1 scheduler_binder.go:279] Supponiamo i volumi del pod per il pod "nxs-stage/cronjob-cron-events-1573793820-xt6q9", nodo "nxs-k8s-s10": tutti i PVC sono legati e non c'è niente da fare

Qui vediamo che inizialmente lo scheduler filtra e crea un elenco di 3 nodi su cui è possibile avviare (nxs-k8s-s8, nxs-k8s-s9, nxs-k8s-s10). Successivamente, calcola i punteggi in base a diversi parametri (tra cui BalancedResourceAllocation, LeastResourceAllocation) per ciascuno di questi nodi al fine di determinare il nodo più adatto. Alla fine, il piano viene eseguito sul nodo con il punteggio più alto (qui due nodi hanno entrambi un punteggio di 100037, quindi ne viene scelto uno a caso — nxs-k8s-s10).

Risultato: se i pod sono in esecuzione su un nodo per i quali non sono stati definiti limiti, per k8s (in termini di consumo di risorse) questo sarà equivalente a non avere affatto tali pod su questo nodo. Pertanto, se, ad esempio, hai un pod con un processo che consuma molte risorse (ad esempio, wowza) e per esso non sono stati definiti limiti, può insorgere una situazione in cui effettivamente questo pod ha consumato tutte le risorse del nodo, ma per k8s questo nodo risulta non sovraccarico e gli verranno attribuiti gli stessi punti nella classifica (proprio nei punteggi relativi alla valutazione delle risorse disponibili) di un nodo senza pod attivi, il che alla fine può portare a una distribuzione non uniforme del carico tra i nodi.

Espulsione del pod

Come noto, a ogni pod viene assegnata una delle 3 classi di QoS:

  1. garantito — viene assegnato quando per ogni contenitore nel pod sono stati definiti request e limit sia per la memoria che per la CPU, e questi valori devono corrispondere
  2. burstable — almeno un contenitore nel pod ha request e limit, con request < limit
  3. best effort — quando nessun contenitore nel pod è limitato in termini di risorse

Quando si verifica una carenza di risorse (disco, memoria) su un nodo, kubelet inizia a classificare ed espellere i pod secondo un algoritmo che tiene conto della priorità del pod e della sua classe QoS. Ad esempio, se si tratta di RAM, i punteggi vengono assegnati in base alla classe QoS secondo il principio seguente:

  • Garantito: -998
  • Miglior sforzo: 1000
  • Variabile: min(max(2, 1000 - (1000 * memoryRequestBytes) / machineMemoryCapacityBytes), 999)

Cioè, a parità di priorità, kubelet espellerà prima i pod con la classe QoS di miglior sforzo.

Risultato: se si desidera ridurre la probabilità di espulsione di un pod importante dal nodo in caso di carenza di risorse, è necessario prestare attenzione anche alla definizione di request/limit per esso, oltre alla priorità.

Meccanismo di scalabilità automatica orizzontale dei pod dell'applicazione (HPA)

Quando si vuole aumentare e diminuire automaticamente il numero di pod in base all'utilizzo delle risorse (sistemiche — CPU/RAM o utente — rps), può aiutare una entità k8s come HPA (Horizontal Pod Autoscaler). Il cui algoritmo è il seguente:

  1. Si determinano le attuali letture della risorsa osservata (currentMetricValue)
  2. Vengono definiti i valori desiderati per la risorsa (desiredMetricValue), che per le risorse di sistema sono impostati tramite la richiesta
  3. Viene definito il numero attuale di repliche (currentReplicas)
  4. Il numero desiderato di repliche (desiredReplicas) viene calcolato secondo la seguente formula
    desiredReplicas = [ currentReplicas * ( currentMetricValue / desiredMetricValue )]

In questo caso, non si verifica alcuna scalabilità quando il coefficiente (currentMetricValue / desiredMetricValue) è vicino a 1 (la tolleranza ammissibile può essere impostata da noi, di default è 0,1).

Consideriamo il funzionamento di hpa con l'esempio dell'applicazione app-test (descritta come Deployment), dove è necessario variare il numero di repliche in base all'utilizzo della CPU:

  • Manifesto dell'applicazione

    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

    Quindi vediamo che con l'applicazione si avvia inizialmente in due istanze, ciascuna contenente due contenitori nginx e nginx-exporter, per ciascuno dei quali è stato specificato requests per la CPU.

  • Manifesto 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

    Quindi abbiamo creato un HPA che monitorerà il Deployment app-test e regolerà il numero di pod con l'applicazione in base al valore della CPU (ci aspettiamo che il pod consumi il 30% della CPU richiesta), mentre il numero di repliche è compreso tra 2 e 10.

    Ora, consideriamo il meccanismo di funzionamento dell'HPA, se si applica un carico a uno dei pod:

     # kubectl top pod
     NAME                                                   CPU(cores)   MEMORY(bytes)
     app-test-78559f8f44-pgs58            101m         243Mi
     app-test-78559f8f44-cj4jz            4m           240Mi

In sintesi abbiamo quanto segue:

  • Il valore desiderato (desiredMetricValue) — secondo le impostazioni dell'HPA è pari al 30%
  • Il valore attuale (currentMetricValue) — per il calcolo il controller-manager calcola la media del consumo delle risorse in %, cioè fa in sostanza quanto segue:
    1. Ottiene i valori assoluti delle metriche dei pod dal metric-server, cioè 101m e 4m
    2. Calcola la media dei valori assoluti, cioè (101m + 4m) / 2 = 53m
    3. Ottiene il valore assoluto per il consumo di risorse desiderato (sommando i request di tutti i container) 60m + 30m = 90m
    4. Calcola la percentuale media di utilizzo della CPU rispetto ai request del pod, cioè 53m / 90m * 100% = 59%

Ora abbiamo tutto il necessario per determinare se è necessario modificare il numero di repliche, quindi calcoliamo il rapporto:

rapporto = 59% / 30% = 1.96

Cioè, il numero di repliche deve essere aumentato di circa 2 volte e diventare [2 * 1.96] = 4.

Risultato: Come si può notare, affinché questo meccanismo funzioni, è necessario avere requests per tutti i container nel pod monitorato.

Meccanismo di autoscaling orizzontale dei nodi (Cluster Autoscaler)

Per mitigare l'impatto negativo sul sistema durante i picchi di carico, avere un hpa configurato può non essere sufficiente. Ad esempio, secondo le impostazioni nel controller manager hpa, viene presa la decisione che il numero di repliche deve essere aumentato di 2 volte; tuttavia, sui nodi non ci sono risorse libere per avviare tale numero di pod (cioè, il nodo non può fornire le risorse richieste dal pod requests) e questi pod passano in stato di Pending.

In questo caso, se il provider dispone di un adeguato IaaS/PaaS (ad esempio, GKE/GCE, AKS, EKS, ecc.), uno strumento utile può essere Node Autoscaler. Questo strumento consente di impostare un numero massimo e minimo di nodi nel cluster e regola automaticamente il numero attuale di nodi (effettuando chiamate all'API del provider cloud per aggiungere/rimuovere nodi) quando ci sono carenze di risorse nel cluster e i pod non possono essere programmati (sono in stato di Pending).

Risultato: Per consentire l'autoscaling dei nodi, è necessario definire le richieste nei container dei pod affinché k8s possa valutare correttamente il carico dei nodi e segnalare che non ci sono risorse disponibili nel cluster per l'avvio di un nuovo pod.

Conclusione

Va notato che configurare i limiti delle risorse del container non è un requisito obbligatorio per l'avvio dell'applicazione con successo, tuttavia è comunque consigliato per le seguenti ragioni:

  1. Per un funzionamento più preciso dello scheduler in merito bilanciamento del carico tra i nodi di k8s
  2. Per ridurre la probabilità di eventi di ‘eviction dei pod’
  3. Per il funzionamento dell'autoscaling orizzontale dei pod dell'applicazione (HPA)
  4. Per il funzionamento dell'autoscaling orizzontale dei nodi (Cluster Autoscaling) nei provider di cloud

Leggi anche altri articoli nel nostro blog:

Fonte: habr.com

Acquista hosting affidabile per siti web con protezione DDoS, VPS VDS server 🔥 Acquista hosting affidabile per siti web con protezione DDoS, VPS VDS server | ProHoster