Resource Quotas e LimitRanges: governança de recursos

1. Introdução à Governança de Recursos no Kubernetes

Em ambientes Kubernetes multi-tenant, a governança de recursos não é opcional — é uma necessidade operacional. Sem controles adequados, um único workload pode consumir toda a capacidade do cluster, prejudicando aplicações vizinhas. ResourceQuotas e LimitRanges são as ferramentas nativas do Kubernetes para implementar essa governança.

A governança opera em três camadas conceituais:

  • Requests: reserva mínima garantida para o container no nó
  • Limits: teto máximo que o container pode consumir (sujeito a throttling em CPU e OOMKill em memória)
  • Uso real: métrica observada, que deve ficar entre request e limit

O ResourceQuota atua no nível do namespace, limitando o consumo agregado. O LimitRange opera no nível do pod/container, definindo valores padrão e restrições individuais. Juntos, formam um sistema complementar de controle.

2. ResourceQuotas: Limitando o Consumo por Namespace

Um ResourceQuota é um objeto Kubernetes que restringe o consumo total de recursos computacionais, de armazenamento e a quantidade de objetos dentro de um namespace.

Estrutura básica:

apiVersion: v1
kind: ResourceQuota
metadata:
  name: quota-computacional
  namespace: dev-team
spec:
  hard:
    requests.cpu: "4"
    requests.memory: "8Gi"
    limits.cpu: "8"
    limits.memory: "16Gi"
    persistentvolumeclaims: "5"
    pods: "20"
    services: "10"
    configmaps: "15"

Quando aplicamos essa quota, qualquer tentativa de criar recursos que excedam esses limites é rejeitada imediatamente pelo API Server. O comando kubectl apply -f quota.yaml registra a política, e o controlador de admissão valida todas as operações subsequentes.

Exemplo de violação: se tentarmos criar um pod com limits.memory: 20Gi em um namespace que já consumiu 12Gi, a API retorna:

Error from server (Forbidden): pods "meu-pod" is forbidden: exceeded quota: quota-computacional, requested: limits.memory=20Gi, used: limits.memory=12Gi, limited: limits.memory=16Gi

3. Tipos de Escopo em ResourceQuotas

Os escopos permitem granularidade fina nas quotas. O Kubernetes suporta quatro escopos padrão:

Escopo Descrição
BestEffort Pods sem requests/limits definidos (QoS classe BestEffort)
NotBestEffort Pods com pelo menos um request ou limit definido
Terminating Pods com spec.activeDeadlineSeconds >= 0
NotTerminating Pods sem deadline definido

Exemplo prático: limitar pods BestEffort a 1 CPU e 512Mi de memória agregados:

apiVersion: v1
kind: ResourceQuota
metadata:
  name: quota-best-effort
  namespace: dev-team
spec:
  hard:
    pods: "10"
  scopeSelector:
    matchExpressions:
    - operator: In
      scopeName: BestEffort

Isso impede que workloads sem requests definidos consumam recursos ilimitados — uma prática comum em ambientes de desenvolvimento desorganizados.

4. LimitRanges: Definindo Limites Padrão e Mínimos/Máximos

Enquanto a quota controla o agregado, o LimitRange regula as dimensões individuais dos containers. Ele pode:

  • Definir valores padrão de requests e limits
  • Estabelecer mínimos e máximos por container/pod
  • Forçar a especificação de limites em todos os containers

Exemplo de LimitRange:

apiVersion: v1
kind: LimitRange
metadata:
  name: limits-padrao
  namespace: dev-team
spec:
  limits:
  - max:
      cpu: "2"
      memory: "2Gi"
    min:
      cpu: "100m"
      memory: "128Mi"
    default:
      cpu: "500m"
      memory: "512Mi"
    defaultRequest:
      cpu: "200m"
      memory: "256Mi"
    type: Container

Quando um pod é criado sem especificar recursos, o LimitRange injeta automaticamente os valores default como limits e defaultRequest como requests. Se o pod tentar definir valores fora do range min-max, a criação é bloqueada.

5. Interação entre ResourceQuotas e LimitRanges

A ordem de validação é crucial: primeiro o LimitRange aplica defaults e valida mínimos/máximos, depois o ResourceQuota verifica se o consumo agregado não excede o permitido.

Cenário problemático: Suponha uma quota com limits.memory: 8Gi e um LimitRange com default: memory: 1Gi. Se 9 pods forem criados sem especificar memória, o LimitRange injeta 1Gi em cada, totalizando 9Gi — excedendo a quota. O nono pod será rejeitado.

Estratégia para evitar conflitos: Calcule a quota considerando os defaults do LimitRange:

# Quota com folga para 10 pods com default de 1Gi cada
spec:
  hard:
    limits.memory: "12Gi"  # 10Gi para pods + 2Gi de margem

Sempre documente essa relação entre quota e LimitRange para evitar surpresas durante deploys automatizados.

6. Boas Práticas para Governança de Recursos

  1. Defina quotas antes de qualquer workload: Crie ResourceQuota e LimitRange como parte do bootstrap do namespace, antes que qualquer pod seja implantado.

  2. Monitore ativamente: Use kubectl describe quota -n <namespace> para ver consumo atual vs hard limit. Ferramentas como Prometheus + Grafana podem alertar quando o uso se aproxima de 80% da quota.

  3. Use namespaces diferentes para ambientes distintos:

  4. Produção: quotas mais generosas, LimitRange com defaults conservadores
  5. Desenvolvimento: quotas restritas, LimitRange forçando mínimos baixos

  6. Revise periodicamente: A análise de utilização histórica ajuda a recalibrar limites. Se um namespace usa consistentemente apenas 30% da quota, reduza-a para liberar recursos.

  7. Combine com Horizontal Pod Autoscaler (HPA): Quotas muito apertadas podem impedir o HPA de escalar. Sempre teste cenários de pico.

7. Casos de Uso e Troubleshooting

Problema: Pod não cria com erro de quota

$ kubectl get events -n dev-team --field-selector reason=FailedCreate
1m   Warning   FailedCreate   replicaset/app-7d8f9   Error creating: pods "app-7d8f9-xk4m2" is forbidden: exceeded quota: quota-computacional, requested: pods=1, used: pods=20, limited: pods=20

Solução: Aumente a quota ou reduza o número de réplicas. Use kubectl describe quota para ver o consumo atual.

Problema: Pod fica em Pending sem motivo aparente

O pod pode estar solicitando mais recursos do que qualquer nó pode oferecer. Verifique com:

$ kubectl describe pod app-pendente
Events:
  Type     Reason            Age   From               Message
  Warning  FailedScheduling  10s   default-scheduler  0/5 nodes are available: 3 Insufficient cpu, 2 Insufficient memory.

Cenário real: Namespace de desenvolvimento com quota de 2 CPUs e 4Gi de memória, LimitRange com default de 500m CPU. Times diferentes criam pods sem especificar recursos — todos recebem 500m, mas rapidamente excedem a quota. A solução foi educar os times a especificar requests realistas e aumentar a quota para 4 CPUs.

Referências