ADRIANOLAUREANO← Artigos
SÉRIE CASULO · 03/03CGROUPSREDE LINUX

Como o Docker limita, conecta e mantém containers vivos?

Nosso processo já enxerga outro mundo, mas ainda pode consumir toda a memória do host — e não possui uma rede própria para conversar de forma controlada.

ARTIGO 26 · CASULO 1.0 · RECURSOS, REDE E CICLO DE VIDA

3limites de recursos

2lados da veth

6comandos de operação

CASULO · UM PROCESSO, VÁRIAS FRONTEIRAS
HOST LINUXPID 4821CPU · RAM · REDE/var/lib/casulo
CASULO 1.0
PIDMNTUTSNET
processomesmo kernel · outra visão
KERNEL COMPARTILHADOnamespaces · cgroups · mounts · signals

VOCABULÁRIO VISUAL

As peças do kernel que entram em cena.

01cgroupgrupo contabilizado e limitado pelo kernel
02Capabilityprivilégio dividido em unidades menores
03vethpar de interfaces virtuais conectadas
04Bridgecomutador virtual no host
05NATtradução entre endereços de rede
06Lifecycleestados do início à limpeza
O nome técnico vem depois do problema. Consulte este mapa quando uma peça reaparecer.
01
Esconder não é limitar.
Namespaces e cgroups resolvem problemas diferentes

Namespaces controlam visibilidade. Cgroups agrupam processos para contabilizar e limitar recursos. O escalonador continua sendo o mesmo descrito em Como uma CPU funciona por dentro; o kernel apenas passa a aplicar uma política ao grupo.

RecursoArquivo cgroup v2Efeito
Memóriamemory.maxlimita bytes; pode acionar OOM no grupo
CPUcpu.maxquota por período, não “meio núcleo” físico
Processospids.maxnovos forks falham no limite
02
Coloque o processo no grupo antes que ele escape.
Configuração de cgroups v2
let group = Cgroup::create("/sys/fs/cgroup/casulo", id)?;
group.write("memory.max", "268435456")?;
group.write("cpu.max", "50000 100000")?;
group.write("pids.max", "64")?;
group.attach(child_pid)?;

O filho nasce bloqueado num pipe. O pai prepara limites, anexa o PID e só então libera a execução. Ao falhar, encerra o filho e remove o grupo vazio. Métricas de memory.current, cpu.stat e pids.current alimentam inspect.

host
└─ cgroup casulo/01J…
   ├─ memory.max = 256 MiB
   ├─ cpu.max = 50 ms / 100 ms
   ├─ pids.max = 64
   └─ cgroup.procs = [4822, 4830]
Todos os descendentes permanecem contabilizados no mesmo grupo.
03
Depois de montar, solte as chaves.
Usuários, capabilities e no_new_privs

O runtime precisa de privilégios para criar mounts e rede, mas a aplicação não. O Casulo configura UID/GID, remove capabilities não necessárias e ativa no_new_privs, que impede ganhar novos privilégios por execve. Um perfil opcional de seccomp bloqueia chamadas claramente fora do escopo.

Limite de segurança

Isso reduz superfície, mas não transforma Casulo numa sandbox para código hostil. Não implementamos todos os perfis, LSMs, rootless completo, atualização contra vulnerabilidades ou auditoria de runtimes maduros.

04
Uma interface com duas pontas atravessa a fronteira.
Network namespace, veth e bridge

Ao criar network namespace, o processo recebe apenas loopback. Criamos um par veth: tudo que entra numa ponta sai pela outra. Uma ponta vai para o namespace; a outra liga-se à bridge do Casulo no host.

container namespace          host                         rede externa
eth0 10.88.0.2 ─ veth ─ veth01 ─ bridge casulo0 ─ rota/NAT ─▶ internet
       │                         10.88.0.1
       └ gateway 10.88.0.1
Bridge conecta interfaces locais; roteamento escolhe o próximo caminho; NAT traduz endereços na borda.

Nomes de interface são derivados de IDs validados e registrados. Se a regra de NAT falha, removemos regra, porta da bridge, veth e namespace em ordem inversa. O Casulo usa API netlink quando disponível; uma fronteira pequena e documentada chama nft para regras transacionais.

05
Publicar uma porta é construir um caminho de volta.
Do host ao serviço interno
cliente → host:8080
        → regra DNAT: destino vira 10.88.0.2:80
        → bridge casulo0 → veth01 → eth0
        → processo escutando :80

resposta → conntrack reconhece fluxo → tradução inversa → cliente
DNAT altera o endereço de destino; o rastreamento de conexões permite desfazer a tradução na resposta.
$ sudo casulo run web:1 -p 8080:80 --memory 256m --cpus 0.5
[casulo] 01J… running · 10.88.0.2 · host:8080 → 80
06
Container vivo é estado, processo e recursos concordando.
Ciclo de vida e processo principal
CREATED → RUNNING → STOPPING → EXITED → REMOVED
   │          │          │          │
recursos   logs + PID   SIGTERM   cleanup    apaga estado
                       prazo/SIGKILL
Uma transição é persistida atomicamente; estados intermediários permitem recuperação após falha.

O supervisor captura stdout e stderr em arquivos rotacionados, grava PID externo e horário, encaminha sinais e registra o código de saída. stop envia SIGTERM e espera; --time 0 pede encerramento imediato. rm recusa container em execução sem --force.

07
Operar sem adivinhar.
ps, inspect, logs, stop e rm
$ casulo ps
ID      IMAGE   STATE     PID    CPU       MEMORY
01J…    web:1   running   4822   37/50 ms  82/256 MiB

$ casulo inspect 01J…
state=running ip=10.88.0.2 cgroup=/casulo/01J…

$ casulo logs -f 01J…
servidor ouvindo em :80

$ casulo stop 01J… --time 10
$ casulo rm 01J…

O registro local não é um daemon. Cada comando lê estado bloqueado por arquivo e confirma a realidade no kernel. PIDs são combinados com o instante de início para evitar atingir um processo diferente após reutilização do número.

08
Casulo cabe num processo; Docker distribui responsabilidades.
Cliente, daemon, containerd, runc e OCI
Ecossistema maduroPapelNo Casulo
cliente Dockerinterface do usuárioCLI
daemon DockerAPI, imagens, rede e estadonão há daemon
containerdlifecycle de alto nívelmódulos locais
runccria container conforme bundle OCIruntime interno didático

A Open Container Initiative define formatos para imagem, runtime e distribuição. Para interoperar, precisaríamos ler manifests OCI, preparar bundles com config.json, obedecer hooks e implementar toda a semântica exigida. Similaridade de peças não significa conformidade.

09
Um comando, todas as fronteiras.
Casulo 1.0 de ponta a ponta
casulo run web:1
 → resolve imagem e verifica hashes
 → monta lower + upper + work = merged
 → cria namespaces e pivot_root
 → cria cgroup e anexa processo bloqueado
 → cria veth, bridge, rota e porta publicada
 → reduz privilégios e libera exec
 → captura logs e observa processo principal
 → recebe sinal, espera, força se necessário
 → limpa rede, cgroup e mounts em ordem inversa
Container é cooperação entre mecanismos do kernel, não uma caixa mágica.
Critérios objetivos

Testes verificam limites, falha de fork, OOM observável, conectividade entre dois containers, porta publicada, encaminhamento de sinais, logs, transições e rollback injetado em cada etapa. Todos usam prefixos exclusivos e comprovam ausência de mounts, interfaces, regras e cgroups órfãos.

10
O que construímos — e o que não prometemos.
Fechamento honesto

O Casulo 1.0 é funcional para laboratório Linux controlado. Não é seguro para imagens desconhecidas ou produção. Sua arquitetura torna visíveis processos, namespaces, rootfs, camadas, cgroups, rede e lifecycle.

Fontes primárias: cgroup v2 do kernel, capabilities(7), network_namespaces(7), veth do kernel e especificações OCI.

Resposta final

Um container Linux é um conjunto de processos comuns cuja visão, recursos, privilégios, filesystem e rede são organizados por mecanismos do kernel do host. A ferramenta coordena essas peças e precisa desfazê-las corretamente.

PROJETO COMPLETO · ESTADO EXATO DESTE CAPÍTULO

Baixe o Casulo 1.0.

Pacote exclusivo deste artigo, com código Rust, exemplos, testes condicionais para Linux e roteiro de limpeza. Não execute em produção nem use imagens desconhecidas.

Baixar Casulo 1.0 (.zip)Linux · Rust · requer privilégios documentados

O que este artigo fez você pensar?

Dúvidas, experiências e contrapontos ajudam a próxima pessoa a enxergar o assunto por outro ângulo.

Todos passam por moderação. Ao enviar, você concorda com a política de privacidade.

Receba os próximos artigos.

Uma mensagem quando uma nova investigação estiver pronta. Só isso.