Un arranque fallido de OpenVPN suele llegar como una queja por recursos junto con una sugerencia bienintencionada de subir algún límite, y la sugerencia solo funciona aproximadamente la mitad de las veces. Detrás de esos fallos hay dos contadores sin relación. Uno es un límite del núcleo sobre el número de tareas que posee un identificador de usuario real, y se hereda cuando se ejecuta el proceso. El otro es un presupuesto de cgroup que el gestor de servicios aplica a una unidad y a todo lo que esta bifurca. Leer el valor efectivo de cada uno, para la unidad que realmente se ejecuta, cuesta menos que una conjetura.

Dos contadores, dos preguntas

LimitNPROC es RLIMIT_NPROC bajo un nombre de systemd, y su unidad contable es el identificador de usuario real. Cada proceso que posee ese usuario cuenta contra el mismo total, incluido el trabajo iniciado por otras unidades, por una sesión de inicio o por una tarea del planificador. El límite, por tanto, no es un tope por servicio aunque se defina dentro de una unidad de servicio, y un servicio que bifurca un ayudante por cada cliente compite con todo lo demás que se ejecuta con esa cuenta. Una tarea cron ocupada bajo el mismo usuario puede empujar una unidad de VPN más allá de una línea que nunca cruzó por sí sola.

TasksMax tiene otro dueño: el controlador pids del cgroup de la unidad, que cuenta tareas en lugar de procesos. Los hilos son tareas, así que un demonio que parece de un solo hilo pero tiene un grupo de trabajadores puede consumir un presupuesto que, a primera vista, parece mucho mayor que su número de procesos. EffectiveTasksMax informa de lo que la jerarquía de cgroup permite realmente una vez que los segmentos padres han dicho la suya.

Por qué la excepción de root cambia la respuesta

El núcleo no aplica RLIMIT_NPROC a un proceso que lleva CAP_SYS_RESOURCE o CAP_SYS_ADMIN, y por eso una unidad que se ejecuta como root sin renunciar a los privilegios queda efectivamente sin límite por ese ajuste. Esta única excepción explica los consejos contradictorios que la gente intercambia: subir LimitNPROC repara un despliegue que renunció a los privilegios y no hace absolutamente nada por uno que siguió privilegiado. Revise User= y si el demonio cambia de identidad tras arrancar antes de dar mérito o culpa a ese ajuste. Una unidad privilegiada que luego pasa a una cuenta sin privilegios vuelve a caer bajo el límite en el momento del cambio, lo que hace que la cronología del fallo también valga la pena de leer.

La etapa del fallo lo acota más. Un rechazo durante la preparación de la ejecución, un error de bifurcación informado por el propio demonio y una creación de tarea rechazada dentro de un cgroup producen líneas de journal distintas, y solo una de ellas se responde con un presupuesto mayor.

Lea los valores efectivos, no los del intérprete

ulimit -u de un intérprete interactivo describe ese intérprete. No describe lo que el gestor de servicios pasó al demonio, y no dice nada sobre la contabilidad de cgroup. Consulte la unidad en su lugar, usando el nombre de instancia que existe en el host y no un ejemplo de una guía.

systemctl show [email protected] -p User -p LimitNPROC -p TasksCurrent -p TasksMax -p EffectiveTasksMax
cat /proc/$(systemctl show -p MainPID --value [email protected])/limits | grep -i processes

Dentro de un contenedor la respuesta necesita dos lecturas más, porque un valor interno de infinity solo describe la visión del invitado. Pida al propietario del host el techo de pids propio del contenedor y confirme si el gestor de servicios interno puede delegar ese controlador. Una unidad anidada no puede sostener un presupuesto que su cgroup padre nunca concedió.

Dimensione la corrección y demuéstrela bajo carga

Elija un número a partir de la carga de trabajo y no del texto del error. Cuente los procesos que el demonio inicia por cliente conectado o por invocación de ayudante, multiplique por la concurrencia que se espera que soporte el despliegue, añada un margen para sesiones administrativas y luego compare esa cifra con los dos valores efectivos. Una sustitución local de tipo drop-in mantiene el cambio revisable y sobrevive a las actualizaciones de paquetes, mientras que editar un archivo de unidad entregado oculta la modificación hasta que la próxima actualización lo reemplaza. Escriba la aritmética junto a la sustitución, porque la próxima persona que la suba o la baje necesita las suposiciones, no el número final.

Los cambios de presupuesto se aplican en la ejecución, así que una recarga por sí sola no redimensiona los procesos que ya están en marcha. Reinicie la unidad por su ciclo de vida normal, aceptando la interrupción de la sesión, y vuelva a leer los contadores en la instancia nueva.

sudo systemctl daemon-reload
sudo systemctl restart [email protected]
systemctl show [email protected] -p TasksCurrent -p EffectiveTasksMax
sudo journalctl -u [email protected] -b --no-pager | tail -n 20

Lo que demuestra la reparación es una unidad reiniciada cuyo recuento de tareas queda por debajo de los dos techos mientras soporta la carga prevista, además de un journal que ya no informa de agotamiento de tareas o de bifurcaciones en la misma etapa. Guarde los números de antes y después con el cambio para que la próxima investigación empiece por una medición y no por el mismo consejo. Si el recuento sigue alcanzando el techo, la aritmética era errónea y no el límite demasiado pequeño por casualidad, y esa diferencia conviene conocerla antes del próximo reinicio.

Artículos relacionados