Nove dicas de desempenho do Kubernetes

Nove dicas de desempenho do Kubernetes

Olá a todos! Meu nome é Oleg Sidorenkov, trabalho na DomClick como líder de equipe de infraestrutura. Há mais de três anos que usamos o Cube à venda e, durante esse período, vivemos muitos momentos interessantes com ele. Hoje vou lhe contar como, com a abordagem certa, você pode extrair ainda mais desempenho do Kubernetes vanilla para o seu cluster. Pronto firme vai!

Todos vocês sabem muito bem que o Kubernetes é um sistema de código aberto escalável para orquestração de contêineres; bem, ou 5 binários que fazem mágica gerenciando o ciclo de vida de seus microsserviços em um ambiente de servidor. Além disso, esta é uma ferramenta bastante flexível que pode ser montada como um construtor de Lego para máxima personalização para diferentes tarefas.

E tudo parece estar bem: jogue servidores no cluster, como lenha em uma fornalha, e não conheça o luto. Mas se você é a favor do meio ambiente, então pensará: “Como posso manter o fogo no fogão e lamentar a floresta?”. Em outras palavras, como encontrar maneiras de melhorar a infraestrutura e reduzir custos.

1. Acompanhe os recursos da equipe e do aplicativo

Nove dicas de desempenho do Kubernetes

Um dos métodos mais banais, mas eficazes, é a introdução de solicitações/limites. Separe os aplicativos por namespaces e os namespaces por equipes de desenvolvimento. Defina o aplicativo antes de implantar valores para o consumo de tempo do processador, memória, armazenamento efêmero.

resources:
   requests:
     memory: 2Gi
     cpu: 250m
   limits:
     memory: 4Gi
     cpu: 500m

Por experiência, chegamos à conclusão: não vale a pena aumentar os pedidos de limites em mais de duas vezes. O volume do cluster é calculado com base nas solicitações e, se você definir a diferença de recursos para os aplicativos, por exemplo, em 5 a 10 vezes, imagine o que acontecerá com o seu nó quando ele estiver cheio de pods e de repente receber uma carga . Nada bom. No mínimo, limitação e, no máximo, diga adeus ao trabalhador e obtenha uma carga cíclica no restante dos nós depois que os pods começarem a se mover.

Além disso, com a ajuda limitranges você pode definir valores de recursos para o contêiner no início - mínimo, máximo e padrão:

➜  ~ kubectl describe limitranges --namespace ops
Name:       limit-range
Namespace:  ops
Type        Resource           Min   Max   Default Request  Default Limit  Max Limit/Request Ratio
----        --------           ---   ---   ---------------  -------------  -----------------------
Container   cpu                50m   10    100m             100m           2
Container   ephemeral-storage  12Mi  8Gi   128Mi            4Gi            -
Container   memory             64Mi  40Gi  128Mi            128Mi          2

Lembre-se de limitar os recursos do namespace para que um comando não possa ocupar todos os recursos do cluster:

➜  ~ kubectl describe resourcequotas --namespace ops
Name:                   resource-quota
Namespace:              ops
Resource                Used          Hard
--------                ----          ----
limits.cpu              77250m        80
limits.memory           124814367488  150Gi
pods                    31            45
requests.cpu            53850m        80
requests.memory         75613234944   150Gi
services                26            50
services.loadbalancers  0             0
services.nodeports      0             0

Como você pode ver na descrição resourcequotas, se o comando ops quiser implantar pods que consumirão mais 10 cpu, o agendador não permitirá que isso seja feito e emitirá um erro:

Error creating: pods "nginx-proxy-9967d8d78-nh4fs" is forbidden: exceeded quota: resource-quota, requested: limits.cpu=5,requests.cpu=5, used: limits.cpu=77250m,requests.cpu=53850m, limited: limits.cpu=10,requests.cpu=10

Para resolver um problema semelhante, você pode escrever uma ferramenta, por exemplo, como este, que pode armazenar e confirmar o estado dos recursos de comando.

2. Escolha o melhor armazenamento de arquivos

Nove dicas de desempenho do Kubernetes

Aqui, gostaria de abordar o tópico de volumes persistentes e o subsistema de disco dos nós de trabalho do Kubernetes. Espero que ninguém use o "Cubo" no HDD na produção, mas às vezes nem um SSD comum já é suficiente. Enfrentamos um problema tão grande que os logs estavam matando o disco por operações de E / S, e não há muitas soluções aqui:

  • Use SSDs de alto desempenho ou mude para NVMe (se você gerencia seu próprio hardware).

  • Diminua o nível de registro.

  • Faça balanceamento "inteligente" de pods que violam o disco (podAntiAffinity).

A captura de tela acima mostra o que acontece no nginx-ingress-controller com um disco quando o access_logs está ativado (~ 12k logs/s). Tal estado, é claro, pode levar à degradação de todos os aplicativos neste nó.

Quanto ao PV, infelizmente, não tentei de tudo. espécie Volumes persistentes. Use a melhor opção que mais lhe convier. Historicamente, em nosso país, uma pequena parte dos serviços precisa de volumes RWX e, há muito tempo, eles começaram a usar o armazenamento NFS para essa tarefa. Barato e ... o suficiente. Claro, comemos merda com ele - seja saudável, mas aprendemos a afiná-lo e sua cabeça não dói mais. E, se possível, mude para o armazenamento de objetos S3.

3. Crie imagens otimizadas

Nove dicas de desempenho do Kubernetes

É melhor usar imagens otimizadas para contêiner para que o Kubernetes possa buscá-las mais rapidamente e executá-las com mais eficiência. 

Otimização significa que as imagens:

  • conter apenas um aplicativo ou executar apenas uma função;

  • tamanho pequeno, porque imagens grandes são pior transmitidas pela rede;

  • ter endpoints de integridade e prontidão que o Kubernetes pode usar para agir em caso de inatividade;

  • usar sistemas operacionais compatíveis com contêineres (como Alpine ou CoreOS) que são mais resistentes a erros de configuração;

  • use compilações de vários estágios para que você possa implantar apenas aplicativos compilados e não as fontes que os acompanham.

Existem muitas ferramentas e serviços que permitem verificar e otimizar imagens em tempo real. É importante mantê-los sempre atualizados e seguros. Como resultado, você obtém:

  1. Carga de rede reduzida em todo o cluster.

  2. Diminuição do tempo de inicialização do contêiner.

  3. Tamanho menor de todo o registro do Docker.

4. Use um cache DNS

Nove dicas de desempenho do Kubernetes

Se falamos de altas cargas, sem ajustar o sistema DNS do cluster, a vida é muito ruim. Antigamente, os desenvolvedores do Kubernetes davam suporte à sua solução kube-dns. Também foi implementado em nosso país, mas este software não sintonizava particularmente e não dava o desempenho necessário, embora, ao que parece, a tarefa seja simples. Aí apareceu o coredns, para o qual mudamos e não conhecíamos o luto, depois passou a ser o serviço DNS padrão nos K8s. Em algum momento, crescemos para 40 mil rps no sistema DNS, e essa solução também não foi suficiente. Mas, por sorte, Nodelocaldns saiu, também conhecido como cache local do nó, também conhecido como NóLocal DNSCache.

Por que estamos usando? Existe um bug no kernel do Linux que, quando múltiplos acessos através do conntrack NAT sobre UDP, levam a uma condição de corrida para escrever nas tabelas conntrack, e parte do tráfego através do NAT é perdido (cada passagem pelo Serviço é NAT). O Nodelocaldns resolve esse problema eliminando o NAT e atualizando a conectividade TCP para o DNS upstream, bem como armazenando em cache as consultas DNS upstream localmente (incluindo um cache negativo curto de 5 segundos).

5. Escale os pods horizontal e verticalmente automaticamente

Nove dicas de desempenho do Kubernetes

Você pode dizer com confiança que todos os seus microsserviços estão prontos para um aumento de carga de duas a três vezes? Como alocar recursos corretamente para seus aplicativos? Manter alguns pods em execução além da carga de trabalho pode ser redundante, e mantê-los consecutivos corre o risco de inatividade devido a um aumento repentino no tráfego para o serviço. A média áurea ajuda a alcançar o feitiço de multiplicação de serviços como Escalonador automático de pod horizontal и Autoescalador vertical de pod.

VPA permite aumentar automaticamente as solicitações/limites de seus contêineres em um pod com base no uso real. Como pode ser útil? Se você tiver pods que, por algum motivo, não podem ser dimensionados horizontalmente (o que não é totalmente confiável), tente confiar no VPA para alterar seus recursos. Sua característica é um sistema de recomendação baseado em dados históricos e atuais do metric-server, portanto, se você não quiser alterar solicitações/limites automaticamente, basta monitorar os recursos recomendados para seus contêineres e otimizar as configurações para economizar CPU e memória no cluster.

Nove dicas de desempenho do KubernetesImagem retirada de https://levelup.gitconnected.com/kubernetes-autoscaling-101-cluster-autoscaler-horizontal-pod-autoscaler-and-vertical-pod-2a441d9ad231

O agendador no Kubernetes é sempre baseado em solicitações. Qualquer que seja o valor que você colocar lá, o agendador procurará um nó adequado com base nele. O valor dos limites é necessário para o kublet saber quando estrangular ou matar um pod. E como o único parâmetro importante é o valor da requisição, o VPA irá trabalhar com ele. Sempre que você dimensiona seu aplicativo verticalmente, define quais solicitações devem ser. E o que acontecerá com os limites então? Este parâmetro também será dimensionado proporcionalmente.

Por exemplo, aqui estão as configurações típicas do pod:

resources:
   requests:
     memory: 250Mi
     cpu: 200m
   limits:
     memory: 500Mi
     cpu: 350m

O mecanismo de recomendação determina que seu aplicativo precisa de 300 milhões de CPU e 500 milhões para funcionar corretamente. Você obterá estas configurações:

resources:
   requests:
     memory: 500Mi
     cpu: 300m
   limits:
     memory: 1000Mi
     cpu: 525m

Conforme mencionado acima, este é o escalonamento proporcional baseado na proporção de solicitações/limites no manifesto:

  • CPU: 200m → 300m: proporção 1:1.75;

  • Memória: 250Mi → 500Mi: proporção de 1:2.

Quanto a HPA, então o mecanismo de operação é mais transparente. Os limites são definidos para métricas como processador e memória e, se a média de todas as réplicas exceder o limite, o aplicativo será dimensionado em +1 pod até que o valor fique abaixo do limite ou até que o número máximo de réplicas seja atingido.

Nove dicas de desempenho do KubernetesImagem retirada de https://levelup.gitconnected.com/kubernetes-autoscaling-101-cluster-autoscaler-horizontal-pod-autoscaler-and-vertical-pod-2a441d9ad231

Além das métricas usuais, como CPU e memória, você pode definir limites em suas métricas personalizadas do Prometheus e trabalhar com elas se achar que essa é a maneira mais precisa de determinar quando dimensionar seu aplicativo. Depois que o aplicativo se estabilizar abaixo do limite de métrica especificado, o HPA começará a reduzir os pods para o número mínimo de réplicas ou até que a carga atinja o limite.

6. Não se esqueça da afinidade do nó e da afinidade do pod

Nove dicas de desempenho do Kubernetes

Nem todos os nós são executados no mesmo hardware e nem todos os pods precisam executar aplicativos com uso intensivo de computação. O Kubernetes permite que você especifique a especialização de nós e pods usando Afinidade do nó и Afinidade do pod.

Se você tiver nós adequados para operações de computação intensiva, para obter eficiência máxima, é melhor vincular os aplicativos aos nós apropriados. Para fazer isso, use nodeSelector com rótulo de nó.

Digamos que você tenha dois nós: um com CPUType=HIGHFREQ e um grande número de núcleos rápidos, outro com MemoryType=HIGHMEMORY mais memória e desempenho mais rápido. A maneira mais fácil é atribuir uma implantação de pod a um nó HIGHFREQadicionando à seção spec um seletor como este:

…
nodeSelector:
	CPUType: HIGHFREQ

Uma maneira mais cara e específica de fazer isso é usar nodeAffinity no campo affinity seção spec. Existem duas opções:

  • requiredDuringSchedulingIgnoredDuringExecution: hard setting (scheduler só irá implantar pods em nós específicos (e em nenhum outro lugar));

  • preferredDuringSchedulingIgnoredDuringExecution: configuração suave (o agendador tentará implantar em nós específicos e, se falhar, tentará implantar no próximo nó disponível).

Você pode especificar uma sintaxe específica para gerenciar rótulos de nó, por exemplo, In, NotIn, Exists, DoesNotExist, Gt ou Lt. No entanto, lembre-se de que métodos complexos em longas listas de rótulos retardarão a tomada de decisões em situações críticas. Em outras palavras, não complique demais.

Conforme mencionado acima, o Kubernetes permite definir a vinculação dos pods atuais. Ou seja, você pode fazer com que determinados pods funcionem em conjunto com outros pods na mesma zona de disponibilidade (relevante para nuvens) ou nós.

В podAffinity margens affinity seção spec os mesmos campos estão disponíveis como no caso de nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution и preferredDuringSchedulingIgnoredDuringExecution. A única diferença é que matchExpressions vinculará os pods a um nó que já está executando um pod com esse rótulo.

Mais Kubernetes oferece um campo podAntiAffinity, que, por outro lado, não vincula um pod a um nó com pods específicos.

Sobre expressões nodeAffinity O mesmo conselho pode ser dado: tente manter as regras simples e lógicas, não tente sobrecarregar a especificação do pod com um conjunto complexo de regras. É muito fácil criar uma regra que não corresponda às condições do cluster, sobrecarregando o agendador e degradando o desempenho geral.

7. Máculas e tolerâncias

Existe outra maneira de gerenciar o agendador. Se você tiver um grande cluster com centenas de nós e milhares de microsserviços, é muito difícil impedir que determinados pods sejam hospedados por determinados nós.

O mecanismo de manchas - regras de proibição - ajuda nisso. Por exemplo, você pode impedir que determinados nós executem pods em determinados cenários. Para aplicar taint a um nó específico, use a opção taint em kubectl. Especifique a chave e o valor e, em seguida, contamine como NoSchedule ou NoExecute:

$ kubectl taint nodes node10 node-role.kubernetes.io/ingress=true:NoSchedule

Também é importante notar que o mecanismo de contaminação suporta três efeitos principais: NoSchedule, NoExecute и PreferNoSchedule.

  • NoSchedule significa que até que haja uma entrada correspondente na especificação do pod tolerations, ele não pode ser implantado no nó (neste exemplo node10).

  • PreferNoSchedule - versão simplificada NoSchedule. Nesse caso, o agendador tentará não alocar pods que não tenham uma entrada correspondente. tolerations por nó, mas este não é um limite rígido. Se não houver recursos no cluster, os pods começarão a ser implantados neste nó.

  • NoExecute - este efeito desencadeia uma evacuação imediata de pods que não possuem uma entrada correspondente tolerations.

Curiosamente, esse comportamento pode ser desfeito usando o mecanismo de tolerâncias. Isso é conveniente quando existe um nó "proibido" e você precisa colocar apenas serviços de infraestrutura nele. Como fazer isso? Permitir apenas os pods para os quais existe uma tolerância adequada.

Veja como seria a especificação do pod:

spec:
   tolerations:
     - key: "node-role.kubernetes.io/ingress"
        operator: "Equal"
        value: "true"
        effect: "NoSchedule"

Isso não significa que durante a próxima reimplantação, o pod atingirá exatamente este nó, este não é o mecanismo de afinidade do nó e nodeSelector. Mas combinando vários recursos, você pode obter uma configuração de agenda muito flexível.

8. Defina a prioridade de implantação do pod

Só porque você configurou ligações de pod para nó não significa que todos os pods devem ser tratados com a mesma prioridade. Por exemplo, talvez você queira implantar alguns pods antes de outros.

O Kubernetes oferece diferentes maneiras de definir a prioridade e a preempção do pod. A configuração consiste em várias partes: objeto PriorityClass e descrições de campo priorityClassName na especificação do pod. Considere um exemplo:

apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
  name: high-priority
value: 99999
globalDefault: false
description: "This priority class should be used for very important pods only"

Nós criamos PriorityClass, dê a ele um nome, uma descrição e um valor. Superior value, maior a prioridade. O valor pode ser qualquer número inteiro de 32 bits menor ou igual a 1. Valores mais altos são reservados para pods de sistema de missão crítica, que normalmente não podem ser antecipados. A remoção ocorrerá apenas se o pod de alta prioridade não tiver para onde se virar, então alguns dos pods de um nó específico serão evacuados. Se este mecanismo for muito rígido para você, você pode adicionar a opção preemptionPolicy: Never, e então não haverá preempção, o pod será o primeiro da fila e aguardará que o escalonador encontre recursos livres para ele.

Em seguida, criamos um pod, no qual especificamos o nome priorityClassName:

apiVersion: v1
kind: Pod
metadata:
  name: static-web
  labels:
    role: myrole
 spec:
  containers:
    - name: web
      image: nginx
      ports:
        - name: web
          containerPort: 80
          protocol: TCP
  priorityClassName: high-priority
          

Você pode criar quantas classes de prioridade quiser, embora seja recomendável não se empolgar com isso (digamos, limite-se a baixa, média e alta prioridade).

Assim, se necessário, você pode aumentar a eficiência da implantação de serviços críticos, como nginx-ingress-controller, coredns, etc.

9. Otimize seu cluster ETCD

Nove dicas de desempenho do Kubernetes

O ETCD pode ser chamado de cérebro de todo o cluster. É muito importante manter o funcionamento desta base de dados em alto nível, pois disso depende a velocidade das operações no “Cubo”. Uma solução razoavelmente padrão e, ao mesmo tempo, uma boa solução seria manter um cluster ETCD nos nós principais para ter um atraso mínimo no kube-apiserver. Se isso não for possível, coloque o ETCD o mais próximo possível, com boa largura de banda entre os participantes. Preste atenção também em quantos nós do ETCD podem cair sem prejudicar o cluster.

Nove dicas de desempenho do Kubernetes

Lembre-se de que um aumento excessivo no número de participantes no cluster pode aumentar a tolerância a falhas em detrimento do desempenho, tudo deve ser moderado.

Se falamos em configurar o serviço, existem algumas recomendações:

  1. Tenha um bom hardware, com base no tamanho do cluster (você pode ler aqui).

  2. Ajuste alguns parâmetros se você espalhou um cluster entre um par de DCs ou sua rede e os discos deixam muito a desejar (você pode ler aqui).

Conclusão

Este artigo descreve os pontos que nossa equipe tenta cumprir. Esta não é uma descrição passo a passo de ações, mas opções que podem ser úteis para otimizar a sobrecarga de um cluster. É claro que cada cluster é único à sua maneira e as soluções de ajuste podem variar muito, por isso seria interessante obter feedback de você: como você monitora seu cluster Kubernetes, como você melhora seu desempenho. Compartilhe sua experiência nos comentários, será interessante conhecê-la.

Fonte: habr.com