--- title: "Cómo limitar CPU, memoria y E/S con cgroups v2 en Ubuntu" description: "Limita CPU, memoria y E/S de procesos y servicios con cgroups v2 en Ubuntu 24.04, en /sys/fs/cgroup y con systemd, y comprueba que los límites se cumplen." category: "Internos de Linux" language: es source: https://cubepath.com/es/docs/internos-de-linux/gestion-de-recursos-con-cgroups-v2 updated: 2026-09-25 --- # Cómo limitar CPU, memoria y E/S con cgroups v2 en Ubuntu cgroups (grupos de control) es el mecanismo del kernel Linux que organiza los procesos en una jerarquía y reparte entre ellos la CPU, la memoria, la E/S de disco y el número de procesos. La versión 2 usa una única jerarquía montada en `/sys/fs/cgroup` y es la base sobre la que systemd, Docker y Kubernetes aplican sus límites. En este tutorial comprobarás que Ubuntu 24.04 usa cgroups v2, aplicarás límites a mano sobre los ficheros del kernel para entender cómo funcionan y después harás lo mismo de forma persistente con systemd, que es como deberías hacerlo en producción. ## Requisitos previos - Un servidor con Ubuntu 24.04 LTS, por ejemplo un VPS de CubePath. Debian 12 y Rocky Linux 9 también usan cgroups v2 por defecto y los comandos son los mismos. - Un usuario no root con privilegios `sudo`. - La herramienta `stress-ng` para generar carga de prueba: ```bash sudo apt update sudo apt install stress-ng ``` ## Paso 1: Comprobar que el sistema usa cgroups v2 Consulta el tipo de sistema de ficheros montado en `/sys/fs/cgroup`: ```bash stat -fc %T /sys/fs/cgroup/ ``` ```output cgroup2fs ``` Si la salida es `tmpfs`, el sistema está en modo v1 o híbrido, algo que solo ocurre en distribuciones antiguas o si alguien añadió `systemd.unified_cgroup_hierarchy=0` a los parámetros del kernel en `/etc/default/grub`. Mira qué controladores ofrece el kernel y cuáles tiene activados la raíz para sus hijos: ```bash cat /sys/fs/cgroup/cgroup.controllers cat /sys/fs/cgroup/cgroup.subtree_control ``` ```output cpuset cpu io memory hugetlb pids rdma misc cpuset cpu io memory pids ``` Un controlador solo puede usarse en un cgroup si su padre lo tiene en `cgroup.subtree_control`. systemd ya activa `cpu`, `io`, `memory` y `pids` en la raíz, así que no necesitas hacerlo tú. ## Paso 2: Explorar la jerarquía que crea systemd systemd organiza todos los procesos en *slices*: `system.slice` para los servicios, `user.slice` para las sesiones de usuario y `init.scope` para el propio PID 1. Visualiza el árbol: ```bash systemd-cgls --no-pager | head -n 15 ``` ```output Control group /: -.slice ├─user.slice │ └─user-1000.slice │ ├─user@1000.service │ └─session-3.scope │ ├─1840 sshd: your_user [priv] │ └─1901 -bash ├─init.scope │ └─1 /sbin/init └─system.slice ├─ssh.service │ └─1022 "sshd: /usr/sbin/sshd -D [listener] 0 of 10-100 startups" ... ``` Cada nodo del árbol es un directorio en `/sys/fs/cgroup`. Para saber en qué cgroup está tu shell: ```bash cat /proc/self/cgroup ``` ```output 0::/user.slice/user-1000.slice/session-3.scope ``` Los ficheros de interfaz más importantes de cada cgroup son estos: | Fichero | Qué hace | |---|---| | `cgroup.procs` | PIDs del cgroup. Escribir un PID mueve el proceso aquí. | | `cpu.max` | Límite de CPU: cuota y periodo en microsegundos (`50000 100000` = 50 % de un núcleo). | | `cpu.weight` | Peso relativo de CPU cuando hay competencia (1 a 10000, por defecto 100). | | `memory.max` | Límite duro de memoria. Si se supera y no se puede recuperar, actúa el OOM killer. | | `memory.high` | Límite suave: por encima, el kernel frena al grupo y recupera memoria de forma agresiva. | | `memory.swap.max` | Máximo de swap que puede usar el grupo. | | `io.max` | Límites de E/S por dispositivo en bytes o operaciones por segundo. | | `pids.max` | Número máximo de procesos e hilos. | ## Paso 3: Limitar memoria a mano Crear cgroups directamente en `/sys/fs/cgroup` es útil para entender el mecanismo, pero systemd no conoce esos grupos. Úsalo solo para pruebas y borra el grupo al terminar. Crea un cgroup de prueba y comprueba que hereda los controladores: ```bash sudo mkdir /sys/fs/cgroup/prueba cat /sys/fs/cgroup/prueba/cgroup.controllers ``` ```output cpuset cpu io memory pids ``` Fija un límite de 200 MB de memoria sin swap: ```bash echo 200M | sudo tee /sys/fs/cgroup/prueba/memory.max echo 0 | sudo tee /sys/fs/cgroup/prueba/memory.swap.max ``` Ahora lanza un proceso que intente usar 400 MB dentro del grupo. El `sh` intermedio escribe su propio PID en `cgroup.procs` y después se reemplaza por `stress-ng`, que hereda el cgroup: ```bash sudo sh -c 'echo $$ > /sys/fs/cgroup/prueba/cgroup.procs; exec stress-ng --vm 1 --vm-bytes 400M --timeout 15s' ``` `stress-ng` informará de que su trabajador ha sido terminado y lo relanzará varias veces. Comprueba que el kernel ha aplicado el límite con los contadores de eventos del grupo: ```bash cat /sys/fs/cgroup/prueba/memory.events ``` ```output low 0 high 0 max 212 oom 6 oom_kill 6 oom_group_kill 0 ``` `max` cuenta las veces que el grupo tocó el límite y `oom_kill` los procesos que el kernel tuvo que matar. ## Paso 4: Limitar CPU a mano `cpu.max` fija una cuota de tiempo de CPU por periodo. Limita el grupo al 50 % de un núcleo: ```bash echo "50000 100000" | sudo tee /sys/fs/cgroup/prueba/cpu.max ``` Lanza dos trabajadores de CPU dentro del grupo durante 20 segundos en segundo plano y observa el consumo con `top` desde otra terminal: ```bash sudo sh -c 'echo $$ > /sys/fs/cgroup/prueba/cgroup.procs; exec stress-ng --cpu 2 --timeout 20s' & ``` En `top` verás que los dos procesos `stress-ng-cpu` suman alrededor del 50 % de CPU aunque intenten usar dos núcleos completos. Al terminar, comprueba cuántas veces se frenó al grupo: ```bash grep -E 'nr_throttled|throttled_usec' /sys/fs/cgroup/prueba/cpu.stat ``` ```output nr_throttled 198 throttled_usec 19654210 ``` Cuando no quedan procesos en el grupo, puedes borrarlo. `rmdir` es la única forma: no uses `rm -r`, porque los ficheros de interfaz no se pueden borrar: ```bash cat /sys/fs/cgroup/prueba/cgroup.procs sudo rmdir /sys/fs/cgroup/prueba ``` ## Paso 5: Ejecutar un comando con límites mediante systemd-run En la práctica no necesitas escribir en `/sys/fs/cgroup`. `systemd-run` lanza un comando como unit transitoria, dentro de un cgroup que systemd gestiona, y acepta los límites como propiedades. Ejecuta la misma prueba de CPU y memoria: ```bash sudo systemd-run --unit=prueba-limites -p CPUQuota=50% -p MemoryMax=200M -p MemorySwapMax=0 stress-ng --cpu 2 --timeout 60s ``` ```output Running as unit: prueba-limites.service; invocation ID: 3f1c... ``` Mientras se ejecuta, comprueba que systemd ha traducido las propiedades a los ficheros del kernel: ```bash cat /sys/fs/cgroup/system.slice/prueba-limites.service/cpu.max cat /sys/fs/cgroup/system.slice/prueba-limites.service/memory.max ``` ```output 50000 100000 209715200 ``` `CPUQuota=50%` se ha convertido en `50000 100000` y `200M` en bytes. Para lanzar un comando interactivo con límites y ver su salida en la terminal, usa `--scope`, que lo ejecuta en primer plano: ```bash sudo systemd-run --scope -p MemoryMax=200M -p MemorySwapMax=0 stress-ng --vm 1 --vm-bytes 400M --timeout 10s ``` ## Paso 6: Aplicar límites persistentes a un servicio Para un servicio real, define los límites en la propia unit o en un fichero de ajuste. Como ejemplo, crea un servicio que consume CPU sin parar: ```bash sudo nano /etc/systemd/system/carga-demo.service ``` ```ini [Unit] Description=Servicio de carga de demostración [Service] ExecStart=/usr/bin/stress-ng --cpu 2 --vm 1 --vm-bytes 150M DynamicUser=yes CPUQuota=50% CPUWeight=50 MemoryHigh=300M MemoryMax=400M TasksMax=50 [Install] WantedBy=multi-user.target ``` Estas directivas se corresponden con los ficheros del paso 2: `CPUQuota` con `cpu.max`, `CPUWeight` con `cpu.weight`, `MemoryHigh` y `MemoryMax` con `memory.high` y `memory.max`, y `TasksMax` con `pids.max`. Arranca el servicio: ```bash sudo systemctl daemon-reload sudo systemctl start carga-demo.service systemctl status carga-demo.service --no-pager | grep -E 'Tasks|Memory|CPU' ``` ```output Tasks: 4 (limit: 50) Memory: 152.3M (high: 300.0M max: 400.0M available: 147.7M peak: 153.1M) CPU: 12.405s ``` Puedes cambiar un límite en caliente con `systemctl set-property`. El cambio se aplica al instante y se guarda en `/etc/systemd/system.control/`, así que sobrevive a los reinicios: ```bash sudo systemctl set-property carga-demo.service CPUQuota=25% cat /sys/fs/cgroup/system.slice/carga-demo.service/cpu.max ls /etc/systemd/system.control/carga-demo.service.d/ ``` ```output 25000 100000 50-CPUQuota.conf ``` Si solo quieres probar un valor hasta el próximo reinicio, añade `--runtime`: el ajuste se guarda en `/run` y desaparece al reiniciar. ## Paso 7: Limitar la E/S de disco El controlador `io` limita el ancho de banda o las operaciones por segundo de un dispositivo de bloque completo (no de una partición). Averigua el nombre del disco: ```bash lsblk -d -o NAME,MAJ:MIN,SIZE,TYPE ``` ```output NAME MAJ:MIN SIZE TYPE vda 253:0 80G disk ``` En un VPS lo habitual es `vda`. Añade a `carga-demo.service` un límite de escritura de 10 MB/s en ese disco: ```bash sudo systemctl set-property carga-demo.service IOWriteBandwidthMax="/dev/vda 10M" cat /sys/fs/cgroup/system.slice/carga-demo.service/io.max ``` ```output 253:0 rbps=max wbps=10485760 riops=max wiops=max ``` Para comprobar un límite de escritura puedes lanzar `dd` con escritura directa dentro de un scope limitado; la velocidad que muestra al final no debería superar el límite: ```bash sudo systemd-run --scope -p IOWriteBandwidthMax="/dev/vda 10M" dd if=/dev/zero of=/var/tmp/prueba-io bs=1M count=100 oflag=direct sudo rm /var/tmp/prueba-io ``` ```output 104857600 bytes (105 MB, 100 MiB) copied, 10.02 s, 10.5 MB/s ``` ## Paso 8: Agrupar servicios en un slice propio Si varios servicios deben compartir un presupuesto común, crea un slice y colócalos dentro. El límite del slice se aplica a la suma de todos: ```bash sudo nano /etc/systemd/system/aplicaciones.slice ``` ```ini [Unit] Description=Slice para servicios de aplicación [Slice] CPUQuota=150% MemoryMax=1G ``` Mueve el servicio de demostración a ese slice con un fichero de ajuste: ```bash sudo systemctl edit carga-demo.service ``` ```ini [Service] Slice=aplicaciones.slice ``` Reinicia el servicio y comprueba su nueva ruta en la jerarquía: ```bash sudo systemctl restart carga-demo.service systemctl show -p ControlGroup carga-demo.service ``` ```output ControlGroup=/aplicaciones.slice/carga-demo.service ``` ## Paso 9: Supervisar el consumo por cgroup `systemd-cgtop` muestra en tiempo real CPU, memoria, E/S y número de tareas de cada cgroup, ordenado por consumo: ```bash sudo systemd-cgtop ``` La presión de recursos (PSI) indica cuánto tiempo pasan los procesos de un grupo esperando por CPU, memoria o E/S, y es mejor indicador de saturación que el porcentaje de uso: ```bash cat /sys/fs/cgroup/aplicaciones.slice/cpu.pressure ``` ```output some avg10=48.12 avg60=47.90 avg300=39.51 total=91532008 full avg10=0.00 avg60=0.00 avg300=0.00 total=0 ``` `some avg10=48.12` significa que durante el 48 % de los últimos 10 segundos al menos un proceso del grupo estaba esperando CPU, lo esperable con una cuota que lo frena. Al terminar, elimina el servicio y el slice de demostración: ```bash sudo systemctl stop carga-demo.service sudo rm -r /etc/systemd/system/carga-demo.service /etc/systemd/system/carga-demo.service.d /etc/systemd/system.control/carga-demo.service.d /etc/systemd/system/aplicaciones.slice sudo systemctl daemon-reload ``` ## Solución de problemas **`echo: write error: Device or resource busy` al activar un controlador en `cgroup.subtree_control`.** cgroups v2 no permite que un grupo con procesos propios reparta controladores a sus hijos (salvo la raíz). Mueve los procesos a un grupo hijo antes de activar el controlador. **`rmdir: failed to remove ... Device or resource busy`.** El cgroup todavía tiene procesos o grupos hijos. Revisa `cgroup.procs` y espera a que terminen o muévelos a otro grupo. **El límite de memoria no parece aplicarse.** Si el grupo puede usar swap, el proceso se mueve a swap en lugar de morir. Fija `MemorySwapMax=0` (o `memory.swap.max` a `0`) si quieres un límite estricto de memoria total. **`IOWriteBandwidthMax` no tiene efecto.** El límite solo cuenta la E/S que llega al disco. Las escrituras que se quedan en la caché de página se contabilizan cuando se vuelcan, así que prueba con `oflag=direct` y comprueba que has usado el disco completo (`/dev/vda`) y no una partición. ## Conclusión Has comprobado que Ubuntu 24.04 usa cgroups v2, has limitado memoria, CPU y E/S escribiendo en los ficheros del kernel y has hecho lo mismo con `systemd-run`, directivas en la unit, `systemctl set-property` y un slice compartido. Como siguientes pasos, define `MemoryMax` y `CPUQuota` en los servicios críticos de tus servidores, vigila `memory.events` y los ficheros de presión para detectar límites demasiado ajustados y revisa los límites de tus contenedores, que usan exactamente estos mismos ficheros.