O que é um container? Isolando nosso primeiro processo em Rust
docker run parece fazer um computador nascer em segundos. Mas, se não surgiu uma máquina virtual, o que realmente foi criado?
namespaces · cgroups · mounts · signalsVOCABULÁRIO VISUAL
As peças do kernel que entram em cena.
Um programa é um arquivo com instruções. Quando o sistema o carrega, cria um processo: memória, registradores, arquivos abertos e um identificador. Já percorremos esse início no artigo Como um computador executa um programa por dentro.
| Coisa | O que possui | Kernel |
|---|---|---|
| Programa | bytes no disco | nenhum em execução |
| Processo | memória e estado de execução | usa o host |
| Container Linux | um ou mais processos com visões e limites | compartilha o host |
| Máquina virtual | hardware virtual e sistema convidado | possui kernel convidado |
Se um container não é uma máquina virtual pequena, o que impede seu processo de enxergar e consumir tudo no computador?
Para criar processo, abrir arquivo, montar filesystem ou enviar pacote, o programa pede ao kernel por uma chamada de sistema. A biblioteca Rust oferece uma interface mais segura, mas a decisão continua no kernel Linux. Por isso o Casulo é Linux: namespaces, cgroups, mounts e capabilities não são uma abstração portátil.
processo Casulo ── clone/unshare/mount ──▶ kernel Linux processo filho ◀── PID, memória, arquivos ──┘ nenhum kernel novo foi iniciado
Nosso primeiro filho enxerga o hostname, todos os processos e as montagens do host. O kernel permite colocar um processo em namespaces: grupos que recebem uma visão própria de certos recursos.
| Namespace | Visão separada | Demonstração |
|---|---|---|
| UTS | hostname | interno: casulo-01 |
| PID | árvore de processos | filho vira PID 1 dentro |
| mount | tabela de montagens | /proc próprio |
| IPC | filas e memória compartilhada | objetos do host somem |
| user | mapeamento de usuários | root interno pode mapear usuário externo |
O Casulo 0.1 usa clone com flags delimitadas. Quando user namespace não está disponível, encerra com erro claro; não continua fingindo isolamento.
let flags = CloneFlags::CLONE_NEWPID
| CloneFlags::CLONE_NEWUTS
| CloneFlags::CLONE_NEWNS
| CloneFlags::CLONE_NEWIPC;
clone(child_main, &mut stack, flags, Some(Signal::SIGCHLD as i32))?HOST DENTRO DO CASULO PID 4821 casulo PID 1 /bin/sh └─ PID 4822 /bin/sh ◀ mesmo ─▶ └─ PID 7 sleep └─ PID 4830 sleep host continua capaz de observar e administrar todos
O PID namespace é hierárquico. O host vê o identificador externo; dentro, o primeiro processo recebe 1. Isso traz responsabilidade.
Processos terminados permanecem como registros mínimos até o pai chamar wait. Se o pai morre, órfãos chegam ao PID 1. Nosso init encaminha sinais para o processo principal e recolhe filhos para evitar zumbis.
loop {
match waitpid(Pid::from_raw(-1), Some(WaitPidFlag::WNOHANG)) {
Ok(WaitStatus::Exited(pid, code)) => record_exit(pid, code),
Ok(WaitStatus::StillAlive) => break,
Err(Errno::ECHILD) => break,
other => handle_wait(other)?,
}
}SIGTERM pede parada graciosa. Depois do prazo configurado, SIGKILL encerra sem possibilidade de tratamento. O Casulo retorna o código do processo principal e sempre executa limpeza registrada.
$ sudo casulo run ./rootfs /bin/sh
[casulo] id=01J... host_pid=4822
/ # hostname
casulo-01
/ # ps
PID COMMAND
1 /bin/sh
8 psvalidar rootfs e programa → criar registro de rollback → clone com namespaces → configurar hostname e /proc → iniciar supervisor PID 1 → execve do programa → esperar saída → desmontar e limpar em ordem inversa
Esta versão separa hostname, PIDs, mounts e IPC e pode usar mapeamento de usuário. Ela ainda não limita CPU ou memória, não cria rede privada, não aplica filtro de chamadas de sistema e recebe um rootfs preparado. Um processo pode esgotar recursos do host. Não execute código desconhecido.
Testes comprovam hostname distinto, PID 1 interno, visibilidade externa pelo host, encaminhamento de SIGTERM, coleta de filhos e rollback de montagens temporárias. Testes privilegiados são marcados e pulados fora de Linux ou sem capacidades necessárias.
A implementação acompanha namespaces(7), clone(2), pid_namespaces(7) e signal(7). No próximo capítulo, responderemos de onde vêm /bin, /etc e bibliotecas — sem chamar chroot de container completo.
PROJETO COMPLETO · ESTADO EXATO DESTE CAPÍTULO
Baixe o Casulo 0.1.
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.