Gestión de Recursos con cgroups v2 en Linux

cgroups (control groups) v2 es la interfaz del kernel Linux para organizar procesos en grupos jerárquicos y aplicar límites de recursos—CPU, memoria, I/O y procesos—a cada grupo. La versión 2 unifica la jerarquía de v1, simplifica la delegación y mejora la gestión de memoria. Es la base de contenedores Docker y Kubernetes, y systemd la usa internamente para todos los servicios. Esta guía cubre la configuración directa de cgroups v2, su integración con systemd y el monitoreo de recursos.

Requisitos Previos

  • Kernel Linux 5.2+ (preferiblemente 5.10+ para soporte completo de cgroups v2)
  • Ubuntu 21.04+, Fedora 31+, Rocky Linux 9+ (cgroups v2 por defecto)
  • Acceso root
# Verificar versión del kernel
uname -r

# Verificar si cgroups v2 está disponible
cat /proc/filesystems | grep cgroup2

Verificar y Habilitar cgroups v2

# Verificar si el sistema usa cgroups v2 (unified hierarchy)
mount | grep cgroup2
# Si aparece: cgroup2 on /sys/fs/cgroup type cgroup2 -> cgroups v2 activo

# Verificar la versión actual
cat /sys/fs/cgroup/cgroup.controllers

# En sistemas con modo híbrido o solo v1, habilitar v2 completamente
# Añadir a los parámetros del kernel en GRUB:
# systemd.unified_cgroup_hierarchy=1

# Ubuntu (añadir parámetro al kernel)
sed -i 's/GRUB_CMDLINE_LINUX=""/GRUB_CMDLINE_LINUX="systemd.unified_cgroup_hierarchy=1"/' \
    /etc/default/grub
update-grub
# Reiniciar para aplicar: reboot

# Verificar controladores disponibles
cat /sys/fs/cgroup/cgroup.controllers
# Debe mostrar: cpuset cpu io memory pids

Estructura y Jerarquía de cgroups v2

# La jerarquía de cgroups v2 está en /sys/fs/cgroup/
ls /sys/fs/cgroup/

# Archivos clave en la raíz
cat /sys/fs/cgroup/cgroup.controllers   # Controladores disponibles
cat /sys/fs/cgroup/cgroup.subtree_control  # Controladores habilitados para hijos

# Ver jerarquía de systemd
ls /sys/fs/cgroup/system.slice/
ls /sys/fs/cgroup/user.slice/

# Crear un cgroup manualmente
mkdir /sys/fs/cgroup/mi-grupo

# Verificar archivos creados automáticamente
ls /sys/fs/cgroup/mi-grupo/
# cgroup.controllers  cgroup.events  cgroup.freeze  cgroup.max.depth
# cgroup.max.descendants  cgroup.procs  cgroup.stat  cgroup.subtree_control
# cgroup.threads  cgroup.type

# Habilitar controladores en el cgroup hijo
echo "+cpu +memory +io" > /sys/fs/cgroup/cgroup.subtree_control
echo "+cpu +memory +io" > /sys/fs/cgroup/mi-grupo/cgroup.subtree_control

Control de CPU

CPU Weight (Prioridad Relativa)

# El peso de CPU determina la prioridad relativa entre grupos (rango: 1-10000, defecto: 100)
mkdir /sys/fs/cgroup/alta-prioridad
mkdir /sys/fs/cgroup/baja-prioridad

# Habilitar controlador CPU
echo "+cpu" > /sys/fs/cgroup/cgroup.subtree_control

# Asignar pesos (alta-prioridad recibe 10x más CPU que baja-prioridad)
echo 1000 > /sys/fs/cgroup/alta-prioridad/cpu.weight
echo 100 > /sys/fs/cgroup/baja-prioridad/cpu.weight

# Verificar el peso asignado
cat /sys/fs/cgroup/alta-prioridad/cpu.weight

CPU Max (Límite Absoluto de CPU)

# Formato: max_cuota periodo (en microsegundos)
# Limitar al 25% de un núcleo (250000 de 1000000 microsegundos)
echo "250000 1000000" > /sys/fs/cgroup/mi-grupo/cpu.max

# Limitar al 200% (2 núcleos completos)
echo "200000 100000" > /sys/fs/cgroup/mi-grupo/cpu.max

# Sin límite
echo "max 100000" > /sys/fs/cgroup/mi-grupo/cpu.max

# Añadir proceso al cgroup y verificar
echo $$ > /sys/fs/cgroup/mi-grupo/cgroup.procs
cat /sys/fs/cgroup/mi-grupo/cpu.stat

Asignar Núcleos Específicos con cpuset

# Habilitar controlador cpuset
echo "+cpuset" > /sys/fs/cgroup/cgroup.subtree_control
mkdir /sys/fs/cgroup/grupo-cpuset

# Asignar solo los núcleos 2 y 3 al grupo
echo "2-3" > /sys/fs/cgroup/grupo-cpuset/cpuset.cpus
echo "0" > /sys/fs/cgroup/grupo-cpuset/cpuset.mems  # Nodo NUMA 0

# Añadir un proceso al grupo con cpuset
echo $(pidof mi-proceso) > /sys/fs/cgroup/grupo-cpuset/cgroup.procs

Control de Memoria

# Habilitar controlador de memoria
echo "+memory" > /sys/fs/cgroup/cgroup.subtree_control
mkdir /sys/fs/cgroup/grupo-memoria

# Límite de memoria RAM (hard limit - proceso recibe OOM kill si lo supera)
echo "512M" > /sys/fs/cgroup/grupo-memoria/memory.max

# Límite suave (el kernel intentará mantener el uso por debajo, pero no mata procesos)
echo "256M" > /sys/fs/cgroup/grupo-memoria/memory.high

# Deshabilitar swap para este grupo
echo "0" > /sys/fs/cgroup/grupo-memoria/memory.swap.max

# Añadir proceso al grupo con límites de memoria
echo $$ > /sys/fs/cgroup/grupo-memoria/cgroup.procs

# Monitorear uso de memoria del grupo
cat /sys/fs/cgroup/grupo-memoria/memory.current  # Uso actual en bytes
cat /sys/fs/cgroup/grupo-memoria/memory.stat     # Estadísticas detalladas

# Habilitar notificaciones de presión de memoria
cat /sys/fs/cgroup/grupo-memoria/memory.pressure

Script de Límite de Memoria con cgroups v2

cat > /usr/local/bin/run-with-memlimit.sh << 'EOF'
#!/bin/bash
# Ejecutar un proceso con límite de memoria usando cgroups v2
LIMITE=${1:-256M}
shift
COMANDO="$@"
GRUPO="memlimit-$$"

# Crear cgroup temporal
mkdir /sys/fs/cgroup/$GRUPO

# Habilitar controladores
echo "+memory +cpu" > /sys/fs/cgroup/cgroup.subtree_control 2>/dev/null
echo "0" > /sys/fs/cgroup/$GRUPO/memory.swap.max
echo "$LIMITE" > /sys/fs/cgroup/$GRUPO/memory.max

# Ejecutar el proceso en el cgroup
echo $$ > /sys/fs/cgroup/$GRUPO/cgroup.procs
exec $COMANDO

# Limpieza (se ejecuta tras el comando)
rmdir /sys/fs/cgroup/$GRUPO 2>/dev/null
EOF
chmod +x /usr/local/bin/run-with-memlimit.sh

# Uso: run-with-memlimit.sh 128M python3 script-intensivo.py

Control de I/O

# Habilitar controlador I/O
echo "+io" > /sys/fs/cgroup/cgroup.subtree_control
mkdir /sys/fs/cgroup/grupo-io

# Obtener el número de dispositivo del disco
ls -la /dev/sda
# Ejemplo: 8:0 (major:minor)

# Limitar ancho de banda de lectura (10 MB/s)
echo "8:0 rbps=10485760" > /sys/fs/cgroup/grupo-io/io.max

# Limitar ancho de banda de escritura (5 MB/s)
echo "8:0 wbps=5242880" > /sys/fs/cgroup/grupo-io/io.max

# Limitar IOPS (operaciones de I/O por segundo)
echo "8:0 riops=1000 wiops=500" > /sys/fs/cgroup/grupo-io/io.max

# Prioridad de I/O (peso relativo, rango 1-10000)
echo "8:0 100" > /sys/fs/cgroup/grupo-io/io.weight

# Ver estadísticas de I/O del grupo
cat /sys/fs/cgroup/grupo-io/io.stat

Integración con systemd

systemd gestiona cgroups v2 automáticamente. Para configurar límites de recursos en servicios:

# Configurar límites directamente en el unit file
cat > /etc/systemd/system/app-con-limites.service << 'EOF'
[Unit]
Description=Aplicación con límites de recursos cgroups v2

[Service]
Type=simple
ExecStart=/opt/app/servidor

# Límites de CPU
CPUQuota=50%           # 50% de un núcleo (equivale a cpu.max 500ms/1s)
CPUWeight=200          # Doble de prioridad que el defecto (100)

# Límites de memoria
MemoryMax=1G           # Límite estricto
MemoryHigh=768M        # Límite suave (throttle)
MemorySwapMax=0        # Sin swap

# Límites de I/O
IOWeight=50            # Menor prioridad de I/O
IOReadBandwidthMax=/dev/sda 50M
IOWriteBandwidthMax=/dev/sda 20M

# Límite de procesos/hilos
TasksMax=100

[Install]
WantedBy=multi-user.target
EOF

systemctl daemon-reload
systemctl start app-con-limites.service

# Modificar límites en caliente (sin reiniciar)
systemctl set-property app-con-limites.service MemoryMax=2G
systemctl set-property app-con-limites.service CPUQuota=75%

# Ver los límites actuales en el cgroup de systemd
cat /sys/fs/cgroup/system.slice/app-con-limites.service/cpu.max
cat /sys/fs/cgroup/system.slice/app-con-limites.service/memory.max

Gestión de Recursos por Slice

# Limitar recursos de todos los servicios del usuario
mkdir -p /etc/systemd/system/user.slice.d/
cat > /etc/systemd/system/user.slice.d/resource-limits.conf << 'EOF'
[Slice]
# Limitar toda la slice de usuarios a 40% CPU y 2GB RAM
CPUQuota=400%
MemoryMax=2G
EOF

# Límites para servicios del sistema
mkdir -p /etc/systemd/system/system.slice.d/
cat > /etc/systemd/system/system.slice.d/resource-limits.conf << 'EOF'
[Slice]
CPUWeight=500
MemoryMax=4G
EOF

systemctl daemon-reload

Monitoreo de Recursos

# Monitor en tiempo real de cgroups (incluido en systemd)
systemd-cgtop

# Ver uso de recursos de un servicio específico
systemctl status mi-servicio.service

# Ver estadísticas de cgroup directamente
CGROUP_PATH="/sys/fs/cgroup/system.slice/mi-servicio.service"

echo "=== Uso de CPU ==="
cat $CGROUP_PATH/cpu.stat

echo "=== Uso de Memoria ==="
echo "Actual: $(cat $CGROUP_PATH/memory.current) bytes"
echo "Máximo configurado: $(cat $CGROUP_PATH/memory.max)"
echo "Alto configurado: $(cat $CGROUP_PATH/memory.high)"

echo "=== Estadísticas de I/O ==="
cat $CGROUP_PATH/io.stat

# Script de monitoreo continuo
cat > /usr/local/bin/cgroup-monitor.sh << 'EOF'
#!/bin/bash
# Monitor de uso de recursos por cgroup
SERVICIO=${1:-"mi-servicio.service"}
CGROUP="/sys/fs/cgroup/system.slice/$SERVICIO"

while true; do
    TIMESTAMP=$(date +"%H:%M:%S")
    MEM_ACTUAL=$(cat "$CGROUP/memory.current" 2>/dev/null || echo 0)
    MEM_MB=$((MEM_ACTUAL / 1024 / 1024))
    CPU_USAGE=$(cat "$CGROUP/cpu.stat" 2>/dev/null | grep "^usage_usec" | awk '{print $2}')
    
    echo "$TIMESTAMP - Memoria: ${MEM_MB}MB - CPU total: ${CPU_USAGE}µs"
    sleep 5
done
EOF
chmod +x /usr/local/bin/cgroup-monitor.sh

Solución de Problemas

Error "No space left on device" al crear cgroups:

# El sistema tiene límite de cgroups simultáneos
cat /sys/fs/cgroup/cgroup.max.descendants
# Aumentar el límite
echo 1000 > /sys/fs/cgroup/cgroup.max.descendants

Los límites de memoria no funcionan:

# Verificar que el controlador memory está habilitado en el cgroup padre
cat /sys/fs/cgroup/cgroup.subtree_control | grep memory
# Si no está, habilitarlo
echo "+memory" > /sys/fs/cgroup/cgroup.subtree_control

Los cambios con systemctl set-property no persisten tras reinicio:

# Los cambios con set-property son por defecto persistentes en /etc/systemd/system/*.service.d/
ls /etc/systemd/system/mi-servicio.service.d/
cat /etc/systemd/system/mi-servicio.service.d/50-CPUQuota.conf
# Si quieres que sea temporal (solo para la sesión actual)
systemctl set-property --runtime mi-servicio.service CPUQuota=50%

El sistema no soporta cgroups v2:

# Verificar si la distribución usa v1
mount | grep "type cgroup "  # cgroup v1
mount | grep "type cgroup2"  # cgroup v2
# Verificar soporte en el kernel
grep CONFIG_CGROUP /boot/config-$(uname -r) | grep -v "^#"

Conclusión

cgroups v2 es una herramienta fundamental para el control de recursos en Linux moderno, ofreciendo una jerarquía unificada y mayor precisión en la limitación de CPU, memoria e I/O. La integración directa con systemd hace que configurar límites de recursos por servicio sea sencillo mediante directivas como CPUQuota, MemoryMax e IOWeight. Para cargas de trabajo en producción, definir límites de recursos apropiados previene que un servicio monopolice el sistema y garantiza la calidad de servicio para todas las aplicaciones.