Namespaces e cgroups no Linux: a verdadeira base dos contêineres

Última atualização: 24 de fevereiro de 2026
  • Em Linux, os contêineres dependem de namespaces para isolar o que os processos podem ver e de cgroups para limitar a quantidade de recursos que podem usar.
  • Existem diferentes tipos de namespaces (PID, NET, MNT, UTS, IPC, USER, cgroup, time) e controladores de cgroup (CPU, memory, I/O, pids) que são combinados dependendo do caso.
  • Docker, Kubernetes e systemd dependem dessas primitivas do kernel para criar ambientes isolados, aplicar políticas de recursos e gerenciar serviços de forma uniforme.
  • Monitorar as métricas do cgroup e entender a topologia do namespace é essencial para diagnosticar erros de falta de memória (OOM), problemas de desempenho e garantir um isolamento sólido em produção.

contêineres linux cgroups namespaces

Se você trabalha com Docker, Kubernetes ou qualquer plataforma de contêineres, mais cedo ou mais tarde terá que se aprofundar no funcionamento interno do sistema. Por trás da "mágica" de iniciar contêineres em segundos, estão dois componentes-chave do kernel do Linux: namespaces e cgroups, a dupla responsável pelo isolamento e pelo gerenciamento preciso de recursos . Compreendê-los faz toda a diferença entre simplesmente usar ferramentas e projetar a infraestrutura.

Ao longo deste artigo, explicaremos de forma calma, mas sem entrar em muitos detalhes, como eles funcionam. namespaces (o que um processo pode ver) e cgroups (o que um processo pode usar)Como o Docker e o Kubernetes se aproveitam deles; como você pode manipulá-los em um nível baixo usando comandos como unshare, ip netns ou escrevendo diretamente em /sys/fs/cgroupE quais truques são úteis para melhorar o desempenho, a segurança e a resolução de problemas em ambientes de produção?

O que um contêiner realmente faz no Linux?

Quando você executa algo como docker runNão se cria uma máquina virtual mágica; O que você obtém é um processo Linux (ou um grupo de processos) com forte isolamento e recursos limitados.O "truque" baseia-se em quatro mecanismos do kernel:

  • NamespacesEles controlam o que o processo vê (PIDs, rede, sistema de arquivos, nome do host, etc.).
  • CgruposEles controlam o quanto o processo pode usar (CPU, RAM, E/S, PIDs...).
  • Chroot / sistema de arquivos raiz isoladoEles fornecem ao processo seu próprio sistema de arquivos.
  • Processos normais do LinuxNo final das contas, tudo se resume aos PIDs do host gerenciados pelo kernel.

Em outras palavras, um contêiner é simplesmente um processo em execução com um conjunto de novos namespaces, aninhados em um ou mais cgroups, e com um sistema de arquivos personalizado . O restante é gerenciado pela ferramenta (Docker, containerd, runc, etc.) com automação e APIs amigáveis ​​ao usuário.

Namespaces do Linux: isolando o que cada processo pode ver.

Os namespaces são uma camada de abstração do kernel que cria "mundos" separados para grupos de processos . Um processo só vê os recursos dentro do seu namespace; o resto do sistema continua lá, mas invisível para ele.

O Linux possui atualmente vários tipos de namespaces, cada um focado em uma parte específica do sistema. Combinados, eles permitem que um contêiner seja percebido como um sistema operacional independente, mesmo que compartilhe o kernel com o host.

Principais tipos de namespaces

Cada tipo de namespace isola uma dimensão diferente. É prática comum em um contêiner "sério" usar vários deles simultaneamente. Estes são os mais importantes:

  • Espaço de nomes PID: isola o espaço de identificadores de processo. Dentro do contêiner, o processo principal geralmente é o PID 1.Mesmo que o host tenha um número diferente, processos de namespaces diferentes podem ter o mesmo PID sem interferir uns com os outros e não podem enviar sinais uns aos outros, a menos que sejam explicitamente permitidos pelo host.
  • Espaço de nomes de rede (NET): cria uma pilha de rede independente, com suas próprias interfaces, rotas, regras de firewall, sockets, etc. Cada contêiner possui seu próprio loopback, seus próprios IPs e seu próprio roteamento.Dessa forma, você pode reutilizar o mesmo endereço IP em diferentes namespaces sem conflito e aplicar regras iptables isoladas.
  • Espaço de nomes de montagem (MNT): separa a visualização dos pontos de montagem. Os processos só enxergam a hierarquia de arquivos do seu espaço de nomes.Mesmo que existam montagens de diretórios, volumes compartilhados, sobreposições, etc., por baixo dos panos, é isso que permite que um contêiner pareça ter sua própria "/" sem acessar a raiz real do host.
  • Espaço de nomes UTS: controla o nome do host e o domínio NIS. Dentro de um namespace UTS, Você pode alterar o nome do host sem afetar o sistema host.Muito útil para distinguir contêineres ou simular nós em ambientes de teste.
  • Espaço de nomes IPC: isola filas de mensagens, semáforos e memória compartilhada do System V. Impeça que diferentes processos de contêineres interfiram no IPC. devido a acidente ou projeto inadequado.
  • Espaço de nome do usuárioProvavelmente o mais delicado. Permite Mapear UIDs e GIDs de namespaces internos para outros IDs de host.Isso possibilita que o "usuário raiz" dentro do contêiner seja mapeado para um usuário sem privilégios no host, reduzindo significativamente o risco de escalonamento de privilégios e melhorando a segurança. segurança de contêineres.
  • Espaço de nomes Cgroup: fornece uma visão virtualizada de /proc/self/cgroup e a hierarquia de cgroups. De dentro do contêiner, apenas seus próprios grupos são visíveis.Isso oculta a estrutura real do hospedeiro e reforça o isolamento.
  • Espaço de nomes de tempo: permite oferecer diferentes percepções de tempo (relógios como CLOCK_MONOTONIC ou CLOCK_BOOTTIME) para diferentes grupos de processos. É útil para testar cenários ou para isolamento muito preciso.
  10 razões pelas quais os sistemas de informação contábil revolucionarão o seu negócio

Todos esses namespaces são expostos em /proc/<pid>/ns/e podem ser criados ou combinados com ferramentas como unshare y nsenterQuando o último processo pertencente a um namespace é encerrado, esse namespace é destruído automaticamente.

Experimentando com namespaces a partir da linha de comando

Comando unshare É um dos princípios básicos para a experimentação: Cria um processo filho com um ou mais novos namespaces.Por exemplo, para testar um namespace PID isolado:

unshare --pid --fork --mount-proc bash
ps aux
echo $$

Internamente, você verá apenas os processos desse namespace, e seu shell aparecerá como PID 1, embora o host ainda tenha todos os seus processos usuais. É basicamente o esqueleto do que um runtime OCI fará quando um contêiner for iniciado.

Namespaces de rede em detalhes

O aspecto de rede é um dos mais versáteis. Ao criar um namespace NET com ip netns addVocê obtém uma pilha vazia, exceto pela pilha de loopback. A partir daí, você pode:

  • Conecte esse namespace ao host. com um par veth (duas interfaces virtuais unidas por um cabo lógico).
  • Criar pontes tipo br0 e conectar vários namespaces a essa ponte para que eles possam se enxergar usando IPs privados.
  • NAT e encaminhamento para dar acesso à internet a esses "contêineres caseiros" com iptables e /proc/sys/net/ipv4/ip_forward.

Isso mesmo, em um nível muito baixo, é isso que o Docker faz com a ponte. docker0 e suas redes padrão: Ele configura namespaces de rede, peers veth, bridges e regras NAT. para que os contêineres possam se comunicar entre si e sair.

Espaços de nomes de montagem e de usuário para o sistema de arquivos raiz e segurança.

Com o `mount namespace`, você pode experimentar a criação de um sistema de arquivos em contêiner sem usar o Docker. O padrão típico é:

  • Montar ou preparar um sistema de arquivos raiz mínimo (por exemplo, um minirootfs alpino baixado por wget e extraído para um diretório).
  • Crie um namespace de montagem e faça pivot_root o chroot para esse diretório.
  • Passeio /proc, /sys e outros pseudo-sistemas dentro da nova raiz.

Com isso, seu shell só verá esse sistema de arquivos, mas Você permanece um processo host normal, com seu PID real e controlável pelo administrador.Se você também usar um namespace de usuário com --map-root-userÉ possível ter um "usuário raiz" que esteja fora do sistema, sem privilégios de administrador.

Cgroups: quanto recurso cada processo pode gastar

Enquanto os namespaces determinam o que um processo pode ver, os cgroups limitam a quantidade de recursos do sistema que um grupo de processos pode consumir . Eles foram introduzidos no kernel 2.6.24 e agora são essenciais em qualquer plataforma de contêineres moderna.

Em um nível conceitual, um cgroup é um diretório dentro /sys/fs/cgroup com arquivos que descrevem limites, prioridades e métricas.Ao adicionar um PID ao arquivo apropriado (por exemplo, cgroup.procs), que o processo está sujeito a essas regras.

Controladores de recursos em cgroups

Os grupos C são compostos por controladores (subsistemas) que atuam sobre recursos específicos. Entre os mais comumente usados ​​estão:

  • CPU: controla o tempo de CPU alocado. Com partes define a prioridade relativa; com quotas limites rígidos (por exemplo, tempo limite de CPU de 50 ms por período de 100 ms); com Conjuntos de CPUs Você fixa processos a núcleos específicos.
  • Memória: limita a RAM e a memória de troca. Permite Limites rígidos e flexíveis, configurações de OOM killer, estatísticas e pressão de memória.Muito útil para impedir que um serviço consuma todo o servidor.
  • Bloco de E/S (blkio / io): restringe a largura de banda e as IOPS por dispositivo. Você pode Atribuir peso proporcional ou definir limites exatos em bytes/s ops/s.
  • NetworkExistem mecanismos para limitar a largura de banda e os pacotes, embora o suporte seja menos maduro e geralmente combinado com tc e QoS.
  • PIDs: marque o número máximo de processos/threads que pode criar um grupo, mitigando ataques de bifurcação (fork bombs) ou erros de código que disparam threads.

A ideia prática é muito simples: você coloca seu aplicativo em um cgroup e especifica os limites máximos de CPU, memória, E/S e PIDs . A partir daí, o kernel se encarrega de aplicar as regras.

cgroups v1 vs cgroups v2

Em produção, você encontrará dois tipos de cgroups, e é importante ter clareza sobre em qual deles você está atuando:

  • cgroup v1Cada controlador tem sua própria hierarquia independente. Você tem rotas como /sys/fs/cgroup/cpu/..., /sys/fs/cgroup/memory/..., etc. É flexível, mas complicado de gerenciar em grande escala.Muitas distribuições e plataformas clássicas ainda o utilizam ou o suportam por questões de compatibilidade.
  • cgroup v2: agrupa tudo em um só hierarquia unificada en /sys/fs/cgroupOs limites são configurados por meio de arquivos como: cpu.max, memory.max, io.max e semelhantes. Simplifica a gestão e melhora o ajuste fino dos recursos.As distribuições modernas tendem a usar a versão 2 por padrão, às vezes com modo híbrido.
  Como instalar C++ no Ubuntu: Guia completo para iniciantes

En /proc/cgroups Você pode ver quais controladores estão habilitados, quantos cgroups estão ativos e se você está na versão 1 ou 2 (na versão 2, você verá uma hierarquia unificada com ID 0). O comando mount | grep cgroup Também indica o tipo de sistema de arquivos (cgroup2fs implica v2, tmpfs (geralmente aponta para v1).

Criação manual de cgroups e limites típicos

Para entender completamente o que o Docker faz internamente, vale a pena criar cgroups manualmente. O padrão geral é:

  • Crie um diretório cgroup.
  • Configure arquivos de limite/prioridade. (CPU, memória, E/S, PIDs…).
  • Adicione PIDs ao cgroup digitando cgroup.procs o tasks (na v1).

Por exemplo, no cgroups v2, você poderia ter algo como isto para CPU e memória:

mkdir /sys/fs/cgroup/test_app
echo "+cpu +memory +io +pids" > /sys/fs/cgroup/cgroup.subtree_control
echo "50000 100000" > /sys/fs/cgroup/test_app/cpu.max
echo "512M" > /sys/fs/cgroup/test_app/memory.max
echo $$ > /sys/fs/cgroup/test_app/cgroup.procs

Com isso, o processo atual fica limitado a 50% de um núcleo e 512 MB de RAMSe você começar algo intensivo (um dd Se você usar /dev/null (por exemplo), verá que o sistema não apresenta problemas. No cgroups v1, o esquema é semelhante, mas com arquivos como cpu.shares, cpu.cfs_quota_us, memory.limit_in_bytesetc., cada um em sua própria hierarquia.

Como o Docker, o Kubernetes e a pilha do kernel funcionam.

Acima dos namespaces e cgroups, existem várias camadas de software que repassam a responsabilidade umas para as outras até chegarem ao kernel. Mesmo que o nome da ferramenta mude, o resultado final é sempre o mesmo: chamadas ao kernel para criar namespaces, configurar cgroups e montar sistemas de arquivos.

A pilha típica de contêineres hoje em dia se parece muito com isto:

  • Camada de orquestraçãoKubernetes, Docker Swarm, Nomad… Eles gerenciam o ciclo de vida de pods/serviços, agendamento, escalonamento e suporte. arquiteturas de microsserviços.
  • runtime de contêiner de alto nível: Docker Engine, containerd, CRI-O. Eles recebem as ordens do orquestrador e as transformam em ações concretas. (baixar imagens, criar contêineres, gerenciar snapshots, etc.).
  • Tempo de execução OCI de baixo nívelRunc, crun e ferramentas similares implementam a especificação OCI e são diretamente responsáveis ​​por sua implementação. eles chamam clone(), unshare(), mount() e companhia materializar o contêiner.
  • Kernel LinuxEle fornece namespaces, cgroups, OverlayFS e todos os outros recursos primitivos. É aqui que a verdadeira "mágica" acontece.

Quando você joga um docker runO caminho é: cliente CLI → dockerd → containerd → runc → chamadas ao kernel. Ao longo desse caminho, namespaces são criados, cgroups são atribuídos, o sistema de arquivos raiz é montado com OverlayFS mais volumes, e as interfaces de rede e a ponte são conectadas. O que você vê como "um contêiner" é, para o kernel, um conjunto de processos com determinadas flags e caminhos específicos..

Requisitos e verificações prévias no sistema

Antes de começar a mexer com namespaces e cgroups em um servidor, é uma boa ideia garantir que o hardware e o kernel atendam aos requisitos mínimos razoáveis , especialmente se você planeja configurar algo semelhante a um ambiente de produção.

Como referência, geralmente são recomendados pelo menos 2 CPUs lógicas, 4 GB de RAM (idealmente 8 GB ou mais) , cerca de 20 GB de espaço livre em disco e um kernel 3.10 ou superior (idealmente 4.x ou 5.x para ter todos os recursos do cgroups v2 e funcionalidades modernas).

Para verificar se o kernel suporta namespaces e cgroups, basta consultar o arquivo de configuração da versão em uso:

grep -E "CONFIG_.*_NS" /boot/config-$(uname -r)
grep -E "CONFIG_CGROUP" /boot/config-$(uname -r)

Opções como essas deveriam aparecer lá CONFIG_NAMESPACES, CONFIG_PID_NS, CONFIG_NET_NS, CONFIG_CGROUPS, CONFIG_MEMCG, CONFIG_CGROUP_SCHED marcado como =ySem isso, você não terá muita diversão no jogo.

Outro detalhe prático é se o sistema permite Criar namespaces de usuário sem ser rootIsso é muito útil para testes e para algumas ferramentas. É controlado com o sysctl. kernel.unprivileged_userns_cloneSe estiver em 0, você terá que ativá-lo (ou usar seu suor por todo lado).

Explorando cgroups e namespaces em máquinas "reais"

Uma maneira bastante simples de internalizar esses conceitos é observar como o próprio sistema e seus contêineres os utilizam . Algumas dicas úteis:

  • ls /proc/self/ns/ Ele mostra os namespaces aos quais seu shell atual pertence (cgroup, ipc, mnt, net, pid, user, uts…).
  • systemd-cgtop Ele mostra o uso de CPU, memória e E/S por cgroup, de forma muito semelhante a top mas agrupados por serviços e contêineres.
  • En /sys/fs/cgroup Você pode localizar os cgroups que o Docker ou o Podman criam para cada contêiner, geralmente usando o ID do contêiner no caminho. Dentro, cgroup.procs Liste os PIDs associados..
  • /proc/<pid>/cgroup Ele informa a quais cgroups um processo específico está atribuído, perfeito para rastrear um aplicativo problemático.
  Como migrar do Mac para o Windows sem perder arquivos ou configurações

Você verá que os contêineres são simplesmente mais um ramo da hierarquia de cgroups, com seus próprios limites de CPU, memória, blkio e outros, assim como você poderia criá-los manualmente . A beleza do Docker é que ele faz isso automaticamente e com uma API amigável.

Estratégias de desempenho, NUMA e afinidade

Em cenários de alta carga (grandes bancos de dados, processamento intensivo, streaming, etc.), simplesmente "iniciar contêineres" não é suficiente. A forma como você aloca CPU e memória para cada serviço impacta significativamente o desempenho.

Com o driver cpuset , você pode atribuir processos ou serviços a conjuntos específicos de CPUs e nós NUMA. É muito simples:

mkdir /sys/fs/cgroup/cpuset/dedicated_cpus
echo "4-7" > /sys/fs/cgroup/cpuset/dedicated_cpus/cpuset.cpus
echo "0" > /sys/fs/cgroup/cpuset/dedicated_cpus/cpuset.mems
echo 1 > /sys/fs/cgroup/cpuset/dedicated_cpus/cpuset.cpu_exclusive

Isso permite reservar um grupo de núcleos para um serviço específico e impedir que o restante do sistema interfira. Isso reduz o ruído, a migração de threads e os problemas de latência que frequentemente surgem quando todos estão competindo pelos mesmos recursos da CPU.

No nível da memória, combinar o numactl com cgroups (ou usar diretamente os parâmetros NUMA do controlador de memória, quando disponíveis) ajuda a manter os dados no nó físico mais próximo dos núcleos que os utilizam. Isso é perceptível em aplicações sensíveis à latência.

Monitoramento e observabilidade de cgroups e namespaces

Definir limites de recursos é bom, mas Se você não medir o que está acontecendo, poderá dar um tiro no próprio pé sem nem perceber.O próprio cgroup v2 oferece diversas métricas úteis nos logs. *.stat, *.events y *.pressure.

Alguns exemplos práticos:

  • cpu.stat Ele informa quanto tempo de CPU o grupo utilizou, quantas vezes o recurso foi limitado, etc.
  • memory.current y memory.max Eles mostram o consumo atual e o limite configurado.
  • memory.events Registra erros de falta de memória (OOM), limites de software atingidos e outros sustos.
  • io.stat Acumula leituras/gravações por dispositivo.

Para uma visão geral rápida, systemd-cgtop É muito prático, e se você quiser algo mais industrial, ferramentas como... cConselheiro Eles exportam métricas de cgroup do Docker/containerd para o Prometheus, permitindo que você crie dashboards completos no Grafana sem muito esforço, ideal para um monitor de sistema avançado.

Ao depurar problemas de namespace, scripts simples que percorrem o namespace podem ser úteis. /proc/<pid>/ns/ e comparar os links simbólicos ajudam a para saber quais processos compartilham exatamente o mesmo espaço de nomesou para localizar um namespace de rede "perdido" que ficou bloqueado.

Utilizando o systemd como gerenciador de cgroups

Na maioria das distribuições modernas, o systemd depende bastante de cgroups para gerenciar serviços, slices e escopos . Quando você define uma unidade com limites de recursos, o que o systemd realmente faz é criar um cgroup para esse serviço e configurar seus manipuladores.

Um serviço declarado com diretrizes como:

CPUQuota=50%
CPUWeight=500
MemoryMax=512M
IOReadBandwidthMax=/dev/sda 10M

Isso se traduz em limites de cgroup para CPU, memória e E/S para o processo dessa unidade. Isso permite alinhar o gerenciamento de serviços "clássico" com o modelo de contêiner, usando o mesmo subsistema do kernel subjacente. Para mais detalhes sobre o systemd e seus requisitos, consulte as alterações do systemd.

Para equipes que combinam serviços tradicionais com contêineres (algo muito comum), usar o systemd para definir limites em daemons críticos é uma maneira elegante de evitar que os recursos sejam consumidos pelos pods no ambiente conteinerizado.

Em ambientes com múltiplas cargas de trabalho, desde bancos de dados a agentes de monitoramento e aplicações personalizadas, dominar namespaces e cgroups permite definir políticas de isolamento robustas, ajustar a qualidade de serviço e garantir que cada componente tenha sua própria alocação de CPU e memória bem controlada . Compreender como o Docker, o Kubernetes e o systemd aproveitam esses recursos do kernel capacita você a projetar infraestruturas multi-tenant mais seguras, depurar problemas complexos (de erros de falta de memória a gargalos de E/S) e obter o máximo do seu hardware sem sacrificar a estabilidade.

suporte do systemd 259 para musl
Artigo relacionado:
systemd 259: suporte para musl, segurança e alterações de chave