Entender y configurar el límite de ficheros abiertos simultáneamente (open files)

Cuando nos referimos a ficheros abiertos, realmente nos referimos a descriptores de ficheros abiertos, los cuales no tienen que ser necesariamente archivos tal y como los conocemos.

Un descriptor de archivo es una clave a una estructura de datos residente en el núcleo, que contiene detalles de todos los archivos abiertos. En POSIX, esta estructura de datos se llama “tabla de descriptores de archivos”, y cada proceso tiene la suya. La aplicación que lanza un usuario pasa al núcleo la clave abstracta mediante una llamada al sistema, y el núcleo accede al archivo correspondiente a la clave. Esa misma aplicación no puede acceder a la tabla de descriptores de archivo directamente, ni para leer ni para escribir.

En los sistemas Unix, los descriptores de archivo se pueden referir a archivos, directorios, dispositivos de bloques o dispositivos de caracteres (también llamados “archivos especiales”), sockets, FIFOs (también llamados “tuberías con nombre”) o tuberías sin nombre. El kernel es el encargado de establecer el número máximo de archivos que puede asignar a procesos, esto lo hace por medio del valor de “fs.file-max”, el cual es modificable si se necesita.

Establecer de forma persistente el límite de descriptores de fichero (/etc/sysctl.conf) a manejar del kernel.

fs.file-max = 1000000

Si se modifica el valor, se deben guardar los cambios y comprobar que el valor es el esperado.

sysctl -p
sysctl fs.file-max

Se puede consultar el valor en el fichero /proc/sys/fs/file-max y /proc/sys/fs/file-nr.

cat /proc/sys/fs/file-nr
3936	0	707452

El fichero file-nr muestra tres parámetros. 3936 Descriptores de ficheros asignados. 0 Descriptores de ficheros que no están en uso pero fueron asignados. 707452 Límite de descriptores de ficheros del sistema del sistema (/proc/sys/fs/file-max).

El núcleo asigna dinámicamente descriptores de archivo cada vez que es solicitado por una aplicación, pero el núcleo no los libera cuando la aplicación ha terminado de utilizarlo. El kernel recicla estos identificadores de archivo en su lugar. Esto significa que con el tiempo el número total de descriptores asignados aumentará a pesar de que el número de descriptores de archivo que se utilizan en el momento puede ser bajo.

Además de los límites establecidos por el núcleo, se permite definir políticas por proceso / usuario, estos límites pueden ser duros o blandos. Los límites se aplican a procesos y no a usuarios, un usuario que tenga un límite de 1024 descriptores de ficheros, puede arrancar tres aplicaciones y por tanto, entre ellas tener un máximo de 3072 ficheros abiertos.

Tipos de límite de ficheros abiertos: duros y blandos.

- Un límite duro determina el máximo número de ficheros abiertos permitido a un proceso de usuario, solo puede ser establecido por root.

- Un límite blando es el límite establecido a un usuario, puede en momentos de necesidad ser aumentado por el propio usuario / proceso. Tiene como máximo ese límite duro establecido por root.

Cantidad máxima de ficheros abiertos por usuario y sesión.

ulimit -n
1024

Mostrar los límites blandos y duros del usuario en uso.

# ulimit -Sn
1024
# ulimit -Hn
1024

Modificar límite de ficheros abiertos simultáneamente en un sistema.

# Modificar los dos límites (S/H)con un solo comando.
ulimit -SHn 10000
# Modificar los límites de forma separada
ulimit -Hn 10000
ulimit -Sn 10000 # Recordemos que el valor podría ser menor si se desea

NOTA: El límite suave (“Sn”) nunca puede ser mayor que el duro (“Hn”).

Estos cambios no son persistentes y duran lo que dure la sesión de ese usuario en el sistema.

Si se están usando systemd, los límites establecidos en el fichero limits.conf serán ignorados ya que systemd usa su propios límites. Por lo tanto, para servicios administrados por systemd se deben especificar en el mismo fichero .service.

Relación entre “systemd limits” y “ulimit”.

Directive        ulimit equivalent     Unit
 
LimitCPU=        ulimit -t             Seconds      
LimitFSIZE=      ulimit -f             Bytes
LimitDATA=       ulimit -d             Bytes
LimitSTACK=      ulimit -s             Bytes
LimitCORE=       ulimit -c             Bytes
LimitRSS=        ulimit -m             Bytes
LimitNOFILE=     ulimit -n             Number of File Descriptors 
LimitAS=         ulimit -v             Bytes
LimitNPROC=      ulimit -u             Number of Processes 
LimitMEMLOCK=    ulimit -l             Bytes
LimitLOCKS=      ulimit -x             Number of Locks 
LimitSIGPENDING= ulimit -i             Number of Queued Signals 
LimitMSGQUEUE=   ulimit -q             Bytes
LimitNICE=       ulimit -e             Nice Level 
LimitRTPRIO=     ulimit -r             Realtime Priority  
LimitRTTIME=     No equivalent

Si con ulimit se utiliza 'unlimited' equivale a 'infinity' en systemd.

ulimit -c unlimited = LimitCORE=infinity
ulimit -v unlimited = LimitAS=infinity
ulimit -m unlimited = LimitRSS=infinity 

Ejemplo (Bloque [Service]).

[Unit]
Description=...
...

[Service]
LimitNPROC=8192:16384
LimitNOFILE=90000:110000
LimitCORE=500M   # Permite crear coredumps hasta 500M.

[Install]
...

Hay otros ficheros para configurar los límites de una manera más global.(Leer).

/etc/systemd/system.conf
/etc/systemd/user.conf
/etc/systemd/<systemd_unit>/override.conf

En sistemas sin systemd, como es SysV, los cambios permanentes sobre los límites de ficheros abiertos se realizan sobre /etc/security/limits.conf (soft y hard).

*               soft    nofile           4096
*               hard    nofile           4096

NOTA: El patrón “*” se refiere a todos los usuarios, también se puede definir por usuario.

Consultar límites de ficheros abiertos de otros usuarios.

su USUARIO --shell /bin/bash --command "ulimit -Hn"

Ver número de ficheros abiertos de un determinado proceso en ejecución. (ej. 709)

cat /proc/709/limits | grep -i open
Max open files            1024                 4096                 files

Dos opciones para incrementar el límite de descriptores de ficheros abiertos en tiempo de ejecución.

Opción 1.

# echo -n "Max open files=soft:hard" > /proc/pid/limits
echo -n "Max open files=4096:4096" > /proc/$pid/limits

NOTA: Depende del núcleo en uso puede funcionar o no.

Opción 2.

prlimit -n1111:12222 -n -p 709
 
cat /proc/709/limits | grep -i open
Max open files            1111                 12222                files     

NOTA: prlimit permite a su vez cambiar muchos otros límites de procesos, al final de la guía se encuentra su manual.

lsof: Conocer qué ficheros están en uso (abiertos) por determinados programas.

Mostrar todos los ficheros abiertos del sistema.

lsof

Ficheros abiertos por aplicación / proceso / programa.

lsof -c java

Ficheros abiertos por usuario

lsof -u rita

Saber qué programa tiene abierto un determinado fichero.

lsof /opt/atlassian/stash_4.5.0/logs/localhost.2018-09-30.log 
COMMAND   PID     USER   FD   TYPE DEVICE SIZE/OFF    NODE NAME
java    23131 atlstash    8w   REG  253,1    33422 5505385 /opt/atlassian/stash_4.5.0/logs/localhost.2018-09-30.log

Visualizar qué ficheros de un directorio están abiertos por programas.

lsof +D /opt/atlassian/stash_4.5.0/logs/

Opciones del comando ulimit.

El comando “ulimit” proporciona control sobre los recursos disponibles para el interprete de comandos (shell) y sus procesos iniciados. ulimit marca los límites por cada proceso que el usuario ejecuta, cada proceso no podrá tener más de X ficheros abiertos.

Prueba de concepto

usuario:~ $ ulimit -n
1024
 
# Ficheros abiertos por todos los procesos del usuario (cada proceso solo puede tener 1024).
usuario:~ $ lsof | grep -i usuario | wc -l
28190

prlimit: Utilidad para modificar / visualizar los límites de uno o varios procesos dados.

NOTA: El parámetro memlock debe estar a “unlimited” si se quiere usar Huge Pages (Típico en bases de datos base de datos). También se recomienda para el uso de aplicaciones nosql como ElasticSearch.

Consulta y modificación del parámetro memlock con ulimit (64 > ulimit).

ulimit -l unlimited
64

ulimit -l unlimited
ulimit -l
unlimiled<code>

<code>Modo de empleo:

 prlimit [opciones] [-p PID]
 prlimit [opciones] ORDEN

Muestra o modifica los límites de recursos de un proceso.

Opciones generales:
 -p, --pid <pid>        id del proceso
 -o, --output <lista>   define las columnas que se usarán en la salida
     --noheadings       no imprime las cabeceras
     --raw              utiliza el formato de salida en bruto
     --verbose          salida con explicaciones
 -h, --help             muestra esta ayuda y termina
 -V, --version          saca información sobre la versión y termina

Opciones de los recursos:
 -c, --core             tamaño máximo de los ficheros «core» que se generen
 -d, --data             tamaño máximo del segmento de datos de los procesos
 -e, --nice             máxima prioridad «nice» que se permite elevar
 -f, --fsize            tamaño máximo de los ficheros escritos por el proceso
 -i, --sigpending       número máximo de señales pendientes
 -l, --memlock          tamaño máximo que un proceso puede bloquear en la memoria
 -m, --rss              tamaño máximo de conjunto residente
 -n, --nofile           número máximo de ficheros abiertos
 -q, --msgqueue         número máximo de bytes en colas de mensajes POSIX
 -r, --rtprio           máxima prioridad de planificación en tiempo real
 -s, --stack            tamaño máximo de pila
 -t, --cpu              cantidad de tiempo de CPU máxima en segundos
 -u, --nproc            número máximo de procesos de usuario
 -v, --as               tamaño de la memoria virtual
 -x, --locks            número máximo de bloqueos de fichero
 -y, --rttime           tiempo de CPU en microsegundos planificado por un proceso
                        bajo planificación en tiempo real

Columnas disponibles (para --output):
 DESCRIPTION  descripción del recurso
    RESOURCE  nombre de recurso
        SOFT  límite blando
        HARD  límite duro (techo)
       UNITS  unidades

Ejecutar “prilimit” en distribuciones antiguas (util-linux =< 2.20).

Si no se dispone del comando “prlimit” / el paquete util-linux (>= 2.21) para instalar desde repositorios, la única solución es compilar “util-linux”. Si solo se desea disponer del comando se puede utilizar la siguiente compilación para no necesitar muchas dependencias.

wget https://www.kernel.org/pub/linux/utils/util-linux/v2.22/util-linux-2.22.2.tar.gz
tar -zxvf util-linux-2.22.2.tar.gz
cd util-linux-2.22.2/
./configure --without-ncurses --disable-su --disable-sulogin --disable-login
make
./prlimit