Embora plataformas como Docker tenham popularizado a contêinerização, a ideia subjacente é uma funcionalidade nativa do Linux que existe há muito tempo. Este artigo detalha como construir um contêiner mínimo no Linux utilizando apenas as ferramentas do sistema, sem depender de plataformas externas. Ao longo deste guia, desvendaremos os segredos por trás da criação de sistemas Linux isolados, a limitação do chroot como uma solução completa de contêiner, o funcionamento dos namespaces para isolamento de processos e o gerenciamento de recursos através de cgroups.
Antes de mergulharmos na construção prática, é essencial compreender os componentes que formam um contêiner. Felizmente, o Linux já oferece os mecanismos necessários; o desafio reside em aprender a utilizá-los de forma eficaz. Nosso "kit de construção de contêineres" se baseará em três pilares principais:
- Namespaces: Responsáveis por isolar processos e seus ambientes de execução, garantindo que cada contêiner tenha sua própria visão do sistema.
- Chroot: Utilizado para isolar o sistema de arquivos, definindo um novo diretório raiz para um processo, simulando um ambiente separado.
- Cgroups (Control Groups): Permitem a limitação e o gerenciamento de recursos do sistema, como CPU e memória, alocados para cada contêiner.
Podemos começar a experimentar cada um desses mecanismos individualmente, mas para uma compreensão completa, seguiremos uma abordagem passo a passo, construindo um contêiner mínimo do zero.
Construindo um Sistema de Arquivos Isolado do Zero
Nosso primeiro objetivo é replicar a funcionalidade de um sistema de arquivos de contêiner. Começaremos com um diretório vazio e o transformaremos gradualmente em um ambiente Linux mínimo. Para o isolamento do sistema de arquivos, empregaremos o mecanismo chroot. Ele altera o diretório raiz de um processo, instruindo o sistema a considerar uma pasta específica como o novo /.
Passo 1. Criando um Sistema Vazio
Primeiro, criamos o diretório que servirá como raiz para nosso futuro contêiner:
bashmkdir xakep
Ao inspecionar o conteúdo, notamos que o diretório está vazio:
bashls xakep
Neste momento, é apenas uma pasta comum, mas em breve abrigará o sistema de arquivos do nosso contêiner.
Passo 2. Tentando Acessar o Sistema
Agora, tentamos iniciar o chroot:
bashsudo chroot xakep /bin/bash
Recebemos o erro esperado:
chroot: failed to run command '/bin/bash': No such file or directory
Isso é lógico, pois o diretório xakep ainda não contém um shell. Para o processo dentro do chroot, esta pasta é a raiz do sistema, e ele procura por /bin/bash especificamente lá.
Passo 3. Adicionando o Primeiro Programa
Criamos o diretório bin dentro de xakep e copiamos o bash para lá:
bashmkdir -p xakep/bin cp /bin/bash xakep/bin/
Tentamos novamente:
bashsudo chroot xakep /bin/bash
O erro persiste. A razão é que a maioria dos programas no Linux depende de bibliotecas dinâmicas, que ainda não estão presentes em nosso ambiente isolado.
Passo 4. Verificando Dependências
Para descobrir quais bibliotecas o bash necessita, usamos o comando ldd:
bashldd /bin/bash
O comando ldd lista as bibliotecas dinâmicas necessárias para a execução de um programa. Neste contexto, ele nos ajuda a identificar os arquivos essenciais para o nosso rootfs mínimo.
Passo 5. Adicionando Bibliotecas
Para que o bash funcione, precisamos copiar as bibliotecas dinâmicas listadas pelo ldd para o diretório /lib (ou /lib64, dependendo da arquitetura) dentro do nosso chroot. Por exemplo, se ldd /bin/bash mostrar que /lib64/ld-linux-x86-64.so.2 é necessário, você copiaria:
bashmkdir -p xakep/lib64 cp /lib64/ld-linux-x86-64.so.2 xakep/lib64/
Você precisará repetir este processo para todas as bibliotecas listadas pelo ldd que não sejam vdso ou linux-vdso.so.1.
Passo 6. Verificando Nosso Mini-Sistema
Após copiar as bibliotecas necessárias, podemos tentar executar o bash novamente:
bashsudo chroot xakep /bin/bash
Se tudo foi copiado corretamente, você agora estará dentro do shell do seu ambiente chroot. No entanto, a experiência ainda é muito limitada. Você não tem acesso a comandos básicos como ls, cp, mv, etc., pois eles também são programas que dependem de bibliotecas.
Passo 7. Adicionando Utilitários Básicos
Para tornar o ambiente minimamente utilizável, precisamos adicionar utilitários básicos. Uma maneira eficiente de fazer isso é utilizando o BusyBox, que combina muitas ferramentas comuns em um único executável pequeno. Para isso, você precisaria baixar o binário do BusyBox, copiá-lo para xakep/bin/ e, em seguida, criar links simbólicos para os comandos que deseja disponibilizar (como ls, cp, mv, sh, etc.) apontando para o executável do BusyBox.
Criando um Sistema Linux Mínimo com BusyBox
Passo 1. Criando o Sistema de Arquivos
Crie um diretório para o seu novo sistema de arquivos:
bashmkdir mycontainer
Passo 2. Copiando o BusyBox
Baixe o binário estático do BusyBox (ou compile-o) e copie-o para o diretório bin do seu contêiner:
bashmkdir -p mycontainer/bin cp /path/to/busybox mycontainer/bin/
Passo 3. Criando os Comandos
Entre no diretório bin do seu contêiner e crie links simbólicos para os comandos que o BusyBox oferece. Por exemplo:
bashcd mycontainer/bin/ ./busybox --install . cd ../..
O comando --install . dentro do diretório bin criará os links simbólicos necessários para os comandos suportados pelo BusyBox.
Passo 4. Executando o Sistema
Agora você pode tentar executar o shell:
bashsudo chroot mycontainer /bin/sh
Você terá um ambiente com utilitários básicos disponíveis.
Verificando a Limitação do Chroot
O chroot isola o sistema de arquivos, mas não isola processos, rede ou outros recursos do sistema host. Um processo rodando dentro de um chroot ainda pode ver e interagir com outros processos no sistema host, e pode até mesmo escapar do ambiente chroot se tiver privilégios suficientes ou explorar vulnerabilidades. Portanto, chroot por si só não constitui um contêiner seguro.
O Que Obtivemos Após o BusyBox
Com o chroot e o BusyBox, criamos um ambiente de sistema de arquivos isolado e com utilitários básicos. No entanto, ainda falta o isolamento de processos e o gerenciamento de recursos, que são cruciais para a funcionalidade completa de um contêiner.
Isolando Processos: Namespaces
Os namespaces são um recurso do kernel Linux que particiona recursos do kernel de forma que um processo em um namespace não veja os recursos de outro. Existem vários tipos de namespaces:
- PID namespace: Isola a árvore de processos. Um processo no PID namespace 1 é o primeiro processo dentro do contêiner.
- Network namespace: Isola a pilha de rede (interfaces de rede, tabelas de roteamento, regras de firewall).
- Mount namespace: Isola os pontos de montagem do sistema de arquivos.
- UTS namespace: Isola o hostname e o nome de domínio.
- IPC namespace: Isola os recursos de comunicação entre processos (semáforos, filas de mensagens).
- User namespace: Isola os IDs de usuário e grupo.
Para criar um contêiner com namespaces, você usaria ferramentas como unshare ou diretamente as chamadas de sistema clone() com as flags apropriadas. Por exemplo, para criar um novo PID e UTS namespace:
bashsudo unshare --pid --uts --mount-proc /bin/bash
Isso iniciará um novo shell em um novo PID e UTS namespace. O hostname dentro deste shell será o mesmo do host, a menos que você o altere explicitamente.
Alterando o Hostname do Contêiner
Dentro de um UTS namespace isolado, você pode definir um hostname diferente:
bashsudo unshare --uts --mount-proc hostname meu-container-host
Agora, ao iniciar um shell dentro deste namespace, o hostname será "meu-container-host".
Limitando Recursos com Cgroups
Cgroups (Control Groups) são um mecanismo do kernel Linux que permite alocar, priorizar, limitar e gerenciar o uso de recursos do sistema (CPU, memória, I/O de disco, rede) por um conjunto de processos. Eles são essenciais para garantir que um contêiner não consuma todos os recursos do host e afete outros serviços.
O Que São Cgroups
Cgroups organizam processos em hierarquias e aplicam limites a cada grupo. Existem duas versões principais: cgroups v1 e cgroups v2. A v2 é mais unificada e recomendada.
Criando Sua Própria Cgroup
Para gerenciar cgroups, você geralmente interage com o sistema de arquivos virtual montado em /sys/fs/cgroup. Vamos criar uma cgroup simples para limitar recursos.
Limitando Memória
Para limitar a memória, você criaria um diretório na hierarquia de memória e definiria os valores apropriados nos arquivos de controle.
Limitando CPU
Similarmente, para limitar o uso da CPU, você criaria um diretório na hierarquia de CPU e configuraria os parâmetros de limitação.
Adicionando um Processo a uma Cgroup
Após criar a cgroup, você adiciona o PID do processo que deseja controlar ao arquivo cgroup.procs (ou tasks em cgroups v1) dentro do diretório da cgroup.
Verificando o Limite de CPU
Você pode verificar o uso da CPU de um processo dentro de uma cgroup monitorando os arquivos de controle de CPU ou usando ferramentas como top ou htop e observando a coluna de uso da CPU.
Verificando a Limitação de Memória
Da mesma forma, o uso de memória pode ser monitorado através dos arquivos de controle de memória da cgroup.
Esquema Geral de um Contêiner
Um contêiner, como implementado por Docker ou outras plataformas, combina esses mecanismos:
- Isolamento do Sistema de Arquivos: Usando
chrootou, mais comumente,namespacesde montagem para criar um sistema de arquivos raiz isolado (geralmente a partir de uma imagem). - Isolamento de Processos: Usando
PID namespacespara que os processos dentro do contêiner tenham sua própria visão da lista de processos. - Isolamento de Rede: Usando
network namespacespara criar interfaces de rede virtuais e configurar o roteamento e firewall independentes. - Isolamento de Recursos: Usando
cgroupspara limitar e gerenciar o consumo de CPU, memória e I/O. - Isolamento de Hostname e IPC: Usando
UTS namespaceseIPC namespaces.
Limpeza
Após experimentar, é importante remover os diretórios e sistemas de arquivos criados para liberar espaço e evitar conflitos.
Conclusão
Construir um contêiner do zero, mesmo que mínimo, revela a complexidade e a engenhosidade por trás da contêinerização. Compreender chroot, namespaces e cgroups não apenas aprofunda o conhecimento sobre como Docker e outras ferramentas funcionam, mas também capacita os administradores e desenvolvedores a gerenciar e otimizar ambientes de contêineres de forma mais eficaz. Essa jornada prática demonstra que a contêinerização é uma poderosa orquestração de recursos nativos do Linux, oferecendo isolamento e eficiência sem a necessidade de virtualização completa.

