22 out 2026 · linux, cgroups, containers, kernel · 4 min
O que são cgroups: como o linux limita cpu e memória
Um contêiner não é uma máquina virtual. É um processo comum do kernel Linux que recebe um limite de recursos via cgroup. O cgroup é a estrutura que diz: este processo pode usar no máximo 256 MB de memória e meio CPU. Sem cgroup, o processo compete com tudo o mais no host.
O parágrafo-resposta
Cgroup (control group) é a estrutura do kernel que agrupa processos e aplica limites de CPU, memória, I/O e número de processos. Quando você roda docker run -m 256m --cpus 0.5, o Docker cria um cgroup com memory.max = 268435456 bytes e cpu.max = 50000 100000. O processo dentro do contêiner enxerga esses números em /sys/fs/cgroup.
O que o kernel mostra
Sem limite, memory.max vale max (sem teto) e cpu.max vale max 100000 (sem quota). Com -m 256m --cpus 0.5, o kernel escreve 268435456 e 50000 100000. A cota de CPU significa: a cada período de 100000 microssegundos, o grupo pode consumir no máximo 50000 microssegundos de CPU. Isso é meio processador.
O artefato: lê o cgroup do próprio processo
#!/usr/bin/env python3
# cgroup-read.py
# Le os arquivos do cgroup v2 do proprio processo e mostra o que o kernel enxerga.
# Rode dentro de um conteiner com limite para ver os numeros mudarem:
# docker run --rm -m 256m --cpus 0.5 python:3.12-slim python3 cgroup-read.py
import pathlib
base = pathlib.Path('/sys/fs/cgroup')
def ler(nome):
p = base / nome
if not p.exists():
return None
try:
return p.read_text().strip()
except OSError:
return None
def legivel(n):
n = int(n)
for unidade in ('B', 'KiB', 'MiB', 'GiB'):
if n < 1024 or unidade == 'GiB':
return f'{n} {unidade}'
n /= 1024
return f'{n} GiB'
mem_max = ler('memory.max')
mem_cur = ler('memory.current')
cpu_max = ler('cpu.max')
cpu_w = ler('cpu.weight')
pids_max = ler('pids.max')
print('cgroup v2 do processo atual')
print('===========================')
if mem_max is not None:
if mem_max == 'max':
print('memory.max : sem limite (max)')
else:
print(f'memory.max : {mem_max} bytes ({legivel(mem_max)})')
if mem_cur is not None:
print(f'memory.current: {mem_cur} bytes ({legivel(mem_cur)})')
if mem_max and mem_max != 'max':
pct = int(mem_cur) * 100 // int(mem_max)
print(f' : {pct}% do limite em uso')
if cpu_max is not None:
partes = cpu_max.split()
if partes and partes[0] == 'max':
print('cpu.max : sem limite (max)')
else:
quota, periodo = partes[0], partes[1]
cpus = int(quota) / int(periodo)
print(f'cpu.max : quota {quota} us / periodo {periodo} us = {cpus:.2f} CPU')
if cpu_w is not None:
print(f'cpu.weight : {cpu_w} (padrao 100; maior = mais prioridade quando ha disputa)')
if pids_max is not None:
print(f'pids.max : {pids_max}')
# cgroup v1 (fallback em hosts antigos)
v1_mem = pathlib.Path('/sys/fs/cgroup/memory/memory.limit_in_bytes')
if v1_mem.exists() and mem_max is None:
print('memory.limit_in_bytes (v1):', v1_mem.read_text().strip())
v1_cpu = pathlib.Path('/sys/fs/cgroup/cpu/cpu.cfs_quota_us')
if v1_cpu.exists() and cpu_max is None:
print('cpu.cfs_quota_us (v1):', v1_cpu.read_text().strip())
O script não instala nada e roda em qualquer contêiner com Python 3.
Como verificar
Rode o script sem limite e com limite, e confira a diferença. Saída esperada, copiada de execução real:
Sem limite:
memory.max : sem limite (max)
cpu.max : sem limite (max)
cpu.weight : 100
Com -m 256m --cpus 0.5:
memory.max : 268435456 bytes (256.0 MiB)
cpu.max : quota 50000 us / periodo 100000 us = 0.50 CPU
cpu.weight : 100
Se memory.max mostrar max mesmo com -m, o contêiner está rodando em cgroup v1 ou o runtime não aplicou o limite. Confira a versão do Docker e do kernel.
Se der errado
memory.max mostra max mesmo com -m 256m. O runtime pode não ter aplicado o limite, ou o host usa cgroup v1 e o caminho é outro. O script tenta o fallback v1 (memory.limit_in_bytes). Se mesmo assim não aparecer, rode docker inspect no contêiner e confira HostConfig.Memory.
cpu.max mostra max 100000 mesmo com –cpus 0.5. O mesmo problema: limite não aplicado. Em cgroup v1, o arquivo é cpu.cfs_quota_us e cpu.cfs_period_us. O script cobre os dois.
O script falha com FileNotFoundError. O caminho /sys/fs/cgroup pode não estar montado no contêiner (raro, mas acontece em ambientes restritos). Monte com --cgroupns host ou rode no host diretamente.
O limite de memória mata o processo (OOM killed). Isso é esperado: quando memory.current atinge memory.max, o kernel invoca o OOM killer. O post limites de memória no Docker cobre como evitar.
cgroup v1 e v2
| Versão | Arquivo de memória | Arquivo de CPU | Quando aparece |
|---|---|---|---|
| v2 (unificada) | memory.max | cpu.max (quota periodo) | kernel >= 5.0, hosts recentes |
| v1 (hierarquia) | memory/memory.limit_in_bytes | cpu/cpu.cfs_quota_us | hosts antigos, alguns ambientes gerenciados |
A maioria dos hosts atuais usa v2. O script detecta qual está presente e usa o fallback v1 quando necessário.
FAQ
Contêiner é só cgroup? Não. Contêiner é namespaces (isolamento de visão: processos, rede, montagens) mais cgroup (limite de recursos) mais uma imagem de sistema de arquivos. O cgroup é a parte que responde pela pergunta ‘quanto posso usar’.
O que acontece quando o processo ultrapassa o limite de memória? O kernel mata o processo (OOM kill) ou retorna erro de alocação, dependendo da configuração. Não há swap por padrão a menos que você configure.
cpu.weight serve para quê? Quando dois grupos disputam CPU e ambos estão no limite, o peso decide a proporção. Padrão 100. Um grupo com peso 200 recebe o dobro quando há contenção.
Como ver o cgroup de um processo no host?
Leia /proc/<pid>/cgroup. Ele aponta para o caminho do cgroup no /sys/fs/cgroup. A partir daí, leia os arquivos.
Docker e Kubernetes usam cgroup de forma diferente? Kubernetes passa os limites do pod para o runtime (containerd ou CRI-O), que cria o cgroup. O mecanismo é o mesmo; a diferença é quem escreve os arquivos.
Fontes: cgroups(7), kernel docs cgroup v2, Docker resource constraints. Veja também limites de memória no Docker e contêiner sem root.