This is the multi-page printable view of this section. Click here to print.

Return to the regular view of this page.

Escalonamento, preempção e remoção

No Kubernetes, escalonamento refere-se à certeza de que os Pods correspondam aos nós para que o kubelet possa executá-los. Preempção é o processo de finalizar Pods com menor prioridade, para que os Pods com maior prioridade possam ser escalonados nos nós. Remoção é o processo de finalização proativa de um ou mais Pods em nós com poucos recursos.

No Kubernetes, escalonamento refere-se à certeza de que Pods correspondam aos nós para que o Kubelet possa executá-los. Preempção é o processo de finalizar Pods com menor prioridade para que os Pods com maior prioridade possam ser escalonados nos nós. Remoção é o processo de finalização de um ou mais Pods em nós.

Escalonamento

Disrupção do Pod

Disrupção do Pod é o processo pelo qual Pods ou nós são interrompidos de forma voluntária ou involuntária.

Disrupções voluntárias são iniciadas intencionalmente pelos donos das aplicações ou administradores dos clusters. Disrupções involuntárias não são intencionais e podem ser encadeadas por problemas inevitáveis como Nós com poucos recursos, ou por exclusões acidentais.

1 - Escalonador do Kubernetes

No Kubernetes, escalonamento refere-se a garantir que os Pods sejam correspondidos aos Nós para que o Kubelet possa executá-los.

Visão geral do Escalonamento

Um escalonador observa Pods recém-criados que não possuem um Nó atribuído. Para cada Pod que o escalonador descobre, ele se torna responsável por encontrar o melhor Nó para execução do Pod. O escalonador chega a essa decisão de alocação levando em consideração os princípios de escalonamento descritos abaixo.

Se você quiser entender por que os Pods são alocados em um Nó específico ou se planeja implementar um escalonador personalizado, esta página ajudará você a aprender sobre escalonamento.

kube-scheduler

kube-scheduler é o escalonador padrão do Kubernetes e é executado como parte da camada de gerenciamento. O kube-scheduler é projetado para que, se você quiser e precisar, possa escrever seu próprio componente de escalonamento e usá-lo.

O kube-scheduler seleciona um Nó ideal para executar Pods recém-criados ou não escalonados (unscheduled). Como os contêineres em Pods — e os próprios Pods — podem ter diferentes requisitos, o escalonador filtra os Nós que não atendem às necessidades específicas de escalonamento do Pod. Alternativamente, a API permite que você especifique um Nó para um Pod ao criá-lo, mas isso é incomum e só é feito em casos especiais.

Em um cluster, Nós que atendem aos requisitos de escalonamento para um Pod são chamados de Nós viáveis. Se nenhum dos Nós for adequado, o Pod permanece não escalonado até que o escalonador consiga alocá-lo.

O escalonador encontra Nós viáveis para um Pod e, em seguida, executa um conjunto de funções para classificar esses Nós viáveis e escolhe o Nó com a maior pontuação entre os possíveis para executar o Pod. O escalonador então notifica o servidor de API sobre essa decisão em um processo chamado binding.

Fatores que precisam ser levados em consideração para decisões de escalonamento incluem requisitos individuais e coletivos de recursos, restrições de hardware / software / política, especificações de afinidade e anti-afinidade, localização de dados, interferência entre cargas de trabalho e assim por diante.

Seleção do Nó no kube-scheduler

O kube-scheduler seleciona um Nó para o Pod em uma operação que consiste em duas etapas:

  1. Filtragem
  2. Pontuação

A etapa de filtragem localiza o conjunto de Nós onde é possível alocar o Pod. Por exemplo, o filtro PodFitsResources verifica se um Nó candidato possui recursos disponíveis suficientes para atender às solicitações de recursos específicas de um Pod. Após esta etapa, a lista de Nós contém quaisquer Nós adequados; frequentemente, haverá mais de um. Se a lista estiver vazia, esse Pod (ainda) não é escalonável.

Na etapa de pontuação, o escalonador classifica os Nós restantes para escolher o mais adequado. O escalonador atribui uma pontuação a cada Nó que passou na filtragem, baseando essa pontuação nas regras de pontuação ativas.

Por fim, o kube-scheduler atribui o Pod ao Nó com a classificação mais alta. Se houver mais de um Nó com pontuações iguais, o kube-scheduler seleciona um deles aleatoriamente.

Existem duas maneiras suportadas de configurar o comportamento de filtragem e pontuação do escalonador:

  1. Políticas de Escalonamento permitem configurar Predicados para filtragem e Prioridades para pontuação.
  2. Perfis de Escalonamento permitem configurar Plugins que implementam diferentes estágios de escalonamento, incluindo: QueueSort, Filter, Score, Bind, Reserve, Permit, e outros. Você também pode configurar o kube-scheduler para executar diferentes perfis.

Próximos passos

2 - Atribuindo Pods a Nós

Você pode restringir um Pod para que ele seja limitado a executar em nó(s) específicos, ou para preferir executar em nós específicos. Existem várias maneiras de fazer isso e as abordagens recomendadas utilizam seletores de rótulos para facilitar a seleção. Frequentemente, você não precisa definir nenhuma dessas restrições; o escalonador fará automaticamente uma alocação adequada (por exemplo, distribuindo seus Pods entre os nós para não alocá-los em um nó com recursos livres insuficientes). No entanto, existem algumas circunstâncias em que você pode querer controlar em qual nó o Pod será implantado, por exemplo, para garantir que um Pod seja alocado em um nó com um SSD conectado, ou para colocalizar Pods de dois serviços diferentes que se comunicam frequentemente na mesma zona de disponibilidade.

Você pode usar qualquer um dos seguintes métodos para escolher onde o Kubernetes aloca Pods específicos:

Rótulos de nós

Assim como muitos outros objetos do Kubernetes, os nós possuem rótulos. Você pode anexar rótulos manualmente. O Kubernetes também preenche um conjunto padrão de rótulos em todos os nós de um cluster.

Nota:

O valor desses rótulos é específico do provedor de nuvem e não é garantido que seja confiável. Por exemplo, o valor de kubernetes.io/hostname pode ser o mesmo que o nome do nó em alguns ambientes e um valor diferente em outros ambientes.

Isolamento/restrição de nós

Adicionar rótulos aos nós permite direcionar Pods para alocação em nós ou grupos de nós específicos. Você pode usar essa funcionalidade para garantir que Pods específicos executem apenas em nós com determinadas propriedades de isolamento, segurança ou conformidade regulatória.

Se você usar rótulos para isolamento de nós, escolha chaves de rótulos que o kubelet não possa modificar. Isso impede que um nó comprometido defina esses rótulos em si mesmo para fazer com que o escalonador aloque cargas de trabalho no nó comprometido.

O plugin de admissão NodeRestriction impede que o kubelet defina ou modifique rótulos com o prefixo node-restriction.kubernetes.io/.

Para utilizar esse prefixo de rótulo para isolamento de nós:

  1. Certifique-se de estar usando o Node authorizer e ter habilitado o plugin de admissão NodeRestriction.
  2. Adicione rótulos com o prefixo node-restriction.kubernetes.io/ aos seus nós e use esses rótulos em seus seletores de nós. Por exemplo, example.com.node-restriction.kubernetes.io/fips=true ou example.com.node-restriction.kubernetes.io/pci-dss=true.

nodeSelector

nodeSelector é a forma recomendada mais simples de restrição de seleção de nós. Você pode adicionar o campo nodeSelector à especificação do seu Pod e especificar os rótulos de nós que você deseja que o nó de destino possua. O Kubernetes aloca o Pod apenas em nós que possuem cada um dos rótulos que você especificar.

Consulte Atribuir Pods a Nós para mais informações.

Afinidade e antiafinidade

nodeSelector é a maneira mais simples de restringir Pods a nós com rótulos específicos. Afinidade e antiafinidade expandem os tipos de restrições que você pode definir. Alguns dos benefícios da afinidade e antiafinidade incluem:

  • A linguagem de afinidade/antiafinidade é mais expressiva. nodeSelector apenas seleciona nós com todos os rótulos especificados. Afinidade/antiafinidade oferece mais controle sobre a lógica de seleção.
  • Você pode indicar que uma regra é flexível ou preferencial, para que o escalonador ainda aloque o Pod mesmo que não consiga encontrar um nó correspondente.
  • Você pode restringir um Pod usando rótulos de outros Pods em execução no nó (ou outro domínio topológico), em vez de apenas rótulos de nós, o que permite definir regras para quais Pods podem ser colocalizados em um nó.

A funcionalidade de afinidade consiste em dois tipos de afinidade:

  • Afinidade de nó funciona como o campo nodeSelector, mas é mais expressiva e permite especificar regras flexíveis.
  • Afinidade/antiafinidade entre Pods permite restringir Pods com base em rótulos de outros Pods.

Afinidade de nó

Afinidade de nó é conceitualmente similar a nodeSelector, permitindo restringir em quais nós seu Pod pode ser alocado com base em rótulos de nós. Existem dois tipos de afinidade de nó:

  • requiredDuringSchedulingIgnoredDuringExecution: O escalonador não pode alocar o Pod a menos que a regra seja atendida. Isso funciona como nodeSelector, mas com uma sintaxe mais expressiva.
  • preferredDuringSchedulingIgnoredDuringExecution: O escalonador tenta encontrar um nó que atenda à regra. Se um nó correspondente não estiver disponível, o escalonador ainda aloca o Pod.

Nota:

Nos tipos anteriores, IgnoredDuringExecution significa que se os rótulos do nó mudarem após o Kubernetes alocar o Pod, o Pod continuará em execução.

Você pode especificar afinidades de nó usando o campo .spec.affinity.nodeAffinity na especificação do seu Pod.

Por exemplo, considere a seguinte especificação de Pod:

apiVersion: v1
kind: Pod
metadata:
  name: with-node-affinity
spec:
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
        - matchExpressions:
          - key: topology.kubernetes.io/zone
            operator: In
            values:
            - antarctica-east1
            - antarctica-west1
      preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 1
        preference:
          matchExpressions:
          - key: another-node-label-key
            operator: In
            values:
            - another-node-label-value
  containers:
  - name: with-node-affinity
    image: registry.k8s.io/pause:3.8

Neste exemplo, as seguintes regras se aplicam:

  • O nó deve ter um rótulo com a chave topology.kubernetes.io/zone e o valor desse rótulo deve ser antarctica-east1 ou antarctica-west1.
  • O nó preferencialmente tem um rótulo com a chave another-node-label-key e o valor another-node-label-value.

Você pode usar o campo operator para especificar um operador lógico para o Kubernetes usar ao interpretar as regras. Você pode usar In, NotIn, Exists, DoesNotExist, Gt e Lt.

Leia Operadores para saber mais sobre como eles funcionam.

NotIn e DoesNotExist permitem definir o comportamento de antiafinidade de nó. Alternativamente, você pode usar taints de nó para repelir Pods de nós específicos.

Nota:

Se você especificar tanto nodeSelector quanto nodeAffinity, ambos devem ser satisfeitos para que o Pod seja alocado em um nó.

Se você especificar múltiplos termos em nodeSelectorTerms associados a tipos de nodeAffinity, então o Pod pode ser alocado em um nó se um dos termos especificados puder ser satisfeito (os termos são combinados com OR).

Se você especificar múltiplas expressões em um único campo matchExpressions associado a um termo em nodeSelectorTerms, então o Pod pode ser alocado em um nó apenas se todas as expressões forem satisfeitas (as expressões são combinadas com AND).

Consulte Atribuir Pods a Nós usando Afinidade de Nó para mais informações.

Peso da afinidade de nó

Você pode especificar um weight (peso) entre 1 e 100 para cada instância do tipo de afinidade preferredDuringSchedulingIgnoredDuringExecution. Quando o escalonador encontra nós que atendem a todos os outros requisitos de alocação do Pod, o escalonador itera por cada regra preferencial que o nó satisfaz e adiciona o valor do weight dessa expressão a uma soma.

A soma final é adicionada à pontuação de outras funções de prioridade do nó. Nós com a maior pontuação total são priorizados quando o escalonador toma uma decisão de alocação para o Pod.

Por exemplo, considere a seguinte especificação de Pod:

apiVersion: v1
kind: Pod
metadata:
  name: with-affinity-preferred-weight
spec:
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
        - matchExpressions:
          - key: kubernetes.io/os
            operator: In
            values:
            - linux
      preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 1
        preference:
          matchExpressions:
          - key: label-1
            operator: In
            values:
            - key-1
      - weight: 50
        preference:
          matchExpressions:
          - key: label-2
            operator: In
            values:
            - key-2
  containers:
  - name: with-node-affinity
    image: registry.k8s.io/pause:3.8

Se houver dois nós possíveis que correspondem à regra preferredDuringSchedulingIgnoredDuringExecution, um com o rótulo label-1:key-1 e outro com o rótulo label-2:key-2, o escalonador considera o weight de cada nó e adiciona o peso às outras pontuações daquele nó, e aloca o Pod no nó com a maior pontuação final.

Nota:

Se você deseja que o Kubernetes aloque os Pods com sucesso neste exemplo, você deve ter nós existentes com o rótulo kubernetes.io/os=linux.

Afinidade de nó por perfil de alocação

ESTADO DA FUNCIONALIDADE: Kubernetes v1.20 [beta]

Ao configurar múltiplos perfis de alocação, você pode associar um perfil a uma afinidade de nó, o que é útil se um perfil se aplica apenas a um conjunto específico de nós. Para fazer isso, adicione um addedAffinity ao campo args do plugin NodeAffinity na configuração do escalonador. Por exemplo:

apiVersion: kubescheduler.config.k8s.io/v1
kind: KubeSchedulerConfiguration

profiles:
  - schedulerName: default-scheduler
  - schedulerName: foo-scheduler
    pluginConfig:
      - name: NodeAffinity
        args:
          addedAffinity:
            requiredDuringSchedulingIgnoredDuringExecution:
              nodeSelectorTerms:
              - matchExpressions:
                - key: scheduler-profile
                  operator: In
                  values:
                  - foo

O addedAffinity é aplicado a todos os Pods que definem .spec.schedulerName como foo-scheduler, além da NodeAffinity especificada no PodSpec. Ou seja, para corresponder ao Pod, os nós precisam satisfazer o addedAffinity e o .spec.NodeAffinity do Pod.

Como o addedAffinity não é visível para os usuários finais, seu comportamento pode ser inesperado para eles. Use rótulos de nós que tenham uma correlação clara com o nome do perfil do escalonador.

Nota:

O controlador DaemonSet, que cria Pods para DaemonSets, não suporta perfis de alocação. Quando o controlador DaemonSet cria Pods, o escalonador padrão do Kubernetes aloca esses Pods e respeita quaisquer regras de nodeAffinity no controlador DaemonSet.

Afinidade e antiafinidade entre Pods

Afinidade e antiafinidade entre Pods permitem restringir em quais nós seus Pods podem ser alocados com base nos rótulos de Pods já em execução naquele nó, em vez dos rótulos do nó.

Tipos de afinidade e antiafinidade entre Pods

Afinidade e antiafinidade entre Pods assumem a forma "este Pod deve (ou, no caso de antiafinidade, não deve) executar em um X se esse X já estiver executando um ou mais Pods que atendem à regra Y", onde X é um domínio topológico como nó, rack, zona ou região do provedor de nuvem, ou similar, e Y é a regra que o Kubernetes tenta satisfazer.

Você expressa essas regras (Y) como seletores de rótulos com uma lista opcional associada de namespaces. Pods são objetos com namespace no Kubernetes, então rótulos de Pods também implicitamente possuem namespaces. Quaisquer seletores de rótulos para rótulos de Pods devem especificar os namespaces nos quais o Kubernetes deve procurar esses rótulos.

Você expressa o domínio topológico (X) usando uma topologyKey, que é a chave do rótulo do nó que o sistema usa para indicar o domínio. Para exemplos, consulte Rótulos, Anotações e Taints conhecidos.

Nota:

Afinidade e antiafinidade entre Pods requerem quantidades substanciais de processamento, o que pode desacelerar significativamente a alocação em clusters grandes. Não recomendamos usá-las em clusters maiores que algumas centenas de nós.

Nota:

Antiafinidade de Pod requer que os nós sejam rotulados de forma consistente, em outras palavras, cada nó no cluster deve ter um rótulo apropriado correspondendo à topologyKey. Se alguns ou todos os nós não tiverem o rótulo topologyKey especificado, isso pode levar a comportamentos não intencionais.

Similar à afinidade de nó, existem dois tipos de afinidade e antiafinidade de Pod, como segue:

  • requiredDuringSchedulingIgnoredDuringExecution
  • preferredDuringSchedulingIgnoredDuringExecution

Por exemplo, você poderia usar afinidade requiredDuringSchedulingIgnoredDuringExecution para dizer ao escalonador para colocalizar Pods de dois serviços na mesma zona do provedor de nuvem porque eles se comunicam muito entre si. Da mesma forma, você poderia usar antiafinidade preferredDuringSchedulingIgnoredDuringExecution para distribuir Pods de um serviço em múltiplas zonas do provedor de nuvem.

Para usar afinidade entre Pods, use o campo affinity.podAffinity na especificação do Pod. Para antiafinidade entre Pods, use o campo affinity.podAntiAffinity na especificação do Pod.

Comportamento de alocação

Ao alocar um novo Pod, o escalonador do Kubernetes avalia as regras de afinidade/antiafinidade do Pod no contexto do estado atual do cluster:

  1. Restrições rígidas (Filtragem de nós):

    • podAffinity.requiredDuringSchedulingIgnoredDuringExecution e podAntiAffinity.requiredDuringSchedulingIgnoredDuringExecution:
      • O escalonador garante que o novo Pod seja atribuído a nós que satisfaçam essas regras obrigatórias de afinidade e antiafinidade com base nos Pods existentes.
  2. Restrições flexíveis (Pontuação):

    • podAffinity.preferredDuringSchedulingIgnoredDuringExecution e podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution:
      • O escalonador pontua os nós com base em quão bem eles atendem a essas regras preferenciais de afinidade e antiafinidade para otimizar a alocação do Pod.
  3. Campos ignorados:

    • podAffinity.preferredDuringSchedulingIgnoredDuringExecution de Pods existentes:
      • Essas regras preferenciais de afinidade não são consideradas durante a decisão de alocação para novos Pods.
    • podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution de Pods existentes:
      • Da mesma forma, regras preferenciais de antiafinidade de Pods existentes são ignoradas durante a alocação.

Alocando um grupo de Pods com afinidade entre Pods consigo mesmos

Se o Pod atual sendo alocado é o primeiro de uma série que tem afinidade consigo mesmos, ele pode ser alocado se passar em todas as outras verificações de afinidade. Isso é determinado verificando que nenhum outro Pod no cluster corresponde ao namespace e seletor deste Pod, que o Pod corresponde aos seus próprios termos, e que o nó escolhido corresponde a todas as topologias solicitadas. Isso garante que não haverá um deadlock mesmo se todos os Pods tiverem afinidade entre Pods especificada.

Exemplo de afinidade de Pod

Considere a seguinte especificação de Pod:

apiVersion: v1
kind: Pod
metadata:
  name: with-pod-affinity
spec:
  affinity:
    podAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
      - labelSelector:
          matchExpressions:
          - key: security
            operator: In
            values:
            - S1
        topologyKey: topology.kubernetes.io/zone
    podAntiAffinity:
      preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 100
        podAffinityTerm:
          labelSelector:
            matchExpressions:
            - key: security
              operator: In
              values:
              - S2
          topologyKey: topology.kubernetes.io/zone
  containers:
  - name: with-pod-affinity
    image: registry.k8s.io/pause:3.8

Este exemplo define uma regra de afinidade de Pod e uma regra de antiafinidade de Pod. A regra de afinidade de Pod usa o "rígido" requiredDuringSchedulingIgnoredDuringExecution, enquanto a regra de antiafinidade usa o "flexível" preferredDuringSchedulingIgnoredDuringExecution.

A regra de afinidade especifica que o escalonador pode alocar o Pod de exemplo em um nó apenas se esse nó pertencer a uma zona específica onde outros Pods foram rotulados com security=S1. Por exemplo, se tivermos um cluster com uma zona designada, vamos chamá-la de "Zona V", consistindo de nós rotulados com topology.kubernetes.io/zone=V, o escalonador pode atribuir o Pod a qualquer nó dentro da Zona V, desde que haja pelo menos um Pod dentro da Zona V já rotulado com security=S1. Por outro lado, se não houver Pods com rótulos security=S1 na Zona V, o escalonador não atribuirá o Pod de exemplo a nenhum nó nessa zona.

A regra de antiafinidade especifica que o escalonador deve tentar evitar alocar o Pod em um nó se esse nó pertencer a uma zona específica onde outros Pods foram rotulados com security=S2. Por exemplo, se tivermos um cluster com uma zona designada, vamos chamá-la de "Zona R", consistindo de nós rotulados com topology.kubernetes.io/zone=R, o escalonador deve evitar atribuir o Pod a qualquer nó dentro da Zona R, desde que haja pelo menos um Pod dentro da Zona R já rotulado com security=S2. Por outro lado, a regra de antiafinidade não impacta a alocação na Zona R se não houver Pods com rótulos security=S2.

Para se familiarizar mais com os exemplos de afinidade e antiafinidade de Pod, consulte a proposta de projeto.

Você pode usar os valores In, NotIn, Exists e DoesNotExist no campo operator para afinidade e antiafinidade de Pod.

Leia Operadores para saber mais sobre como eles funcionam.

Em princípio, a topologyKey pode ser qualquer chave de rótulo permitida, com as seguintes exceções por razões de desempenho e segurança:

  • Para afinidade e antiafinidade de Pod, um campo topologyKey vazio não é permitido tanto em requiredDuringSchedulingIgnoredDuringExecution quanto em preferredDuringSchedulingIgnoredDuringExecution.
  • Para regras de antiafinidade de Pod requiredDuringSchedulingIgnoredDuringExecution, o controlador de admissão LimitPodHardAntiAffinityTopology limita topologyKey a kubernetes.io/hostname. Você pode modificar ou desabilitar o controlador de admissão se quiser permitir topologias personalizadas.

Além de labelSelector e topologyKey, você pode opcionalmente especificar uma lista de namespaces com os quais o labelSelector deve corresponder usando o campo namespaces no mesmo nível que labelSelector e topologyKey. Se omitido ou vazio, namespaces assume como padrão o namespace do Pod onde a definição de afinidade/antiafinidade aparece.

Seletor de Namespace

ESTADO DA FUNCIONALIDADE: Kubernetes v1.24 [stable]

Você também pode selecionar namespaces correspondentes usando namespaceSelector, que é uma consulta de rótulos sobre o conjunto de namespaces. O termo de afinidade é aplicado aos namespaces selecionados tanto pelo namespaceSelector quanto pelo campo namespaces. Note que um namespaceSelector vazio ({}) corresponde a todos os namespaces, enquanto uma lista namespaces nula ou vazia e um namespaceSelector nulo correspondem ao namespace do Pod onde a regra é definida.

matchLabelKeys

ESTADO DA FUNCIONALIDADE: Kubernetes v1.33 [stable](habilitado por padrão)

Nota:

O campo matchLabelKeys é um campo de nível beta e está habilitado por padrão no Kubernetes 1.36. Quando você quiser desabilitá-lo, você deve desabilitá-lo explicitamente através do feature gate MatchLabelKeysInPodAffinity.

O Kubernetes inclui um campo opcional matchLabelKeys para afinidade ou antiafinidade de Pod. O campo especifica chaves para os rótulos que devem corresponder aos rótulos do Pod de entrada, ao satisfazer a (anti)afinidade de Pod.

As chaves são usadas para buscar valores dos rótulos do Pod; esses rótulos de chave-valor são combinados (usando AND) com as restrições de correspondência definidas usando o campo labelSelector. A filtragem combinada seleciona o conjunto de Pods existentes que será considerado no cálculo de (anti)afinidade de Pod.

Cuidado:

Não é recomendado usar matchLabelKeys com rótulos que possam ser atualizados diretamente nos pods. Mesmo se você editar o rótulo do pod que está especificado em matchLabelKeys diretamente (isto é, não através de um Deployment), o kube-apiserver não reflete a atualização do rótulo no labelSelector mesclado.

Um caso de uso comum é usar matchLabelKeys com pod-template-hash (definido em Pods gerenciados como parte de um Deployment, onde o valor é único para cada revisão). Usar pod-template-hash em matchLabelKeys permite selecionar os Pods que pertencem à mesma revisão que o Pod de entrada, para que uma atualização gradual não quebre a afinidade.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: application-server
...
spec:
  template:
    spec:
      affinity:
        podAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchExpressions:
              - key: app
                operator: In
                values:
                - database
            topologyKey: topology.kubernetes.io/zone
            # Apenas Pods de um determinado rollout são considerados ao calcular a afinidade de pod.
            # Se você atualizar o Deployment, os Pods substitutos seguem suas próprias regras de afinidade
            # (se houver alguma definida no novo template de Pod)
            matchLabelKeys:
            - pod-template-hash

mismatchLabelKeys

ESTADO DA FUNCIONALIDADE: Kubernetes v1.33 [stable](habilitado por padrão)

Nota:

O campo mismatchLabelKeys é um campo de nível beta e está habilitado por padrão no Kubernetes 1.36. Quando você quiser desabilitá-lo, você deve desabilitá-lo explicitamente através do feature gate MatchLabelKeysInPodAffinity.

O Kubernetes inclui um campo opcional mismatchLabelKeys para afinidade ou antiafinidade de Pod. O campo especifica chaves para os rótulos que não devem corresponder aos rótulos do Pod de entrada, ao satisfazer a (anti)afinidade de Pod.

Cuidado:

Não é recomendado usar mismatchLabelKeys com rótulos que possam ser atualizados diretamente nos pods. Mesmo se você editar o rótulo do pod que está especificado em mismatchLabelKeys diretamente (isto é, não através de um Deployment), o kube-apiserver não reflete a atualização do rótulo no labelSelector mesclado.

Um exemplo de caso de uso é garantir que os Pods vão para o domínio topológico (nó, zona, etc.) onde apenas Pods do mesmo locatário ou equipe são alocados. Em outras palavras, você quer evitar executar Pods de dois locatários diferentes no mesmo domínio topológico ao mesmo tempo.

apiVersion: v1
kind: Pod
metadata:
  labels:
    # Assume que todos os Pods relevantes têm um rótulo "tenant" definido
    tenant: tenant-a
...
spec:
  affinity:
    podAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
      # garante que os Pods associados a este locatário sejam alocados no pool de nós correto
      - matchLabelKeys:
          - tenant
        labelSelector: {}
        topologyKey: node-pool
    podAntiAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
      # garante que os Pods associados a este locatário não possam ser alocados em nós usados por outro locatário
      - mismatchLabelKeys:
        - tenant # qualquer que seja o valor do rótulo "tenant" para este Pod, impede
                 # a alocação em nós de qualquer pool onde qualquer Pod de um
                 # locatário diferente esteja em execução.
        labelSelector:
          # Precisamos ter o labelSelector que seleciona apenas Pods com o rótulo tenant,
          # caso contrário, este Pod teria antiafinidade contra Pods de DaemonSets também, por exemplo,
          # que não deveriam ter o rótulo tenant.
          matchExpressions:
          - key: tenant
            operator: Exists
        topologyKey: node-pool

Casos de uso mais práticos

Afinidade e antiafinidade entre Pods podem ser ainda mais úteis quando são usadas com coleções de nível superior, como ReplicaSets, StatefulSets, Deployments, etc. Essas regras permitem configurar que um conjunto de cargas de trabalho deve ser colocalizado na mesma topologia definida; por exemplo, preferindo alocar dois Pods relacionados no mesmo nó.

Por exemplo: imagine um cluster de três nós. Você usa o cluster para executar uma aplicação web e também um cache em memória (como Redis). Para este exemplo, assuma também que a latência entre a aplicação web e o cache em memória deve ser a mais baixa possível. Você poderia usar afinidade e antiafinidade entre Pods para colocalizar os servidores web com o cache tanto quanto possível.

No seguinte exemplo de Deployment para o cache Redis, as réplicas recebem o rótulo app=store. A regra podAntiAffinity diz ao escalonador para evitar alocar múltiplas réplicas com o rótulo app=store em um único nó. Isso cria cada cache em um nó separado.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: redis-cache
spec:
  selector:
    matchLabels:
      app: store
  replicas: 3
  template:
    metadata:
      labels:
        app: store
    spec:
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchExpressions:
              - key: app
                operator: In
                values:
                - store
            topologyKey: "kubernetes.io/hostname"
      containers:
      - name: redis-server
        image: redis:3.2-alpine

O seguinte exemplo de Deployment para os servidores web cria réplicas com o rótulo app=web-store. A regra de afinidade de Pod diz ao escalonador para alocar cada réplica em um nó que tenha um Pod com o rótulo app=store. A regra de antiafinidade de Pod diz ao escalonador para nunca alocar múltiplos servidores app=web-store em um único nó.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-server
spec:
  selector:
    matchLabels:
      app: web-store
  replicas: 3
  template:
    metadata:
      labels:
        app: web-store
    spec:
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchExpressions:
              - key: app
                operator: In
                values:
                - web-store
            topologyKey: "kubernetes.io/hostname"
        podAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchExpressions:
              - key: app
                operator: In
                values:
                - store
            topologyKey: "kubernetes.io/hostname"
      containers:
      - name: web-app
        image: nginx:1.16-alpine

Criar os dois Deployments anteriores resulta no seguinte layout de cluster, onde cada servidor web é colocalizado com um cache, em três nós separados.

node-1 node-2 node-3
webserver-1 webserver-2 webserver-3
cache-1 cache-2 cache-3

O efeito geral é que cada instância de cache provavelmente será acessada por um único cliente que está executando no mesmo nó. Esta abordagem visa minimizar tanto a assimetria (carga desbalanceada) quanto a latência.

Você pode ter outras razões para usar antiafinidade de Pod. Consulte o tutorial do ZooKeeper para um exemplo de um StatefulSet configurado com antiafinidade para alta disponibilidade, usando a mesma técnica deste exemplo.

nodeName

nodeName é uma forma mais direta de seleção de nó do que afinidade ou nodeSelector. nodeName é um campo na especificação do Pod. Se o campo nodeName não estiver vazio, o escalonador ignora o Pod e o kubelet no nó nomeado tenta alocar o Pod naquele nó. Usar nodeName sobrepõe o uso de nodeSelector ou regras de afinidade e antiafinidade.

Algumas das limitações de usar nodeName para selecionar nós são:

  • Se o nó nomeado não existir, o Pod não será executado e, em alguns casos, pode ser automaticamente excluído.
  • Se o nó nomeado não tiver os recursos para acomodar o Pod, o Pod falhará e seu motivo indicará o porquê, por exemplo OutOfmemory ou OutOfcpu.
  • Nomes de nós em ambientes de nuvem nem sempre são previsíveis ou estáveis.

Aviso:

nodeName é destinado para uso por escalonadores personalizados ou casos de uso avançados onde você precisa ignorar quaisquer escalonadores configurados. Ignorar os escalonadores pode levar a Pods com falha se os nós atribuídos ficarem sobrecarregados. Você pode usar afinidade de nó ou o campo nodeSelector para atribuir um Pod a um nó específico sem ignorar os escalonadores.

Aqui está um exemplo de uma especificação de Pod usando o campo nodeName:

apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  containers:
  - name: nginx
    image: nginx
  nodeName: kube-01

O Pod acima será executado apenas no nó kube-01.

nominatedNodeName

ESTADO DA FUNCIONALIDADE: Kubernetes v1.35 [beta](habilitado por padrão)

nominatedNodeName pode ser usado por componentes externos para nomear um nó para um pod pendente. Esta nomeação é de melhor esforço: ela pode ser ignorada se o escalonador determinar que o pod não pode ir para o nó nomeado.

Além disso, este campo pode ser escrito (ou sobrescrito) pelo escalonador:

  • Se o escalonador encontrar um nó para nomear através da preempção.
  • Se o escalonador decidir para onde o pod vai e movê-lo para o ciclo de binding.
    • Note que, neste caso, nominatedNodeName é definido apenas quando o pod precisa passar pelos pontos de extensão WaitOnPermit ou PreBind.

Aqui está um exemplo de um status de Pod usando o campo nominatedNodeName:

apiVersion: v1
kind: Pod
metadata:
  name: nginx
...
status:
  nominatedNodeName: kube-01

Restrições de distribuição de topologia de Pod

Você pode usar restrições de distribuição de topologia para controlar como os Pods são distribuídos pelo seu cluster entre domínios de falha como regiões, zonas, nós, ou entre quaisquer outros domínios de topologia que você definir. Você pode fazer isso para melhorar o desempenho, a disponibilidade esperada ou a utilização geral.

Leia Restrições de distribuição de topologia de Pod para saber mais sobre como elas funcionam.

Rótulos de topologia de Pod

ESTADO DA FUNCIONALIDADE: Kubernetes v1.35 [beta](habilitado por padrão)

Os Pods herdam os rótulos de topologia (topology.kubernetes.io/zone e topology.kubernetes.io/region) do nó atribuído se esses rótulos estiverem presentes. Esses rótulos podem então ser utilizados através da Downward API para fornecer à carga de trabalho a informação da topologia do nó.

Aqui está um exemplo de um Pod usando a Downward API para sua zona e região:

apiVersion: v1
kind: Pod
metadata:
  name: pod-with-topology-labels
spec:
  containers:
    - name: app
      image: alpine
      command: ["sh", "-c", "env"]
      env:
        - name: MY_ZONE
          valueFrom:
            fieldRef:
              fieldPath: metadata.labels['topology.kubernetes.io/zone']
        - name: MY_REGION
          valueFrom:
            fieldRef:
              fieldPath: metadata.labels['topology.kubernetes.io/region']

Operadores

A seguir estão todos os operadores lógicos que você pode usar no campo operator para nodeAffinity e podAffinity mencionados acima.

Operador Comportamento
In O valor do rótulo está presente no conjunto de strings fornecido
NotIn O valor do rótulo não está contido no conjunto de strings fornecido
Exists Um rótulo com esta chave existe no objeto
DoesNotExist Nenhum rótulo com esta chave existe no objeto

Os seguintes operadores só podem ser usados com nodeAffinity.

Operador Comportamento
Gt O valor do campo será interpretado como um inteiro, e o inteiro resultante da interpretação do valor de um rótulo nomeado por este seletor é maior que esse inteiro
Lt O valor do campo será interpretado como um inteiro, e o inteiro resultante da interpretação do valor de um rótulo nomeado por este seletor é menor que esse inteiro

Nota:

Os operadores Gt e Lt não funcionarão com valores não inteiros. Se o valor fornecido não puder ser interpretado como um inteiro, o Pod não conseguirá ser alocado. Além disso, Gt e Lt não estão disponíveis para podAffinity.

Próximos passos

3 - Taints e Tolerâncias

Afinidade de nó é uma propriedade dos Pods que os associa a um conjunto de nós (seja como uma preferência ou uma exigência). Taints são o oposto -- eles permitem que um nó repudie um conjunto de pods.

Tolerâncias são aplicadas em pods e permitem, mas não exigem, que os pods sejam alocados em nós com taints correspondentes.

Taints e tolerâncias trabalham juntos para garantir que pods não sejam alocados em nós inapropriados. Um ou mais taints são aplicados em um nó; isso define que o nó não deve aceitar nenhum pod que não tolera essas taints.

Conceitos

Você adiciona um taint a um nó utilizando kubectl taint. Por exemplo,

kubectl taint nodes node1 key1=value1:NoSchedule

define um taint no nó node1. O taint tem a chave key1, valor value1 e o efeito NoSchedule. Isso significa que nenhum pod conseguirá ser executado no nó node1 a menos que possua uma tolerância correspondente.

Para remover o taint adicionado pelo comando acima, você pode executar:

kubectl taint nodes node1 key1=value1:NoSchedule-

Você especifica uma tolerância para um pod na especificação do Pod. Ambas as seguintes tolerâncias "correspondem" ao taint criado pelo kubectl taint acima, e assim um pod com qualquer uma delas poderia ser executado no node1:

tolerations:
- key: "key1"
  operator: "Equal"
  value: "value1"
  effect: "NoSchedule"
tolerations:
- key: "key1"
  operator: "Exists"
  effect: "NoSchedule"

Aqui está um exemplo de um pod que utiliza tolerâncias:

apiVersion: v1
kind: Pod
metadata:
  name: nginx
  labels:
    env: test
spec:
  containers:
  - name: nginx
    image: nginx
    imagePullPolicy: IfNotPresent
  tolerations:
  - key: "example-key"
    operator: "Exists"
    effect: "NoSchedule"

O valor padrão de operator é Equal.

Uma tolerância "casa" um taint se as chaves e efeitos são os mesmos, e:

  • o valor de operator é Exists (no caso nenhum value deve ser especificado), ou
  • o valor de operator é Equal e os valores de value são iguais.

Nota:

Existem dois casos especiais:

Uma key vazia com o operador Exists "casa" todas as chaves, valores e efeitos, o que significa que o pod irá tolerar tudo.

Um effect vazio "casa" todos os efeitos com a chave key1.

O exemplo acima usou effect de NoSchedule. De forma alternativa, você pode usar effect de PreferNoSchedule. Nesse efeito, o sistema tentará evitar que o pod seja alocado ao nó caso ele não tolere os taints definidos, contudo a alocação não será evitada de forma obrigatória. Pode-se dizer que o PreferNoSchedule é uma versão permissiva do NoSchedule. O terceiro tipo de effect é o NoExecute que será descrito posteriormente.

Você pode colocar múltiplos taints no mesmo nó e múltiplas tolerâncias no mesmo pod. O jeito que o Kubernetes processa múltiplos taints e tolerâncias é como um filtro: começa com todos os taints de um nó, em seguida ignora aqueles para os quais o pod tem uma tolerância relacionada; os taints restantes que não foram ignorados indicam o efeito no pod. Mais especificamente,

  • se existe pelo menos um taint não tolerado com o efeito NoSchedule, o Kubernetes não alocará o pod naquele nó
  • se existe um taint não tolerado com o efeito NoSchedule, mas existe pelo menos um taint não tolerado com o efeito PreferNoSchedule, o Kubernetes tentará não alocar o pod no nó
  • se existe pelo menos um taint não tolerado com o efeito NoExecute, o pod será expulso do nó (caso já esteja em execução) e não será alocado ao nó (caso ainda não esteja em execução).

Por exemplo, imagine que você tem um nó com os seguintes taints

kubectl taint nodes node1 key1=value1:NoSchedule
kubectl taint nodes node1 key1=value1:NoExecute
kubectl taint nodes node1 key2=value2:NoSchedule

E um pod com duas tolerâncias:

tolerations:
- key: "key1"
  operator: "Equal"
  value: "value1"
  effect: "NoSchedule"
- key: "key1"
  operator: "Equal"
  value: "value1"
  effect: "NoExecute"

Nesse caso, o pod não será alocado ao nó porque não possui uma tolerância para o terceiro taint. Porém, se ele já estiver rodando no nó quando o taint foi adicionado, não será afetado e continuará rodando, tendo em vista que o terceiro taint é o único não tolerado pelo pod.

Normalmente, se um taint com o efeito NoExecute é adicionado a um nó, qualquer pod que não o tolere será expulso imediatamente e pods que o toleram nunca serão expulsos. Contudo, uma tolerância com efeito NoExecute pode especificar de forma opcional o campo tolerationSeconds, que determina quanto tempo o pod continuará alocado ao nó depois que o taint é adicionado. Por exemplo,

tolerations:
- key: "key1"
  operator: "Equal"
  value: "value1"
  effect: "NoExecute"
  tolerationSeconds: 3600

significa que se esse pod está sendo executado e um taint correspondente é adicionado ao nó, o pod irá continuar rodando neste nó por 3600 segundos e depois será expulso. Se o taint for removido antes desse tempo acabar, o pod não será expulso.

Exemplos de Casos de Uso

Taints e tolerâncias são um modo flexível de conduzir pods para fora dos nós ou expulsar pods que não deveriam estar sendo executados. Alguns casos de uso são

  • Nós Dedicados: Se você quiser dedicar um conjunto de nós para uso exclusivo de um conjunto específico de usuários, poderá adicionar um taint nesses nós. (digamos, kubectl taint nodes nodename dedicated=groupName:NoSchedule) e em seguida adicionar uma tolerância correspondente para seus pods (isso seria feito mais facilmente com a escrita de um controlador de admissão customizado). Os pods com tolerância terão sua execução permitida nos nós com taints (dedicados), assim como em qualquer outro nó no cluster. Se você quiser dedicar nós a esses pods e garantir que eles usem apenas os nós dedicados, precisará adicionar uma label similar ao taint para o mesmo conjunto de nós (por exemplo, dedicated=groupName), e o controle de admissão deverá adicionar uma afinidade de nó para exigir que os pods podem ser executados apenas nos nós definidos com a label dedicated=groupName.

  • Nós com hardware especial: Em um cluster no qual um pequeno grupo de nós possui hardware especializado (por exemplo, GPUs), é desejável manter pods que não necessitem desse tipo de hardware fora desses nós, dessa forma o recurso estará disponível para pods que precisem do hardware especializado. Isso pode ser feito aplicando taints nos nós com o hardware especializado (por exemplo, kubectl taint nodes nodename special=true:NoSchedule or kubectl taint nodes nodename special=true:PreferNoSchedule) e aplicando uma tolerância correspondente nos pods que usam o hardware especial. Assim como no caso de uso de nós dedicados, é provavelmente mais fácil aplicar as tolerâncias utilizando um controlador de admissão. Por exemplo, é recomendado usar Extended Resources para representar hardware especial, adicione um taint ao seus nós de hardware especializado com o nome do recurso estendido e execute o controle de admissão ExtendedResourceToleration. Agora, tendo em vista que os nós estão marcados com um taint, nenhum pod sem a tolerância será executado neles. Porém, quando você submete um pod que requisita o recurso estendido, o controlador de admissão ExtendedResourceToleration irá adicionar automaticamente as tolerâncias necessárias ao pod que irá, por sua vez, ser alocado no nó com hardware especial. Isso garantirá que esses nós de hardware especial serão dedicados para os pods que requisitarem tal recurso e você não precisará adicionar manualmente as tolerâncias aos seus pods.

  • Expulsões baseadas em Taint: Um comportamento de expulsão configurada por pod quando problemas existem em um nó, o qual será descrito na próxima seção.

Expulsões baseadas em Taint

ESTADO DA FUNCIONALIDADE: Kubernetes v1.18 [stable]

O efeito de taint NoExecute, mencionado acima, afeta pods que já estão rodando no nó da seguinte forma

  • pods que não toleram o taint são expulsos imediatamente
  • pods que toleram o taint sem especificar tolerationSeconds em sua especificação de tolerância, ficam alocados para sempre
  • pods que toleram o taint com um tolerationSeconds especificado, permanecem alocados pela quantidade de tempo definida

O controlador de nó automaticamente adiciona um taint ao Nó quando certas condições se tornam verdadeiras. Os seguintes taints são embutidos:

  • node.kubernetes.io/not-ready: Nó não está pronto. Isso corresponde ao NodeCondition Ready com o valor "False".
  • node.kubernetes.io/unreachable: Nó é inalcançável a partir do controlador de nó. Isso corresponde ao NodeCondition Ready com o valor "Unknown".
  • node.kubernetes.io/memory-pressure: Nó possui pressão de memória.
  • node.kubernetes.io/disk-pressure: Nó possui pressão de disco.
  • node.kubernetes.io/pid-pressure: Nó possui pressão de PID.
  • node.kubernetes.io/network-unavailable: A rede do nó está indisponível.
  • node.kubernetes.io/unschedulable: Nó não é alocável.
  • node.cloudprovider.kubernetes.io/uninitialized: Quando o kubelet é iniciado com um provedor de nuvem "externo", esse taint é adicionado ao nó para que ele seja marcado como não utilizável. Após o controlador do cloud-controller-manager inicializar o nó, o kubelet remove esse taint.

No caso de um nó estar prestes a ser expulso, o controlador de nó ou kubelet adicionam os taints relevantes com o efeito NoExecute. Se a condição de falha retorna ao normal, o kubelet ou controlador de nó podem remover esses taints.

Nota:

A camada de gerenciamento limita a taxa de adição de novos taints aos nós. Esse limite gerencia o número de expulsões que são disparadas quando muitos nós se tornam inalcançáveis ao mesmo tempo (por exemplo: se ocorre uma falha na rede).

Você pode especificar tolerationSeconds em um Pod para definir quanto tempo ele ficará alocado em um nó que está falhando ou está sem resposta.

Por exemplo, você talvez queira manter uma aplicação com vários estados salvos localmente alocado em um nó por um longo período na ocorrência de uma divisão na rede, esperando que essa divisão se recuperará e assim a expulsão do pod pode ser evitada. A tolerância que você define para esse Pod poderia ficar assim:

tolerations:
- key: "node.kubernetes.io/unreachable"
  operator: "Exists"
  effect: "NoExecute"
  tolerationSeconds: 6000

Nota:

O Kubernetes automaticamente adiciona uma tolerância para node.kubernetes.io/not-ready e node.kubernetes.io/unreachable com tolerationSeconds=300, a menos que você, ou um controlador, defina essas tolerâncias explicitamente.

Essas tolerâncias adicionadas automaticamente significam que Pods podem continuar alocados aos Nós por 5 minutos após um desses problemas ser detectado.

Pods do tipo DaemonSet são criados com tolerâncias NoExecute sem a propriedade tolerationSeconds para os seguintes taints:

  • node.kubernetes.io/unreachable
  • node.kubernetes.io/not-ready

Isso garante que esses pods do DaemonSet nunca sejam expulsos por conta desses problemas.

Taints por condições de nó

A camada de gerenciamento, usando o controlador do nó, cria taints automaticamente com o efeito NoSchedule para condições de nó.

O agendador verifica taints, não condições de nó, quando realiza suas decisões de agendamento. Isso garante que as condições de nó não afetem diretamente o agendamento. Por exemplo, se a condição de nó DiskPressure está ativa, a camada de gerenciamento adiciona o taint node.kubernetes.io/disk-pressure e não aloca novos pods no nó afetado. Se a condição MemoryPressure está ativa, a camada de gerenciamento adiciona o taint node.kubernetes.io/memory-pressure.

Você pode ignorar condições de nó para pods recém-criados adicionando tolerâncias correspondentes. A camada de controle também adiciona a tolerância node.kubernetes.io/memory-pressure em pods que possuem uma classe de QoS diferente de BestEffort. Isso ocorre porque o Kubernetes trata pods nas classes de QoS Guaranteed ou Burstable (até mesmo pods sem requisitos de memória definidos) como se fossem capazes de lidar com pressão de memória, enquanto novos pods com BestEffort não são alocados no nó afetado.

O controlador DaemonSet adiciona automaticamente as seguintes tolerâncias de NoSchedule para todos os daemons, prevenindo que DaemonSets quebrem.

  • node.kubernetes.io/memory-pressure
  • node.kubernetes.io/disk-pressure
  • node.kubernetes.io/pid-pressure (1.14 ou superior)
  • node.kubernetes.io/unschedulable (1.10 ou superior)
  • node.kubernetes.io/network-unavailable (somente rede do host)

Adicionando essas tolerâncias garante retro compatibilidade. Você também pode adicionar tolerâncias de forma arbitrária aos DaemonSets.

Próximos passos

4 - Sobrecarga de Pod

ESTADO DA FUNCIONALIDADE: Kubernetes v1.18 [beta]

Quando você executa um Pod num nó, o próprio Pod usa uma quantidade de recursos do sistema. Estes recursos são adicionais aos recursos necessários para executar o(s) contêiner(s) dentro do Pod. Sobrecarga de Pod, do inglês Pod Overhead, é uma funcionalidade que serve para contabilizar os recursos consumidos pela infraestrutura do Pod para além das solicitações e limites do contêiner.

No Kubernetes, a sobrecarga de Pods é definido no tempo de admissão de acordo com a sobrecarga associada à RuntimeClass do Pod.

Quando é ativada a Sobrecarga de Pod, a sobrecarga é considerada adicionalmente à soma das solicitações de recursos do contêiner ao agendar um Pod. Semelhantemente, o kubelet incluirá a sobrecarga do Pod ao dimensionar o cgroup do Pod e ao executar a classificação de prioridade de migração do Pod em caso de drain do Node.

Habilitando a Sobrecarga de Pod

Terá de garantir que o Feature Gate PodOverhead esteja ativo (está ativo por padrão a partir da versão 1.18) em todo o cluster, e uma RuntimeClass utilizada que defina o campo overhead.

Exemplo de uso

Para usar a funcionalidade PodOverhead, é necessário uma RuntimeClass que define o campo overhead. Por exemplo, poderia usar a definição da RuntimeClass abaixo com um agente de execução de contêiner virtualizado que use cerca de 120MiB por Pod para a máquina virtual e o sistema operacional convidado:

---
kind: RuntimeClass
apiVersion: node.k8s.io/v1beta1
metadata:
    name: kata-fc
handler: kata-fc
overhead:
    podFixed:
        memory: "120Mi"
        cpu: "250m"

As cargas de trabalho que são criadas e que especificam o manipulador RuntimeClass kata-fc irão usar a sobrecarga de memória e cpu em conta para os cálculos da quota de recursos, agendamento de nós, assim como dimensionamento do cgroup do Pod.

Considere executar a seguinte carga de trabalho de exemplo, test-pod:

apiVersion: v1
kind: Pod
metadata:
  name: test-pod
spec:
  runtimeClassName: kata-fc
  containers:
  - name: busybox-ctr
    image: busybox
    stdin: true
    tty: true
    resources:
      limits:
        cpu: 500m
        memory: 100Mi
  - name: nginx-ctr
    image: nginx
    resources:
      limits:
        cpu: 1500m
        memory: 100Mi

No tempo de admissão o controlador de admissão RuntimeClass atualiza o PodSpec da carga de trabalho de forma a incluir o overhead como descrito na RuntimeClass. Se o PodSpec já tiver este campo definido o Pod será rejeitado. No exemplo dado, como apenas o nome do RuntimeClass é especificado, o controlador de admissão muda o Pod de forma a incluir um overhead.

Depois do controlador de admissão RuntimeClass, pode verificar o PodSpec atualizado:

kubectl get pod test-pod -o jsonpath='{.spec.overhead}'

A saída é:

map[cpu:250m memory:120Mi]

Se for definido um ResourceQuota, a soma das requisições dos contêineres assim como o campo overhead são contados.

Quando o kube-scheduler está decidindo que nó deve executar um novo Pod, o agendador considera o overhead do pod, assim como a soma de pedidos aos contêineres para esse Pod. Para este exemplo, o agendador adiciona as requisições e a sobrecarga, depois procura um nó com 2.25 CPU e 320 MiB de memória disponível.

Assim que um Pod é agendado a um nó, o kubelet nesse nó cria um novo cgroup para o Pod. É dentro deste Pod que o agente de execução de contêiners subjacente vai criar contêineres.

Se o recurso tiver um limite definido para cada contêiner (QoS garantida ou Burstrable QoS com limites definidos), o kubelet definirá um limite superior para o cgroup do Pod associado a esse recurso (cpu.cfs_quota_us para CPU e memory.limit_in_bytes de memória). Este limite superior é baseado na soma dos limites do contêiner mais o overhead definido no PodSpec.

Para CPU, se o Pod for QoS garantida ou Burstrable QoS, o kubelet vai definir cpu.shares baseado na soma dos pedidos ao contêiner mais o overhead definido no PodSpec.

Olhando para o nosso exemplo, verifique as requisições ao contêiner para a carga de trabalho:

kubectl get pod test-pod -o jsonpath='{.spec.containers[*].resources.limits}'

O total de requisições ao contêiner são 2000m CPU e 200MiB de memória:

map[cpu: 500m memory:100Mi] map[cpu:1500m memory:100Mi]

Verifique isto comparado ao que é observado pelo nó:

kubectl describe node | grep test-pod -B2

A saída mostra que 2250m CPU e 320MiB de memória são solicitados, que inclui PodOverhead:

  Namespace                   Name                CPU Requests  CPU Limits   Memory Requests  Memory Limits  AGE
  ---------                   ----                ------------  ----------   ---------------  -------------  ---
  default                     test-pod            2250m (56%)   2250m (56%)  320Mi (1%)       320Mi (1%)     36m

Verificar os limites cgroup do Pod

Verifique os cgroups de memória do Pod no nó onde a carga de trabalho está em execução. No seguinte exemplo, crictl é usado no nó, que fornece uma CLI para agentes de execução compatíveis com CRI. Isto é um exemplo avançado para mostrar o comportamento do PodOverhead, e não é esperado que os usuários precisem verificar cgroups diretamente no nó.

Primeiro, no nó em particular, determine o identificador do Pod:

# Execute no nó onde o Pod está agendado
POD_ID="$(sudo crictl pods --name test-pod -q)"

A partir disto, pode determinar o caminho do cgroup para o Pod:

# Execute no nó onde o Pod está agendado
sudo crictl inspectp -o=json $POD_ID | grep cgroupsPath

O caminho do cgroup resultante inclui o contêiner pause do Pod. O cgroup no nível do Pod está um diretório acima.

        "cgroupsPath": "/kubepods/podd7f4b509-cf94-4951-9417-d1087c92a5b2/7ccf55aee35dd16aca4189c952d83487297f3cd760f1bbf09620e206e7d0c27a"

Neste caso especifico, o caminho do cgroup do Pod é kubepods/podd7f4b509-cf94-4951-9417-d1087c92a5b2. Verifique a configuração cgroup de nível do Pod para a memória:

# Execute no nó onde o Pod está agendado
# Mude também o nome do cgroup para combinar com o cgroup alocado ao Pod.
 cat /sys/fs/cgroup/memory/kubepods/podd7f4b509-cf94-4951-9417-d1087c92a5b2/memory.limit_in_bytes

Isto é 320 MiB, como esperado:

335544320

Observabilidade

Uma métrica kube_pod_overhead está disponível em kube-state-metrics para ajudar a identificar quando o PodOverhead está sendo utilizado e para ajudar a observar a estabilidade das cargas de trabalho em execução com uma sobrecarga (Overhead) definida. Esta funcionalidade não está disponível na versão 1.9 do kube-state-metrics, mas é esperado em uma próxima versão. Os usuários necessitarão entretanto construir o kube-state-metrics a partir do código fonte.

Próximos passos

5 - Prioridade de Pod e Preempção

ESTADO DA FUNCIONALIDADE: Kubernetes v1.14 [stable]

Pods podem ter prioridade. A prioridade indica a importância de um Pod em relação a outros Pods. Se um Pod não puder ser alocado, o escalonador tenta realizar a preempção (remoção) de Pods de menor prioridade para tornar possível a alocação do Pod pendente.

Aviso:

Em um cluster onde nem todos os usuários são confiáveis, um usuário mal-intencionado poderia criar Pods com as maiores prioridades possíveis, fazendo com que outros Pods sejam removidos ou não consigam ser alocados. Um administrador pode usar ResourceQuota para impedir que usuários criem Pods com prioridades altas.

Veja limitar o consumo de PriorityClass por padrão para mais detalhes.

Como usar prioridade e preempção

Para usar prioridade e preempção:

  1. Adicione uma ou mais PriorityClasses.

  2. Crie Pods com priorityClassName definido como uma das PriorityClasses adicionadas. Obviamente, você não precisa criar os Pods diretamente; normalmente você adicionaria priorityClassName ao template do Pod de um objeto de coleção como um Deployment.

Continue lendo para mais informações sobre essas etapas.

Nota:

O Kubernetes já inclui duas PriorityClasses: system-cluster-critical e system-node-critical. Essas são classes comuns e são usadas para garantir que componentes críticos sejam sempre alocados primeiro.

PriorityClass

Uma PriorityClass é um objeto sem namespace que define um mapeamento entre o nome de uma classe de prioridade e o valor inteiro da prioridade. O nome é especificado no campo name dos metadados do objeto PriorityClass. O valor é especificado no campo obrigatório value. Quanto maior o valor, maior a prioridade. O nome de um objeto PriorityClass deve ser um nome de subdomínio DNS válido, e não pode ser prefixado com system-.

Um objeto PriorityClass pode ter qualquer valor inteiro de 32 bits menor ou igual a 1 bilhão. Isso significa que o intervalo de valores para um objeto PriorityClass é de -2147483648 a 1000000000, inclusive. Números maiores são reservados para PriorityClasses embutidas que representam Pods críticos do sistema. Um administrador do cluster deve criar um objeto PriorityClass para cada mapeamento desejado.

PriorityClass também possui dois campos opcionais: globalDefault e description. O campo globalDefault indica que o valor desta PriorityClass deve ser usado para Pods sem priorityClassName. Apenas uma PriorityClass com globalDefault definido como true pode existir no sistema. Se não houver PriorityClass com globalDefault definido, a prioridade dos Pods sem priorityClassName será zero.

O campo description é uma string arbitrária. Ele serve para informar os usuários do cluster sobre quando devem usar esta PriorityClass.

Observações sobre PodPriority e clusters existentes

  • Se você atualizar um cluster existente sem essa funcionalidade, a prioridade dos seus Pods existentes será efetivamente zero.

  • A adição de uma PriorityClass com globalDefault definido como true não altera as prioridades dos Pods existentes. O valor dessa PriorityClass é usado apenas para Pods criados após a adição da PriorityClass.

  • Se você excluir uma PriorityClass, os Pods existentes que usam o nome da PriorityClass excluída permanecem inalterados, mas você não poderá criar mais Pods que usem o nome da PriorityClass excluída.

Exemplo de PriorityClass

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

PriorityClass sem preempção

ESTADO DA FUNCIONALIDADE: Kubernetes v1.24 [stable]

Pods com preemptionPolicy: Never serão colocados na fila de alocação à frente de Pods de menor prioridade, mas não podem remover outros Pods por preempção. Um Pod sem preempção aguardando alocação permanecerá na fila de alocação até que recursos suficientes estejam livres e ele possa ser alocado. Pods sem preempção, assim como outros Pods, estão sujeitos ao back-off do escalonador. Isso significa que, se o escalonador tentar alocar esses Pods e eles não puderem ser alocados, eles serão tentados novamente com menor frequência, permitindo que outros Pods com menor prioridade sejam alocados antes deles.

Pods sem preempção ainda podem ser removidos por preempção por outros Pods de alta prioridade.

O valor padrão do campo preemptionPolicy é PreemptLowerPriority, que permitirá que Pods dessa PriorityClass removam por preempção Pods de menor prioridade (como é o comportamento padrão existente). Se preemptionPolicy for definido como Never, os Pods dessa PriorityClass serão Pods sem preempção.

Um exemplo de caso de uso é para cargas de trabalho de ciência de dados. Um usuário pode enviar uma tarefa que deseja que seja priorizada acima de outras cargas de trabalho, mas não deseja descartar o trabalho existente removendo por preempção Pods em execução. A tarefa de alta prioridade com preemptionPolicy: Never será alocada à frente de outros Pods na fila, assim que recursos suficientes do cluster ficarem "naturalmente" livres.

Exemplo de PriorityClass sem preempção

apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
  name: high-priority-nonpreempting
value: 1000000
preemptionPolicy: Never
globalDefault: false
description: "This priority class will not cause other pods to be preempted."

Prioridade de Pod

Depois de ter uma ou mais PriorityClasses, você pode criar Pods que especifiquem um desses nomes de PriorityClass em suas especificações. O controlador de admissão de prioridade usa o campo priorityClassName e preenche o valor inteiro da prioridade. Se a classe de prioridade não for encontrada, o Pod é rejeitado.

O YAML a seguir é um exemplo de configuração de Pod que usa a PriorityClass criada no exemplo anterior. O controlador de admissão de prioridade verifica a especificação e resolve a prioridade do Pod para 1000000.

apiVersion: v1
kind: Pod
metadata:
  name: nginx
  labels:
    env: test
spec:
  containers:
  - name: nginx
    image: nginx
    imagePullPolicy: IfNotPresent
  priorityClassName: high-priority

Efeito da prioridade de Pod na ordem de alocação

Quando a prioridade de Pod está habilitada, o escalonador ordena os Pods pendentes por sua prioridade, e um Pod pendente é colocado à frente de outros Pods pendentes com menor prioridade na fila de alocação. Como resultado, o Pod de maior prioridade pode ser alocado antes dos Pods com menor prioridade, se seus requisitos de alocação forem atendidos. Se tal Pod não puder ser alocado, o escalonador continuará e tentará alocar outros Pods de menor prioridade.

Preempção

Quando os Pods são criados, eles entram em uma fila e aguardam para serem alocados. O escalonador seleciona um Pod da fila e tenta alocá-lo em um nó. Se nenhum nó for encontrado que satisfaça todos os requisitos especificados do Pod, a lógica de preempção é acionada para o Pod pendente. Vamos chamar o Pod pendente de P. A lógica de preempção tenta encontrar um nó onde a remoção de um ou mais Pods com prioridade menor que P permitiria que P fosse alocado nesse nó. Se tal nó for encontrado, um ou mais Pods de menor prioridade são removidos do nó. Após a remoção dos Pods, P pode ser alocado no nó.

Informações expostas ao usuário

Quando o Pod P remove por preempção um ou mais Pods no nó N, o campo nominatedNodeName do status do Pod P é definido com o nome do nó N. Este campo ajuda o escalonador a rastrear recursos reservados para o Pod P e também fornece aos usuários informações sobre remoções por preempção em seus clusters.

Observe que o Pod P não é necessariamente alocado no "nó nomeado". O escalonador sempre tenta o "nó nomeado" antes de iterar sobre quaisquer outros nós. Após os Pods alvo serem removidos por preempção, eles recebem seu período de encerramento controlado. Se outro nó ficar disponível enquanto o escalonador aguarda o encerramento dos Pods alvo, o escalonador pode usar o outro nó para alocar o Pod P. Como resultado, nominatedNodeName e nodeName da especificação do Pod nem sempre são iguais. Além disso, se o escalonador remover por preempção Pods no nó N, mas então um Pod de prioridade maior que o Pod P chegar, o escalonador pode atribuir o nó N ao novo Pod de maior prioridade. Nesse caso, o escalonador limpa o nominatedNodeName do Pod P. Ao fazer isso, o escalonador torna o Pod P elegível para remover por preempção Pods em outro nó.

Limitações da preempção

Encerramento controlado dos alvos de preempção

Quando os Pods são removidos por preempção, os alvos recebem seu período de encerramento controlado. Elas têm esse tempo para finalizar seu trabalho e encerrar. Se não o fizerem, são finalizadas. Esse período de encerramento controlado cria um intervalo de tempo entre o ponto em que o escalonador remove os Pods por preempção e o momento em que o Pod pendente (P) pode ser alocado no nó (N). Enquanto isso, o escalonador continua alocando outros Pods pendentes. À medida que os alvos encerram ou são finalizadas, o escalonador tenta alocar Pods na fila de pendentes. Portanto, geralmente há um intervalo de tempo entre o ponto em que o escalonador remove os alvos por preempção e o momento em que o Pod P é alocado. Para minimizar esse intervalo, pode-se definir o período de encerramento controlado dos Pods de menor prioridade como zero ou um número pequeno.

PodDisruptionBudget é suportado, mas não garantido

Um PodDisruptionBudget (PDB) permite que proprietários de aplicações limitem o número de Pods de uma aplicação replicada que ficam indisponíveis simultaneamente por interrupções voluntárias. O Kubernetes suporta PDB ao remover Pods por preempção, mas o respeito ao PDB é feito com base no melhor esforço. O escalonador tenta encontrar alvos cujo PDB não seja violado pela preempção, mas se nenhum alvo assim for encontrada, a preempção ainda ocorrerá, e os Pods de menor prioridade serão removidos apesar de seus PDBs serem violados.

Afinidade entre Pods em Pods de menor prioridade

Um nó é considerado para preempção somente quando a resposta a esta pergunta for sim: "Se todos os Pods com prioridade menor que o Pod pendente forem removidos do nó, o Pod pendente pode ser alocado no nó?"

Nota:

A preempção não remove necessariamente todos os Pods de menor prioridade. Se o Pod pendente puder ser alocado removendo apenas parte dos Pods de menor prioridade, então somente uma parte dos Pods de menor prioridade será removida. Mesmo assim, a resposta à pergunta anterior deve ser sim. Se a resposta for não, o nó não é considerado para preempção.

Se um Pod pendente tiver afinidade entre Pods com um ou mais dos Pods de menor prioridade no nó, a regra de afinidade entre Pods não poderá ser satisfeita na ausência desses Pods de menor prioridade. Nesse caso, o escalonador não remove nenhum Pod do nó por preempção. Em vez disso, ele procura outro nó. O escalonador pode ou não encontrar um nó adequado. Não há garantia de que o Pod pendente possa ser alocado.

A solução recomendada para este problema é criar afinidade entre Pods apenas com Pods de prioridade igual ou superior.

Preempção entre nós

Suponha que o nó N esteja sendo considerado para preempção para que um Pod pendente P possa ser alocado no nó N. P pode se tornar viável no nó N somente se um Pod em outro nó for removido por preempção. Aqui está um exemplo:

  • O Pod P está sendo considerado para o nó N.
  • O Pod Q está em execução em outro nó na mesma zona que o nó N.
  • O Pod P tem anti-afinidade em nível de zona com o Pod Q (topologyKey: topology.kubernetes.io/zone).
  • Não há outros casos de anti-afinidade entre o Pod P e outros Pods na zona.
  • Para alocar o Pod P no nó N, o Pod Q poderia ser removido por preempção, mas o escalonador não realiza preempção entre nós. Portanto, o Pod P será considerado não alocável no nó N.

Se o Pod Q fosse removido de seu nó, a violação de anti-afinidade de Pod seria eliminada, e o Pod P poderia possivelmente ser alocado no nó N.

A adição de preempção entre nós poderá ser considerada em versões futuras, se houver demanda suficiente e se for encontrado um algoritmo com desempenho razoável.

Solução de problemas

A prioridade e preempção de Pods podem ter efeitos colaterais indesejados. Aqui estão alguns exemplos de problemas potenciais e formas de lidar com eles.

Pods são removidos por preempção desnecessariamente

A preempção remove Pods existentes de um cluster sob pressão de recursos para abrir espaço para Pods pendentes de maior prioridade. Se você atribuir prioridades altas a certos Pods por engano, esses Pods com prioridade alta não intencional podem causar preempção em seu cluster. A prioridade do Pod é especificada definindo o campo priorityClassName na especificação do Pod. O valor inteiro da prioridade é então resolvido e preenchido no campo priority do podSpec.

Para resolver o problema, você pode alterar o priorityClassName desses Pods para usar classes de prioridade mais baixas, ou deixar o campo vazio. Um priorityClassName vazio é resolvido como zero por padrão.

Quando um Pod é removido por preempção, eventos serão registrados para o Pod removido. A preempção deve ocorrer somente quando um cluster não possui recursos suficientes para um Pod. Nesses casos, a preempção acontece somente quando a prioridade do Pod pendente (que iniciou a preempção) é maior que a dos Pods alvo. A preempção não deve ocorrer quando não há Pod pendente, ou quando os Pods pendentes têm prioridade igual ou menor que os alvos. Se a preempção ocorrer nesses cenários, por favor registre uma issue.

Pods são removidos por preempção, mas o Pod que iniciou a preempção não é alocado

Quando os Pods são removidos por preempção, eles recebem o período de encerramento controlado solicitado, que é de 30 segundos por padrão. Se os Pods alvo não encerrarem dentro desse período, eles são finalizados à força. Uma vez que todas os alvos sejam removidas, o Pod que iniciou a preempção pode ser alocado.

Enquanto o Pod que iniciou a preempção aguarda a remoção dos alvos, um Pod de maior prioridade pode ser criado e caber no mesmo nó. Nesse caso, o escalonador alocará o Pod de maior prioridade em vez do que iniciou a preempção.

Este é o comportamento esperado: o Pod com maior prioridade deve tomar o lugar de um Pod com menor prioridade.

Pods de maior prioridade são removidos por preempção antes dos Pods de menor prioridade

O escalonador tenta encontrar nós que possam executar um Pod pendente. Se nenhum nó for encontrado, o escalonador tenta remover Pods de menor prioridade de um nó arbitrário para abrir espaço para o Pod pendente. Se um nó com Pods de baixa prioridade não for viável para executar o Pod pendente, o escalonador pode escolher outro nó com Pods de maior prioridade (em comparação com os Pods no outro nó) para preempção. Os alvos ainda devem ter prioridade menor que o Pod que iniciou a preempção.

Quando há múltiplos nós disponíveis para preempção, o escalonador tenta escolher o nó com o conjunto de Pods de menor prioridade. No entanto, se tais Pods tiverem PodDisruptionBudget que seria violado caso sejam removidos por preempção, então o escalonador pode escolher outro nó com Pods de maior prioridade.

Quando múltiplos nós existem para preempção e nenhum dos cenários acima se aplica, o escalonador escolhe o nó com a menor prioridade.

Interações entre prioridade de Pod e qualidade de serviço

A prioridade de Pod e a classe de QoS são duas funcionalidades ortogonais com poucas interações e sem restrições padrão na definição da prioridade de um Pod com base em suas classes de QoS. A lógica de preempção do escalonador não considera QoS ao escolher alvos de preempção. A preempção considera a prioridade do Pod e tenta escolher um conjunto de alvos com a menor prioridade. Pods de maior prioridade são considerados para preempção somente se a remoção dos Pods de menor prioridade não for suficiente para permitir que o escalonador aloque o Pod que iniciou a preempção, ou se os Pods de menor prioridade estiverem protegidos por PodDisruptionBudget.

O kubelet usa a prioridade para determinar a ordem dos Pods para remoção por pressão de nó. Você pode usar a classe de QoS para estimar a ordem em que os Pods têm maior probabilidade de serem removidos. O kubelet classifica os Pods para remoção com base nos seguintes fatores:

  1. Se o uso do recurso escasso excede as requisições
  2. Prioridade do Pod
  3. Quantidade de uso de recurso em relação às requisições

Veja Seleção de Pods para remoção pelo kubelet para mais detalhes.

A remoção por pressão de nó do kubelet não remove Pods quando seu uso não excede suas requisições. Se um Pod com menor prioridade não está excedendo suas requisições, ele não será removido. Outro Pod com maior prioridade que exceda suas requisições pode ser removido.

Próximos passos