Guia Detalhado sobre Cgroups (Control Groups) no Linux

Os Control Groups, ou cgroups, são uma funcionalidade poderosa e essencial do kernel Linux. Eles permitem organizar processos em grupos hierárquicos e, mais importante, controlar e limitar os recursos do sistema que cada grupo pode consumir. Recursos como CPU, memória, I/O de disco e largura de banda de rede podem ser gerenciados com precisão.

Esta capacidade de isolamento de recursos é a espinha dorsal de tecnologias de containerização como Docker e Kubernetes, garantindo que aplicações coexistam de forma estável e previsível em um único host.

Arquitetura e Conceitos Fundamentais

O funcionamento dos cgroups é baseado em três conceitos principais:

A Hierarquia no Cgroup Filesystem

A interação com os cgroups é feita através de um sistema de arquivos virtual chamado cgroupfs, geralmente montado em /sys/fs/cgroup. Cada diretório criado dentro deste caminho representa um novo cgroup.

Dentro do diretório de um cgroup, você encontrará arquivos especiais que permitem configurar limites, adicionar processos e monitorar o uso de recursos. A estrutura hierárquica permite que um cgroup filho herde as configurações do cgroup pai, mas também possa ter seus próprios limites mais restritivos.

Cgroup v1 vs. Cgroup v2: O Linux possui duas versões de cgroups. A v1 permite múltiplas hierarquias paralelas (ex: uma para CPU, outra para memória), o que pode ser confuso. A v2, mais moderna e recomendada, unifica todos os controladores em uma única hierarquia, simplificando o gerenciamento e garantindo maior consistência. A maioria dos sistemas modernos usa a v2 por padrão.

Controladores (Subsystems) Mais Comuns

Uso Prático e Comandos

Embora seja possível gerenciar cgroups manualmente, é mais comum e recomendado usar ferramentas de alto nível, como o systemd, que abstraem grande parte da complexidade.

Ferramentas do Systemd

O systemd integra-se profundamente com os cgroups e organiza todos os serviços, usuários e máquinas virtuais em uma hierarquia de cgroups bem definida.

1. Visualizando a Árvore de Cgroups com `systemd-cgls`

Este comando exibe a árvore de cgroups de forma legível.

$ systemd-cgls
Control group /:
-.slice
├─user.slice
│ └─user-1000.slice
│   ├─user@1000.service
│   │ ├─...
│   └─session-2.scope
│     ├─1500 /usr/lib/gnome-terminal/gnome-terminal-server
│     ├─...
└─system.slice
  ├─docker.service
  │ ├─1100 /usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock
  │ └─...
  ├─sshd.service
  │ └─1000 /usr/sbin/sshd -D
  └─...
        

2. Monitorando o Uso de Recursos com `systemd-cgtop`

Semelhante ao comando top, mas exibe o consumo de recursos por cgroup.

$ systemd-cgtop
Path                                    Tasks   %CPU   Memory  Input/s Output/s

/                                         234   15.2   7.8G        -        -
/system.slice/docker.service               25    8.5   512.3M      -        -
/user.slice/user-1000.slice/session-2.scope  15    5.1   1.2G        -        -
...
        

Manipulação Manual (Exemplo com Cgroup v2)

Vamos criar um cgroup manualmente, atribuir um processo a ele e limitar sua memória a 100MB.

Passo 1: Criar o novo cgroup

Criamos um diretório dentro da hierarquia unificada.

# mkdir /sys/fs/cgroup/meu_grupo

Passo 2: Definir o limite de memória

O controlador de memória precisa ser habilitado para este subgrupo. Fazemos isso escrevendo +memory no arquivo cgroup.subtree_control do cgroup pai. Depois, definimos o limite no arquivo memory.max do nosso novo grupo.

# Habilita o controlador de memória para subgrupos
# echo "+memory" > /sys/fs/cgroup/cgroup.subtree_control

# Define o limite máximo de memória para 100MB
# echo "102400000" > /sys/fs/cgroup/meu_grupo/memory.max

Passo 3: Mover um processo para o cgroup

Para mover um processo em execução para o cgroup, basta escrever seu PID no arquivo cgroup.procs.

# Supondo que o PID do processo seja 12345
# echo "12345" > /sys/fs/cgroup/meu_grupo/cgroup.procs

# Para executar um novo comando diretamente no cgroup:
# systemd-run --unit=meu-teste --slice=meu_grupo.slice stress -m 1 --vm-bytes 200M

Se o processo stress tentar alocar mais de 100MB, ele será finalizado pelo OOM (Out-of-Memory) Killer do kernel, demonstrando que o limite do cgroup funcionou.

Passo 4: Monitorar e Limpar

Você pode verificar o uso atual de memória e outros status lendo os arquivos no diretório do cgroup.

# cat /sys/fs/cgroup/meu_grupo/memory.current
99852288

Para remover o cgroup, primeiro certifique-se de que não há processos nele e, em seguida, remova o diretório.

# rmdir /sys/fs/cgroup/meu_grupo

Cgroups no Mundo dos Containers

Ferramentas como o Docker utilizam a API do kernel para manipular cgroups de forma automática. Quando você executa um container com limites, o Docker está, na verdade, criando um cgroup para ele e configurando os arquivos apropriados.

Exemplo com Docker:

# Limita o container a usar no máximo 50% de um núcleo de CPU e 512MB de RAM
docker run -d --name meu-container --cpus="0.5" --memory="512m" nginx

Nos bastidores, o Docker fará algo equivalente a:

  1. Criar um cgroup, por exemplo: /sys/fs/cgroup/system.slice/docker-<ID_do_container>.scope/
  2. Escrever 512 * 1024 * 1024 no arquivo memory.max.
  3. Configurar os arquivos cpu.max (em cgroup v2) ou cpu.cfs_period_us e cpu.cfs_quota_us (em cgroup v1) para corresponder a 50% de um núcleo.
  4. Adicionar o PID do processo Nginx ao arquivo cgroup.procs daquele cgroup.

Conclusão

Os cgroups são uma tecnologia de baixo nível, mas seu impacto é imenso. Eles fornecem o controle de recursos fundamental que possibilita a computação em nuvem moderna, os microsserviços e o desenvolvimento baseado em containers. Compreender como funcionam oferece uma visão profunda sobre como o Linux gerencia processos e garante a estabilidade do sistema em ambientes de alta densidade.