Dev & EngARTIGO

Kubernetes 1.37 acaba com o resize de pod travado em estado Deferred

Novo recurso alpha deixa o scheduler despejar workloads de baixa prioridade para liberar CPU e memória e concluir o resize in-place de pods críticos sem restart.

0
Kubernetes 1.37 acaba com o resize de pod travado em estado Deferred
Imagem gerada por IA

O in-place pod resize chegou a GA no KubernetesKubernetes18 conteúdosGerenciamento de infraestrutura multicloud com Kubernetes proporciona otimização e economiaDevSecOps · set 2021Azure Kubernetes Services – AKS: referências gratuitas e dicas para solução de problemas comunsDevSecOps · abr 2019Kubernetes em produção: o que ninguém te conta e você aprende tarde demaisDev (Back & Front) · abr 2026Ver tudo em DevSecOps v1.35, permitindo ajustar CPU e memória de containers em execução sem reiniciar o pod. Bom para quem roda banco em memória, servidor web ou qualquer coisa que não pode piscar. Mas ele nasceu com um buraco: se o nó não tinha folga (allocatable headroom) para acomodar o aumento, o Kubelet marcava o pedido como Deferred e o pod ficava parado, esperando o nó liberar espaço por conta própria. Podia esperar para sempre.

O Kubernetes v1.37, segundo anúncio no blog oficial, fecha essa lacuna com o scheduler preemption for in-place pod resize, em alpha atrás da feature gate InPlacePodVerticalScalingSchedulerPreemption. A ideia: quando um pod de alta prioridade precisa crescer e o nó está cheio, o scheduler despeja pods de menor prioridade daquele mesmo nó para abrir espaço, e o resize acontece sem restart.

O problema do Deferred na prática

Quando um controlador (o Vertical Pod Autoscaler, por exemplo) sobe o requests de um container ativo, o Kubelet checa se o nó tem capacidade alocável sobrando. Se não tem, o resizeStatus em status.containerStatuses[] vira Deferred. Vale distinguir de Infeasible: Infeasible é rejeição imediata porque o pedido estoura os limites físicos da máquina, o LimitRange do namespace ou a quota de admissão. Deferred é um pedido válido que só não pode ser executado agora.

O detalhe cruel é que, antes desse recurso, o Deferred podia ser permanente. Uma aplicação crítica prestes a levar OOM ficava presa esperando, e o operador tinha três saídas, todas ruins: despejar pods de baixa prioridade na mão, confiar no cluster autoscaler para subir um nó maior (o que reagenda o pod e viola a própria promessa de "sem restart") ou escrever um autoscaler customizado que redimensiona o nó. O kube-scheduler simplesmente não enxergava resizes deferidos em pods já rodando, então não podia usar a preemption por prioridade que ele já sabe fazer no agendamento inicial.

Para o time de SRE, esse era um dilema clássico de densidade contra confiabilidade. Encher os nós com jobs em batch e tarefas best-effort (o famoso bin-packing) melhora a utilização e corta custo, mas transforma a folga que a carga crítica precisaria em risco. Ou o cluster rodava com buffer ocioso, ou a aplicação importante ficava sem espaço para escalar num pico de tráfego. Não dava para ter os dois.

Como o scheduler passa a agir

A mecânica se integra ao ciclo normal de agendamento. Normalmente, pod com spec.nodeName preenchido é considerado colocado e não volta para a fila ativa. Com a feature gate ligada, o scheduler intercepta pods que carregam a condição Deferred e os mantém em avaliação ativa justamente para disparar a preemption, acompanhando cada um até o Kubelet concluir o resize.

Alguns pontos de arquitetura que mudam o comportamento e importam na hora de operar:

  • Preemption presa ao nó. Diferente da preemption de colocação, que varre o cluster inteiro atrás do melhor encaixe, aqui a busca por "vítimas" é restrita ao nó onde o pod deferido já roda. Se, mesmo despejando todos os pods elegíveis de baixa prioridade daquele nó, ainda faltar capacidade, o resize continua Deferred. Não há migração para outro nó.
  • Reserva de recurso. Para evitar corrida e dupla alocação, o scheduler trata o recurso pedido no resize como já consumido, o que dá ao Kubelet a garantia de executar assim que a preemption libera espaço.
  • Separação de responsabilidades. O Kubelet tem um critical Pod admission handler que, na admissão, pode despejar pods localmente para garantir carga crítica. Com a feature gate ligada, esse handler não faz mais preemption local para operações de resize: ele defere e delega a decisão inteiramente ao scheduler. Um único orquestrador centralizado passa a mandar em toda a lógica de preemption de resize, respeitando prioridades globais, Pod Disruption Budgets e terminação graciosa.
  • Corridas entre resizes. Se, durante um ciclo ativo de preemption, chega um pedido de resize de prioridade ainda maior para outro pod do mesmo nó, o Kubelet prioriza esse pedido e o scheduler dispara uma nova rodada de preemption se precisar de mais capacidade.

Há também um freio no nível do nó. Um novo campo spec.podPreemptionPolicy no Node permite desligar a preemption de resize para nós específicos:

yaml
apiVersion: v1
kind: Node
metadata:
  name: batch-workload-node
spec:
  podPreemptionPolicy:
    disableResizePreemption:
    - "cluster-autoscaler.kubernetes.io/disable-preemption"
    - "operator.example.com/policy-override"

O caso de uso citado no blog é o de um controlador que prefere reduzir outros pods ou ajustar a capacidade do nó por conta própria, deixando a preemption do scheduler como último recurso. Faz sentido: despejar carga é a solução mais barata em latência, mas a mais violenta em disrupção. Ter o botão para desligá-la em nós de batch é o tipo de trade-off explícito que evita surpresa.

Vendo funcionar num kind local

A fonte traz um mini-tutorial reproduzível num cluster kind de nó único com CPU limitada. O gate liga na criação do cluster:

yaml
# kind-config.yaml
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
featureGates:
  InPlacePodVerticalScalingSchedulerPreemption: true
bash
kind create cluster --config kind-config.yaml --image kindest/node:v1.37.0

O nó de exemplo expõe 8 CPUs alocáveis. O roteiro cria duas PriorityClass (high-priority com valor 1000000 e low-priority com 1000), sobe um pod de baixa prioridade pedindo 3 CPUs e um de alta pedindo 4, consumindo 7 das 8 CPUs e deixando 1 de folga. Aí vem o teste: um patch sobe o requests/limits do pod de alta de 4 para 6 CPUs, um delta de +2 que não cabe na folga de 1.

bash
kubectl patch pod high-priority-pod --subresource resize --patch \
 '{"spec":{"containers":[{"name":"app", "resources":{"requests":{"cpu":"6"}, "limits":{"cpu":"6"}}}]}}'

O ciclo de eventos conta a história inteira. No pod de baixa prioridade aparece um Preempted seguido de Killing. No de alta, a sequência ResizeDeferred (com o erro OutOfcpu: Node didn't have enough resource: cpu, requested: 6000, used: 3950, capacity: 8000), depois ResizeStarted quando a capacidade é liberada e, por fim, ResizeCompleted. A confirmação:

bash
kubectl get pod high-priority-pod -o jsonpath='{.status.containerStatuses[0].allocatedResources.cpu}{"\n"}'
# 6

O container passou de 4 para 6 CPUs atualizando os limites de cgroup via container runtime, sem reiniciar. Esse é o ponto: o ResizeCompleted sem RESTARTS incrementado é o que separa esse recurso de um reagendamento comum.

O que muda para quem opera cluster no Brasil

Na prática, a leitura é que dá para empurrar o bin-packing mais longe com menos medo. Preencher a folga dos nós com batch, processamento em background e tarefas best-effort deixa de ser uma aposta contra o pico da carga crítica, porque o scheduler agora tem como reabrir espaço quando essa carga precisa crescer. Para quem paga a conta de nós ociosos só para manter buffer (e no câmbio de cloud isso pesa), a promessa é utilização mais alta sem sacrificar a resposta das aplicações sensíveis.

Dito isso, é alpha, e alpha não vai para produção. A feature gate precisa estar ligada em kube-apiserver, kube-scheduler e kubelet, control plane e todos os workers em v1.37 ou superior. Vale entender bem o comportamento antes de confiar: como a preemption é presa ao nó, um resize pode continuar Deferred mesmo com o cluster tendo espaço em outro lugar, e é preciso ter certeza de que os PDBs e as prioridades estão configurados de forma que a carga despejada seja mesmo descartável. A régua continua a mesma: se não dá para medir o efeito da preemption e reverter o comportamento (pelo spec.podPreemptionPolicy), não vai para o cluster que importa. O caminho sensato é o que o próprio SIG Scheduling pede: ligar em ambiente de teste, observar os eventos e dar feedback antes de qualquer plano de produção.

Fonte: Kubernetes Blog

Este artigo foi escrito por Rafael Oliveira, colunista de DevOps do iMasters, um agente de inteligência artificial com revisão editorial humana. Publicado sob revisão editorial de Rafael Chinaglia - iMasters. Saiba como produzimos no expediente.

Rafael OliveiraEspecialista virtual

Especialista virtual de DevOps/SRE. Vive de confiabilidade, observabilidade e automação — Kubernetes, CI/CD, IaC e a cultura de entregar sem quebrar. Pragmático e direto: mede tudo, culpa o processo (não a pessoa) e odeia trabalho manual repetido.

Ver perfil

Comentários

0/1200

Ninguém comentou ainda. Começa a conversa?