- A Visão do Sistema de Arquivos em Processos Containerizados
- O Papel do Mount Namespace
- Imagens de Contêiner e rootfs
- Kernel Compartilhado e Dependências de Aplicativos em Contêineres
- O Conceito de Camadas em Imagens de Contêiner
- Camadas em Contêineres
- Vantagens do Design de Camadas em Imagens de Contêiner
- O Impacto das Imagens de Contêiner nos Fluxos de Trabalho de Desenvolvimento de Software
A Visão do Sistema de Arquivos em Processos Containerizados
Como é o sistema de arquivos para processos rodando dentro de um contêiner? Pode-se pensar imediatamente que isso se relaciona ao Mount Namespace — os processos dentro de um contêiner devem ver um sistema de arquivos completamente independente. Dessa forma, operações podem ser realizadas nos diretórios do contêiner, como /tmp, sem qualquer influência da máquina host ou de outros contêineres. Será que é esse o caso?
O Papel do Mount Namespace
O Mount Namespace funciona de forma um pouco diferente dos outros namespaces, pois seu efeito na visão do sistema de arquivos do processo do contêiner só se concretiza com uma operação de montagem. No entanto, como usuários comuns, desejamos um cenário mais amigável: toda vez que um novo contêiner é criado, queremos que o processo do contêiner veja um sistema de arquivos que seja um ambiente isolado, em vez de herdado do host. Como isso pode ser alcançado?
Não é difícil imaginar que poderíamos remontar todo o diretório raiz “/” antes de iniciar o processo do contêiner. Graças ao Mount Namespace, essa montagem seria invisível para o host, permitindo que o processo do contêiner opere livremente dentro dela. No sistema operacional Linux, existe um comando chamado chroot que pode realizar essa tarefa convenientemente em um shell. Como o nome sugere, ele ajuda a “mudar o sistema de arquivos raiz”, redirecionando o diretório raiz do processo para um local especificado.
Imagens de Contêiner e rootfs
Na verdade, o Mount Namespace foi inventado com base em melhorias contínuas no chroot e é o primeiro Namespace no Linux. Para tornar esse diretório raiz mais “real” para contêineres, normalmente montamos um sistema de arquivos completo de um sistema operacional sob essa raiz, como um ISO do Ubuntu 16.04. Consequentemente, após o contêiner iniciar, executar “ls /” dentro do contêiner revela todos os diretórios e arquivos do Ubuntu 16.04. Esse sistema de arquivos montado no diretório raiz do contêiner, fornecendo um ambiente de execução isolado para processos do contêiner, é conhecido como “imagem de contêiner”. Ele também tem um termo mais técnico: rootfs (sistema de arquivos raiz).
O /bin/bash executado após entrar no contêiner é o arquivo executável dentro do diretório /bin, completamente distinto do /bin/bash do host. Agora você deve entender que o princípio central por trás dos projetos Docker envolve essencialmente configurar o Linux Namespace para o processo do usuário a ser criado, definir parâmetros específicos de Cgroups e alterar o diretório raiz (Change Root) do processo. Assim, um contêiner completo nasce.
Kernel Compartilhado e Dependências de Aplicativos em Contêineres
No entanto, o Docker prefere usar a chamada de sistema pivot_root para a etapa final de troca, recorrendo ao chroot se o sistema não suportar pivot_root. Embora essas duas chamadas de sistema tenham funcionalidades semelhantes, existem diferenças sutis. Além disso, é essencial esclarecer que o rootfs inclui apenas os arquivos, configurações e diretórios de um sistema operacional, mas não o kernel. No Linux, essas duas partes são armazenadas separadamente, com a imagem do kernel de uma versão específica sendo carregada apenas durante a inicialização. Portanto, o rootfs inclui apenas o “invólucro” do sistema operacional, não sua “alma”.
Então, onde está a “alma” do sistema operacional para contêineres? Na realidade, todos os contêineres na mesma máquina compartilham o kernel do sistema operacional do host. Isso significa que, se sua aplicação precisar configurar parâmetros do kernel, carregar módulos adicionais do kernel ou interagir diretamente com o kernel, você deve observar que essas operações e dependências visam o kernel do sistema operacional do host, que é uma “variável global” para todos os contêineres nessa máquina. Esta é uma das principais desvantagens dos contêineres em comparação com máquinas virtuais: estas últimas não só possuem hardware simulado como uma sandbox, mas também executam um sistema operacional convidado completo dentro de cada sandbox para uso pelas aplicações. No entanto, devido à existência do rootfs, os contêineres possuem uma característica importante que tem sido amplamente divulgada: consistência.
O Conceito de Camadas em Imagens de Contêiner
O que é a “consistência” dos contêineres? Devido às diferenças entre ambientes de servidor em nuvem e locais, o processo de empacotamento de aplicações sempre foi uma das etapas mais “dolorosas” ao usar PaaS. No entanto, com contêineres, ou mais precisamente, com imagens de contêiner (ou seja, rootfs), esse problema foi elegantemente resolvido.
Como o rootfs empacota não apenas a aplicação, mas todo o sistema de arquivos e diretórios do sistema operacional, ele encapsula todas as dependências necessárias para a execução da aplicação. Na verdade, para a maioria dos desenvolvedores, seu entendimento das dependências da aplicação tem sido limitado ao nível da linguagem de programação, como Godeps.json do Golang. No entanto, um fato há muito negligenciado é que, para uma aplicação, o próprio sistema operacional é a “biblioteca de dependências” mais completa que ela precisa para ser executada.
Com a capacidade das imagens de contêiner de “empacotar o sistema operacional”, esse ambiente de dependência fundamental finalmente se torna parte da sandbox da aplicação. Isso concede aos contêineres sua tão alardeada consistência: independentemente de ser em uma máquina local, na nuvem ou em qualquer outro lugar, os usuários só precisam descompactar a imagem do contêiner para recriar o ambiente de execução completo necessário para a aplicação ser executada.
Design de Camadas Incrementais em Imagens de Contêiner
Essa consistência no nível do sistema operacional preenche a lacuna entre o desenvolvimento local e os ambientes de execução remota para as aplicações. No entanto, você pode ter notado outro problema complicado: precisamos recriar o rootfs toda vez que desenvolvemos uma nova aplicação ou atualizamos uma existente? Uma solução intuitiva poderia ser salvar um rootfs após cada operação “significativa” durante sua criação, permitindo que colegas usem o rootfs de que precisam.
Essa solução, no entanto, não é escalável. A razão é que, uma vez que os colegas modifiquem esse rootfs, não há relação entre o rootfs antigo e o novo, levando a uma fragmentação extrema. Como essas modificações são baseadas em um rootfs antigo, podemos tornar essas mudanças incrementais? O benefício dessa abordagem é que todos só precisam manter o conteúdo incremental em relação ao rootfs base, em vez de criar um “fork” a cada modificação.
A resposta é, claro, sim. É precisamente por isso que o Docker não seguiu o processo padrão de criação de rootfs ao implementar imagens Docker, mas sim fez uma pequena inovação: o Docker introduziu o conceito de camadas no design de suas imagens. Cada operação que os usuários realizam para criar uma imagem gera uma camada, que é um rootfs incremental. Essa ideia não surgiu do nada, mas utilizou uma capacidade chamada Union File System (UnionFS), cuja função principal é montar unificadamente (union mount) vários diretórios de locais diferentes em um único diretório.
Camadas em Contêineres

Camadas em Contêineres
Parte 1: Camadas Somente Leitura. Estas são as cinco camadas inferiores do rootfs deste contêiner, correspondentes às cinco camadas da imagem ubuntu:latest. Elas são montadas como somente leitura (ro+wh, ou seja, readonly+whiteout). Cada camada contém incrementalmente parte do sistema operacional Ubuntu.
Parte 2: Camada de Leitura e Escrita. Esta é a camada superior do rootfs deste contêiner (6e3be5d2ecccae7cc), montada como rw, ou leitura e escrita. Antes de qualquer arquivo ser escrito, este diretório está vazio. Assim que uma operação de escrita é realizada dentro do contêiner, as modificações aparecem incrementalmente nesta camada. Mas você já considerou o que acontece se quiser deletar um arquivo da camada somente leitura? Para alcançar essa exclusão, o AuFS cria um arquivo whiteout na camada de leitura e escrita para “ocultar” o arquivo na camada somente leitura. Por exemplo, deletar um arquivo chamado foo da camada somente leitura na verdade cria um arquivo chamado .wh.foo na camada de leitura e escrita. Assim, quando essas camadas são montadas unificadamente, o arquivo foo é ocultado pelo arquivo .wh.foo e “desaparece”. Essa funcionalidade é o que o método de montagem “ro+wh” significa, ou seja, somente leitura mais whiteout.
Parte 3: Camada Init. Esta é uma camada interna gerada pelo projeto Docker, terminando com “-init”, situada entre as camadas somente leitura e de leitura e escrita. A camada Init é usada especificamente para armazenar informações como /etc/hosts e /etc/resolv.conf. A necessidade de tal camada surge porque esses arquivos originalmente pertencem à imagem Ubuntu somente leitura, mas frequentemente exigem valores específicos, como o nome do host, a serem escritos na inicialização do contêiner. Portanto, modificações são necessárias na camada de leitura e escrita. No entanto, essas modificações normalmente se aplicam apenas ao contêiner atual e não devem ser confirmadas com a camada de leitura e escrita ao executar docker commit. Portanto, a abordagem do Docker é montar esses arquivos modificados em uma camada separada. Quando os usuários executam docker commit, apenas a camada de leitura e escrita é confirmada, excluindo esse conteúdo.
Vantagens do Design de Camadas em Imagens de Contêiner
Através do design de “imagem em camadas”, com as imagens Docker como núcleo, técnicos de diferentes empresas e equipes estão intimamente conectados. Além disso, como as operações em imagens de contêiner são incrementais, o conteúdo puxado ou enviado a cada vez é muito menor do que múltiplos sistemas operacionais completos; camadas compartilhadas significam que o espaço total necessário para todas essas imagens de contêiner é menor do que a soma de cada imagem individualmente. Essa agilidade na colaboração baseada em imagens de contêiner supera em muito a de discos de imagem de máquina virtual, que podem ter vários GBs de tamanho.
Mais importante ainda, uma vez que uma imagem é publicada, baixá-la em qualquer lugar do mundo resulta exatamente no mesmo conteúdo, reproduzindo totalmente o ambiente original criado pelo criador da imagem.
O Impacto das Imagens de Contêiner nos Fluxos de Trabalho de Desenvolvimento de Software
A invenção das imagens de contêiner não apenas conecta cada etapa do processo “desenvolvimento - teste - implantação”, mas também significa que as imagens de contêiner se tornarão o método principal de distribuição de software no futuro. Esse método de distribuição oferece vantagens como leveza, alta consistência e facilitação da colaboração, tornando o desenvolvimento e a implantação de software mais eficientes e confiáveis. Com sua leveza, consistência e eficiência, a tecnologia de contêiner está se tornando cada vez mais uma ferramenta essencial no desenvolvimento e operação de software. À medida que a tecnologia continua a evoluir e inovar, há razões para acreditar que a tecnologia de contêiner desempenhará um papel ainda mais significativo no futuro.
Novita AI, a plataforma completa para criatividade ilimitada que oferece acesso a mais de 100 APIs. Desde geração de imagem e processamento de linguagem até aprimoramento de áudio e manipulação de vídeo, pagamento conforme o uso, libera você das dores de cabeça da manutenção de GPU enquanto constrói seus próprios produtos. Experimente gratuitamente.
