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.
O funcionamento dos cgroups é baseado em três conceitos principais:
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.
cpu: Limita o tempo de CPU. Usa "shares" para distribuir o tempo de CPU de forma proporcional e quotas para definir um limite máximo absoluto.memory: Controla o uso de memória RAM e swap. Pode definir limites rígidos (hard limits) e suaves (soft limits).pids: Limita o número máximo de processos que podem ser criados dentro do cgroup.blkio (v1) / io (v2): Limita a taxa de leitura/escrita (throughput) e o número de operações por segundo (IOPS) para dispositivos de bloco (HDs, SSDs).devices: Permite ou nega o acesso a dispositivos específicos (ex: /dev/sda, /dev/tty0).freezer: Permite pausar (congelar) e resumir todos os processos de um cgroup.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.
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.
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
└─...
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 - -
...
Vamos criar um cgroup manualmente, atribuir um processo a ele e limitar sua memória a 100MB.
Criamos um diretório dentro da hierarquia unificada.
# mkdir /sys/fs/cgroup/meu_grupo
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
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.
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
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:
/sys/fs/cgroup/system.slice/docker-<ID_do_container>.scope/512 * 1024 * 1024 no arquivo memory.max.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.cgroup.procs daquele cgroup.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.