🛑 Pare de usar CPU limits no Kubernetes

Muitos de nós fomos ensinados que, para manter a vizinhança do cluster em harmonia, precisamos colocar rédeas curtas em todo mundo.
Por puro instinto de sobrevivência (ou trauma de alguma conta da AWS de cinco dígitos), configuramos em todos nossos containers do do Kubernetes limites como limits: cpu: "200m".

Achamos que estamos sendo engenheiros prudentes, entretanto, contudo e porém, talvez esteja criando uma armadilha perfeita.

A verdade é que a CPU é um recurso compressível. Ela não é como a memória RAM, que se estourar o container toma um OOMKill (Out Of Memory) na testa e cai morto, a CPU sabe fatiar o tempo.

Se o pod precisa de mais processamento e ele está disponível no nó, o Kernel do Linux sabe fatiar esse tempo de forma inteligente.

Quando você define um teto artificial para a CPU, o Kubernetes joga a responsabilidade para o CFS (Completely Fair Scheduler) do Linux. O problema é que o CFS não liga se o seu nó está com 85% de CPU sobrando e ociosa. Ele trabalha com janelas rígidas de tempo, geralmente de 100 milissegundos. Se sua aplicação precisa mastigar uma carga e consome a sua milimétrica cota logo no início dessa janela, o CFS congela a sua aplicação pelo resto do período.

O resultado? O temido CPU throttling. Uma requisição que levaria 10ms passa a demorar 100ms. A sua latência P99 explode. Você passa a noite caçando lentidão no banco de dados, enquanto a aplicação está apenas amordaçada por um crime que não cometeu.

A virtude não está em impor restrições arbitrárias para simular ordem, mas em compreender a natureza do Kernel e agir de acordo com a razão. No Kubernetes, o caminho da sabedoria e do desapego é simples: pare de usar CPU limits e confie nos mecanismos nativos de priorização. O verdadeiro escudo contra a ganância de um container maluco não é o limite que o estrangula preventivamente, mas sim a reserva justa que garante a sobrevivência de todos quando o bicho pega.

A Matemática do caos e o fantasma do Garbage Collector

A tecnologia é só o cenário; a verdadeira batalha é lidar com a entropia. Na teoria, a disponibilidade de um serviço pode ser modelada usando Cadeias de Markov, operando basicamente em dois estados: Up (funcionando) e Down (falha).

A disponibilidade real é o equilíbrio entre o MTBF (Tempo Médio Entre Falhas) e o MTTR (Tempo Médio de Reparo). O Kubernetes brilha porque, ao notar uma falha, ele mata e recria um pod em segundos, esmagando o MTTR. E escalando horizontalmente, ele usa o princípio da disponibilidade em paralelo: se um cai, o tráfego flui para o outro.

Mas e se as suas restrições artificiais quebrarem essa matemática?

Imagine uma aplicação em Java ou Go. O Garbage Collector (GC) entra em ação para limpar a memória e causa aquela pausa brusca, o famoso Stop-The-World (STW). Bem nesse milésimo de segundo em que as threads estão travadas, o kubelet bate na porta com um HTTP GET da sua Liveness Probe. Como o pod já está asfixiado pelo limite de CPU e pausado pelo GC, ele não responde no timeout curto de 200ms.

O orquestrador marca uma falha. Conta três dessas e ele reinicia um pod que estava perfeitamente saudável. Você acabou de introduzir instabilidade sistêmica achando que estava mantendo a ordem.

Prioridade, QoS e a Arte do Desapego

Neste momento, você pode me perguntar: “Mas Toledo, se eu tirar os limites de CPU, um container maluco com vazamento de processamento não vai engolir os recursos do nó inteiro?”

É aqui que abraçamos as maravilhas do Kernel. O seu escudo não é o limite rígido, é o CPU Requests.

O requests é o seu contrato de sobrevivência. Quando há disputa de recursos, o kubelet traduz esses requests em cpu.shares no cgroup do Linux. Sob contenção, quem tem um request maior ganha mais fatias de processamento, de forma proporcional. Quem não declara nada, fica com o resto.

Ao configurar apenas os requests, você permite o bursting. Seu container ganha o direito de pegar a CPU ociosa do nó emprestada, processar aquele pico repentino (ou acelerar o penoso startup do Java) e depois devolver a capacidade sem afetar ninguém.

Mais do que isso, o Kubernetes gerencia a saturação real usando as Classes de Qualidade de Serviço (QoS) e Preempção.

Se a memória ou o nó lotar, o kubelet assume o papel de ceifeiro e não sacrifica de forma aleatória. Ele atinge primeiro os pods BestEffort (sem request nem limit), sobe para os Burstable (com request, mas sem limite) e protege com unhas e dentes os seus pods vitais que estão classificados como Guaranteed (request igual ao limite). A priorização salva a infraestrutura.

E falando em limites rígidos: guarde-os exclusivamente para a Memória RAM, pois ela não é compressível. A GitLab quase enlouqueceu rastreando reinicializações abruptas nos pods do Sidekiq porque a aplicação batia no teto de memória e levava um tiro (OOMKill) do Linux no meio da execução. A saída não foi asfixiar a CPU, mas adequar os tetos de memória para a realidade do Ruby (entre 3 e 8 GB) e colocar a própria aplicação para gerir seu Garbage Collection interno, reiniciando graciosamente antes do Kernel intervir.


Administrar sistemas distribuídos é aceitar que não controlamos todas as variáveis. É lidar com a natureza de processos em rede, falhas aleatórias e tráfego intermitente. A gente costuma amarrar limites justos no YAML tentando trazer previsibilidade ao caos, mas só estamos transferindo a dor para a latência dos usuários.

A resiliência real baseia-se em coletar os Golden Signals (latência, saturação, tráfego, erro), confiar no escalonamento horizontal inteligente (HPA), aplicar requests precisos com base no mundo real e permitir que os algoritmos de balanceamento adaptativo do Linux e do Cluster façam o seu trabalho sujo.

Pode fechar a aba do terminal. Beba esse resto de café, ou melhor, me acompanhe numa cerveja. Durma tranquilo esta noite. E amanhã, quando chegar no escritório, experimente o desapego: apague as linhas de limits: cpu dos seus Helm charts.

Sua latência vai respirar aliviada. E o mais importante: a sua saúde mental também.


Referências:

Ewaschuk, Rob. “Monitoring Distributed Systems.” In Site Reliability Engineering.
Google. Disponível em: https://sre.google/sre-book/monitoring-distributed-systems/.
Acesso em: 04 set. 2025.


GitLab. “Sidekiq Memory Limits and Memory Killer in Kubernetes (#1084).” GitLab
Infrastructure Team – Issues. Disponível em: https://gitlab.com/gitlab-com/glinfra/delivery/-/issues/1084. Acesso em: 04 set. 2025.
Hun, Erick. “Kubernetes: Make Your Services Faster by Removing CPU Limits.”


Disponivel em: https://erickhun.com/posts/kubernetes-faster-services-no-cpu-limits/.
Acesso em: 04 set. 2025.


Kubernetes. “Assign Memory Resources to Containers and Pods.” Kubernetes
Documentation. Disponível em: https://kubernetes.io/docs/tasks/configure-podcontainer/assign-memory-resource/. Acesso em 04 set. 2025.