Configurando namespaces e cgroups manualmente para entender containers
1. Introdução aos fundamentos de containers Linux
Containers Linux não são uma tecnologia mágica — eles são construídos sobre dois mecanismos fundamentais do kernel: namespaces e cgroups (control groups). Enquanto namespaces fornecem isolamento de recursos (cada processo vê apenas seu próprio mundo), cgroups impõem limites sobre quanto desses recursos um processo pode consumir.
A diferença entre um container completo (Docker, Podman) e a criação manual é que ferramentas modernas automatizam dezenas de configurações complexas. Ao fazer manualmente, você ganha compreensão profunda do que realmente acontece quando executa docker run.
Pré-requisitos: Kernel Linux 4.15+ com suporte a namespaces e cgroups v2. Verifique com:
uname -r
mount | grep cgroup
2. Isolando processos com namespaces
Namespaces isolam processos em "visões" separadas do sistema. Vamos começar com o namespace de montagem:
# Criar um processo filho em novo namespace de montagem
unshare --mount --propagation private bash
Para isolar PID, rede e UTS simultaneamente:
# Isolar PID, rede e hostname
unshare --pid --net --uts --fork bash
Exemplo prático: executar um shell em namespace de rede vazio:
# No terminal 1
unshare --net bash
ip link show # Mostra apenas loopback
ip addr # Sem interfaces reais
No namespace de rede, você não tem acesso à internet do host. Para verificar:
ping 8.8.8.8 # Falha - rede isolada
3. Montando um sistema de arquivos isolado
Agora vamos criar uma raiz isolada com pivot_root:
# Baixar Alpine Linux minimalista
cd /tmp
wget https://dl-cdn.alpinelinux.org/alpine/v3.19/releases/x86_64/alpine-minirootfs-3.19.1-x86_64.tar.gz
mkdir container_root
tar -xzf alpine-minirootfs-3.19.1-x86_64.tar.gz -C container_root
# Criar diretório para novo root
mkdir -p container_root/old_root
# Montar sistemas de arquivos essenciais
mount --bind /proc container_root/proc
mount --bind /sys container_root/sys
mount --bind /dev container_root/dev
# Usar pivot_root para mudar a raiz
pivot_root container_root container_root/old_root
Após pivot_root, o sistema vê apenas os arquivos dentro de container_root. Para verificar:
ls / # Mostra apenas o conteúdo do Alpine
4. Limitando recursos com cgroups v2
Cgroups v2 usa uma hierarquia unificada de diretórios. Vamos criar limites:
# Criar subgrupo
mkdir /sys/fs/cgroup/my_container
# Limitar CPU a 50% de um core (50000 microssegundos de 100000)
echo "50000 100000" > /sys/fs/cgroup/my_container/cpu.max
# Limitar memória a 64MB
echo 67108864 > /sys/fs/cgroup/my_container/memory.max
# Verificar configurações
cat /sys/fs/cgroup/my_container/cpu.max
cat /sys/fs/cgroup/my_container/memory.max
Depuração: Para verificar se os limites estão ativos:
stat /sys/fs/cgroup/my_container/
cat /sys/fs/cgroup/my_container/cpu.stat
5. Combinando namespaces e cgroups em um container funcional
Script completo para criar um container funcional:
#!/bin/bash
set -e
# Configurar cgroups
CGROUP_DIR="/sys/fs/cgroup/meu_container"
mkdir -p $CGROUP_DIR
echo "50000 100000" > $CGROUP_DIR/cpu.max
echo 67108864 > $CGROUP_DIR/memory.max
# Criar diretório raiz do container
mkdir -p /tmp/container_root
cd /tmp/container_root
# Baixar e extrair Alpine
wget -q https://dl-cdn.alpinelinux.org/alpine/v3.19/releases/x86_64/alpine-minirootfs-3.19.1-x86_64.tar.gz
tar -xzf alpine-minirootfs-3.19.1-x86_64.tar.gz
rm alpine-minirootfs-3.19.1-x86_64.tar.gz
# Criar par veth para rede
ip link add veth0 type veth peer name veth1
ip link set veth0 up
ip addr add 10.0.1.1/24 dev veth0
# Iniciar processo em namespaces isolados
unshare --mount --pid --net --uts --fork /bin/bash << 'INNER'
# Configurar hostname
hostname meu-container
# Mover veth1 para o namespace
ip link set veth1 netns $$
ip addr add 10.0.1.2/24 dev veth1
ip link set veth1 up
# Montar sistemas de arquivos
mount -t proc proc /proc
mount -t sysfs sys /sys
# Aplicar cgroups
echo $$ > /sys/fs/cgroup/meu_container/cgroup.procs
# Executar comando
exec busybox sh
INNER
Para executar um comando simples dentro do ambiente:
# Depois de entrar no shell do container
echo "Container funcionando!"
ps aux # Mostra apenas processos do container
6. Gerenciamento de processos e limpeza
Para encerrar processos dentro do namespace:
# Do host, encontrar PID real
ps aux | grep busybox
kill -9 <PID> # Mata o processo no host
# Ou dentro do namespace
kill -9 1 # Mata o processo init do container
Para limpar completamente:
# Remover cgroups
rmdir /sys/fs/cgroup/meu_container
# Desmontar pontos de montagem
umount /tmp/container_root/proc
umount /tmp/container_root/sys
umount /tmp/container_root/dev
# Remover par veth
ip link delete veth0
# Remover diretório raiz
rm -rf /tmp/container_root
Boas práticas:
- Sempre verifique processos órfãos com ps aux | grep defunct
- Use namespace sandboxing para evitar vazamentos
- Implemente timeouts para processos que não respondem
7. Comparação com ferramentas modernas e próximos passos
Docker e Podman abstraem todo esse processo em comandos simples:
docker run -it alpine sh
Esse comando executa automaticamente:
1. Criação de múltiplos namespaces (mount, pid, net, uts, ipc, user)
2. Configuração de cgroups com limites padrão
3. Montagem de sistemas de arquivos
4. Configuração de rede com bridge
5. Gerenciamento de ciclo de vida
Limitações da abordagem manual:
- Rede complexa (múltiplos veth pairs, bridges, NAT)
- Armazenamento persistente (volumes, camadas)
- Isolamento de usuário (user namespace)
- Limites de I/O (blkio)
Próximos experimentos sugeridos:
- Adicionar isolamento de usuário com unshare --user
- Configurar limites de I/O com io.max em cgroups v2
- Implementar montagens bind para volumes
- Criar uma bridge de rede manual com ip link add br0 type bridge
Referências
- Documentação oficial de namespaces Linux — Página de manual completa sobre os 8 tipos de namespaces no Linux
- Documentação cgroups v2 do kernel — Guia oficial da hierarquia unificada de cgroups v2
- Container from scratch com namespaces e cgroups — Tutorial passo a passo para criar containers manualmente
- Entendendo containers: um guia prático — Explicação acessível de Julia Evans sobre os mecanismos de containers
- unshare(1) - Linux manual page — Documentação do comando unshare para executar programas em namespaces isolados
- Alpine Linux - Mini root filesystem — Distribuição minimalista usada como base para containers leves
- Linux containers in 500 lines of code — Implementação minimalista de container runtime explicada em detalhes