>_ josetilabs.com
← blog

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

Terminal com dois painéis: sem limite memory.max aparece como max e cpu.max como max 100000; com -m 256m e --cpus 0.5 aparecem 268435456 bytes e quota 50000 us por 100000 us
O mesmo script nos dois casos. O limite vira arquivo em /sys/fs/cgroup.

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

python
#!/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.

Leia também

// Por e-mail

Os textos publicados aqui também vão por e-mail. O envio ainda não começou: quem deixar o endereço agora entra na primeira lista.

Guardo só o seu e-mail, a data e o registro desta autorização. Sem cookies, sem rastreador e nada compartilhado com terceiros. Detalhes na política de privacidade.