Exploração

📦 Como containers funcionam

Containers não são máquinas virtuais — não há hypervisor, não há kernel separado. Eles são processos Linux com dois superpoderes do kernel: namespaces (isolamento de visibilidade) e cgroups (limitação de recursos). A imagem é apenas um sistema de arquivos em camadas.

① Intuição

Um container é uma ilusão cuidadosamente construída. Por dentro, o processo acredita que é o único no sistema, tem seu próprio IP e vê apenas seus arquivos. Por fora, o host sabe que é apenas mais um processo entre muitos.

Essa ilusão é criada inteiramente pelo kernel Linux — sem nenhuma camada de virtualização extra. É por isso que containers sobem em milissegundos e consomem muito menos memória que VMs.

② Visualização — explore cada camada

Use as abas para explorar a estrutura de uma imagem Docker, os namespaces que criam isolamento, os cgroups que limitam recursos, e a comparação com VMs.

Uma imagem Docker é um stack de camadas imutáveis. Cada instrução Dockerfile cria uma camada. Camadas são compartilhadas entre imagens — sem duplicação.
Sua app: server.py
18 KB
camada 5
Dependências (pip)
62 MB
camada 4
Sistema base (Alpine)
5 MB
camada 3
Sistema de arquivos
2 MB
camada 2
Metadados / Config
1 KB
camada 1
Clique em uma camada para mais detalhes. A camada mais alta sobrescreve as de baixo.

③ Explicação técnica

Imagens em camadas — Dockerfile

Cada instrução no Dockerfile cria uma camada imutável. Camadas são compartilhadas entre imagens — se duas imagens usam a mesma base Alpine, essa camada existe apenas uma vez no disco:

# Cada instrução = uma camada no índice
FROM python:3.12-alpine          # camada base (5 MB)

WORKDIR /app

COPY requirements.txt .
RUN pip install -r requirements.txt  # camada de dependências

COPY . .                         # camada da sua app

EXPOSE 8080
CMD ["python", "server.py"]

Namespaces — isolamento por syscall

A syscall clone() (ou unshare()) cria namespaces. O Docker usa 6 deles por container:

# Python: criar namespace isolado (simplificado)
import os
import ctypes

# Em C: clone(fn, stack, CLONE_NEWPID | CLONE_NEWNET | ...)
# Python-nível: usar unshare()
libc = ctypes.CDLL("libc.so.6")

CLONE_NEWPID = 0x20000000
CLONE_NEWNET = 0x40000000

# Criar processo em novo namespace de PIDs e rede
libc.unshare(CLONE_NEWPID | CLONE_NEWNET)

# A partir daqui, este processo tem PID 1
# e uma interface de rede virtual própria

Cgroups — limitação de recursos

O kernel expõe cgroups como arquivos em /sys/fs/cgroup/. Escrever neles é tão simples quanto escrever em arquivo:

# Criar cgroup e limitar CPU/memória
import os

CGROUP = "/sys/fs/cgroup/meu-container"

# 1. Criar o cgroup
os.makedirs(CGROUP, exist_ok=True)

# 2. Limitar CPU a 50% de 1 core
with open(f"{CGROUP}/cpu.max", "w") as f:
    f.write("50000 100000")   # 50ms a cada 100ms

# 3. Limitar memória a 256 MB
with open(f"{CGROUP}/memory.max", "w") as f:
    f.write(str(256 * 1024 * 1024))

# 4. Adicionar nosso PID ao cgroup
with open(f"{CGROUP}/cgroup.procs", "w") as f:
    f.write(str(os.getpid()))

O que docker run realmente faz

O Docker é um orchestrador que combina namespaces, cgroups, bind mounts e iptables em uma única chamada de alto nível:

# docker run por dentro (simplificado)

docker run \
  --memory=256m \         # cgroup: memory.max
  --cpus=0.5 \            # cgroup: cpu.max
  --network=bridge \      # namespace: NET
  --pid=container \       # namespace: PID
  -v /host/dados:/app/dados \  # bind mount
  -p 8080:80 \            # port mapping (iptables NAT)
  minha-imagem:latest

④ Projeto

Container do zero em Python (Linux required):

  1. Fork um processo filho
  2. No filho, chame unshare(CLONE_NEWPID | CLONE_NEWUTS)
  3. Mude o hostname para "meu-container" com os.uname_result
  4. Faça chroot para um rootfs mínimo (Alpine tarballed)
  5. Monte /proc para que comandos como ps funcionem
  6. Execute um shell — parabéns, você criou um container!

⑤ Perguntas rápidas

⑥ Aplicações no mundo real