ProHoster > Blog > administração > Escalonamento automático e gerenciamento de recursos no Kubernetes (visão geral e relatório de vídeo)
Escalonamento automático e gerenciamento de recursos no Kubernetes (visão geral e relatório de vídeo)
27 de abril na conferência Greve 2019, como parte da seção “DevOps”, foi entregue o relatório “Autoscaling e gerenciamento de recursos em Kubernetes”. Ele fala sobre como você pode usar K8s para garantir alta disponibilidade de seus aplicativos e desempenho máximo.
Por tradição, temos o prazer de apresentar vídeo da reportagem (44 minutos, muito mais informativo que o artigo) e o resumo principal em forma de texto. Ir!
Vamos analisar o tema do relatório palavra por palavra e começar pelo fim.
Kubernetes
Digamos que temos contêineres Docker em nosso host. Para que? Para garantir repetibilidade e isolamento, o que por sua vez permite uma implantação simples e boa, CI/CD. Temos muitos desses veículos com contêineres.
O que o Kubernetes oferece neste caso?
Paramos de pensar nessas máquinas e começamos a trabalhar com a “nuvem” aglomerado de contêineres ou pods (grupos de contêineres).
Além disso, nem pensamos em grupos individuais, mas gerenciamos maisоgrupos maiores. Tal primitivos de alto nível permita-nos dizer que existe um modelo para executar uma determinada carga de trabalho e aqui está o número necessário de instâncias para executá-la. Se alterarmos posteriormente o modelo, todas as instâncias serão alteradas.
Com API declarativa Em vez de executar uma sequência de comandos específicos, descrevemos a “estrutura do mundo” (em YAML), que é criada pelo Kubernetes. E novamente: quando a descrição muda, sua exibição real também muda.
Gestão de recursos
CPU
Vamos executar nginx, php-fpm e mysql no servidor. Na verdade, esses serviços terão ainda mais processos em execução, cada um dos quais requer recursos computacionais:
(os números no slide são “papagaios”, a necessidade abstrata de cada processo em termos de poder computacional)
Para facilitar o trabalho com isso, é lógico combinar processos em grupos (por exemplo, todos os processos nginx em um grupo “nginx”). Uma maneira simples e óbvia de fazer isso é colocar cada grupo em um contêiner:
Para continuar, você precisa lembrar o que é um contêiner (no Linux). Seu surgimento foi possível graças a três recursos principais do kernel, implementados há muito tempo: capacidades, namespaces и grupos. E o desenvolvimento posterior foi facilitado por outras tecnologias (incluindo “shells” convenientes como o Docker):
No contexto do relatório, estamos apenas interessados em grupos, porque os grupos de controle fazem parte da funcionalidade dos contêineres (Docker, etc.) que implementam o gerenciamento de recursos. Os processos combinados em grupos, como queríamos, são grupos de controle.
Voltemos aos requisitos de CPU para esses processos e agora para grupos de processos:
(Repito que todos os números são uma expressão abstrata da necessidade de recursos)
Ao mesmo tempo, a própria CPU possui um certo recurso finito (no exemplo é 1000), que pode faltar a todos (a soma das necessidades de todos os grupos é 150+850+460=1460). O que acontecerá neste caso?
O kernel começa a distribuir recursos e faz isso de forma “justa”, dando a mesma quantidade de recursos para cada grupo. Mas no primeiro caso, há mais deles do que o necessário (333>150), então o excesso (333-150=183) fica na reserva, que também é distribuída igualmente entre outros dois contêineres:
Como resultado: o primeiro contentor tinha recursos suficientes, o segundo – não tinha recursos suficientes, o terceiro – não tinha recursos suficientes. Este é o resultado de ações agendador "honesto" no Linux - CFS. Sua operação pode ser ajustada usando a atribuição pesos cada um dos recipientes. Por exemplo, assim:
Vejamos o caso de falta de recursos no segundo container (php-fpm). Todos os recursos do contêiner são distribuídos igualmente entre os processos. Como resultado, o processo mestre funciona bem, mas todos os trabalhadores ficam mais lentos, recebendo menos da metade do que precisam:
É assim que funciona o agendador CFS. Chamaremos ainda os pesos que atribuímos aos contêineres solicitações de. Por que isso acontece - veja mais.
Vejamos toda a situação do outro lado. Como você sabe, todos os caminhos levam a Roma e, no caso de um computador, à CPU. Uma CPU, muitas tarefas – você precisa de um semáforo. A maneira mais simples de gerenciar recursos é o “semáforo”: eles davam a um processo um tempo fixo de acesso à CPU, depois ao próximo, etc.
Esta abordagem é chamada de cotas rígidas (limitação rígida). Vamos lembrar disso simplesmente como limites. Porém, se você distribuir limites para todos os containers, surge um problema: o mysql estava dirigindo pela estrada e em algum momento sua necessidade de CPU acabou, mas todos os outros processos são forçados a esperar até que a CPU parado.
Voltemos ao kernel Linux e sua interação com a CPU - o quadro geral é o seguinte:
cgroup tem duas configurações - essencialmente, são duas “reviravoltas” simples que permitem determinar:
o peso do contêiner (pedidos) é partes;
a porcentagem do tempo total de CPU para trabalhar em tarefas de contêiner (limites) é quota.
Como medir a CPU?
Existem diferentes maneiras:
o que papagaios, ninguém sabe - você precisa negociar sempre.
Juros mais claro, mas relativo: 50% de um servidor com 4 núcleos e com 20 núcleos são coisas completamente diferentes.
Você pode usar os já mencionados pesos, que o Linux conhece, mas também são relativos.
A opção mais adequada é medir os recursos computacionais em segundos. Aqueles. em segundos de tempo do processador em relação a segundos de tempo real: 1 segundo de tempo do processador foi fornecido por 1 segundo real - este é um núcleo inteiro da CPU.
Para facilitar ainda mais a fala, eles começaram a medir diretamente em grãos, significando por eles o mesmo tempo de CPU em relação ao real. Como o Linux entende pesos, mas não tanto tempo/núcleos de CPU, era necessário um mecanismo para traduzir de um para outro.
Vamos considerar um exemplo simples com um servidor com 3 núcleos de CPU, onde três pods receberão pesos (500, 1000 e 1500) que são facilmente convertidos nas partes correspondentes dos núcleos alocados a eles (0,5, 1 e 1,5).
Se você pegar um segundo servidor, onde haverá o dobro de núcleos (6), e colocar os mesmos pods lá, a distribuição dos núcleos pode ser facilmente calculada simplesmente multiplicando por 2 (1, 2 e 3, respectivamente). Mas um momento importante ocorre quando um quarto pod aparece neste servidor, cujo peso, por conveniência, será 3000. Ele tira parte dos recursos da CPU (metade dos núcleos), e para os demais pods eles são recalculados (divididos pela metade):
Kubernetes e recursos de CPU
No Kubernetes, os recursos da CPU geralmente são medidos em miliadrax, ou seja 0,001 núcleos são considerados o peso base. (A mesma coisa na terminologia Linux/cgroups é chamada de compartilhamento de CPU, embora, mais precisamente, 1000 miliccores = 1024 compartilhamentos de CPU.) O K8s garante que não coloque mais pods no servidor do que os recursos da CPU para a soma dos pesos de todos os pods.
Como isso acontece? Quando você adiciona um servidor a um cluster Kubernetes, é relatado quantos núcleos de CPU ele tem disponíveis. E ao criar um novo pod, o agendador do Kubernetes sabe de quantos núcleos esse pod precisará. Assim, o pod será atribuído a um servidor onde haja núcleos suficientes.
O que acontecerá se não a solicitação é especificada (ou seja, o pod não tem um número definido de núcleos necessários)? Vamos descobrir como o Kubernetes geralmente conta os recursos.
Para um pod você pode especificar solicitações (agendador CFS) e limites (lembra do semáforo?):
Se eles forem especificados iguais, o pod receberá uma classe de QoS garantido. Este número de núcleos sempre disponíveis é garantido.
Se a solicitação for menor que o limite - classe de QoS estourável. Aqueles. Esperamos que um pod, por exemplo, use sempre 1 núcleo, mas este valor não é uma limitação para isso: às vezes pod pode usar mais (quando o servidor tiver recursos livres para isso).
Há também uma classe de QoS melhor esforço — inclui os mesmos pods para os quais a solicitação não foi especificada. Os recursos são dados a eles por último.
Память
Com a memória a situação é semelhante, mas um pouco diferente – afinal, a natureza desses recursos é diferente. Em geral, a analogia é a seguinte:
Vamos ver como as solicitações são implementadas na memória. Deixe os pods viverem no servidor, alterando o consumo de memória, até que um deles fique tão grande que fique sem memória. Neste caso, o assassino OOM aparece e mata o processo maior:
Isso nem sempre nos convém, por isso é possível regular quais processos são importantes para nós e não devem ser eliminados. Para fazer isso, use o parâmetro oom_score_adj.
Vamos voltar às classes de QoS da CPU e fazer uma analogia com os valores oom_score_adj que determinam as prioridades de consumo de memória dos pods:
O valor oom_score_adj mais baixo para um pod - -998 - significa que tal pod deve ser eliminado por último, este garantido.
O mais alto - 1000 - é melhor esforço, esses frutos são eliminados primeiro.
Para calcular os valores restantes (estourável) existe uma fórmula cuja essência se resume ao fato de que quanto mais recursos um pod solicitar, menor será a probabilidade de ele ser eliminado.
A segunda "reviravolta" - limite_em_bytes - para limites. Com ele tudo fica mais simples: simplesmente atribuímos a quantidade máxima de memória emitida, e aqui (ao contrário da CPU) não há dúvida de como medi-la (memória).
No total
Cada pod no Kubernetes é fornecido requests и limits - ambos os parâmetros para CPU e memória:
com base nas solicitações, funciona o agendador Kubernetes, que distribui pods entre servidores;
com base em todos os parâmetros, a classe de QoS do pod é determinada;
Os pesos relativos são calculados com base nas solicitações da CPU;
o agendador CFS é configurado com base nas solicitações da CPU;
O OOM killer é configurado com base em solicitações de memória;
um “semáforo” é configurado com base nos limites da CPU;
Com base nos limites de memória, um limite é configurado para o cgroup.
Em geral, esta imagem responde a todas as perguntas sobre como ocorre a parte principal do gerenciamento de recursos no Kubernetes.
Escalonamento automático
Autoescalador de cluster K8s
Vamos imaginar que todo o cluster já esteja ocupado e um novo pod precise ser criado. Embora o pod não possa aparecer, ele fica suspenso no status Pendente. Para que apareça, podemos conectar um novo servidor ao cluster ou... instalar o cluster-autoscaler, que fará isso por nós: solicitar uma máquina virtual do provedor de nuvem (usando uma solicitação de API) e conectá-la ao cluster , após o qual o pod será adicionado.
Este é o escalonamento automático do cluster Kubernetes, que funciona muito bem (em nossa experiência). No entanto, como em outros lugares, existem algumas nuances aqui...
Contanto que aumentássemos o tamanho do cluster, tudo ficaria bem, mas o que acontece quando o cluster começou a se libertar? O problema é que a migração de pods (para liberar hosts) é muito difícil tecnicamente e cara em termos de recursos. O Kubernetes usa uma abordagem completamente diferente.
Considere um cluster de 3 servidores que possui Deployment. Possui 6 pods: agora são 2 para cada servidor. Por algum motivo, queríamos desligar um dos servidores. Para fazer isso usaremos o comando kubectl drain, qual:
proibirá o envio de novos pods para este servidor;
excluirá os pods existentes no servidor.
Como o Kubernetes é responsável por manter o número de pods (6), ele simplesmente vai recriar em outros nós, mas não naquele que está desabilitado, pois já está marcado como indisponível para hospedagem de novos pods. Esta é uma mecânica fundamental para Kubernetes.
No entanto, há uma nuance aqui também. Numa situação semelhante, para StatefulSet (em vez de Deployment), as ações serão diferentes. Agora já temos um aplicativo com estado - por exemplo, três pods com MongoDB, um dos quais apresenta algum tipo de problema (os dados foram corrompidos ou outro erro que impede o início correto do pod). E novamente decidimos desabilitar um servidor. O que vai acontecer?
MongoDB poderia morrer porque precisa de quorum: para um cluster de três instalações, pelo menos duas devem funcionar. No entanto, isso não ocorre - graças a PodDisruptionOrçamento. Este parâmetro determina o número mínimo necessário de pods em funcionamento. Saber que um dos pods do MongoDB não está mais funcionando e ver que PodDisruptionBudget está definido para MongoDB minAvailable: 2, o Kubernetes não permitirá que você exclua um pod.
Resumindo: para que a movimentação (e de fato, a recriação) dos pods funcione corretamente quando o cluster for liberado, é necessário configurar o PodDisruptionBudget.
Escala horizontal
Vamos considerar outra situação. Há um aplicativo em execução como Deployment no Kubernetes. O tráfego do usuário chega aos seus pods (por exemplo, há três deles) e medimos um determinado indicador neles (digamos, carga da CPU). Quando a carga aumenta, registramos isso em um agendamento e aumentamos o número de pods para distribuir solicitações.
Hoje no Kubernetes isso não precisa ser feito manualmente: um aumento/diminuição automático no número de pods é configurado dependendo dos valores dos indicadores de carga medidos.
As principais questões aqui são: o que exatamente medir и como interpretar valores obtidos (para tomar uma decisão sobre a alteração do número de pods). Você pode medir muito:
Como fazer isso tecnicamente – coletar métricas, etc. — Falei detalhadamente no relatório sobre Monitoramento e Kubernetes. E o principal conselho para escolher os parâmetros ideais é experimentar!
Tem Método de uso(Utilização de Saturação e Erros), cujo significado é o seguinte. Com base em que faz sentido escalar, por exemplo, php-fpm? Com base no facto de que os trabalhadores estão a esgotar-se, isto é utilização. E se acabarem os trabalhadores e não forem aceitas novas ligações, isso já é saturação. Ambos os parâmetros devem ser medidos e, dependendo dos valores, a escala deve ser realizada.
Em vez de uma conclusão
O relatório tem uma continuação: sobre a escala vertical e como selecionar os recursos certos. Falarei sobre isso em vídeos futuros no nosso YouTube - inscreva-se para não perder!