Kubernetes requests vs limits: como isso afeta scheduling e throttling

Tabela de Conteúdo

Muitas vezes o pod esta sendo morto por OOM ou a aplicação fica lenta por conta de falta de recursos. Isso geralmente acontece por conta de requests/limits mal configurados.

A ideia é simples:

  • requests = reserva que o scheduler usa pra decidir onde alocar
  • limits = teto que o container não pode passar

Classes de QoS (o que o K8s usa internamente)

Guaranteed (requests == limits)

  • Garantia de recursos
  • Não sofre limitação de CPU
  • Se estourar memória, OOMKill
  • Critérios: todos os containers devem ter requests == limits para CPU e memory

Burstable (requests < limits)

  • Pode ser “preemptado” por pods Guaranteed
  • CPU pode ser limitada se o nó estiver lotado
  • Memória: OOM se passar do limit
  • Critérios: pelo menos um container com request ou limit (mas não todos iguais)

BestEffort (sem requests/limits)

  • Primeiro a ser “preemptado” se o nó precisar de recursos
  • CPU pode ser quase zero se o nó estiver sob pressão
  • OOMKill se o nó precisar de memória
  • Critérios: nenhum container com requests ou limits

Troubleshooting do dia a dia

kubectl top pod -A
kubectl describe pod -n <ns> <pod>
kubectl describe node <node>
kubectl get events -n <ns> --sort-by=.lastTimestamp | tail -n 30

Procure por:

  • Killing container with id ... (OOM)
  • Throttling (limitação de CPU)
  • Insufficient cpu/memory (preemption)

Erros comuns que podem ser evitados

  • Usar só limits e virar BestEffort sem querer
  • Setar requests muito altos e desperdiçar recursos
  • Não setar limits de memória e tomar OOMKill no nó
  • CPU limits muito baixos e a aplicação ficar lenta

YAML de exemplo

Aqui temos um exemplo de deployment com requests e limits configurados, onde a reserva é de 128 MiB de memória e 0.1 vCPU, com teto de 256 MiB de memória e 0.5 vCPU.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: api
spec:
  replicas: 2
  selector:
    matchLabels:
      app: api
  template:
    metadata:
      labels:
        app: api
    spec:
      containers:
        - name: api
          image: nginx:1.27
          resources:
            requests:
              # reserva 128 MiB (o scheduler usa isso pra alocar)
              memory: "128Mi"
              # reserva 0.1 vCPU (100 millicores)
              cpu: "100m"
            limits:
              # teto de 256 MiB
              memory: "256Mi"
              # teto de 0.5 vCPU (se passar, sofre limitação)
              cpu: "500m"

Quando usar cada classe

Guaranteed

  • Apps críticos que precisam de performance garantida
  • Serviços com carga previsível
  • Quando você pode pagar pelos recursos garantidos

Burstable

  • Maioria das aplicações web
  • Serviços com carga variável
  • Quando você quer flexibilidade com algum controle

BestEffort

  • Jobs batch que podem ser reiniciados
  • Apps de baixa prioridade
  • Quando recursos não são críticos

Comandos úteis

# Ver classe QoS dos pods
kubectl get pod -o custom-columns=NAME:.metadata.name,QOS:.status.qosClass

# Ver uso de recursos por nó
kubectl describe node <node> | grep -A 10 "Allocated resources"

# Ver pods que estão consumindo mais que o pedido
kubectl top pod -n <ns> --sort-by=cpu
kubectl top pod -n <ns> --sort-by=memory

# Ver eventos de OOM
kubectl get events -A --sort-by=.lastTimestamp | grep "Killing"

Boas práticas

  • Se você setar limits, SEMPRE sete requests também
  • Para apps críticos, considere Guaranteed (requests == limits)
  • Para apps “batch” ou de baixa prioridade, Burstable costuma ser suficiente
  • Monitore o uso real e ajuste conforme necessário

Se você quiser entender melhor como rollout e probes interagem com recursos, dá uma olhada neste post: Kubernetes probes: liveness, readiness e startup (sem mistério).

Simples assim! :)


Referências