Kubernetes: ¿por qué es tan importante configurar la gestión de recursos del sistema?

Por lo general, siempre surge la necesidad de proporcionar un conjunto de recursos dedicados a alguna aplicación para su correcto y estable funcionamiento. Pero, ¿qué sucede si varias aplicaciones funcionan simultáneamente en la misma infraestructura? ¿Cómo garantizar que cada una de ellas cuente con los recursos mínimos necesarios? ¿De qué manera se puede limitar el consumo de recursos? ¿Cómo distribuir adecuadamente la carga entre los nodos? ¿Cómo asegurar el funcionamiento del mecanismo de escalado horizontal en caso de un aumento de la carga sobre las aplicaciones?

Kubernetes: ¿por qué es tan importante configurar la gestión de recursos del sistema?

Lo primero es identificar los principales tipos de recursos que existen en el sistema: esto incluye, por supuesto, el tiempo de CPU y la memoria RAM. En los manifiestos de k8s, estos tipos de recursos se miden en las siguientes unidades:

  • CPU — en núcleos
  • RAM — en bytes

Además, para cada recurso es posible establecer dos tipos de requisitos: requests y limits. Requests — describe los requisitos mínimos de recursos libres en el nodo para ejecutar el contenedor (y el pod en general), mientras que limits establece un límite estricto de recursos disponibles para el contenedor.

Es importante comprender que no es necesario definir explícitamente ambos tipos en el manifiesto, siendo el comportamiento el siguiente:

  • Si solo se especifica explícitamente el límite de un recurso, entonces requests para ese recurso automáticamente adoptará un valor igual a limits (esto se puede verificar al invocar describe en la entidad). Es decir, de hecho, el funcionamiento del contenedor estará limitado a la misma cantidad de recursos que requiere para su ejecución.
  • Si para un recurso solo se especifican requests, no se establece ningún límite superior para ese recurso: es decir, el contenedor está limitado solo a los recursos del propio nodo.

También existe la posibilidad de administrar recursos no solo a nivel de un contenedor específico, sino también a nivel de namespace utilizando las siguientes entidades:

  • LimitRange — describe la política de límites a nivel de contenedor/pod en el ns y es necesaria para definir las limitaciones predeterminadas en el contenedor/pod, así como para prevenir la creación de contenedores/pods excesivamente grandes (o a la inversa), limitar su cantidad y determinar la posible diferencia de valores en limits y requests.
  • ResourceQuotas — describe la política de límites en general para todos los contenedores en el ns y se utiliza, como regla general, para la delimitación de recursos por entornos (útil cuando los entornos no están estrictamente separados a nivel de nodos)

A continuación se presentan ejemplos de manifiestos donde se establecen límites en los recursos:

  • A nivel de un contenedor específico:

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

    Es decir, en este caso, se necesitará al menos 1G de RAM libre y 0.2 de CPU en el nodo para ejecutar el contenedor con nginx, siendo que el contenedor puede consumir un máximo de 0.2 de CPU y toda la RAM disponible en el nodo.

  • A nivel de todo el ns:

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

    Es decir, la suma de todas las solicitudes de los contenedores en el ns por defecto no puede exceder 300m para CPU y 1G para RAM, mientras que la suma de todos los límites debe ser 700m para CPU y 2G para RAM.

  • Límites predeterminados para contenedores en el 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

    Es decir, en el namespace por defecto se establecerá una solicitud de 100m para CPU y 1G para RAM para todos los contenedores, y un límite de 1 CPU y 2G. También se ha establecido un límite sobre los valores posibles en las solicitudes/límites para CPU (50m < x < 2) y RAM (500M < x < 4G).

  • Límites a nivel de pods en el ns:

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

    Es decir, para cada pod en el ns por defecto se establecerá un límite de 4 vCPU y 1G.

Ahora me gustaría hablar sobre las ventajas que la implementación de estos límites puede ofrecernos.

El mecanismo de balanceo de carga entre nodos

Como es sabido, la distribución de pods entre nodos es responsabilidad de un componente de k8s llamado scheduler, que opera según un algoritmo específico. Este algoritmo, durante la selección del nodo óptimo para la ejecución, pasa por dos etapas:

  1. Filtrado
  2. Clasificación

Es decir, según la política descrita, inicialmente se seleccionan los nodos en los que es posible ejecutar el pod en función de un conjunto de predicados (incluyendo la comprobación de si hay suficientes recursos en el nodo para ejecutar el pod — PodFitsResources), y luego para cada uno de estos nodos, de acuerdo con prioridades se asignan puntos (es decir, cuanto más recursos libres tenga el nodo, más puntos se le asignan — LeastResourceAllocation/LeastRequestedPriority/BalancedResourceAllocation) y se inicia en el nodo con la mayor cantidad de puntos (si varias nodos cumplen con esta condición, se elige uno al azar).

Es importante entender que el scheduler, al evaluar los recursos disponibles en el nodo, se basa en datos almacenados en etcd — es decir, en la suma de los recursos solicitados/límite de cada pod que se ejecuta en ese nodo, pero no en el consumo real de recursos. Esta información se puede obtener con el comando kubectl describe node $NODE, por ejemplo:

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

Aquí vemos todos los pods ejecutándose en un nodo específico, así como los recursos que solicita cada uno de ellos. Así es como aparecen los registros del scheduler al iniciar el pod cronjob-cron-events-1573793820-xt6q9 (esta información aparecerá en el registro del scheduler al establecer el nivel de registro en 10 en los argumentos del comando de inicio —v=10):

registro

I1115 07:57:21.637791       1 scheduling_queue.go:908] A punto de intentar programar el pod nxs-stage/cronjob-cron-events-1573793820-xt6q9                                                                                                                                           
I1115 07:57:21.637804       1 scheduler.go:453] Intentando programar el pod: nxs-stage/cronjob-cron-events-1573793820-xt6q9                                                                                                                                                    
I1115 07:57:21.638285       1 predicates.go:829] Se permite programar el Pod nxs-stage/cronjob-cron-events-1573793820-xt6q9 en el Nodo nxs-k8s-s5, el Nodo está ejecutando solo 16 de 110 Pods.                                                                               
I1115 07:57:21.638300       1 predicates.go:829] Se permite programar el Pod nxs-stage/cronjob-cron-events-1573793820-xt6q9 en el Nodo nxs-k8s-s6, el Nodo está ejecutando solo 20 de 110 Pods.                                                                               
I1115 07:57:21.638322       1 predicates.go:829] Se permite programar el Pod nxs-stage/cronjob-cron-events-1573793820-xt6q9 en el Nodo nxs-k8s-s3, el Nodo está ejecutando solo 20 de 110 Pods.                                                                               
I1115 07:57:21.638322       1 predicates.go:829] Se permite programar el Pod nxs-stage/cronjob-cron-events-1573793820-xt6q9 en el Nodo nxs-k8s-s4, el Nodo está ejecutando solo 17 de 110 Pods.                                                                               
I1115 07:57:21.638334       1 predicates.go:829] Se permite programar el Pod nxs-stage/cronjob-cron-events-1573793820-xt6q9 en el Nodo nxs-k8s-s10, el Nodo está ejecutando solo 16 de 110 Pods.                                                                              
I1115 07:57:21.638365       1 predicates.go:829] Se permite programar el Pod nxs-stage/cronjob-cron-events-1573793820-xt6q9 en el Nodo nxs-k8s-s12, el Nodo está ejecutando solo 9 de 110 Pods.                                                                               
I1115 07:57:21.638334       1 predicates.go:829] Se permite programar el Pod nxs-stage/cronjob-cron-events-1573793820-xt6q9 en el Nodo nxs-k8s-s11, el Nodo está ejecutando solo 11 de 110 Pods.                                                                              
I1115 07:57:21.638385       1 predicates.go:829] Se permite programar el Pod nxs-stage/cronjob-cron-events-1573793820-xt6q9 en el Nodo nxs-k8s-s1, el Nodo está ejecutando solo 19 de 110 Pods.                                                                               
I1115 07:57:21.638402       1 predicates.go:829] Se permite programar el Pod nxs-stage/cronjob-cron-events-1573793820-xt6q9 en el Nodo nxs-k8s-s2, el Nodo está ejecutando solo 21 de 110 Pods.                                                                               
I1115 07:57:21.638383       1 predicates.go:829] Se permite programar el Pod nxs-stage/cronjob-cron-events-1573793820-xt6q9 en el Nodo nxs-k8s-s9, el Nodo está ejecutando solo 16 de 110 Pods.                                                                               
I1115 07:57:21.638335       1 predicates.go:829] Se permite programar el Pod nxs-stage/cronjob-cron-events-1573793820-xt6q9 en el Nodo nxs-k8s-s8, el Nodo está ejecutando solo 18 de 110 Pods.                                                                               
I1115 07:57:21.638408       1 predicates.go:829] Se permite programar el Pod nxs-stage/cronjob-cron-events-1573793820-xt6q9 en el Nodo nxs-k8s-s13, el Nodo está ejecutando solo 8 de 110 Pods.                                                                               
I1115 07:57:21.638478       1 predicates.go:1369] Se permite programar el Pod nxs-stage/cronjob-cron-events-1573793820-xt6q9 en el Nodo nxs-k8s-s10, se satisfacen los términos de anti-afinidad de los pods existentes.                                                                         
I1115 07:57:21.638505       1 predicates.go:1369] Se permite programar el Pod nxs-stage/cronjob-cron-events-1573793820-xt6q9 en el Nodo nxs-k8s-s8, se satisfacen los términos de anti-afinidad de los pods existentes.                                                                          
I1115 07:57:21.638577       1 predicates.go:1369] Se permite programar el Pod nxs-stage/cronjob-cron-events-1573793820-xt6q9 en el Nodo nxs-k8s-s9, se satisfacen los términos de anti-afinidad de los pods existentes.                                                                          
I1115 07:57:21.638583       1 predicates.go:829] Se permite programar el Pod nxs-stage/cronjob-cron-events-1573793820-xt6q9 en el Nodo nxs-k8s-s7, el Nodo está ejecutando solo 25 de 110 Pods.                                                                               
I1115 07:57:21.638932       1 resource_allocation.go:78] cronjob-cron-events-1573793820-xt6q9 -> nxs-k8s-s10: BalancedResourceAllocation, capacidad 39900 milicore 66620178432 bytes de memoria, solicitud total 2343 milicore 9640186880 bytes de memoria, puntuación 9        
I1115 07:57:21.638946       1 resource_allocation.go:78] cronjob-cron-events-1573793820-xt6q9 -> nxs-k8s-s10: LeastResourceAllocation, capacidad 39900 milicore 66620178432 bytes de memoria, solicitud total 2343 milicore 9640186880 bytes de memoria, puntuación 8           
I1115 07:57:21.638961       1 resource_allocation.go:78] cronjob-cron-events-1573793820-xt6q9 -> nxs-k8s-s9: BalancedResourceAllocation, capacidad 39900 milicore 66620170240 bytes de memoria, solicitud total 4107 milicore 11307422720 bytes de memoria, puntuación 9        
I1115 07:57:21.638971       1 resource_allocation.go:78] cronjob-cron-events-1573793820-xt6q9 -> nxs-k8s-s8: BalancedResourceAllocation, capacidad 39900 milicore 66620178432 bytes de memoria, solicitud total 5847 milicore 24333637120 bytes de memoria, puntuación 7        
I1115 07:57:21.638975       1 resource_allocation.go:78] cronjob-cron-events-1573793820-xt6q9 -> nxs-k8s-s9: LeastResourceAllocation, capacidad 39900 milicore 66620170240 bytes de memoria, solicitud total 4107 milicore 11307422720 bytes de memoria, puntuación 8           
I1115 07:57:21.638990       1 resource_allocation.go:78] cronjob-cron-events-1573793820-xt6q9 -> nxs-k8s-s8: LeastResourceAllocation, capacidad 39900 milicore 66620178432 bytes de memoria, solicitud total 5847 milicore 24333637120 bytes de memoria, puntuación 7           
I1115 07:57:21.639022       1 generic_scheduler.go:726] cronjob-cron-events-1573793820-xt6q9_nxs-stage -> nxs-k8s-s10: TaintTolerationPriority, Puntuación: (10)                                                                                                        
I1115 07:57:21.639030       1 generic_scheduler.go:726] cronjob-cron-events-1573793820-xt6q9_nxs-stage -> nxs-k8s-s8: TaintTolerationPriority, Puntuación: (10)                                                                                                         
I1115 07:57:21.639034       1 generic_scheduler.go:726] cronjob-cron-events-1573793820-xt6q9_nxs-stage -> nxs-k8s-s9: TaintTolerationPriority, Puntuación: (10)                                                                                                         
I1115 07:57:21.639041       1 generic_scheduler.go:726] cronjob-cron-events-1573793820-xt6q9_nxs-stage -> nxs-k8s-s10: NodeAffinityPriority, Puntuación: (0)                                                                                                            
I1115 07:57:21.639053       1 generic_scheduler.go:726] cronjob-cron-events-1573793820-xt6q9_nxs-stage -> nxs-k8s-s8: NodeAffinityPriority, Puntuación: (0)                                                                                                             
I1115 07:57:21.639059       1 generic_scheduler.go:726] cronjob-cron-events-1573793820-xt6q9_nxs-stage -> nxs-k8s-s9: NodeAffinityPriority, Puntuación: (0)                                                                                                             
I1115 07:57:21.639061       1 interpod_affinity.go:237] cronjob-cron-events-1573793820-xt6q9 -> nxs-k8s-s10: InterPodAffinityPriority, Puntuación: (0)                                                                                                                   
I1115 07:57:21.639063       1 selector_spreading.go:146] cronjob-cron-events-1573793820-xt6q9 -> nxs-k8s-s10: SelectorSpreadPriority, Puntuación: (10)                                                                                                                   
I1115 07:57:21.639073       1 interpod_affinity.go:237] cronjob-cron-events-1573793820-xt6q9 -> nxs-k8s-s8: InterPodAffinityPriority, Puntuación: (0)                                                                                                                    
I1115 07:57:21.639077       1 selector_spreading.go:146] cronjob-cron-events-1573793820-xt6q9 -> nxs-k8s-s8: SelectorSpreadPriority, Puntuación: (10)                                                                                                                    
I1115 07:57:21.639085       1 interpod_affinity.go:237] cronjob-cron-events-1573793820-xt6q9 -> nxs-k8s-s9: InterPodAffinityPriority, Puntuación: (0)                                                                                                                    
I1115 07:57:21.639088       1 selector_spreading.go:146] cronjob-cron-events-1573793820-xt6q9 -> nxs-k8s-s9: SelectorSpreadPriority, Puntuación: (10)                                                                                                                    
I1115 07:57:21.639103       1 generic_scheduler.go:726] cronjob-cron-events-1573793820-xt6q9_nxs-stage -> nxs-k8s-s10: SelectorSpreadPriority, Puntuación: (10)                                                                                                         
I1115 07:57:21.639109       1 generic_scheduler.go:726] cronjob-cron-events-1573793820-xt6q9_nxs-stage -> nxs-k8s-s8: SelectorSpreadPriority, Puntuación: (10)                                                                                                          
I1115 07:57:21.639114       1 generic_scheduler.go:726] cronjob-cron-events-1573793820-xt6q9_nxs-stage -> nxs-k8s-s9: SelectorSpreadPriority, Puntuación: (10)                                                                                                          
I1115 07:57:21.639127       1 generic_scheduler.go:781] Host nxs-k8s-s10 => Puntuación 100037                                                                                                                                                                            
I1115 07:57:21.639150       1 generic_scheduler.go:781] Host nxs-k8s-s8 => Puntuación 100034                                                                                                                                                                             
I1115 07:57:21.639154       1 generic_scheduler.go:781] Host nxs-k8s-s9 => Puntuación 100037                                                                                                                                                                             
I1115 07:57:21.639267       1 scheduler_binder.go:269] Asumir Volúmenes de Pod para el pod "nxs-stage/cronjob-cron-events-1573793820-xt6q9", nodo "nxs-k8s-s10"                                                                                                               
I1115 07:57:21.639286       1 scheduler_binder.go:279] Asumir Volúmenes de Pod para el pod "nxs-stage/cronjob-cron-events-1573793820-xt6q9", nodo "nxs-k8s-s10": todos los PVCs están vinculados y no hay nada que hacer                                                                             
I1115 07:57:21.639333       1 factory.go:733] Intentando vincular cronjob-cron-events-1573793820-xt6q9 al nxs-k8s-s10

Aquí vemos que inicialmente el scheduler filtra y forma una lista de 3 nodos, en los cuales es posible iniciar (nxs-k8s-s8, nxs-k8s-s9, nxs-k8s-s10). Luego, realiza el conteo de puntos en base a varios parámetros (incluyendo BalancedResourceAllocation, LeastResourceAllocation) para cada uno de estos nodos con el fin de determinar el nodo más adecuado. Al final se despliega en el nodo con más puntos (aquí, dos nodos tienen la misma cantidad de puntos, 100037, por lo que se elige uno aleatoriamente: nxs-k8s-s10).

Salida: si en el nodo están en funcionamiento pods para los cuales no se han establecido límites, para k8s (en términos de consumo de recursos) será como si esos pods no estuvieran presentes en absoluto en ese nodo. Por lo tanto, si tiene, hipotéticamente, un pod con un proceso que consume muchos recursos (por ejemplo, wowza) y no se han establecido límites para él, podría haber una situación en la que este pod consuma todos los recursos del nodo. Sin embargo, para k8s, este nodo se considera sin carga y recibirá la misma cantidad de puntos en la clasificación (específicamente en los puntos de evaluación de recursos disponibles) que un nodo donde no hay pods activos, lo que puede llevar, en última instancia, a una distribución desigual de la carga entre los nodos.

Desalojo de pod

Como se sabe, a cada pod se le asigna una de las 3 clases de QoS:

  1. garantizada — se asigna cuando para cada contenedor en el pod se han establecido tanto el request como el limit de memoria y CPU, y estos valores deben coincidir
  2. burstable — al menos un contenedor en el pod tiene request y limit, siendo request < limit
  3. mejor esfuerzo — cuando ningún contenedor en el pod está limitado en recursos

Además, cuando existe una escasez de recursos (disco, memoria) en el nodo, kubelet comienza a clasificar y desalojar pods según un algoritmo específico que toma en cuenta la prioridad del pod y su clase de QoS. Por ejemplo, si se trata de RAM, se asignan puntos con base en la clase de QoS de la siguiente manera:

  • Guaranteed: -998
  • BestEffort: 1000
  • Burstable: min(max(2, 1000 — (1000 * memoryRequestBytes) / machineMemoryCapacityBytes), 999)

Es decir, con la misma prioridad, kubelet desalojará primero los pods con la clase de QoS de mejor esfuerzo.

Salida: si desea reducir la probabilidad de que un pod necesario sea desalojado de un nodo en caso de escasez de recursos, deberá preocuparse no solo por la prioridad, sino también por establecer request/limit para él.

Mecanismo de escalado automático horizontal de pods de aplicación (HPA)

Cuando se busca aumentar y disminuir automáticamente el número de pods según el uso de recursos (sistémicos — CPU/RAM o de usuario — rps), puede ayudar una entidad de k8s como HPA (Horizontal Pod Autoscaler). Su algoritmo consiste en lo siguiente:

  1. Se determinan los valores actuales del recurso observado (currentMetricValue)
  2. Se determinan los valores deseados para el recurso (desiredMetricValue), que para los recursos sistémicos se establecen mediante request
  3. Se determina el número actual de réplicas (currentReplicas)
  4. Se calcula el número deseado de réplicas (desiredReplicas) según la siguiente fórmula
    desiredReplicas = [ currentReplicas * ( currentMetricValue / desiredMetricValue )]

En este caso, no se producirá escalado cuando el coeficiente (currentMetricValue / desiredMetricValue) esté cerca de 1 (la tolerancia que podemos establecer nosotros mismos, por defecto es 0.1).

Consideremos el funcionamiento del hpa con el ejemplo de la aplicación app-test (descrita como Deployment), donde es necesario cambiar el número de réplicas en función del consumo de CPU:

  • Manifiesto de la aplicación

    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

    Es decir, vemos que el pod de la aplicación se inicia inicialmente en dos instancias, cada una de las cuales contiene dos contenedores nginx y nginx-exporter, para cada uno de los cuales se ha establecido requests para CPU.

  • Manifiesto del 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

    Es decir, hemos creado un hpa que supervisará el Deployment app-test y regulará el número de pods de la aplicación en función del índice de cpu (esperamos que el pod consuma el 30% del CPU solicitado), manteniendo el número de réplicas entre 2 y 10.

    Ahora consideremos el mecanismo de funcionamiento del hpa al aplicar carga a uno de los pods:

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

En total, tenemos lo siguiente:

  • El valor deseado (desiredMetricValue) — según la configuración del hpa es igual a 30%
  • El valor actual (currentMetricValue) — para el cálculo, el controller-manager calcula el valor promedio del consumo de recursos en %, es decir, hace lo siguiente:
    1. Obtiene los valores absolutos de las métricas de los pods del metric-server, es decir, 101m y 4m
    2. Calcula el valor absoluto promedio, es decir, (101m + 4m) / 2 = 53m
    3. Obtiene el valor absoluto para el consumo deseado de recursos (para esto se suman los requests de todos los contenedores) 60m + 30m = 90m
    4. Calcula el porcentaje promedio de consumo de CPU en relación con el request del pod, es decir, 53m / 90m * 100% = 59%

Ahora tenemos todo lo necesario para determinar si es necesario cambiar la cantidad de réplicas, para esto calculamos el coeficiente:

ratio = 59% / 30% = 1.96

Es decir, la cantidad de réplicas debe aumentarse en aproximadamente 2 veces y ser [2 * 1.96] = 4.

Salida: Como se puede observar, para que este mecanismo funcione, es condición necesaria tener requests para todos los contenedores en el pod observado.

Mecanismo de escalamiento automático horizontal de nodos (Cluster Autoscaler)

Para mitigar la influencia negativa en el sistema durante picos de carga, tener un hpa configurado a veces no es suficiente. Por ejemplo, según la configuración del hpa, el controller manager decide que es necesario aumentar la cantidad de réplicas en 2 veces, sin embargo, en los nodos no hay recursos libres para ejecutar tal cantidad de pods (es decir, el nodo no puede proporcionar los recursos solicitados por los requests del pod) y esos pods pasan al estado Pendiente.

En este caso, si el proveedor tiene un IaaS/PaaS correspondiente (por ejemplo, GKE/GCE, AKS, EKS, etc.), puede ayudarnos una herramienta como Node Autoscaler. Permite establecer la cantidad máxima y mínima de nodos en el clúster y regula automáticamente la cantidad actual de nodos (mediante la llamada a la API del proveedor de la nube para solicitar/eliminar un nodo), cuando se observa escasez de recursos en el clúster y los pods no pueden ser programados (están en estado Pendiente).

Salida: Para la posibilidad de escalamiento automático de nodos, es necesario establecer requests en los contenedores de los pods, para que k8s pueda evaluar correctamente la carga de los nodos y, en consecuencia, informar que no hay suficientes recursos en el clúster para lanzar un nuevo pod.

Conclusión

Es importante señalar que establecer restricciones de recursos para el contenedor no es un requisito obligatorio para el inicio exitoso de la aplicación, sin embargo, es recomendable hacerlo por las siguientes razones:

  1. Para que el scheduler funcione de manera más precisa en cuanto al balanceo de carga entre los nodos de k8s
  2. Para reducir la probabilidad de un evento de “desalojo de pod”
  3. Para que funcione el escalado automático horizontal de los pods de la aplicación (HPA)
  4. Para que funcione el escalado automático horizontal de nodos (Cluster Autoscaling) en proveedores de nube

También lee otros artículos en nuestro blog:

Fuente: habr.com

Compra un hosting fiable para sitios web con protección contra DDoS, servidores VPS VDS 🔥 Compra un hosting fiable para sitios web con protección contra DDoS, servidores VPS VDS | ProHoster