Как справиться с лог-штормом “Too Many Open Files”

Как справиться с лог-штормом “Too Many Open Files”


Если ваш HTTP-сервер встречает вас ошибкой 599: Network Connection Timed out, значит, дела так себе. Первым делом стоит проверить, вообще ли сервер доступен и можете ли вы зайти на него по SSH. А дальше в бой вступает его лог-файл. Скорее всего, там вы увидите что-то в духе:

HTTP: Accept error: accept tcp [::]:<port_number>: accept4: too many open files

Можно задуматься: какое отношение “слишком много открытых файлов” имеет к ошибке accept в HTTP? На самом деле объяснение довольно занятное. В Debian каждый сокет считается открытым файлом. Вот вам и связь! Поскольку каждый сокет — это файл, а сервер превысил лимит на количество открытых файлов, он просто не в состоянии принимать новые TCP-подключения.

# команда, которая считает количество открытых файлов сервисом
lsof -u <user-running-the-service> | wc -l

Если это продовый сервер и вы не до конца понимаете, что происходит, кроме того, что очевидно слишком много открытых соединений, — быстрый и не самый изящный способ временно решить проблему — перезапустить сервис. Поскольку это TCP-сервер, все существующие соединения закроются, и активные клиенты попытаются переподключиться. Такой рестарт очищает “зависшие” сокеты, которые почему-то не были освобождены. Но это лишь временная мера. В этой заметке как раз речь пойдёт о том, как всё исправить по-настоящему.

Как это вообще исправить?

Короткий ответ — поднять лимит открытых файлов для сервиса с помощью ulimit. А подробности — ниже.

Постоянное решение состоит из двух частей:

  1. Убедиться, что сервер поддерживает большее число открытых файлов.
  2. Проследить, чтобы сервер корректно очищал “протухшие” сокеты.

Первая часть почти наверняка избавит вас от ошибок 599. А вторая нужна, чтобы количество открытых сокетов не росло бесконтрольно из-за того, что сервер не закрывает их после использования. Этот второй пункт уже зависит от конкретной реализации сервера и никак не привязан к настройкам системы.

Что такое ulimit

Как гласит man:

“The ulimit builtin is used to set the resource usage limits of the
shell and any processes spawned by it.”

По сути, это команда, которая задаёт ограничения на использование ресурсов в рамках сессии: количество процессов на пользователя, загрузку CPU, лимит открытых файловых дескрипторов и прочее.

Проверяем текущие лимиты

Есть несколько способов узнать, какие лимиты сейчас действуют.

Проверить лимиты конкретного процесса

1.Найдите его PID:

ps -afx | grep <process_name>

2.Возьмите PID из вывода и запустите:

cat /proc/<PID>/limits

Примерный вывод:

Limit                     Soft Limit           Hard Limit           Units     
Max cpu time              unlimited            unlimited            seconds   
Max file size             unlimited            unlimited            bytes     
Max data size             unlimited            unlimited            bytes     
Max stack size            8388608              unlimited            bytes     
Max core file size        0                    unlimited            bytes     
Max resident set          unlimited            unlimited            bytes     
Max processes             15671                15671                processes 
Max open files            1024                 524288               files     
Max locked memory         65536                65536                bytes     
Max address space         unlimited            unlimited            bytes     
Max file locks            unlimited            unlimited            locks     
Max pending signals       15671                15671                signals   
Max msgqueue size         819200               819200               bytes     
Max nice priority         0                    0                    
Max realtime priority     0                    0                    
Max realtime timeout      unlimited            unlimited            us

Этот файл перечисляет все лимиты, которые нам могут быть интересны. В контексте ошибки выше важен Max open files.

Проверить лимит в текущей оболочке

Если вы вошли на сервер по терминалу и хотите посмотреть лимиты для пользователя, пригодится команда:

ulimit -a

Пример:

core file size          (blocks, -c) 0
data seg size           (kbytes, -d) unlimited
scheduling priority             (-e) 0
file size               (blocks, -f) unlimited
pending signals                 (-i) 15671
max locked memory       (kbytes, -l) 65536
max memory size         (kbytes, -m) unlimited
open files                      (-n) 1024
pipe size            (512 bytes, -p) 8
POSIX message queues     (bytes, -q) 819200
real-time priority              (-r) 0
stack size              (kbytes, -s) 8192
cpu time               (seconds, -t) unlimited
max user processes              (-u) 15671
virtual memory          (kbytes, -v) unlimited
file locks                      (-x) unlimited

Что такое Soft Limit и Hard Limit?

У каждого ресурсного ограничения есть две границы:

Hard Limit. Это верхняя граница для soft limit. Значение soft limit не может её превышать. И если hard limit уже установлен для пользователя, изменить его нельзя.

Soft Limit. Это ограничение ресурса, которое задаётся пользователю или программе внутри сессии. Пользователь или процесс не могут выйти за пределы своего soft limit. Именно этот лимит и нужно увеличить, чтобы устранить ошибку.

Настройка ресурсных лимитов в bash-скриптах — собственно, фикc

В качестве примера здесь используется run-скрипт для сервиса под runit. Если вы не знакомы с runit, автор отсылает к своей отдельной заметке — но для понимания примера это не критично.

Чтобы задать лимит на количество открытых файлов для сервиса runit, достаточно вызвать ulimit перед запуском бинаря, который обслуживает сам сервис. Пример скрипта:

# /etc/service/dhcp-client/run
#!/bin/sh
exec 2>&1  # redirect output of stderr to stdout 
ulimit -n 409600
exec /sbin/dhclient -4 -v -i -d --no-pid \
    -lf /var/lib/dhcp/dhclient.eth1.leases \
    -I eth1

Когда вы выставляете ulimit в большее значение, увеличивается число доступных открытых файлов — а значит, и лимит открытых сокетов растёт.

Если сервер запускается под отдельным системным пользователем, можно поднять лимит для него вручную:

su <user> — shell /bin/bash — command "ulimit -n <limit>"

Если же нужно задать общий лимит для пользователя или группы по умолчанию, правят файл /etc/security/limits.conf:

$ cat /etc/security/limits.conf
# <domain>      <type>  <item>   <value>
*               soft    core     <value>
*               hard    nofile   <value>
@<group>        hard    nproc    <value>
<user>          soft    nproc    <value>

Такой подход даёт глобальные настройки, которые будут применяться при следующем входе пользователя в систему.

Основные выводы:

  • Soft limit — это фактический лимит, который действует для сессии или процесса.
  • Soft limit не может быть выше, чем соответствующий hard limit.
  • Общие лимиты для пользователя, группы или даже для всех сразу можно задать в /etc/security/limits.conf.
  • Hard limit, однажды установленный для пользователя, можно только снизить — поднять его обратно уже нельзя.

Надеюсь, эта заметка оказалась для вас такой же полезной, как автору было интересно её писать. Если захотите что-то обсудить — пишите.

Report Page