Namespaces e cgroups: a base do isolamento no Linux
1. Introdução ao isolamento no Linux
Nos primórdios da computação, servidores físicos executavam uma única aplicação por máquina. Com o tempo, a virtualização permitiu rodar múltiplos sistemas operacionais em um mesmo hardware, mas com custo elevado de recursos. A evolução natural foi o surgimento dos contêineres, que compartilham o kernel do host enquanto isolam processos em ambientes independentes.
O problema central é conciliar o compartilhamento eficiente de recursos com o isolamento necessário para segurança e estabilidade. Dois mecanismos do kernel Linux resolvem essa equação: namespaces (isolamento de visão) e cgroups (isolamento de recursos). Juntos, formam a espinha dorsal de tecnologias como Docker, Podman, LXC e Kubernetes.
2. Namespaces: isolando visões do sistema
Namespaces criam instâncias isoladas de recursos globais do sistema. Um processo dentro de um namespace enxerga apenas os recursos pertencentes àquele namespace, como se estivesse em um sistema independente.
Os principais tipos de namespace no Linux são:
| Tipo | Flag | Isola |
|---|---|---|
| PID | CLONE_NEWPID | IDs de processo |
| Network | CLONE_NEWNET | Interfaces, roteamento, portas |
| Mount | CLONE_NEWNS | Pontos de montagem |
| UTS | CLONE_NEWUTS | Hostname e domínio NIS |
| IPC | CLONE_NEWIPC | Filas de mensagem, semáforos |
| User | CLONE_NEWUSER | IDs de usuário e grupo |
| Cgroup | CLONE_NEWCGROUP | Visão da hierarquia de cgroups |
Exemplo prático: criando um processo em novo namespace
O código abaixo usa a chamada de sistema clone() para criar um processo filho em namespaces isolados:
#define _GNU_SOURCE
#include <sched.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#define STACK_SIZE (1024 * 1024)
static int child_func(void *arg) {
printf("Filho: PID %d, hostname: ", getpid());
sethostname("container", 9);
system("hostname");
return 0;
}
int main() {
char *stack = malloc(STACK_SIZE);
if (!stack) { perror("malloc"); exit(1); }
pid_t pid = clone(child_func,
stack + STACK_SIZE,
CLONE_NEWPID | CLONE_NEWUTS | CLONE_NEWNS | SIGCHLD,
NULL);
if (pid == -1) { perror("clone"); exit(1); }
waitpid(pid, NULL, 0);
printf("Pai: PID %d, hostname: ", getpid());
system("hostname");
free(stack);
return 0;
}
Para testar sem compilar, use o comando unshare:
$ sudo unshare --pid --uts --mount --fork /bin/bash
# O shell agora está em namespaces isolados
# hostname container1
# echo $$
1
# exit
3. Funcionamento interno dos namespaces
No kernel, cada namespace é representado por uma estrutura de dados específica. A estrutura central é struct nsproxy, que contém ponteiros para todos os namespaces de um processo:
struct nsproxy {
atomic_t count;
struct uts_namespace *uts_ns;
struct ipc_namespace *ipc_ns;
struct mnt_namespace *mnt_ns;
struct pid_namespace *pid_ns_for_children;
struct net *net_ns;
struct cgroup_namespace *cgroup_ns;
struct time_namespace *time_ns;
};
Quando um processo usa clone() com flags de namespace, o kernel cria novas estruturas filhas. A hierarquia é gerenciada por relações pai/filho: um namespace filho enxerga os recursos do pai, mas não vice-versa. Por exemplo, um processo no namespace PID filho vê seu próprio PID 1 e pode ver processos do pai, mas o pai não vê os processos do filho.
Limitações: namespaces não oferecem isolamento completo. Um processo com capacidades privilegiadas (CAP_SYS_ADMIN) pode escapar de certos namespaces. O namespace de usuário (user namespace) mitiga parcialmente esse risco, permitindo que processos não-root criem namespaces.
4. Cgroups: controlando recursos do sistema
Cgroups (control groups) permitem limitar, priorizar e monitorar o uso de recursos por grupos de processos. Enquanto namespaces isolam a visão, cgroups controlam o consumo.
Arquitetura: cgroups v1 vs. v2
O cgroups v1 (legado) usava múltiplas hierarquias, uma para cada subsistema (cpu, memória, etc.). Isso gerava complexidade e inconsistências. O cgroups v2 (unified hierarchy) unifica todos os subsistemas em uma única árvore em /sys/fs/cgroup/.
Exemplo prático: criando grupos de controle manualmente
# Criar um grupo filho
sudo mkdir /sys/fs/cgroup/meu_grupo
# Limitar uso de CPU a 50% de um core
echo "50000 100000" | sudo tee /sys/fs/cgroup/meu_grupo/cpu.max
# Limitar memória a 256 MB
echo "268435456" | sudo tee /sys/fs/cgroup/meu_grupo/memory.max
# Adicionar um processo ao grupo
echo $$ | sudo tee /sys/fs/cgroup/meu_grupo/cgroup.procs
# Verificar limites
cat /sys/fs/cgroup/meu_grupo/cpu.max
cat /sys/fs/cgroup/meu_grupo/memory.current
5. Subsistemas essenciais de cgroups
CPU
cpu.max: limite máximo de uso (formato:[período máximo] [período total])cpu.weight: prioridade relativa entre grupos (1-10000)
# Limitar a 20% de um core
echo "20000 100000" > /sys/fs/cgroup/grupo/cpu.max
Memória
memory.max: limite máximo absolutomemory.high: limite suave (inicia throttling antes do máximo)
# 512 MB
echo "536870912" > /sys/fs/cgroup/grupo/memory.max
I/O
io.max: limite por dispositivo (leituras/escritas por segundo)io.weight: prioridade relativa de I/O
# Limitar leitura a 10 MB/s no device 8:0
echo "8:0 rbps=10485760" > /sys/fs/cgroup/grupo/io.max
Outros subsistemas
- PID: limita número de processos (
pids.max) - cpuset: restringe CPUs e nós NUMA (
cpuset.cpus) - freezer: pausa/retoma processos (
cgroup.freeze)
6. Interseção entre namespaces e cgroups
Contêineres combinam namespaces para isolamento de visão com cgroups para controle de recursos. O Docker, por exemplo, cria um conjunto completo de namespaces para cada contêiner e aplica cgroups para limitar CPU, memória e I/O.
O papel do cgroup namespace
O cgroup namespace (CLONE_NEWCGROUP) oculta a hierarquia real de cgroups do host. Dentro do contêiner, o processo vê apenas seu próprio grupo como raiz, impedindo que ele modifique grupos de outros contêineres ou do sistema.
Simulação manual de um contêiner minimalista
# 1. Criar grupo de cgroups
sudo mkdir /sys/fs/cgroup/meu_container
echo "50000 100000" | sudo tee /sys/fs/cgroup/meu_container/cpu.max
echo "268435456" | sudo tee /sys/fs/cgroup/meu_container/memory.max
# 2. Criar namespaces e executar shell
sudo unshare --fork --pid --net --uts --mount --cgroup /bin/bash
# 3. Dentro do novo namespace, adicionar ao cgroup
echo $$ | sudo tee /sys/fs/cgroup/meu_container/cgroup.procs
# 4. Verificar isolamento
hostname container_teste
ps aux # Apenas processos do namespace
cat /sys/fs/cgroup/meu_container/cpu.max # 50000 100000
7. Ferramentas de diagnóstico e depuração
Comandos essenciais
# Listar namespaces ativos
lsns
# Entrar em um namespace existente
nsenter -t 1234 -n -p /bin/bash
# Listar grupos de cgroups
lscgroup
# Monitorar uso de CPU por cgroup
systemd-cgtop
# Ver hierarquia de cgroups
systemd-cgls
Análise via /proc
# Ver namespaces de um processo
ls -l /proc/$$/ns/
# Ver cgroups de um processo
cat /proc/$$/cgroup
# Monitorar estatísticas de cgroup
cat /sys/fs/cgroup/meu_grupo/memory.stat
cat /sys/fs/cgroup/meu_grupo/cpu.stat
8. Considerações finais e boas práticas
Namespaces e cgroups são mecanismos complementares: namespaces isolam a visão (o que o processo enxerga), enquanto cgroups controlam o consumo (quanto o processo usa). Um contêiner seguro e eficiente requer ambos.
Armadilhas comuns:
- Vazamento de recursos: processos que não são movidos para o cgroup correto continuam consumindo sem limites
- Escalabilidade: muitos namespaces aninhados aumentam a complexidade e overhead do kernel
- Segurança: namespaces não são uma barreira de segurança completa; use ferramentas como seccomp e capabilities
Boas práticas:
- Sempre use cgroups v2 em sistemas modernos
- Combine namespaces de usuário com outros namespaces para operação não-root
- Monitore regularmente com systemd-cgtop e lsns
- Documente a hierarquia de cgroups para facilitar depuração
Referências
- Documentação oficial do kernel: cgroups v2 — Guia completo sobre a hierarquia unificada de cgroups, subsistemas e arquivos de controle
- man 7 namespaces — Página de manual detalhada sobre todos os tipos de namespace e suas flags
- man 7 cgroups — Documentação oficial das chamadas de sistema e arquitetura de cgroups
- LWN.net: Control group v2 — Artigo técnico aprofundado sobre o design e motivações do cgroups v2
- Red Hat: Introduction to Linux namespaces — Tutorial prático sobre namespaces com exemplos de comando e código
- DigitalOcean: Understanding cgroups — Guia passo a passo para configurar e gerenciar cgroups no Ubuntu
- Kernel Newbies: cgroup and namespace — Visão geral das estruturas de dados e implementação no kernel