Nos últimos dias, tenho estudado sobre Docker e contêineres, e um assunto me chamou bastante atenção: como os contêineres conseguem isolar recursos do sistema operacional no próprio kernel, sem precisar criar um sistema operacional completo, como acontece com as máquinas virtuais (VMs).
1. Como é feito o isolamento de processos
Para que isso seja possível, as ferramentas de conteinerização como o Docker utilizam dois recursos do kernel Linux:
Cgroups (Control Groups): permitem regular quanto de recursos, como CPU e memória, o processo dentro do namespace poderá utilizar.
Namespace: é o responsável pelo isolamento. Cada namespace isola uma visão específica do sistema para um grupo de processos, como a árvore de processos (PIDs), as interfaces de rede ou o hostname, fazendo com que esses processos enxerguem apenas o que está dentro do seu próprio ambiente.
Resumindo, o namespace define o que os processos podem ver e acessar, e o cgroup define quanto de recursos do computador esses processos podem consumir.
Abaixo criei uma ilustração a qual eu acredito deixar mais clara a diferença entre os dois conceitos.
Importante lembrar que o nosso sistema operacional tem um namespace raiz onde os processos rodam por padrão. Quando executamos um contêiner Docker, um novo namespace é criado para isolar os processos desse contêiner.
E temos o Cgroup com a função de controlar quanto esses processos podem utilizar de recursos computacionais.
2. Como criar Namespaces e Cgroups na prática
Agora que vimos uma visão geral de como os namespaces e os cgroups funcionam que tal tentarmos construir os nossos próprios Namespaces e Cgroups?
OBS: Nos exemplos abaixo, procurei colocar a explicação completa do significado de cada conceito para um melhor entendimento.
Namespaces
Um namespace envolve um recurso global do sistema numa abstração que faz com que os processos dentro do namespace acreditem que têm sua própria instância isolada desse recurso. Existem 8 tipos de namespaces no Linux moderno.
Link oficial da documentação Linux sobre Namespaces: https://man7.org/linux/man-pages/man7/namespaces.7.html
Nos exemplos abaixo estarei mostrando como você pode criar Namespaces de PID, Rede e de UTC. (OBS: Lembrando que você só consegue replicar esses exemplos em um ambiente linux)
Antes de iniciar verique se você já tem o unshare instalado que será usado para criar alguns dos namespaces
unshare --version
Caso não tenha o unshare baixe o pacote util-linux
# Ubuntu/Debian
sudo apt install util-linux
# Fedora/RHEL
sudo dnf install util-linux
# Arch Linux
sudo pacman -S util-linux
Agora sim, estamos prontos para replicar os exemplos!
Namespace de PID
# Ver os namespaces do processo atual
ls -la /proc/$$/ns/
# Criar um namespace de PID isolado
sudo unshare --pid --fork --mount-proc bash
# Dentro do namespace — verificar que somos o PID 1
ps aux
# Sair do namespace
exit
Link da documentação do Namespace de PID: https://man7.org/linux/man-pages/man7/pid_namespaces.7.html
Namespace de Rede (NET)
# Criar um namespace de rede
sudo ip netns add meu-container
# Listar namespaces de rede existentes
ip netns list
# Criar par de interfaces virtuais
sudo ip link add veth0 type veth peer name veth1
# Mover veth1 para dentro do namespace
sudo ip link set veth1 netns meu-container
# Configurar IPs
sudo ip addr add 10.0.0.1/24 dev veth0
sudo ip netns exec meu-container ip addr add 10.0.0.2/24 dev veth1
# Ativar as interfaces
sudo ip link set veth0 up
sudo ip netns exec meu-container ip link set veth1 up
sudo ip netns exec meu-container ip link set lo up
# Testar comunicação
sudo ip netns exec meu-container ping 10.0.0.1
# Limpar
sudo ip netns delete meu-container
Link da documentação do Namespace de NET: https://man7.org/linux/man-pages/man7/network_namespaces.7.html
Namespace de UTS (UNIX Time-Sharing) - Hostname Isolado
# Criar namespace de UTS (hostname isolado)
sudo unshare --uts bash
# Dentro do namespace — alterar o hostname sem afetar o host
hostname meu-container
hostname
# Em outro terminal, confirmar que o host não foi afetado
hostname
# Sair do namespace
exit
Link da documentação do Namespace de UTS: https://man7.org/linux/man-pages/man7/uts_namespaces.7.html
Cgroups
Control Groups (cgroups) são um mecanismo do kernel Linux para organizar processos em grupos hierárquicos e limitar, contabilizar e controlar o uso de recursos como CPU, memória, I/O e rede.
Link oficial da documentação Linux sobre Cgroups: https://www.kernel.org/doc/html/latest/admin-guide/cgroup-v2.html
# Ver a hierarquia de cgroups do sistema
systemd-cgls
# Ver os controllers disponíveis
cat /sys/fs/cgroup/cgroup.controllers
# Criar um cgroup
sudo mkdir /sys/fs/cgroup/meu-container
# Habilitar os controllers de CPU e memória
echo "+cpu +memory" | sudo tee /sys/fs/cgroup/cgroup.subtree_control
# Limitar a 50% de 1 CPU
echo "50000 100000" | sudo tee /sys/fs/cgroup/meu-container/cpu.max
# Limitar memória a 128 MB
echo 134217728 | sudo tee /sys/fs/cgroup/meu-container/memory.max
# Associar o processo atual ao cgroup
echo $$ | sudo tee /sys/fs/cgroup/meu-container/cgroup.procs
# Verificar que o processo está no cgroup
cat /proc/$$/cgroup
# Verificar os limites aplicados
cat /sys/fs/cgroup/meu-container/cpu.max
cat /sys/fs/cgroup/meu-container/memory.max
# Limpar — mover processo de volta ao root e remover o cgroup
echo $$ | sudo tee /sys/fs/cgroup/cgroup.procs
sudo rmdir /sys/fs/cgroup/meu-container
Utilizando Namespaces e Cgroups para criar o nosso próprio contêiner
# Baixar o rootfs
mkdir ~/rootfs
curl -L https://dl-cdn.alpinelinux.org/alpine/v3.19/releases/x86_64/alpine-minirootfs-3.19.0-x86_64.tar.gz \
| tar -xz -C ~/rootfs
# Criar o cgroup no host
sudo mkdir -p /sys/fs/cgroup/meu-container
echo "+cpu +memory" | sudo tee /sys/fs/cgroup/cgroup.subtree_control
echo "50000 100000" | sudo tee /sys/fs/cgroup/meu-container/cpu.max
echo 134217728 | sudo tee /sys/fs/cgroup/meu-container/memory.max
# Entrar no namespace
sudo unshare \
--pid \
--net \
--mount \
--uts \
--ipc \
--fork \
--mount-proc \
chroot ~/rootfs /bin/sh
# De outro terminal no HOST (não dentro do container), pegar o PID e associar ao cgroup
PID=$(pgrep -f "chroot.*rootfs" | head -1)
echo $PID | sudo tee /sys/fs/cgroup/meu-container/cgroup.procs
3. Conclusão
Como você pode ver utilizar os comandos docker run e docker compose up facilitam bastante as coisas hahaha...
Inclusive, o Docker utiliza bem mais conceitos do que apenas Cgroups e Namespaces para criar containers, como o Union filesystem (que é o que permite as camadas de imagem) mas o meu objetivo era apenas mostrar como o Docker consegue fazer o isolamento de processos.
Por fim, agradeço à sua atenção e qualquer dúvida, sugestão, crítica ou erro encontrado no texto acima não hesite em me chamar.
Um Abraço!!!


Top comments (0)