LimitNPROC против TasksMax: что считает юнит OpenVPN
LimitNPROC считает процессы одного пользователя во всей системе; TasksMax ограничивает cgroup одного юнита. Прочитайте эффективные значения, прежде чем поднимать любое из них.
Неудачный запуск OpenVPN часто приходит как жалоба на ресурсы вместе с доброжелательным предложением поднять какой-нибудь лимит, и предложение помогает лишь примерно в половине случаев. За этими сбоями стоят два несвязанных счётчика. Один — лимит ядра на число задач, принадлежащих реальному идентификатору пользователя, и он наследуется при запуске процесса. Другой — бюджет cgroup, который менеджер служб применяет к юниту и всему, что он порождает. Прочитать эффективное значение каждого из них для юнита, который действительно работает, дешевле, чем догадка.
Два счётчика, два вопроса
LimitNPROC — это RLIMIT_NPROC под именем systemd, а его единица учёта — реальный идентификатор пользователя. Каждый процесс, принадлежащий этому пользователю, считается в одну и ту же сумму, включая работу, начатую другими юнитами, сеансом входа или заданием планировщика. Поэтому лимит не является потолком на службу, даже когда он задан внутри юнита службы, и служба, которая порождает помощника для каждого клиента, конкурирует со всем остальным, что работает под этой учётной записью. Занятое задание cron под тем же пользователем может вытолкнуть юнит VPN за линию, которую он сам никогда не переходил.
TasksMax имеет другого владельца: контроллер pids на cgroup юнита, который считает задачи, а не процессы. Потоки — это задачи, поэтому демон, выглядящий однопоточным, но имеющий пул обработчиков, может израсходовать бюджет, который на первый взгляд кажется куда больше числа его процессов. EffectiveTasksMax сообщает, что на самом деле разрешает иерархия cgroup после того, как родительские срезы сказали своё.
Почему исключение для root меняет ответ
Ядро не применяет RLIMIT_NPROC к процессу, несущему CAP_SYS_RESOURCE или CAP_SYS_ADMIN, поэтому юнит, работающий как root без отказа от привилегий, практически не ограничен этой настройкой. Именно это единственное исключение объясняет противоречивые советы, которыми обмениваются люди: подъём LimitNPROC чинит развёртывание, отказавшееся от привилегий, и не делает ровным счётом ничего для того, что осталось привилегированным. Проверьте User= и то, меняет ли демон личность после запуска, прежде чем приписывать этой настройке заслугу или вину. Привилегированный юнит, который позже переходит на непривилегированную учётную запись, в момент перехода снова оказывается под лимитом, и это делает хронологию сбоя тоже достойной прочтения.
Стадия сбоя сужает это ещё сильнее. Отказ при подготовке выполнения, ошибка ветвления, о которой сообщает сам демон, и отклонённое создание задачи внутри cgroup дают разные строки journal, и только на одну из них отвечает больший бюджет.
Читайте эффективные значения, а не значения оболочки
ulimit -u интерактивной оболочки описывает эту оболочку. Он не описывает, что менеджер служб передал демону, и ничего не говорит об учёте cgroup. Спрашивайте вместо этого сам юнит, используя имя экземпляра, которое существует на хосте, а не пример из руководства.
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
Внутри контейнера ответ требует ещё двух чтений, потому что внутреннее значение infinity описывает только взгляд гостя. Спросите у владельца хоста собственный потолок pids контейнера и подтвердите, может ли внутренний менеджер служб вообще делегировать этот контроллер. Вложенный юнит не может держать бюджет, который его родительский cgroup никогда не выдавал.
Оцените размер исправления и докажите его под нагрузкой
Берите число из рабочей нагрузки, а не из текста ошибки. Посчитайте процессы, которые демон запускает на каждого подключённого клиента или на каждый вызов помощника, умножьте на параллельность, которую развёртывание должно выдержать, добавьте запас на административные сеансы и затем сравните это число с двумя эффективными значениями. Локальное переопределение drop-in оставляет изменение проверяемым и переживает обновления пакетов, тогда как правка поставляемого файла юнита скрывает изменение, пока следующее обновление не заменит его. Запишите арифметику рядом с переопределением, потому что следующему человеку, который будет его поднимать или опускать, нужны допущения, а не итоговое число.
Изменения бюджета применяются при выполнении, поэтому одна перезагрузка не меняет размер уже работающих процессов. Перезапустите юнит через его обычный жизненный цикл, примите прерывание сеанса и снова прочитайте счётчики на новом экземпляре.
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
Исправление доказывает перезапущенный юнит, у которого число задач остаётся ниже обоих потолков, пока он несёт предполагаемую нагрузку, плюс journal, который больше не сообщает об исчерпании задач или ветвлений на той же стадии. Сохраните числа до и после вместе с изменением, чтобы следующее расследование начиналось с измерения, а не с того же совета. Если число всё ещё достигает потолка, значит арифметика была неверной, а не лимит случайно оказался слишком мал, и это различие стоит знать до следующего перезапуска.