Limitar recursos a usuarios con Control Groups (Cgroups)

Los grupos de control (cgroups) son una funcionalidad del kernel Linux disponible a partir de la versión 2.6.24 que permite limitar y distribuir el uso de recursos de sistema como la CPU, memoria, red o disco entre determinados usuarios, grupos o procesos del sistema. Con esta funcionalidad podemos establecer con bastante granularidad el uso que cada usuario, grupo o proceso puede hacer de los recursos de hardware disponibles.

Los recursos que podemos gestionar se configuran a través de subsistemas. Algunos de los disponibles son los siguientes:

  • blkio: limita la entrada/salida a dispositivos de almacenamiento.
  • cpu: a través del scheduler controla el acceso a las CPU.
  • memory: establece límites en el uso de memoria.
  • net_prio: este subsistema permite establecer prioridades dinámicas en el tráfico de las interfaces de red.

La documentación de RHEL sobre cgroups es bastante completa, os recomiendo echarle un vistazo para profundizar sobre el tema. En esta entrada vamos a ir al grano y ver algunas de las posibilidades que nos ofrece esta funcionalidad del kernel.

Instalación de cgroups

En Red Hat y derivados es tan sencillo como instalar el paquete libcgroup:

# yum install libcgroup

Configuración de cgroups

Una vez instalado, tenemos que saber que toda la jerarquía de subsistemas de control de recursos se montan automáticamente en /cgroup al arrancar el demonio cgconfig:

# chkconfig cgconfig on
# service cgconfig start

Este demonio monta estos subsistemas y activa los grupos de recursos que hemos especificado en el archivo de configuración /etc/cgconfig.conf, en el que por defecto únicamente aparece la configuración de montado de los subsistemas:

mount {
        cpuset  = /cgroup/cpuset;
        cpu     = /cgroup/cpu;
        cpuacct = /cgroup/cpuacct;
        memory  = /cgroup/memory;
        devices = /cgroup/devices;
        freezer = /cgroup/freezer;
        net_cls = /cgroup/net_cls;
        blkio   = /cgroup/blkio;
}

Con el comando lscgroups podemos listar los subsistemas activos:

# lscgroup 
cpuset:/
cpu:/
cpuacct:/
memory:/
devices:/
freezer:/
net_cls:/
blkio:/

Justo debajo del bloque mount del archivo de configuración /etc/cgconfig.conf es donde debemos especificar nuestros grupos de limitación. En el siguiente ejemplo añadimos un grupo llamado «iolimit» en el cual especificamos que los usuarios, grupos o procesos que estén «enganchados» a él tendrán una limitación de 10 MB/s lectura en el disco «sda»:

$ grep sda /proc/partitions  | head -1
   8        0    2774016 sda
group iolimit {
    blkio  {
        blkio.throttle.read_bps_device="8:0 10485760"; 
    }
}

Si ahora reiniciamos el servicio veremos que nuestro grupo «iolimit» está asociado al subsistema blkio:

# service cgconfig restart
# lscgroup 
cpuset:/
cpu:/
cpuacct:/
memory:/
devices:/
freezer:/
net_cls:/
blkio:/
blkio:/iolimit

Igual que hemos hecho un grupo de limitación de MB/s podríamos hacer otro de IOPS, ciclos de CPU, ancho de banda, memoria, etc. También se puede hacer un grupo combinando varios subsistemas y parámetros. Si navegáis por /cgroup/* podréis ver toda la parametrización posible.

Por el momento no le hemos dicho qué usuarios o grupos de sistema deben tener esta limitación de I/O, para ello se utiliza el demonio cgred y el archivo de configuración /etc/cgrules.conf:

# chkconfig cgred on
# service cgred start

El archivo de configuración es muy sencillo. Indicamos usuario/grupo de sistema, subsistema y grupo de limitación:

# Example:
#            
#@student       cpu,memory      usergroup/student/
#peter          cpu             test1/
#%              memory          test2/
foo            blkio           iolimit/

Probando cgroups

Según la configuración anterior hemos establecido una limitación de 10MB/s al usuario foo contra el disco /dev/sda así que hacemos la prueba para confirmar el correcto funcionamiento:

$ sudo hdparm --direct -t /dev/sda
[sudo] password for foo: 

/dev/sda:
 Timing O_DIRECT disk reads:  32 MB in  3.19 seconds =  10.03 MB/sec

¡Funcionando! si hacéis la prueba con «dd» aseguraos de utilizar la flag direct oflag=direct para hacer bypass de los buffers y cachés de SO.

Si lo que queremos es aplicar la limitación a un proceso concreto en lugar de a un usuario podemos lanzar el comando con cgexec del siguiente modo:

# cgexec -g SUBSISTEMA:GRUPO COMANDO

En el siguiente ejemplo podemos ver la diferencia de ejecutar el comando con y sin la limitación como root:

[root@lab1 ~]# cgexec -g blkio:iolimit hdparm --direct -t /dev/sda

/dev/sda:
 Timing O_DIRECT disk reads:  32 MB in  3.20 seconds =  10.02 MB/sec

[root@lab1 ~]# hdparm --direct -t /dev/sda

/dev/sda:
 Timing O_DIRECT disk reads: 278 MB in  3.01 seconds =  92.30 MB/sec

La configuración que he puesto en este artículo es sólo la punta del iceberg de las posibilidades que ofrece cgroup. Para más información empapaos la documentación :)