ProHoster > Blog > Administración > Kubernetes: por que é tan importante configurar a xestión de recursos do sistema?
Kubernetes: por que é tan importante configurar a xestión de recursos do sistema?
Como regra xeral, sempre hai que proporcionar un conxunto de recursos dedicados a unha aplicación para o seu funcionamento correcto e estable. Pero que pasa se varias aplicacións funcionan coa mesma enerxía? Como dotar a cada un deles dos recursos mínimos necesarios? Como se pode limitar o consumo de recursos? Como distribuír correctamente a carga entre os nodos? Como garantir o funcionamento do mecanismo de escala horizontal se a carga da aplicación aumenta?
Debe comezar cos principais tipos de recursos que existen no sistema: isto, por suposto, é o tempo do procesador e a memoria RAM. Nos manifestos k8s estes tipos de recursos mídense nas seguintes unidades:
CPU - en núcleos
RAM - en bytes
Ademais, para cada recurso é posible establecer dous tipos de requisitos: solicitudes и límites. Solicitudes: describe os requisitos mínimos dos recursos gratuítos dun nodo para executar un contedor (e un pod no seu conxunto), mentres que os límites establecen un límite estricto aos recursos dispoñibles para o contedor.
É importante entender que o manifesto non ten que definir explícitamente ambos tipos, pero o comportamento será o seguinte:
Se só se especifican explícitamente os límites dun recurso, entón as solicitudes deste recurso tomarán automaticamente un valor igual aos límites (podes verificar isto chamando ás entidades describe). Eses. de feito, o contedor estará limitado á mesma cantidade de recursos que precisa para funcionar.
Se só se especifican de xeito explícito as solicitudes para un recurso, non se establecen restricións superiores neste recurso, é dicir. o contedor está limitado só polos recursos do propio nodo.
Tamén é posible configurar a xestión de recursos non só a nivel dun contedor específico, senón tamén a nivel de espazo de nomes mediante as seguintes entidades:
Rango límite — describe a política de restrición a nivel de recipiente/vaina en ns e é necesaria para describir os límites predeterminados do recipiente/vaina, así como evitar a creación de recipientes/vainas obviamente gordos (ou viceversa), limitar o seu número e determinar a posible diferenza nos valores en límites e solicitudes
Cotas de recursos — describir a política de restrición en xeral para todos os contedores en ns e utilízase, como regra, para delimitar recursos entre ambientes (útil cando os ambientes non están estrictamente delimitados a nivel de nodos)
Os seguintes son exemplos de manifestos que establecen límites de recursos:
Eses. neste caso, para executar un contedor con nginx, necesitará polo menos 1G de RAM libre e 0.2 CPU no nodo, mentres que como máximo o contedor pode consumir 0.2 CPU e toda a RAM dispoñible no nodo.
Eses. a suma de todos os contedores de solicitude no ns predeterminado non pode superar os 300 m para a CPU e 1G para o OP, e a suma de todos os límites é de 700 m para a CPU e 2G para o OP.
Eses. no espazo de nomes predeterminado para todos os contedores, a solicitude establecerase en 100 m para CPU e 1G para OP, límite: 1 CPU e 2G. Ao mesmo tempo, tamén se establece un límite nos posibles valores en solicitude/límite para CPU (50m < x < 2) e RAM (500M < x < 4G).
Eses. para cada pod no ns predeterminado haberá un límite de 4 vCPU e 1G.
Agora gustaríame dicirvos cales son as vantaxes que nos pode dar establecer estas restricións.
Mecanismo de equilibrio de carga entre nodos
Como sabes, o compoñente k8s é responsable da distribución de pods entre nós, como planificador, que funciona segundo un algoritmo específico. Este algoritmo pasa por dúas etapas á hora de seleccionar o nodo óptimo para lanzar:
filtrado
Variando
Eses. segundo a política descrita, selecciónanse inicialmente os nodos nos que é posible lanzar un pod baseado nun conxunto predicados (incluíndo a comprobación de se o nodo ten recursos suficientes para executar o pod - PodFitsResources), e despois para cada un destes nodos, segundo prioridades outórganse puntos (incluíndo, cantos máis recursos gratuítos teña un nodo, máis puntos se lle asignen - LeastResourceAllocation/LeastRequestedPriority/BalancedResourceAllocation) e o pod lánzase no nodo con máis puntos (se varios nodos cumpren esta condición á vez, entón se selecciona un aleatorio).
Ao mesmo tempo, cómpre entender que o planificador, ao avaliar os recursos dispoñibles dun nodo, está guiado polos datos que se almacenan en etcd, é dicir. para a cantidade de recurso solicitado/límite de cada pod que se executa neste nodo, pero non para o consumo real de recursos. Esta información pódese obter da saída do comando kubectl describe node $NODE, por exemplo:
Aquí vemos todos os pods que se executan nun nodo específico, así como os recursos que cada pod solicita. E aquí está o aspecto dos rexistros do planificador cando se inicia o pod cronjob-cron-events-1573793820-xt6q9 (esta información aparecerá no rexistro do planificador cando estableza o décimo nivel de rexistro nos argumentos do comando de inicio -v=10):
rexistro
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 o planificador filtra e xera unha lista de 3 nodos nos que se pode lanzar (nxs-k8s-s8, nxs-k8s-s9, nxs-k8s-s10). A continuación, calcula as puntuacións en función de varios parámetros (incluíndo BalancedResourceAllocation, LeastResourceAllocation) para cada un destes nodos co fin de determinar o nodo máis axeitado. En definitiva, o pod está programado no nodo con maior número de puntos (aquí dous nodos á vez teñen o mesmo número de puntos 100037, polo que se selecciona un aleatorio: nxs-k8s-s10).
Saída: se un nodo executa pods para os que non se establecen restricións, entón para k8s (desde o punto de vista do consumo de recursos) isto será equivalente a que non houbese tales pods neste nodo. Polo tanto, se tes, condicionalmente, un pod cun proceso glotón (por exemplo, wowza) e non se establecen restricións para el, entón pode xurdir unha situación cando este pod consumiu realmente todos os recursos do nodo, pero para k8s este nodo considérase descargado e outorgaráselle o mesmo número de puntos á hora de clasificar (precisamente en puntos que avalían os recursos dispoñibles) que un nodo que non teña pods de traballo, o que finalmente pode levar a unha distribución desigual da carga entre nodos.
Desaloxo de Pod
Como sabes, a cada pod ten asignada unha das 3 clases de QoS:
garantido — atribúese cando para cada contedor do pod se especifica unha solicitude e un límite para a memoria e a CPU, e estes valores deben coincidir
estourable — polo menos un contedor do pod ten unha solicitude e un límite, con solicitude < límite
mellor esforzo — cando nin un só contedor da vaina teña recursos limitados
Ao mesmo tempo, cando un nodo experimenta unha falta de recursos (disco, memoria), kubelet comeza a clasificar e desaloxar os pods segundo un algoritmo específico que ten en conta a prioridade do pod e a súa clase de QoS. Por exemplo, se falamos de RAM, en base á clase de QoS, os puntos concédense segundo o seguinte principio:
Eses. coa mesma prioridade, o kubelet expulsará primeiro os pods coa clase de QoS do mellor esforzo do nodo.
Saída: se quere reducir a probabilidade de que o pod desexado sexa desaloxado do nodo en caso de falta de recursos nel, entón, xunto coa prioridade, tamén debe ocuparse de establecer a solicitude/límite para el.
Mecanismo de escalado automático horizontal de módulos de aplicación (HPA)
Cando a tarefa é aumentar e diminuír automaticamente o número de pods dependendo do uso dos recursos (sistema - CPU/RAM ou usuario - rps), unha entidade k8s como HPA (Horizontal Pod Autoscaler). O algoritmo do cal é o seguinte:
Determináronse as lecturas actuais do recurso observado (currentMetricValue)
Determínanse os valores desexados para o recurso (desiredMetricValue), que para os recursos do sistema establécense mediante a solicitude
Determínase o número actual de réplicas (currentReplicas)
A seguinte fórmula calcula o número desexado de réplicas (réplicas desexadas)
Réplicas desexadas = [ Réplicas actuais * (ValorMetrica actual /ValorMetrica desexada )]
Neste caso, a escala non se producirá cando o coeficiente (currentMetricValue / wantMetricValue) sexa próximo a 1 (neste caso, podemos establecer o erro permitido nós mesmos; por defecto é 0.1).
Vexamos como funciona hpa usando o exemplo da aplicación de proba de aplicacións (descrita como Implementación), onde é necesario cambiar o número de réplicas dependendo do consumo da CPU:
Eses. vemos que o pod de aplicación lanzase inicialmente en dúas instancias, cada unha das cales contén dous contedores nginx e nginx-exporter, para cada un dos cales un solicitudes para CPU.
Eses. Creamos un hpa que supervisará a proba da aplicación de implementación e axustará o número de pods coa aplicación en función do indicador da CPU (esperamos que o pod consuma o 30 % da CPU que solicita), co número de réplicas en rango de 2-10.
Agora, vexamos o mecanismo de funcionamento de hpa se aplicamos unha carga a un dos fogares:
# kubectl top pod
NAME CPU(cores) MEMORY(bytes)
app-test-78559f8f44-pgs58 101m 243Mi
app-test-78559f8f44-cj4jz 4m 240Mi
En total temos o seguinte:
O valor desexado (desiredMetricValue): segundo a configuración de hpa, temos un 30%
Valor actual (currentMetricValue): para o cálculo, o controlador-xestor calcula o valor medio do consumo de recursos en %, é dicir. condicionalmente fai o seguinte:
Recibe valores absolutos das métricas de pod do servidor de métricas, é dicir. 101 m e 4 m
Calcula o valor absoluto medio, é dicir. (101 m + 4 m) / 2 = 53 m
Obtén o valor absoluto para o consumo de recursos desexado (para iso, as solicitudes de todos os contedores son resumidas) 60m + 30m = 90m
Calcula a porcentaxe media de consumo de CPU en relación ao pod de solicitude, é dicir. 53 min/90 min * 100 % = 59 %
Agora temos todo o necesario para determinar se hai que cambiar o número de réplicas; para iso, calculamos o coeficiente:
ratio = 59% / 30% = 1.96
Eses. o número de réplicas debería aumentarse en ~2 veces e ascender a [2 * 1.96] = 4.
Conclusión: Como podes ver, para que este mecanismo funcione, unha condición necesaria é a presenza de solicitudes para todos os contedores na vaina observada.
Mecanismo de escalado automático horizontal de nodos (Cluster Autoscaler)
Para neutralizar o impacto negativo sobre o sistema durante os aumentos de carga, non é suficiente ter un hpa configurado. Por exemplo, segundo a configuración do xestor do controlador hpa, decide que o número de réplicas debe aumentarse 2 veces, pero os nodos non teñen recursos gratuítos para executar tal número de pods (é dicir, o nodo non pode proporcionar o recursos solicitados ao módulo de solicitudes) e estes módulos cambian ao estado Pendente.
Neste caso, se o provedor ten un IaaS/PaaS correspondente (por exemplo, GKE/GCE, AKS, EKS, etc.), unha ferramenta como Escalador automático de nodos. Permítelle establecer o número máximo e mínimo de nós no clúster e axustar automaticamente o número actual de nós (chamando á API do provedor de nube para ordenar/eliminar un nodo) cando faltan recursos no clúster e nos pods. non se poden programar (están no estado Pendente).
Conclusión: Para poder escalar os nodos automaticamente, é necesario establecer solicitudes nos contedores de pods para que k8s poida avaliar correctamente a carga dos nodos e, en consecuencia, informar de que non hai recursos no clúster para lanzar o seguinte pod.
Conclusión
Nótese que establecer límites de recursos do contedor non é un requisito para que a aplicación se execute correctamente, pero aínda así é mellor facelo polos seguintes motivos:
Para un funcionamento máis preciso do planificador en termos de equilibrio de carga entre os nodos k8s
Para reducir a probabilidade de que se produza un evento de "desaloxo de vainas".
Para que funcione a escala automática horizontal de módulos de aplicación (HPA).
Para a escala automática horizontal de nodos (Cluster Autoscaling) para provedores de nube