ProHoster > Blog > administração > Kubernetes: por que é tão importante configurar o gerenciamento de recursos do sistema?
Kubernetes: por que é tão importante configurar o gerenciamento de recursos do sistema?
Via de regra, há sempre a necessidade de fornecer um pool de recursos dedicado a uma aplicação para seu correto e estável funcionamento. Mas e se vários aplicativos estiverem sendo executados com a mesma energia? Como dotar cada um deles dos recursos mínimos necessários? Como você pode limitar o consumo de recursos? Como distribuir corretamente a carga entre os nós? Como garantir que o mecanismo de escalonamento horizontal funcione se a carga do aplicativo aumentar?
Você precisa começar com os principais tipos de recursos existentes no sistema - isso, é claro, é o tempo do processador e a RAM. Nos manifestos k8s, esses tipos de recursos são medidos nas seguintes unidades:
CPU - em núcleos
RAM - em bytes
Além disso, para cada recurso é possível definir dois tipos de requisitos - pedidos и limites. Solicitações - descreve os requisitos mínimos para recursos livres de um nó para executar um contêiner (e pod como um todo), enquanto limites definem um limite rígido para os recursos disponíveis para o contêiner.
É importante entender que o manifesto não precisa definir explicitamente os dois tipos, mas o comportamento será o seguinte:
Se apenas os limites de um recurso forem especificados explicitamente, as solicitações para esse recurso assumirão automaticamente um valor igual aos limites (você pode verificar isso chamando entidades de descrição). Aqueles. na verdade, o contêiner será limitado à mesma quantidade de recursos necessários para funcionar.
Se apenas as solicitações forem especificadas explicitamente para um recurso, nenhuma restrição superior será definida para esse recurso - ou seja, o contêiner é limitado apenas pelos recursos do próprio nó.
Também é possível configurar o gerenciamento de recursos não apenas no nível de um contêiner específico, mas também no nível do namespace usando as seguintes entidades:
LimitRange — descreve a política de restrição no nível do contêiner/pod em ns e é necessária para descrever os limites padrão no contêiner/pod, bem como evitar a criação de contêineres/pods obviamente gordos (ou vice-versa), limitar seu número e determinar a possível diferença nos valores em limites e solicitações
Cotas de recursos — descreve a política de restrição em geral para todos os contêineres em ns e é usada, via de regra, para delimitar recursos entre ambientes (útil quando os ambientes não são estritamente demarcados no nível do nó)
A seguir estão exemplos de manifestos que definem limites de recursos:
Aqueles. neste caso, para executar um contêiner com nginx, você precisará de pelo menos 1G de RAM livre e 0.2 CPU no nó, enquanto no máximo o contêiner pode consumir 0.2 CPU e toda a RAM disponível no nó.
Aqueles. a soma de todos os contêineres de solicitação no ns padrão não pode exceder 300m para a CPU e 1G para o OP, e a soma de todos os limites é 700m para a CPU e 2G para o OP.
Aqueles. no namespace padrão para todos os contêineres, a solicitação será definida como 100m para CPU e 1G para OP, limite - 1 CPU e 2G. Ao mesmo tempo, também é definido um limite nos valores possíveis em solicitação/limite para CPU (50m < x < 2) e RAM (500M < x < 4G).
Aqueles. para cada pod no ns padrão haverá um limite de 4 vCPU e 1G.
Agora gostaria de contar quais vantagens a definição dessas restrições pode nos trazer.
Mecanismo de balanceamento de carga entre nós
Como você sabe, o componente k8s é responsável pela distribuição dos pods entre os nós, como agendador, que funciona de acordo com um algoritmo específico. Este algoritmo passa por dois estágios ao selecionar o nó ideal para iniciar:
filtragem
Variação
Aqueles. de acordo com a política descrita, são inicialmente selecionados nós nos quais é possível lançar um pod com base em um conjunto predicados (incluindo verificar se o nó possui recursos suficientes para executar o pod - PodFitsResources) e, em seguida, para cada um desses nós, de acordo com prioridades pontos são concedidos (incluindo quanto mais recursos livres um nó tiver, mais pontos ele será atribuído - LeastResourceAllocation/LeastRequestedPriority/BalancedResourceAllocation) e o pod será iniciado no nó com mais pontos (se vários nós satisfizerem esta condição ao mesmo tempo, então um aleatório é selecionado).
Ao mesmo tempo, você precisa entender que o agendador, ao avaliar os recursos disponíveis de um nó, é guiado pelos dados que estão armazenados no etcd - ou seja, para a quantidade de recurso solicitado/limite de cada pod em execução neste nó, mas não para o consumo real de recursos. Esta informação pode ser obtida na saída do comando kubectl describe node $NODE, Por exemplo:
Aqui vemos todos os pods em execução em um nó específico, bem como os recursos que cada pod solicita. E aqui está a aparência dos logs do agendador quando o pod cronjob-cron-events-1573793820-xt6q9 é iniciado (esta informação aparecerá no log do agendador quando você definir o 10º nível de log nos argumentos do comando de inicialização -v=10):
log
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
Aqui vemos que inicialmente o agendador filtra e gera uma lista de 3 nós nos quais pode ser iniciado (nxs-k8s-s8, nxs-k8s-s9, nxs-k8s-s10). Em seguida, ele calcula pontuações com base em vários parâmetros (incluindo BalancedResourceAllocation, LeastResourceAllocation) para cada um desses nós, a fim de determinar o nó mais adequado. Em última análise, o pod é agendado no nó com o maior número de pontos (aqui dois nós ao mesmo tempo têm o mesmo número de pontos 100037, então um nó aleatório é selecionado - nxs-k8s-s10).
Jogar aviator online grátis: hack aviator funciona: se um nó executa pods para os quais nenhuma restrição é definida, então para k8s (do ponto de vista do consumo de recursos) isso será equivalente a como se não existissem tais pods neste nó. Portanto, se você, condicionalmente, tiver um pod com um processo guloso (por exemplo, wowza) e nenhuma restrição for definida para ele, então pode surgir uma situação em que esse pod realmente comeu todos os recursos do nó, mas para k8s este nó é considerado descarregado e receberá o mesmo número de pontos na classificação (precisamente em pontos que avaliam os recursos disponíveis) como um nó que não possui pods funcionais, o que em última análise pode levar a uma distribuição desigual da carga entre os nós.
Despejo de pod
Como você sabe, cada pod recebe uma das três classes de QoS:
garantido — é atribuído quando para cada contêiner no pod uma solicitação e limite são especificados para memória e CPU, e esses valores devem corresponder
estourável — pelo menos um contêiner no pod tem uma solicitação e um limite, com solicitação <limite
melhor esforço — quando nenhum contêiner no pod tem recursos limitados
Ao mesmo tempo, quando um nó apresenta falta de recursos (disco, memória), o kubelet começa a classificar e despejar pods de acordo com um algoritmo específico que leva em consideração a prioridade do pod e sua classe de QoS. Por exemplo, se estamos falando de RAM, então com base na classe QoS, os pontos são atribuídos de acordo com o seguinte princípio:
Aqueles. com a mesma prioridade, o kubelet primeiro removerá os pods com a classe de QoS de melhor esforço do nó.
Jogar aviator online grátis: hack aviator funciona: se você deseja reduzir a probabilidade de o pod desejado ser despejado do nó em caso de falta de recursos nele, junto com a prioridade, você também precisa definir a solicitação/limite para ele.
Mecanismo para escalonamento automático horizontal de pods de aplicativos (HPA)
Quando a tarefa é aumentar e diminuir automaticamente o número de pods dependendo do uso de recursos (sistema - CPU/RAM ou usuário - rps), uma entidade k8s como HPA (escalador automático de pod horizontal). O algoritmo é o seguinte:
As leituras atuais do recurso observado são determinadas (currentMetricValue)
São determinados os valores desejados para o recurso (desiredMetricValue), que para recursos do sistema são definidos usando request
O número atual de réplicas é determinado (currentReplicas)
A fórmula a seguir calcula o número desejado de réplicas (desiredReplicas)
desejadaReplicas = [ currentReplicas * ( currentMetricValue / desejadoMetricValue )]
Neste caso, o escalonamento não ocorrerá quando o coeficiente (currentMetricValue/desejadoMetricValue) estiver próximo de 1 (neste caso, podemos definir nós mesmos o erro permitido; por padrão é 0.1).
Vejamos como funciona o hpa usando o exemplo da aplicação app-test (descrita como Deployment), onde é necessário alterar o número de réplicas dependendo do consumo de CPU:
Aqueles. vemos que o pod do aplicativo é inicialmente iniciado em duas instâncias, cada uma contendo dois contêineres nginx e nginx-exporter, para cada um dos quais um especificado pedidos para CPU.
Aqueles. Criamos um hpa que irá monitorar o Deployment app-test e ajustar o número de pods com a aplicação com base no indicador de CPU (esperamos que o pod consuma 30% da CPU que ele solicita), com o número de réplicas sendo em o intervalo de 2 a 10.
Agora, vejamos o mecanismo de funcionamento do hpa se aplicarmos uma carga a uma das lareiras:
# kubectl top pod
NAME CPU(cores) MEMORY(bytes)
app-test-78559f8f44-pgs58 101m 243Mi
app-test-78559f8f44-cj4jz 4m 240Mi
No total temos o seguinte:
O valor desejado (desiredMetricValue) - de acordo com as configurações do hpa, temos 30%
Valor atual (currentMetricValue) - para cálculo, o controller-manager calcula o valor médio do consumo de recursos em%, ou seja, condicionalmente faz o seguinte:
Recebe valores absolutos de métricas de pod do servidor de métricas, ou seja, 101m e 4m
Calcula o valor absoluto médio, ou seja, (101m + 4m) / 2 = 53m
Obtém o valor absoluto do consumo de recursos desejado (para isso somam-se as solicitações de todos os containers) 60m + 30m = 90m
Calcula a porcentagem média de consumo de CPU em relação ao pod de solicitação, ou seja, 53m / 90m * 100% = 59%
Agora temos tudo o que precisamos para determinar se precisamos alterar o número de réplicas; para fazer isso, calculamos o coeficiente:
ratio = 59% / 30% = 1.96
Aqueles. o número de réplicas deve ser aumentado em aproximadamente 2 vezes e totalizar [2 * 1.96] = 4.
Conclusão: Como você pode ver, para que esse mecanismo funcione, uma condição necessária é a presença de solicitações para todos os containers no pod observado.
Mecanismo para escalonamento automático horizontal de nós (Cluster Autoscaler)
Para neutralizar o impacto negativo no sistema durante picos de carga, não basta ter um HPa configurado. Por exemplo, de acordo com as configurações no gerenciador do controlador HPA, ele decide que o número de réplicas precisa ser aumentado em 2 vezes, mas os nós não têm recursos livres para executar tal número de pods (ou seja, o nó não pode fornecer o recursos solicitados para o pod de solicitações) e esses pods mudam para o estado Pendente.
Neste caso, se o provedor tiver um IaaS/PaaS correspondente (por exemplo, GKE/GCE, AKS, EKS, etc.), uma ferramenta como Escalonador automático de nós. Ele permite que você defina o número máximo e mínimo de nós no cluster e ajuste automaticamente o número atual de nós (chamando a API do provedor de nuvem para solicitar/remover um nó) quando houver falta de recursos no cluster e nos pods não podem ser agendados (estão no estado Pendente).
Conclusão: Para poder dimensionar automaticamente os nós, é necessário definir solicitações nos contêineres do pod para que o k8s possa avaliar corretamente a carga nos nós e, consequentemente, relatar que não há recursos no cluster para iniciar o próximo pod.
Conclusão
Deve-se observar que definir limites de recursos do contêiner não é um requisito para que o aplicativo seja executado com êxito, mas ainda é melhor fazê-lo pelos seguintes motivos:
Para uma operação mais precisa do agendador em termos de balanceamento de carga entre nós k8s
Para reduzir a probabilidade de ocorrência de um evento de “despejo de pod”
Para que o escalonamento automático horizontal de pods de aplicativos (HPA) funcione
Para escalonamento automático horizontal de nós (escalonamento automático de cluster) para provedores de nuvem