Tools: Update: Como foi possível a criação do Docker?

Tools: Update: Como foi possível a criação do Docker?

1. Como é feito o isolamento de processos

2. Como criar Namespaces e Cgroups na prática

Namespaces

Cgroups

Utilizando Namespaces e Cgroups para criar o nosso próprio contêiner

3. Conclusão 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). 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. 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. 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 Caso não tenha o unshare baixe o pacote util-linux Agora sim, estamos prontos para replicar os exemplos! Link da documentação do Namespace de PID: https://man7.org/linux/man-pages/man7/pid_namespaces.7.html Namespace de Rede (NET) 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 Link da documentação do Namespace de UTS: https://man7.org/linux/man-pages/man7/uts_namespaces.7.html 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 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. Templates let you quickly answer FAQs or store snippets for re-use. Hide child comments as well For further actions, you may consider blocking this person and/or reporting abuse

Code Block

Copy

unshare --version unshare --version unshare --version # Ubuntu/Debian sudo apt install util-linux # Fedora/RHEL sudo dnf install util-linux # Arch Linux sudo pacman -S util-linux # Ubuntu/Debian sudo apt install util-linux # Fedora/RHEL sudo dnf install util-linux # Arch Linux sudo pacman -S util-linux # Ubuntu/Debian sudo apt install util-linux # Fedora/RHEL sudo dnf install util-linux # Arch Linux sudo pacman -S util-linux # 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 # 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 # 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 # 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 # 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 # 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 # 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 # 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 # 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 # 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 # 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 # 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 # 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 # 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 # 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