ProHoster > Blog > administración > Kubernetes: ¿por qué es tan importante configurar la gestión de recursos del sistema?
Kubernetes: ¿por qué es tan importante configurar la gestión de recursos del sistema?
Como regla general, siempre existe la necesidad de proporcionar un conjunto de recursos dedicado a una aplicación para su funcionamiento correcto y estable. Pero ¿qué pasa si varias aplicaciones se ejecutan con la misma potencia? ¿Cómo dotar a cada uno de ellos de los recursos mínimos necesarios? ¿Cómo se puede limitar el consumo de recursos? ¿Cómo distribuir correctamente la carga entre nodos? ¿Cómo garantizar que el mecanismo de escala horizontal funcione si aumenta la carga de la aplicación?
Debe comenzar con los principales tipos de recursos que existen en el sistema; esto, por supuesto, es el tiempo del procesador y la 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: solicitudes и límites. Solicitudes: describe los requisitos mínimos de recursos libres de un nodo para ejecutar un contenedor (y el pod en su conjunto), mientras que los límites establecen un límite estricto en los recursos disponibles para el contenedor.
Es importante entender que el manifiesto no tiene por qué definir explícitamente ambos tipos, pero el comportamiento será el siguiente:
Si solo se especifican explícitamente los límites de un recurso, las solicitudes de este recurso automáticamente toman un valor igual a los límites (puede verificar esto llamando a describir entidades). Aquellos. de hecho, el contenedor estará limitado a la misma cantidad de recursos que requiere para ejecutarse.
Si solo se especifican explícitamente solicitudes para un recurso, entonces no se establecen restricciones superiores en este recurso, es decir, el contenedor está limitado únicamente por los recursos del propio nodo.
También es posible configurar la gestión de recursos no sólo a nivel de un contenedor específico, sino también a nivel de espacio de nombres utilizando las siguientes entidades:
LímiteRango — describe la política de restricción a nivel de contenedor/pod en ns y es necesaria para describir los límites predeterminados en el contenedor/pod, así como para evitar la creación de contenedores/pods obviamente gordos (o viceversa), limitar su número y determinar la posible diferencia en los valores en límites y solicitudes
Cuotas de recursos — describe la política de restricción en general para todos los contenedores en ns y se utiliza, por regla general, para delimitar recursos entre entornos (útil cuando los entornos no están estrictamente delimitados a nivel de nodo)
Los siguientes son ejemplos de manifiestos que establecen límites de recursos:
Aquellos. en este caso, para ejecutar un contenedor con nginx, necesitará al menos 1G de RAM libre y 0.2 CPU en el nodo, mientras que como máximo el contenedor puede consumir 0.2 CPU y toda la RAM disponible en el nodo.
Aquellos. la suma de todos los contenedores de solicitudes en el ns predeterminado no puede exceder los 300 m para la CPU y 1G para el OP, y la suma de todos los límites es 700 m para la CPU y 2G para el OP.
Aquellos. en el espacio de nombres predeterminado para todos los contenedores, la solicitud se establecerá en 100 m para CPU y 1 G para OP, límite: 1 CPU y 2 G. Al mismo tiempo, también se establece un límite en los posibles valores en solicitud/límite para CPU (50m < x < 2) y RAM (500M < x < 4G).
Aquellos. para cada pod en el ns predeterminado habrá un límite de 4 vCPU y 1G.
Ahora me gustaría contaros qué ventajas nos puede aportar establecer estas restricciones.
Mecanismo de equilibrio de carga entre nodos.
Como sabes, el componente k8s es responsable de la distribución de pods entre nodos, como planificador, que funciona según un algoritmo específico. Este algoritmo pasa por dos etapas al seleccionar el nodo óptimo para lanzar:
filtración
rango
Aquellos. De acuerdo con la política descrita, inicialmente se seleccionan nodos en los que es posible lanzar un pod basado en un conjunto predicados (incluida la verificación de si el nodo tiene suficientes recursos para ejecutar el pod: PodFitsResources), y luego para cada uno de estos nodos, de acuerdo con prioridades Se otorgan puntos (incluidos, cuantos más recursos libres tenga un nodo, más puntos se le asignan: LeastResourceAllocation/LeastRequestedPriority/BalancedResourceAllocation) y el pod se inicia en el nodo con más puntos (si varios nodos satisfacen esta condición a la vez, entonces se selecciona uno al azar).
Al mismo tiempo, debe comprender que el programador, al evaluar los recursos disponibles de un nodo, se guía por los datos almacenados en etcd, es decir, para la cantidad de recurso solicitado/límite de cada pod que se ejecuta en este nodo, pero no para el consumo de recursos real. Esta información se puede obtener de la salida del comando. kubectl describe node $NODE, Por ejemplo:
Aquí vemos todos los pods que se ejecutan en un nodo específico, así como los recursos que solicita cada pod. Y así es como se ven los registros del programador cuando se inicia el pod cronjob-cron-events-1573793820-xt6q9 (esta información aparecerá en el registro del programador cuando establezca el décimo nivel de registro en los argumentos del comando de inicio -v=10):
gaviota ancha
I1115 07:57:21.637791 1 scheduling_queue.go:908] About to try and schedule pod nxs-stage/cronjob-cron-events-1573793820-xt6q9
I1115 07:57:21.637804 1 scheduler.go:453] Attempting to schedule pod: nxs-stage/cronjob-cron-events-1573793820-xt6q9
I1115 07:57:21.638285 1 predicates.go:829] Schedule Pod nxs-stage/cronjob-cron-events-1573793820-xt6q9 on Node nxs-k8s-s5 is allowed, Node is running only 16 out of 110 Pods.
I1115 07:57:21.638300 1 predicates.go:829] Schedule Pod nxs-stage/cronjob-cron-events-1573793820-xt6q9 on Node nxs-k8s-s6 is allowed, Node is running only 20 out of 110 Pods.
I1115 07:57:21.638322 1 predicates.go:829] Schedule Pod nxs-stage/cronjob-cron-events-1573793820-xt6q9 on Node nxs-k8s-s3 is allowed, Node is running only 20 out of 110 Pods.
I1115 07:57:21.638322 1 predicates.go:829] Schedule Pod nxs-stage/cronjob-cron-events-1573793820-xt6q9 on Node nxs-k8s-s4 is allowed, Node is running only 17 out of 110 Pods.
I1115 07:57:21.638334 1 predicates.go:829] Schedule Pod nxs-stage/cronjob-cron-events-1573793820-xt6q9 on Node nxs-k8s-s10 is allowed, Node is running only 16 out of 110 Pods.
I1115 07:57:21.638365 1 predicates.go:829] Schedule Pod nxs-stage/cronjob-cron-events-1573793820-xt6q9 on Node nxs-k8s-s12 is allowed, Node is running only 9 out of 110 Pods.
I1115 07:57:21.638334 1 predicates.go:829] Schedule Pod nxs-stage/cronjob-cron-events-1573793820-xt6q9 on Node nxs-k8s-s11 is allowed, Node is running only 11 out of 110 Pods.
I1115 07:57:21.638385 1 predicates.go:829] Schedule Pod nxs-stage/cronjob-cron-events-1573793820-xt6q9 on Node nxs-k8s-s1 is allowed, Node is running only 19 out of 110 Pods.
I1115 07:57:21.638402 1 predicates.go:829] Schedule Pod nxs-stage/cronjob-cron-events-1573793820-xt6q9 on Node nxs-k8s-s2 is allowed, Node is running only 21 out of 110 Pods.
I1115 07:57:21.638383 1 predicates.go:829] Schedule Pod nxs-stage/cronjob-cron-events-1573793820-xt6q9 on Node nxs-k8s-s9 is allowed, Node is running only 16 out of 110 Pods.
I1115 07:57:21.638335 1 predicates.go:829] Schedule Pod nxs-stage/cronjob-cron-events-1573793820-xt6q9 on Node nxs-k8s-s8 is allowed, Node is running only 18 out of 110 Pods.
I1115 07:57:21.638408 1 predicates.go:829] Schedule Pod nxs-stage/cronjob-cron-events-1573793820-xt6q9 on Node nxs-k8s-s13 is allowed, Node is running only 8 out of 110 Pods.
I1115 07:57:21.638478 1 predicates.go:1369] Schedule Pod nxs-stage/cronjob-cron-events-1573793820-xt6q9 on Node nxs-k8s-s10 is allowed, existing pods anti-affinity terms satisfied.
I1115 07:57:21.638505 1 predicates.go:1369] Schedule Pod nxs-stage/cronjob-cron-events-1573793820-xt6q9 on Node nxs-k8s-s8 is allowed, existing pods anti-affinity terms satisfied.
I1115 07:57:21.638577 1 predicates.go:1369] Schedule Pod nxs-stage/cronjob-cron-events-1573793820-xt6q9 on Node nxs-k8s-s9 is allowed, existing pods anti-affinity terms satisfied.
I1115 07:57:21.638583 1 predicates.go:829] Schedule Pod nxs-stage/cronjob-cron-events-1573793820-xt6q9 on Node nxs-k8s-s7 is allowed, Node is running only 25 out of 110 Pods.
I1115 07:57:21.638932 1 resource_allocation.go:78] cronjob-cron-events-1573793820-xt6q9 -> nxs-k8s-s10: BalancedResourceAllocation, capacity 39900 millicores 66620178432 memory bytes, total request 2343 millicores 9640186880 memory bytes, score 9
I1115 07:57:21.638946 1 resource_allocation.go:78] cronjob-cron-events-1573793820-xt6q9 -> nxs-k8s-s10: LeastResourceAllocation, capacity 39900 millicores 66620178432 memory bytes, total request 2343 millicores 9640186880 memory bytes, score 8
I1115 07:57:21.638961 1 resource_allocation.go:78] cronjob-cron-events-1573793820-xt6q9 -> nxs-k8s-s9: BalancedResourceAllocation, capacity 39900 millicores 66620170240 memory bytes, total request 4107 millicores 11307422720 memory bytes, score 9
I1115 07:57:21.638971 1 resource_allocation.go:78] cronjob-cron-events-1573793820-xt6q9 -> nxs-k8s-s8: BalancedResourceAllocation, capacity 39900 millicores 66620178432 memory bytes, total request 5847 millicores 24333637120 memory bytes, score 7
I1115 07:57:21.638975 1 resource_allocation.go:78] cronjob-cron-events-1573793820-xt6q9 -> nxs-k8s-s9: LeastResourceAllocation, capacity 39900 millicores 66620170240 memory bytes, total request 4107 millicores 11307422720 memory bytes, score 8
I1115 07:57:21.638990 1 resource_allocation.go:78] cronjob-cron-events-1573793820-xt6q9 -> nxs-k8s-s8: LeastResourceAllocation, capacity 39900 millicores 66620178432 memory bytes, total request 5847 millicores 24333637120 memory bytes, score 7
I1115 07:57:21.639022 1 generic_scheduler.go:726] cronjob-cron-events-1573793820-xt6q9_nxs-stage -> nxs-k8s-s10: TaintTolerationPriority, Score: (10)
I1115 07:57:21.639030 1 generic_scheduler.go:726] cronjob-cron-events-1573793820-xt6q9_nxs-stage -> nxs-k8s-s8: TaintTolerationPriority, Score: (10)
I1115 07:57:21.639034 1 generic_scheduler.go:726] cronjob-cron-events-1573793820-xt6q9_nxs-stage -> nxs-k8s-s9: TaintTolerationPriority, Score: (10)
I1115 07:57:21.639041 1 generic_scheduler.go:726] cronjob-cron-events-1573793820-xt6q9_nxs-stage -> nxs-k8s-s10: NodeAffinityPriority, Score: (0)
I1115 07:57:21.639053 1 generic_scheduler.go:726] cronjob-cron-events-1573793820-xt6q9_nxs-stage -> nxs-k8s-s8: NodeAffinityPriority, Score: (0)
I1115 07:57:21.639059 1 generic_scheduler.go:726] cronjob-cron-events-1573793820-xt6q9_nxs-stage -> nxs-k8s-s9: NodeAffinityPriority, Score: (0)
I1115 07:57:21.639061 1 interpod_affinity.go:237] cronjob-cron-events-1573793820-xt6q9 -> nxs-k8s-s10: InterPodAffinityPriority, Score: (0)
I1115 07:57:21.639063 1 selector_spreading.go:146] cronjob-cron-events-1573793820-xt6q9 -> nxs-k8s-s10: SelectorSpreadPriority, Score: (10)
I1115 07:57:21.639073 1 interpod_affinity.go:237] cronjob-cron-events-1573793820-xt6q9 -> nxs-k8s-s8: InterPodAffinityPriority, Score: (0)
I1115 07:57:21.639077 1 selector_spreading.go:146] cronjob-cron-events-1573793820-xt6q9 -> nxs-k8s-s8: SelectorSpreadPriority, Score: (10)
I1115 07:57:21.639085 1 interpod_affinity.go:237] cronjob-cron-events-1573793820-xt6q9 -> nxs-k8s-s9: InterPodAffinityPriority, Score: (0)
I1115 07:57:21.639088 1 selector_spreading.go:146] cronjob-cron-events-1573793820-xt6q9 -> nxs-k8s-s9: SelectorSpreadPriority, Score: (10)
I1115 07:57:21.639103 1 generic_scheduler.go:726] cronjob-cron-events-1573793820-xt6q9_nxs-stage -> nxs-k8s-s10: SelectorSpreadPriority, Score: (10)
I1115 07:57:21.639109 1 generic_scheduler.go:726] cronjob-cron-events-1573793820-xt6q9_nxs-stage -> nxs-k8s-s8: SelectorSpreadPriority, Score: (10)
I1115 07:57:21.639114 1 generic_scheduler.go:726] cronjob-cron-events-1573793820-xt6q9_nxs-stage -> nxs-k8s-s9: SelectorSpreadPriority, Score: (10)
I1115 07:57:21.639127 1 generic_scheduler.go:781] Host nxs-k8s-s10 => Score 100037
I1115 07:57:21.639150 1 generic_scheduler.go:781] Host nxs-k8s-s8 => Score 100034
I1115 07:57:21.639154 1 generic_scheduler.go:781] Host nxs-k8s-s9 => Score 100037
I1115 07:57:21.639267 1 scheduler_binder.go:269] AssumePodVolumes for pod "nxs-stage/cronjob-cron-events-1573793820-xt6q9", node "nxs-k8s-s10"
I1115 07:57:21.639286 1 scheduler_binder.go:279] AssumePodVolumes for pod "nxs-stage/cronjob-cron-events-1573793820-xt6q9", node "nxs-k8s-s10": all PVCs bound and nothing to do
I1115 07:57:21.639333 1 factory.go:733] Attempting to bind cronjob-cron-events-1573793820-xt6q9 to nxs-k8s-s10
Aquí vemos que inicialmente el planificador filtra y genera una lista de 3 nodos en los que se puede iniciar (nxs-k8s-s8, nxs-k8s-s9, nxs-k8s-s10). Luego calcula puntuaciones basadas en varios parámetros (incluidos BalancedResourceAllocation, LeastResourceAllocation) para cada uno de estos nodos para determinar el nodo más adecuado. En última instancia, el pod se programa en el nodo con la mayor cantidad de puntos (aquí dos nodos tienen la misma cantidad de puntos a la vez 100037, por lo que se selecciona uno aleatorio: nxs-k8s-s10).
conclusión: si un nodo ejecuta pods para los cuales no se establecen restricciones, entonces para k8 (desde el punto de vista del consumo de recursos) esto será equivalente a como si no existieran tales pods en este nodo. Por lo tanto, si tiene condicionalmente un pod con un proceso glotón (por ejemplo, wowza) y no se le imponen restricciones, entonces puede surgir una situación en la que este pod realmente se comió todos los recursos del nodo, pero para k8 este nodo se considera descargado y se le otorgará la misma cantidad de puntos al clasificarse (precisamente en puntos que evalúan los recursos disponibles) como un nodo que no tiene pods en funcionamiento, lo que en última instancia puede conducir a una distribución desigual de la carga entre los nodos.
El desalojo de la vaina
Como sabes, a cada pod se le asigna una de las 3 clases de QoS:
garantizado — se asigna cuando para cada contenedor en el pod se especifican una solicitud y un límite para la memoria y la CPU, y estos valores deben coincidir
rompible — al menos un contenedor en el pod tiene una solicitud y un límite, con solicitud <límite
mejor esfuerzo — cuando ni un solo contenedor en el pod tiene recursos limitados
Al mismo tiempo, cuando un nodo experimenta una falta de recursos (disco, memoria), kubelet comienza a clasificar y desalojar los pods de acuerdo con un algoritmo específico que tiene en cuenta la prioridad del pod y su clase de QoS. Por ejemplo, si hablamos de RAM, según la clase QoS, los puntos se otorgan de acuerdo con el siguiente principio:
Aquellos. Con la misma prioridad, el kubelet primero desalojará del nodo los pods con la clase QoS de mejor esfuerzo.
conclusión: si desea reducir la probabilidad de que el pod deseado sea desalojado del nodo en caso de falta de recursos, entonces, junto con la prioridad, también debe encargarse de establecer la solicitud/límite para él.
Mecanismo de escalado automático horizontal de pods de aplicaciones (HPA)
Cuando la tarea es aumentar y disminuir automáticamente la cantidad de pods dependiendo del uso de recursos (sistema - CPU/RAM o usuario - rps), una entidad k8s como HPA (Escalador automático de cápsulas horizontales). cuyo algoritmo es el siguiente:
Se determinan las lecturas actuales del recurso observado (currentMetricValue)
Se determinan los valores deseados para el recurso (desiredMetricValue), que para los recursos del sistema se establecen mediante solicitud
Se determina el número actual de réplicas (currentReplicas)
La siguiente fórmula calcula el número deseado de réplicas (desiredReplicas)
Réplicas deseadas = [Replicas actuales * (ValorMetricActual / ValorMetricoDeseado)]
En este caso, la escala no se producirá cuando el coeficiente (currentMetricValue / wantedMetricValue) esté cerca de 1 (en este caso, podemos establecer el error permitido nosotros mismos; de forma predeterminada es 0.1).
Veamos cómo funciona hpa usando el ejemplo de la aplicación app-test (descrita como Deployment), donde es necesario cambiar la cantidad de réplicas dependiendo del consumo de CPU:
Aquellos. 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 un especificado solicitudes para CPU.
Aquellos. Creamos un hpa que monitoreará la prueba de implementación de la aplicación y regulará la cantidad de pods con la aplicación según el indicador de CPU (esperamos que el pod consuma el 30% de la CPU que solicita), siendo la cantidad de réplicas en el rango de 2-10.
Ahora, veamos el mecanismo de funcionamiento del hpa si aplicamos una carga a uno de los hogares:
# 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 de hpa, tenemos 30%
Valor actual (currentMetricValue): para el cálculo, el controlador-administrador calcula el valor promedio del consumo de recursos en %, es decir, condicionalmente hace lo siguiente:
Recibe valores absolutos de las métricas del pod del servidor de métricas, es decir. 101m y 4m
Calcula el valor absoluto medio, es decir (101m + 4m) / 2 = 53m
Obtiene el valor absoluto del consumo de recursos deseado (para ello, se suman las solicitudes de todos los contenedores) 60m + 30m = 90m
Calcula el porcentaje promedio de consumo de CPU en relación con el pod de solicitudes, es decir, 53m / 90m * 100% = 59%
Ahora tenemos todo lo que necesitamos para determinar si necesitamos cambiar el número de réplicas, para ello calculamos el coeficiente:
ratio = 59% / 30% = 1.96
Aquellos. el número de réplicas debe aumentarse aproximadamente 2 veces y ascender a [2 * 1.96] = 4.
Conclusión: Como puede ver, para que este mecanismo funcione, una condición necesaria es la presencia de solicitudes para todos los contenedores en el pod observado.
Mecanismo de escalado automático horizontal de nodos (Cluster Autoscaler)
Para neutralizar el impacto negativo en el sistema durante los aumentos repentinos de carga, no basta con tener un hpa configurado. Por ejemplo, de acuerdo con la configuración en el administrador del controlador hpa, decide que la cantidad de réplicas debe aumentarse 2 veces, pero los nodos no tienen recursos libres para ejecutar tal cantidad de pods (es decir, el nodo no puede proporcionar la recursos solicitados al pod de solicitudes) y estos pods cambian al estado Pendiente.
En este caso, si el proveedor tiene una IaaS/PaaS correspondiente (por ejemplo, GKE/GCE, AKS, EKS, etc.), una herramienta como Escalador automático de nodos. Le permite establecer la cantidad máxima y mínima de nodos en el clúster y ajustar automáticamente la cantidad actual de nodos (llamando a la API del proveedor de la nube para ordenar/eliminar un nodo) cuando faltan recursos en el clúster y los pods. no se pueden programar (están en estado Pendiente).
Conclusión: Para poder escalar automáticamente los nodos, es necesario configurar solicitudes en los contenedores del pod para que k8s pueda evaluar correctamente la carga en los nodos y, en consecuencia, informar que no hay recursos en el clúster para lanzar el siguiente pod.
Conclusión
Cabe señalar que establecer límites de recursos del contenedor no es un requisito para que la aplicación se ejecute correctamente, pero es mejor hacerlo por las siguientes razones:
Para un funcionamiento más preciso del programador en términos de equilibrio de carga entre nodos k8s
Para reducir la probabilidad de que ocurra un evento de “desalojo de cápsulas”
Para que funcione el escalado automático horizontal de pods de aplicaciones (HPA)
Para escalado automático horizontal de nodos (Cluster Autoscaling) para proveedores de nube