Desmistificando o Agendamento do Kubernetes: Um Mergulho Profundo em Predicados e Prioridades

Desmistificando o Agendamento do Kubernetes: Um Mergulho Profundo em Predicados e Prioridades

O artigo de hoje introduz principalmente as etapas do processo de agendamento onde as estratégias de agendamento de Predicados e Prioridades entram em ação.

Predicados

Vamos primeiro dar uma olhada nos Predicados. O papel dos Predicados no processo de agendamento pode ser entendido como um Filtro, ou seja: ele “filtra” uma série de nós que atendem às condições de todos os nós do cluster atual de acordo com a política de agendamento. Esses nós são todos hospedeiros potenciais para o Pod que está aguardando para ser agendado. No Kubernetes, existem quatro políticas de agendamento padrão.

O primeiro tipo é chamado de GeneralPredicates.

Como o nome sugere, este conjunto de regras de filtragem é responsável pelas políticas de agendamento mais básicas. Por exemplo, PodFitsResources calcula se os recursos de CPU e memória do hospedeiro são suficientes. Claro, como mencionei anteriormente, PodFitsResources apenas verifica o campo requests do Pod. Deve-se notar que o agendador do Kubernetes não define tipos específicos de recursos para recursos de hardware como GPUs, mas usa um formato de Chave-Valor chamado Extended Resource para descrevê-los. Por exemplo:

apiVersion: v1
kind: Pod
metadata:
  name: extended-resource-demo
spec:
  containers:
  - name: extended-resource-demo-ctr
    image: nginx
    resources:
      requests:
        alpha.kubernetes.io/nvidia-gpu: "2"
      limits:
        alpha.kubernetes.io/nvidia-gpu: "2"

Pode-se ver que nosso Pod declara o uso de duas GPUs do tipo NVIDIA com a definição alpha.kubernetes.io/nvidia-gpu=2. No PodFitsResources, o agendador não sabe realmente que o significado desse campo Chave é GPU, mas usa diretamente o Valor subsequente para o cálculo. Claro, no campo Capacity do Node, você também precisa adicionar o número total de GPUs neste hospedeiro, como: alpha.kubernetes.io/nvidia-gpu=4. Explicarei esses processos em detalhes posteriormente ao explicar o Device Plugin.

PodFitsHost verifica se o nome do hospedeiro corresponde ao spec.nodeName do Pod.

PodFitsHostPorts verifica se as portas do hospedeiro (spec.nodePort) solicitadas pelo Pod entram em conflito com portas já utilizadas.

PodMatchNodeSelector verifica se os nós especificados pelo nodeSelector ou nodeAffinity do Pod correspondem ao nó em exame, e assim por diante.

Pode-se ver que tal conjunto de GeneralPredicates é a condição de filtragem mais básica para o Kubernetes examinar se um Pod pode ser executado em um Node. Portanto, GeneralPredicates também são chamados diretamente por outros componentes (como o kubelet).

O segundo tipo são as regras de filtragem relacionadas ao Volume.

Este conjunto de regras de filtragem é responsável pela política de agendamento relacionada ao Volume persistente do contêiner.

Entre elas, NoDiskConflict verifica se há conflito entre os Volumes persistentes declarados por múltiplos Pods. Por exemplo, Volumes do tipo AWS EBS não podem ser usados por dois Pods ao mesmo tempo. Então, quando um Volume EBS chamado A já foi montado em um determinado nó, outro Pod que também declara o uso deste Volume A não pode ser agendado para este nó.

MaxPDVolumeCountPredicate verifica se um determinado tipo de Volume persistente em um nó excedeu um certo número. Se sim, então o Pod que declara o uso deste tipo de Volume persistente não pode mais ser agendado para este nó.

VolumeZonePredicate verifica se o rótulo Zone (domínio de alta disponibilidade) do Volume persistente corresponde ao rótulo Zone do nó em exame.

Além disso, há uma regra chamada VolumeBindingPredicate. Ela é responsável por verificar se o campo nodeAffinity do PV correspondente ao Pod corresponde ao rótulo de um determinado nó. O Volume Persistente Local (local persistent volume) deve usar nodeAffinity para se vincular a um nó específico. Isso significa na prática que, durante a fase de Predicados, o Kubernetes deve ser capaz de agendar com base nos atributos de Volume do Pod.

Além disso, se o PVC do Pod ainda não foi vinculado a um PV específico, o agendador também é responsável por verificar todos os PVs a serem vinculados. Quando há um PV disponível e o nodeAffinity deste PV é consistente com o nó em exame, esta regra retornará “sucesso”. Por exemplo:

apiVersion: v1
kind: PersistentVolume
metadata:
  name: example-local-pv
spec:
  capacity:
    storage: 500Gi
  accessModes:
  - ReadWriteOnce
  persistentVolumeReclaimPolicy: Retain
  storageClassName: local-storage
  local:
    path: /mnt/disks/vol1
  nodeAffinity:
    required:
      nodeSelectorTerms:
      - matchExpressions:
        - key: kubernetes.io/hostname
          operator: In
          values:
          - my-node

Pode-se ver que o diretório persistente correspondente a este PV só aparecerá no hospedeiro chamado my-node. Portanto, qualquer Pod que use este PV através de um PVC deve ser agendado para my-node para funcionar corretamente. O VolumeBindingPredicate é exatamente onde o agendador toma essa decisão.

O terceiro tipo são as regras de filtragem relacionadas ao hospedeiro.

Este conjunto de regras examina principalmente se o Pod a ser agendado atende a certas condições do próprio Node.

Por exemplo, PodToleratesNodeTaints verifica o mecanismo de “taint” do Node que frequentemente usamos. Somente quando o campo Toleration do Pod corresponde ao campo Taint do Node é que este Pod pode ser agendado para o Node.

NodeMemoryPressurePredicate verifica se a memória atual do Node não é mais suficiente. Se sim, o Pod aguardando para ser agendado não pode ser agendado para este Node.

O quarto tipo são as regras de filtragem relacionadas ao Pod.

Este conjunto de regras se sobrepõe à maioria dos GeneralPredicates. O que é especial é o PodAffinityPredicate. O papel desta regra é verificar as relações de afinidade e anti-afinidade entre o Pod a ser agendado e os Pods existentes no Node. Por exemplo:

apiVersion: v1
kind: Pod
metadata:
  name: with-pod-antiaffinity
spec:
  affinity:
    podAntiAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
      - weight: 100
        podAffinityTerm:
          labelSelector:
            matchExpressions:
            - key: security
              operator: In
              values:
              - S2
          topologyKey: kubernetes.io/hostname
  containers:
  - name: with-pod-affinity
    image: docker.io/ocpqe/hello-pod

Neste exemplo, a regra podAntiAffinity especifica que este Pod não deseja estar no mesmo Node que qualquer Pod que carregue o rótulo security=S2. Deve-se notar que PodAffinityPredicate tem um escopo, como a regra acima, que é válida apenas para Nodes que carregam o rótulo de chave kubernetes.io/hostname. Este é o papel da palavra-chave topologyKey.

Esses quatro tipos de Predicados constituem a estratégia básica para o agendador determinar se um Node pode executar o Pod a ser agendado.

Quando executado, ao agendar um Pod, o agendador do Kubernetes inicia simultaneamente 16 Goroutines para calcular concorrentemente Predicados para todos os Nodes no cluster, e finalmente retorna a lista de hospedeiros que podem executar este Pod.

Deve-se notar que ao executar Predicados para cada Node, o agendador verificará em uma ordem fixa. Esta ordem é determinada de acordo com o significado dos próprios Predicados. Por exemplo, Predicados relacionados ao hospedeiro serão verificados mais cedo. Caso contrário, calcular PodAffinityPredicate em um hospedeiro com recursos gravemente insuficientes não teria sentido.

Prioridades

Após a “filtragem” de nós na fase de Predicados, o trabalho da fase de Prioridades é pontuar esses nós. A faixa de pontuação aqui é de 0 a 10 pontos, e o nó com a maior pontuação é o melhor nó para o qual o Pod será finalmente vinculado.

A regra de pontuação mais comumente usada em Prioridades é LeastRequestedPriority. Este algoritmo na verdade seleciona o hospedeiro com os recursos mais ociosos (CPU e Memória).

Além disso, existem três outras Prioridades: NodeAffinityPriority, TaintTolerationPriority e InterPodAffinityPriority. Como os nomes sugerem, eles são semelhantes em significado e método de cálculo aos três Predicados anteriores: PodMatchNodeSelector, PodToleratesNodeTaints e PodAffinityPredicate. No entanto, como uma Prioridade, quanto mais campos um Node atender às regras acima, maior será sua pontuação.

Nas Prioridades padrão, também existe uma estratégia chamada ImageLocalityPriority. É uma nova regra de agendamento habilitada no Kubernetes v1.12, ou seja: se a imagem necessária para o Pod a ser agendado for grande e já existir em alguns Nodes, então esses Nodes terão uma pontuação mais alta.

Claro, para evitar que o algoritmo cause empilhamento de agendamento (scheduling stacking), o agendador também otimizará de acordo com a distribuição da imagem ao calcular a pontuação, ou seja: se o número de nós onde a imagem grande está distribuída for muito pequeno, então o peso desses nós será reduzido, “compensando” assim o risco de causar empilhamento de agendamento.

Em resumo, este é o princípio de funcionamento principal das regras de agendamento padrão no agendador do Kubernetes.

No processo de execução real, as informações sobre o cluster e os Pods no agendador já foram armazenadas em cache, portanto, o processo de execução desses algoritmos é relativamente rápido.

Além disso, para algoritmos de agendamento mais complexos, como PodAffinityPredicate, eles não apenas prestam atenção ao Pod a ser agendado e ao Node em exame ao calcular, mas também precisam prestar atenção às informações de todo o cluster, como percorrer todos os nós e ler seus Labels. Neste momento, o agendador do Kubernetes primeiro calculará as informações do cluster necessárias para o algoritmo com antecedência antes de executar o algoritmo de agendamento para cada Pod a ser agendado, e depois as armazenará em cache. Dessa forma, ao executar o algoritmo, o agendador só precisa ler as informações em cache para o cálculo, evitando a necessidade de recuperar e calcular repetidamente as informações de todo o cluster para cada Node ao calcular Predicados.

Resumo

Em resumo, este artigo discutiu os principais algoritmos de agendamento dentro do agendador padrão do Kubernetes. É importante notar que, além das regras abordadas neste artigo, existem algumas estratégias no agendador do Kubernetes que não estão habilitadas por padrão. Você pode especificar um arquivo de configuração para o kube-scheduler ou criar um ConfigMap para configurar quais regras habilitar ou desabilitar. Além disso, você pode controlar o comportamento do agendador definindo pesos para as Prioridades.

Isso conclui a explicação dos mecanismos de agendamento padrão no agendador do Kubernetes. Vale mencionar que, embora o agendador opere rapidamente devido ao cache das informações do cluster e do Pod, para algoritmos mais complexos, o agendador deve levar em consideração todo o estado do cluster, não apenas o Pod e o Node em questão. Isso inclui cálculos preliminares e armazenamento em cache das informações necessárias do cluster antes da execução real do algoritmo de agendamento, garantindo eficiência e precisão no processo de agendamento.

Novita AI é a plataforma completa em nuvem que impulsiona suas ambições de IA. Com APIs integradas perfeitamente, computação serverless e aceleração de GPU, fornecemos as ferramentas econômicas que você precisa para construir e escalar rapidamente seu negócio impulsionado por IA. Elimine dores de cabeça de infraestrutura e comece gratuitamente - Novita AI torna seus sonhos de IA realidade.

Leitura Recomendada:

  1. O que você precisa saber sobre Docker
  2. Por que precisamos de Pods