Erros de Agendamento no Kubernetes Explicados: Soluções e Melhores Práticas

Domine o agendamento no Kubernetes! Este guia desmistifica por que os Pods ficam presos no estado 'Pending'. Aprenda a diagnosticar erros usando `kubectl describe`, resolver problemas relacionados a CPU/Memória insuficientes, superar restrições de Node Affinity e utilizar corretamente Taints e Tolerations para um posicionamento robusto de workloads.

Erros de Agendamento no Kubernetes Explicados: Soluções e Melhores Práticas

Erros de agendamento no Kubernetes geralmente se manifestam como um pod preso em Pending. Esse status pode parecer vago, mas tem um significado específico: o Kubernetes aceitou o objeto pod, mas o scheduler não encontrou um nó que satisfaça os requisitos do pod. O contêiner não falhou. O aplicativo não iniciou. Em muitos casos, a imagem nem foi baixada ainda.

A maneira mais rápida de resolver esses problemas é comparar o que o pod solicita com o que o cluster pode oferecer. Solicitações de CPU e memória, labels de nós, regras de afinidade, taints, tolerations, volumes persistentes, regras de topologia e cotas de namespace podem bloquear o posicionamento. O scheduler é rigoroso quanto a restrições obrigatórias. Se uma regra obrigatória excluir todos os nós, o pod espera.

Diagnosticando Pods Pendentes: O Primeiro Passo

Antes de tentar corrigir, você deve diagnosticar com precisão por que o Scheduler está falhando. A principal ferramenta para essa investigação é o kubectl describe pod.

Quando um Pod está preso em Pending, a seção Events da saída do describe contém informações críticas detalhando o processo de decisão de agendamento e quaisquer rejeições.

Usando kubectl describe pod

Sempre direcione o Pod problemático:

kubectl describe pod <nome-do-pod> -n <namespace>

Examine a saída, observando especificamente a seção Events no final. As mensagens aqui geralmente indicarão a restrição que impediu o agendamento. Mensagens comuns estão relacionadas a Insufficient cpu, Insufficient memory, incompatibilidades de node selector, taints não tolerados ou binding de volume.

Categorias Comuns de Erros de Agendamento e Soluções

As falhas de agendamento geralmente se enquadram em três categorias principais: Restrições de Recursos, Restrições de Política (Afinidade/Anti-Afinidade) e Configuração de Nó (Taints/Tolerations).

1. Restrições de Recursos (Recursos Insuficientes)

Esta é a causa mais frequente. O Scheduler requer um Nó que possa satisfazer as requests definidas na especificação do Pod. Se nenhum nó tiver CPU ou Memória alocável suficiente disponível, o Pod permanecerá em Pending.

Identificando o Problema

A seção Events mostrará mensagens como:

  • 0/3 nodes are available: 3 Insufficient cpu.
  • 0/3 nodes are available: 1 node(s) had taint {node-role.kubernetes.io/master: }, that the pod didn't tolerate, 2 node(s) didn't match node selector.

Essas mensagens podem ser combinadas. Não pare na primeira frase. Se três nós falharem por três razões diferentes, corrigir apenas uma razão pode ainda deixar o pod pendente.

Soluções para Escassez de Recursos

  1. Reduza as Solicitações do Pod: Se as solicitações do Pod forem excessivamente altas, tente diminuir as requests de CPU ou Memória no YAML do Pod ou Deployment.
  2. Aumente a Capacidade do Cluster: Adicione mais Nós ao cluster Kubernetes.
  3. Limpe Workloads Existentes: Reduza a escala de workloads não essenciais, remova jobs abandonados ou ajuste solicitações superdimensionadas em deployments existentes. Use kubectl drain para manutenção de nós, não como um comando de limpeza casual.
  4. Use Limit Ranges: Se seu namespace não tiver limites de recursos definidos, implemente objetos LimitRange para evitar que Pods únicos acumulem recursos.

2. Node Selectors e Regras de Afinidade/Anti-Afinidade

O Kubernetes permite controle refinado sobre onde os Pods podem ou devem ser colocados usando nodeSelector, nodeAffinity e podAffinity/podAntiAffinity.

Incompatibilidade de Node Selector

Se você definir um nodeSelector que não corresponda a nenhum label presente em qualquer Nó disponível, o Pod não pode ser agendado.

Exemplo de Trecho YAML (Causa da Falha):

spec:
  nodeSelector:
    disktype: ssd-fast
  containers: [...] # Pod permanece Pending se nenhum nó tiver disktype=ssd-fast

Solução: Certifique-se de que o label especificado em nodeSelector existe em pelo menos um Nó (kubectl get nodes --show-labels) e que a correspondência de maiúsculas/minúsculas seja exata.

Use verificações de label direcionadas quando o cluster tiver muitos labels:

kubectl get nodes -L disktype,topology.kubernetes.io/zone
kubectl describe node <nome-do-no>

Um erro comum é usar um label que existia em um grupo de nós anterior, mas não no grupo de nós substituto. Após uma atualização de cluster ou migração de grupo de auto scaling, regras de posicionamento antigas podem se tornar silenciosamente impossíveis.

Restrições de Node Affinity

nodeAffinity oferece regras mais flexíveis (por exemplo, requiredDuringSchedulingIgnoredDuringExecution ou preferredDuringSchedulingIgnoredDuringExecution). Se uma regra required não puder ser atendida, o Pod permanece em Pending.

Dica de Diagnóstico: Ao usar regras de afinidade complexas, a seção Events geralmente afirma: node(s) didn't match node selector.

Afinidade e Anti-Afinidade de Pod

Essas regras controlam o posicionamento em relação a outros Pods. Se, por exemplo, uma regra de Anti-Afinidade exigir que um Pod não seja executado em um Nó que hospeda um serviço específico, mas todos os nós já hospedam esse serviço, o agendamento falhará.

Solução: Revise cuidadosamente a chave de topologia e o seletor em suas regras de afinidade. Se uma regra de anti-afinidade for muito restritiva, relaxe o requisito ou verifique se os Pods alvo selecionados pela regra estão de fato sendo executados nos nós que você deseja evitar.

Prefira preferredDuringSchedulingIgnoredDuringExecution quando a regra expressar uma preferência em vez de um requisito rígido. Anti-afinidade obrigatória é útil para distribuir réplicas de serviços críticos, mas pode bloquear deployments em clusters pequenos. Por exemplo, três réplicas com anti-afinidade estrita de uma por zona não podem ser agendadas corretamente em um cluster com apenas duas zonas utilizáveis.

3. Taints e Tolerations

Taints são aplicados diretamente aos Nós para repelir Pods, enquanto Tolerations são adicionadas às especificações dos Pods para permitir que eles sejam executados em nós com taints.

  • Taint: Repele Pods a menos que eles tenham uma toleration correspondente.
  • Toleration: Permite que um Pod seja agendado em um nó com um taint correspondente.

Identificando Rejeição por Taint

Os Events indicarão explicitamente o motivo da rejeição:

0/3 nodes are available: 2 node(s) had taint {dedicated: special-workload, effect: NoSchedule}, that the pod didn't tolerate.

Soluções para Taints e Tolerations

Você tem dois caminhos principais:

  1. Modifique o Pod (Recomendado para Pods de Aplicação): Adicione as tolerations necessárias à especificação do Pod que correspondam ao taint do nó.

    Exemplo de Toleration:

    spec:
      tolerations:
      - key: "dedicated"
        operator: "Equal"
        value: "special-workload"
        effect: "NoSchedule"
      containers: [...] 
    
  2. Modifique o Nó (Recomendado para Administradores de Cluster): Remova o taint do Nó se a restrição não for mais necessária.

    # Para remover um taint
    kubectl taint nodes <nome-do-no> dedicated:special-workload:NoSchedule-
    

Alerta de Melhor Prática: Evite tolerar o taint global node-role.kubernetes.io/master:NoSchedule em Pods de aplicação, a menos que você esteja intencionalmente agendando componentes críticos do plano de controle nos nós master.

Em clusters mais novos, os nós do plano de controle comumente usam o taint node-role.kubernetes.io/control-plane em vez de, ou juntamente com, a terminologia master antiga. Verifique os taints reais antes de copiar uma toleration de um manifesto antigo:

kubectl describe node <nome-do-no> | grep -i taints

Restrições Avançadas de Agendamento

Restrições menos comuns, mas importantes, também podem bloquear o agendamento:

Restrições de Volume de Armazenamento

Se um Pod solicitar um PersistentVolumeClaim (PVC) que não pode ser vinculado a um Nó disponível (por exemplo, devido a requisitos específicos do provisionador de armazenamento ou indisponibilidade do volume), o Pod pode permanecer em Pending.

Diagnóstico: Verifique o status do PVC primeiro (kubectl describe pvc <nome-do-pvc>). Se o PVC estiver preso em Pending, o agendamento do Pod é interrompido até que o volume esteja disponível.

O armazenamento também pode ser atrasado intencionalmente por volumeBindingMode: WaitForFirstConsumer na StorageClass. Nesse modo, o binding espera até que o scheduler escolha um nó adequado, porque o volume pode precisar ser criado na mesma zona que o pod. Isso é normal, mas se nenhum nó satisfizer as restrições do pod e do armazenamento juntos, o pod permanece pendente.

DaemonSets e Topology Spreads

DaemonSets só serão agendados em nós que correspondam aos seus critérios de seleção (se houver). Se um cluster for particionado ou um novo nó não corresponder ao seletor do DaemonSet, ele não será executado.

Topology Spread Constraints (se definidos) garantem distribuição uniforme. Se a distribuição atual impedir o posicionamento em qualquer nó, respeitando as restrições de spread, o agendamento falhará.

Falhas de topology spread geralmente aparecem após uma falha parcial. Suponha que uma zona esteja indisponível e um deployment tenha restrições estritas de spread entre zonas. O Kubernetes pode se recusar a colocar novas réplicas nas zonas restantes porque isso violaria a regra de skew. Esse comportamento protege as metas de distribuição, mas durante uma falha, você pode precisar relaxar temporariamente a restrição para restaurar a capacidade.

Cotas de Namespace e LimitRanges

Um pod também pode ser bloqueado pela política do namespace. ResourceQuota controla o uso agregado em um namespace. LimitRange pode definir valores padrão ou mínimo e máximo de recursos.

Verifique-os quando a especificação do pod parecer razoável, mas a criação ou o agendamento ainda falhar:

kubectl get resourcequota -n <namespace>
kubectl describe resourcequota -n <namespace>
kubectl get limitrange -n <namespace>
kubectl describe limitrange -n <namespace>

Problemas de cota são comuns em clusters de desenvolvimento compartilhados. Uma equipe pode ter capacidade física de cluster suficiente, mas sua cota de namespace está esgotada por ambientes de pré-visualização antigos ou jobs concluídos que nunca foram limpos.

Uma Sequência Realista de Depuração

Quando um pod está pendente, use esta ordem:

  1. Execute kubectl describe pod e copie o evento de agendamento mais recente.
  2. Verifique a CPU e memória solicitadas em relação à capacidade alocável do nó com kubectl describe node.
  3. Verifique os labels dos nós se o pod usar nodeSelector, node affinity ou chaves de topologia.
  4. Verifique os taints nos nós candidatos e as tolerations no pod.
  5. Verifique PVCs e StorageClasses se o pod montar armazenamento persistente.
  6. Verifique as cotas do namespace e LimitRanges.
  7. Se o Cluster Autoscaler deve ajudar, inspecione seus logs ou eventos.

Esta ordem é importante porque um pod pendente não é um problema de tempo de execução do aplicativo. Reiniciar o deployment raramente ajuda, a menos que a restrição subjacente tenha mudado.

Melhores Práticas para Agendamento Bem-Sucedido

Para minimizar problemas de agendamento, adote estas melhores práticas operacionais:

  1. Defina Solicitações de Recursos Explicitamente: Sempre defina requests razoáveis (e limits opcionais) para CPU e memória. Isso permite que o scheduler avalie com precisão a capacidade do nó.
  2. Use Labels de Nó para Zoneamento: Implemente rotulagem consistente de nós (por exemplo, hardware=gpu, zone=us-east-1a) e use nodeSelector ou nodeAffinity para direcionar workloads ao hardware apropriado.
  3. Documente Taints e Tolerations: Se os nós tiverem taints para manutenção ou segregação de hardware, documente esses taints centralmente. Certifique-se de que os manifestos do aplicativo que exigem acesso a recursos com taint incluam as tolerations correspondentes.
  4. Monitore o Cluster Autoscaler (se usado): Se você depende de soluções de escalonamento, certifique-se de que estejam funcionais. A falta de capacidade que deveria acionar o escalonamento pode estar falhando silenciosamente, deixando os Pods pendentes.
  5. Revise os Logs do Scheduler (Avançado): Para investigações profundas, revise os logs do componente kube-scheduler. Em clusters gerenciados, o acesso pode variar por provedor, então comece com eventos de pod e logs do plano de controle específicos do provedor.

Corrija a Restrição, Não o Sintoma

A correção correta depende se a restrição é acidental ou intencional. Se o pod solicitar 8 CPUs porque alguém copiou um manifesto de produção para um pequeno cluster de staging, reduza a solicitação para esse ambiente. Se o pod precisar de uma GPU e nenhum nó GPU existir, adicionar uma toleration não ajudará; o cluster precisa do hardware certo. Se um taint proteger nós de banco de dados de workloads gerais, não remova o taint apenas para agendar um pod não relacionado.

Para alterações em produção, torne o motivo visível no Git. Labels de nós, taints, regras de afinidade e solicitações de recursos são contratos de posicionamento. Operadores futuros precisam saber se uma regra existe por desempenho, conformidade, acesso a hardware, controle de custos ou simples acidente histórico.

Exemplos de Correções Rápidas Enganosas

Várias correções comuns fazem o status Pending imediato desaparecer, enquanto criam um problema pior depois.

Reduzir as solicitações de CPU pode ajudar se a solicitação original foi inflada, mas não é uma ferramenta de capacidade gratuita. Se o aplicativo realmente precisar dessa CPU durante pico de tráfego, o pod pode ser agendado e depois ter desempenho ruim sob carga. Verifique o histórico de uso e latência antes de cortar solicitações agressivamente.

Adicionar uma toleration ampla pode fazer um pod ser agendado, mas pode colocá-lo em nós reservados para outro propósito. Uma toleration diz "este pod é permitido aqui." Não diz "este pod deve preferir aqui." Se você precisar de permissão e intenção, combine tolerations com node affinity ou node selectors.

Remover uma regra de anti-afinidade pode restaurar réplicas rapidamente, mas pode colocar todas as réplicas em um nó ou uma zona. Isso às vezes é aceitável durante uma falha, mas deve ser uma mudança temporária consciente, não uma deriva permanente silenciosa.

Expandir o cluster é frequentemente a resposta certa, mas apenas depois que você souber que o pod pendente pode usar os novos nós. Se o pod exigir um label que o grupo de nós com auto scaling não terá, adicionar nós apenas lhe dá mais nós inadequados.

Verificação Final

Um pod pendente é uma falha de negociação entre o pod e o cluster. O pod solicita recursos, labels, armazenamento, topologia e permissão para pousar em certos nós. O cluster responde com capacidade, taints, labels, cotas e volumes disponíveis. kubectl describe pod mostra onde essa negociação falhou. Depois de ler o evento com cuidado, a maioria das correções se torna direta: altere os requisitos do pod, altere a capacidade disponível do cluster ou corrija a política que não corresponde mais à realidade.