Мир сетевого стека Linux постоянно меняется. Это приводит к разночтениям и ошибкам в различных постах в статьях и форумах. На момент написания этой статьи детали актуальны, начиная с ядра Linux версии 2.2 и далее, и, где это возможно, подчеркивают различия в настройках между ядрами вплоть до версии 5.5.
Примеры команд в этой статье основаны на командах Linux netstat (пакет net-tools) и ss (пакет iproute2).
Настройка параметров ядра
Сетевой стек настраивается через изменение настроек ядра (kernel). В текущих ядрах Linux конфигурации по умолчанию можно изменить, поместив файлы в одну из следующих папок в порядке приоритета:
/etc/sysctl.d/
/usr/lib/sysctl.d/
/run/sysctl.d/
Настройки в /etc/sysctl.conf устарели и их можно игнорировать, поэтому проверьте документацию вашей системы. Актуальные конфиги рекомендуется поместить в каталог /etc/sysctl.d/
и добавить к этим именам префикс из двух цифр плюс дефис, затем имя группы и расширение .conf (т. е.: 10-nginx.conf). Эти файлы будут загружаться в лексикографическом порядке, поэтому настройки, которые всегда необходимо настраивать, должны находиться в файле с наивысшим приоритетом, чтобы гарантировать, что они переопределяют любые настройки, сделанные в файлах с более низким лексикографическим приоритетом (т. е. настройки, сделанные в /etc/sysctl.d/99-sysctl.conf
* всегда будет переопределять один и тот же параметр в /etc/sysctl.d/10-nginx.conf).
Чтобы активировать настройки, выполните следующую команду (требуется sudo) или перезагрузите систему:
sudo sysctl -p /etc/sysctl.d/10-nginx.conf
Параметры сетевого стека
Рассмотрим настройки, которые важны для настройки сетевых соединений.
Очередь приема TCP и netdev_max_backlog
Каждое ядро CPU может хранить несколько пакетов в кольцевом буфере, прежде чем сетевой стек сможет их обработать. Если буфер заполняется быстрее, чем стек TCP может обработать пакет, счетчик отброшенных пакетов увеличивается, и пакет отбрасывается. Параметр net.core.netdev_max_backlog следует увеличить, чтобы максимизировать количество пакетов, поставленных в очередь для обработки на серверах с высоким пакетным трафиком.
net.core.netdev_max_backlog — это настройка для каждого ядра процессора.
Очередь бэклога TCP и tcp_max_syn_backlog
Очередь TCP Backlog содержит незавершенные соединения, ожидающие завершения. Соединение создается для любых пакетов SYN, которые выбираются из очереди приема и перемещаются в очередь SYN Backlog. Соединение помечается как «SYN_RECV», и клиенту отправляется обратно SYN+ACK. Эти соединения не перемещаются в очередь приема до тех пор, пока не будет получен и обработан соответствующий ACK.
Максимальное количество соединений в очереди задается в настройке ядра net.ipv4.tcp_max_syn_backlog.
При нормальной нагрузке количество записей бэклога SYN не должно превышать 1 при нормальной нагрузке и оставаться ниже предела tcp_max_syn_backlog при большой нагрузке. Чтобы проверить текущий размер журнала SYN TCP-порта, выполните следующую команду (в примере используется TCP-порт 80):
ss -n state syn-recv sport = :80 | wc -l
Если в состоянии «SYN_RECV» имеется большое количество соединений, это может вызвать проблемы на сервере, принимающем большой объем трафика. Прежде чем увеличивать этот предел, можно сократить время нахождения пакета SYN в этой очереди путем настройки соответствующих параметров TCP.
SYN Cookies
Эта настройка может уменьшить продолжительность пребывания пакета SYN в очереди приема. Если файлы cookie SYN не включены, клиент просто повторит попытку отправки пакета SYN. Если файлы cookie SYN включены (net.ipv4.tcp_syncookies), соединение не создается и не помещается в журнал SYN, но пакет SYN+ACK отправляется клиенту, как если бы он был. Файлы cookie SYN могут быть полезны при обычном трафике, но при интенсивном трафике некоторые детали соединения будут потеряны, и у клиента возникнут проблемы при установке соединения.
SYN+ACK повторные попытки
Настройка этого параметра может значительно сократить время нахождения пакета SYN в очереди приема. Что произойдет, если SYN+ACK отправлен, но ответный пакет ACK так и не получен? В этом случае сетевой стек на сервере повторит попытку отправки SYN+ACK. Задержка между попытками рассчитывается с учетом возможности восстановления сервера.
Если сервер получает SYN, отправляет SYN+ACK и не получает ACK, продолжительность повторной попытки соответствует алгоритму экспоненциальной отсрочки и, следовательно, зависит от счетчика повторов для количества попыток.
Параметр ядра, определяющий количество повторов SYN+ACK, — net.ipv4.tcp_synack_retries со значением по умолчанию, равным 5. Повторные попытки будут выполняться со следующими интервалами после первой попытки: 1 с, 3 с, 7 с, 15 с, 31 с. Тайм-аут последней повторной попытки истечет примерно через 63 секунды после первой попытки, что соответствует моменту, когда была бы предпринята следующая попытка, если бы количество повторных попыток было 6. Одно это может удерживать пакет SYN в журнале SYN более 60 секунд до истечения времени ожидания пакета. Если очередь бэклога SYN невелика, не требуется большого объема соединений, чтобы вызвать событие в сетевом стеке, когда полуоткрытые соединения никогда не завершаются и новые соединения не могут быть установлены. Установите количество повторов SYN+ACK на 0 или 1, чтобы избежать такого поведения на высокопроизводительных серверах.
SYN повторные попытки
Настройка этого параметра может значительно сократить время нахождения пакета SYN в очереди приема. Хотя повторы SYN относятся к числу повторных попыток клиента отправить SYN во время ожидания SYN+ACK, это также может повлиять на высокопроизводительные серверы, которые устанавливают прокси-соединения. Сервер nginx, выполняющий несколько десятков прокси-подключений к бэкэнд-серверу из-за всплеска трафика, может на короткое время перегрузить сетевой стек бэкэнд-сервера, а повторные попытки могут привести к нарастающему эффекту на бэкэнде как в очереди приема, так и в очереди бэклога SYN. Это, в свою очередь, может повлиять на обслуживаемые клиентские соединения. Параметр ядра для повторных попыток SYN — net.ipv4.tcp_syn_retries, по умолчанию он равен 5 или 6 в зависимости от дистрибутива. Вместо того, чтобы повторять попытки в течение более 63–130 секунд (экспоненциальная задержка), ограничьте количество повторных попыток SYN до 0 или 1.
Очередь приема TCP
Приложения отвечают за создание своей очереди приема при открытии порта прослушивателя при вызове listen () путем указания параметра «backlog». Начиная с ядра Linux версии 2.2, этот параметр изменен с установки максимального количества незавершенных соединений, которые может удерживать сокет, на максимальное количество завершенных соединений, ожидающих приема. Как описано выше, максимальное количество незавершенных подключений теперь устанавливается с помощью настройки ядра net.ipv4.tcp_max_syn_backlog.
TCP listen () backlog
Хотя приложение отвечает за размер очереди приема для каждого открываемого им прослушивателя, существует ограничение на количество соединений, которые могут находиться в очереди приема прослушивателя. Есть две настройки, которые управляют размером очереди:
- Параметр бэклога в вызове TCP Listen (), сделанном из приложения.
- Максимальный предел ядра из sysctl ядра:
net.core.somaxconn
Настройки очереди приема
Значение по умолчанию для net.core.somaxconn получается из константы SOMAXCONN, для которой в ядрах Linux вплоть до версии 5.3 установлено значение 128, а в версии 5.4 значение SOMAXCONN было увеличено до 4096.
Приложения обычно используют значение константы SOMAXCONN при настройке журнала невыполненной работы по умолчанию для прослушивателя, если оно не задано в конфигурации приложения или иногда просто жестко запрограммировано в серверном программном обеспечении. Некоторые приложения устанавливают собственное значение по умолчанию, например nginx, который устанавливает его равным 511, которое автоматически усекается до 128 в ядрах Linux до версии 5.3. Проверьте документацию приложения по настройке прослушивателя, чтобы узнать, что реально используется.
Чтобы проверить размер очереди Accept (), настроенной для открытых портов прослушивателя TCP, выполните следующую команду (пример порта 80):
ss -plnt sport = :80|cat
Максимальное значение net.core.somaxconn составляет 65535 в ядрах версий от 2.2 до 4.0.x и 4294967295 в ядрах версии 4.1.0+.
Многие приложения позволяют указывать размер очереди приема в конфигурации, предоставляя значение «backlog» в директиве прослушивателя или конфигурации, которая будет использоваться при вызове Listen (). Например, в nginx есть параметр backlog, который можно добавить в директиву прослушивания и использовать для настройки размера очереди приема для порта прослушивателя:
listen 80 backlog=65535;
Если приложение вызывает метод Listen () со значением backlog, превышающим net.core.somaxconn, то backlog для этого прослушивателя будет автоматически усечен до значения somaxconn.
Если очередь приема большая, рассмотрите также возможность увеличения количества потоков, которые могут обрабатывать запросы из очереди в приложении. Например, установка backlog = 20480 на HTTP listener для большого сервера nginx без изменения worker_connections для управления очередью приведет к тому, что сервер будет отдавать connection refused.
Настройки дескрипторов файлов в ядре
В системах Linux все представляет собой файл. Сюда входят, среди прочего, реальные файлы и папки, символические ссылки, каналы и сокеты. По этой причине настройка максимального количества подключений для процесса также требует настройки количества файлов, которые процесс может открыть.
Каждый сокет в соединении также использует дескриптор файла.
Системный лимит открытых файлов
Максимальное количество всех дескрипторов файлов, которые могут быть выделены системе, задается настройкой ядра fs.file-max. Параметр fs.file-max — это общее максимальное количество дескрипторов файлов, которые можно выделить и использовать в системе.
Чтобы увидеть текущее количество выделенных файловых дескрипторов и максимально допустимое количество, введите следующий файл:
# cat /proc/sys/fs/file-nr
1976 0 2048
Вывод показывает, что количество используемых файловых дескрипторов равно 1976, количество выделенных, но свободных файловых дескрипторов равно 0 (в ядре версии 2.6+ всегда будет отображаться «0», что означает, что используемые и выделенные всегда совпадают), а максимум равен 2048. В высокопроизводительной системе это значение должно быть установлено достаточно высоким, чтобы обрабатывать максимальное количество соединений и любые другие потребности в файловых дескрипторах для всех процессов в системе. 2048 — это очень мало для такой системы, а 1976 — очень близко к максимуму.
В Ubuntu WSL2 с ядром 5.15 вывод будет уже такой:
$ cat /proc/sys/fs/file-nr
7040 0 9223372036854775807
Лимит процесса открытия файлов
Максимальное количество файлов, которые может открыть один процесс, определяется настройкой ядра fs.nr_open. Этот параметр не должен превышать одну треть от fs.file-max. По умолчанию fs.nr_open должен быть достаточно большим для любого отдельного процесса, работающего в системе, без необходимости его настройки.
Параметр fs.nr_open — это максимальное значение, которое можно установить для «количества открытых файлов» пользовательского лимита.
Пользовательский лимит открытых файлов
Помимо ограничений системы файловых дескрипторов и процессов, каждый пользователь ограничен максимальным количеством открытых файловых дескрипторов. Это устанавливается с помощью системного файла limit.conf (nofile) или в unit-файле процессов systemd, если процесс выполняется под systemd (LimitNOFILE). Чтобы увидеть максимальное количество файловых дескрипторов, которые пользователь может открыть по умолчанию:
$ ulimit -n
1024
А под systemd, на примере nginx:
$ systemctl show nginx | grep LimitNOFILE
4096
Изменение настроек открытия файлов
Настройки необходимо применять в комбинации.
1. Настройте системный лимит открытых файлов
Выберите системный лимит, который будет соответствовать общему количеству открытых файлов, необходимых в системе. Умножение количества открытых файлов, необходимых для одного процесса рабочей нагрузки, на количество ожидаемых процессов. Установите для параметра ядра fs.max-file плюс некоторый буфер. Например, в системе запущено 4 процесса, которым требуется 800 000 открытых файлов. Можно использовать значение 3200000:
<span class="hljs-attr">fs.file-max</span> = <span class="hljs-number">3400000</span> <span class="hljs-comment"># (800000 * 4) + 200000</span>
2. Настройте лимит процесса открытия файлов
Выберите лимит для процессов, чтобы покрыть максимальное количество открытых файлов, необходимое для процессов одной рабочей нагрузки. Например, для процессов рабочей нагрузки требуется максимум 800 000 открытых файлов (пример выше):
fs.nr_open = 801000
3. Настройте пользовательский лимит на открытие файлов
Чтобы настроить лимит для пользователя, установите значение nofile на максимальное количество открытых файлов, необходимых сокетов подключения для всех прослушивателей, а также любые другие файловые дескрипторы, необходимые для рабочих процессов, и добавьте некоторый буфер. Пользовательские ограничения устанавливаются в /etc/security/limits.conf, в файле конфигурации /etc/security/limits.d/ или в systemd юнит-файле службы. Пример:
# cat /etc/security/limits.d/nginx.conf
nginx soft nofile 800000
nginx hard nofile 800000
# cat /lib/systemd/system/nginx.service
[Unit]
Description=OpenResty Nginx - high performance web server
Documentation=https://www.nginx.org/en/docs/
After=network-online.target remote-fs.target nss-lookup.target
Wants=network-online.target
[Service]
Type=forking
LimitNOFILE=800000
PIDFile=/var/run/nginx.pid
ExecStart=/usr/local/openresty/nginx/sbin/nginx -c /usr/local/openresty/nginx/conf/nginx.conf
ExecReload=/bin/kill -s HUP $MAINPID
ExecStop=/bin/kill -s TERM $MAINPID
[Install]
WantedBy=multi-user.target
Ограничения воркеров
Как и ограничения файловых дескрипторов, количество рабочих процессов или потоков, которые может создать процесс, ограничено как настройкой ядра, так и пользовательским лимитом.
Системный лимит потоков
Процессы могут размножать потоки воркеров. Максимальное количество всех потоков, которые могут быть созданы, задается настройкой ядра kernel.threads-max. Чтобы увидеть максимальное количество потоков, а также текущее количество потоков, выполняющихся в системе, выполните следующие команды.
Получить текущее максимальное число потоков:
cat /proc/sys/kernel/threads-max
По умолчанию используется количество страниц памяти, разделенное на 4.
Всего запущенных потоков:
$ ps -eo nlwp | awk '$1 ~ /^[0-9]+$/ { n += $1 } END { print n }'
Пока общее количество потоков меньше максимального, сервер сможет создавать новые потоки для процессов, если они находятся в пределах пользовательских лимитов.
Ограничение потоков для процесса
В отличие от настроек ядра для лимитов открытых файлов, для потоков нет прямой настройки лимита для процесса. Это обрабатывается ядром косвенно.
Параметр, который может повлиять на количество потоков, которые можно форкнуть — это kernel.pid_max. Он установит максимальное количество потоков, которые могут выполняться одновременно, ограничив количество доступных идентификаторов процессов (pid). Увеличение этого параметра позволит системе выполнять больше потоков одновременно.
Другой параметр — vm.max_map_count. Он контролирует количество отображаемых областей памяти для каждого потока. Общее практическое правило — увеличить это значение до удвоенного количества ожидаемых одновременных потоков в системе.
Пользовательский лимит потоков
Помимо системного ограничения максимального количества потоков, каждый пользовательский процесс ограничен максимальным количеством потоков. Это снова устанавливается с помощью системного файла Limits.conf (nproc) или в юнит-файле сервиса systemd, если процесс выполняется под systemd (LimitNPROC). Чтобы увидеть максимальное количество потоков, которые процесс может разветвить():
$ ulimit -u
4096
А под systemd, на примере nginx:
$ systemctl показать nginx | grep LimitNPROC
4096
Изменение настроек для потоков
В большинстве систем системный лимит уже установлен достаточно высоким, чтобы обрабатывать количество потоков, необходимое высокопроизводительному серверу. Однако, чтобы настроить системный предел, установите для параметра ядра kernel.threads-max максимальное количество потоков, необходимое системе плюс некоторый буфер. Пример:
kernel.threads-max = 3261780
Чтобы настроить ограничение для пользователя, установите значение, достаточно высокое для количества рабочих потоков, необходимых для обработки объема трафика, включая некоторый буфер. Как и в случае с nofile, пользовательские ограничения nproc устанавливаются в /etc/security/limits.conf
, в файле конфигурации /etc/security/limits.d/
или в юнит-файле службы. Пример с nproc и nofile:
# cat /etc/security/limits.d/nginx.conf
nginx soft nofile 800000
nginx hard nofile 800000
nginx soft nproc 800000
nginx hard nproc 800000
# cat /lib/systemd/system/nginx.service
[Unit]
Description=OpenResty Nginx - high performance web server
Documentation=https://www.nginx.org/en/docs/
After=network-online.target remote-fs.target nss-lookup.target
Wants=network-online.target
[Service]
Type=forking
LimitNOFILE=800000
LimitNPROC=800000
PIDFile=/var/run/nginx.pid
ExecStart=/usr/local/openresty/nginx/sbin/nginx -c /usr/local/openresty/nginx/conf/nginx.conf
ExecReload=/bin/kill -s HUP $MAINPID
ExecStop=/bin/kill -s TERM $MAINPID
[Install]
WantedBy=multi-user.target
Обратный прокси и TIME_WAIT
При большом объеме трафика прокси-соединения, зависшие в состоянии «TIME_WAIT», могут привести к блокированию большого количества ресурсов во время установления связи при закрытии соединения. Это состояние указывает на то, что клиент получил окончательный пакет FIN от сервера (или от upstream worker) и ожидает, что все задержанные пакеты в полете будут правильно обработаны. Время существования соединения в «TIME_WAIT» по умолчанию составляет 2 x MSL (максимальная длина сегмента), что составляет 2 x 60 с. Во многих случаях это нормальное и ожидаемое поведение, и значение по умолчанию, равное 120 с, является приемлемым. Однако, когда объем подключений в состоянии «TIME_WAIT» высок, это может привести к тому, что приложению не хватит эфемерных портов для подключения к клиентскому сокету. В этом случае увеличьте время ожидания, уменьшив время ожидания FIN.
Параметр ядра, который управляет этим тайм-аутом — net.ipv4.tcp_fin_timeout, и хорошая настройка для высокопроизводительного сервера составляет от 5 до 7 секунд.
Подведем итоги
Размер очереди приема (receive queue) должен быть таким, чтобы обрабатывать столько пакетов, сколько Linux может обработать за пределами сетевого адаптера, не вызывая отбрасывания пакетов, включая небольшой буфер на случай, если пики немного выше, чем ожидалось. Следует отслеживать файл softnet_stat на предмет отброшенных пакетов, чтобы определить правильное значение. Хорошее практическое правило — использовать значение, установленное для tcp_max_syn_backlog, чтобы разрешить как минимум столько же пакетов SYN, которые могут быть обработаны для создания полуоткрытых соединений. Помните, что это количество пакетов, которые каждый CPU может иметь в своем приемном буфере, поэтому разделите желаемую сумму на количество CPU.
Очередь SYN backlog должна иметь такой размер, чтобы обеспечить возможность большого количества полуоткрытых соединений на высокопроизводительном сервере для обработки всплесков случайного всплеска трафика. Хорошее правило — установить это значение как минимум на максимальное количество установленных соединений, которое может иметь прослушиватель в очереди приема, но не более чем в два раза превышающее количество установленных соединений, которое он может установить. Также рекомендуется отключить защиту SYN cookie в этих системах, чтобы избежать потери данных при интенсивных первоначальных подключениях от нормальных клиентов.
Очередь приема (accept queue) должна иметь размер, позволяющий удерживать объем установленных соединений, ожидающих обработки, в качестве временного буфера в периоды интенсивного трафика. Хорошее правило — устанавливать это значение в пределах 20–25% от количества рабочих потоков (worker threads).
Пример конфигурации ядра для nginx
# /etc/sysctl.d/99-nginx.conf
# /proc/sys/fs/file-max
# Maximum number of file handles that can be allocated.
# aka: open files.
# NOTES
# - This should be sized to accommodate the number of connections
# (aka: file handles or open files) needed by all processes.
# RECOMMENDATION
# - Increase this setting if more high connection processes are
# started.
# SEE ALSO
# - /proc/sys/fs/file-nr
fs.file-max = 3400000
# /proc/sys/fs/nr_open
# Maximum number of file handles that a single process can
# allocate, aka: open files or connections.
# NOTES
# - Each process requires a high number of connections to operate.
# RECOMMENDATION
# - None
# SEE ALSO
# - net.core.somaxconn
# - user limits: nofile
fs.nr_open = 801000
# /proc/sys/net/core/somaxconn
# Accept Queue Limit, maximum number of established connections
# waiting for accept() per listener.
# NOTES
# - Maximum size of accept() for each listener.
# - Do not size this less than net.ipv4.tcp_max_syn_backlog
# SEE ALSO
# net.ipv4.tcp_max_syn_backlog
net.core.somaxconn = 65535
# /proc/sys/net/ipv4/tcp_max_syn_backlog
# SYN Backlog Queue, number of half-open connections
# NOTES
# - Example server: 8 cores, can handle over 65535 total half-open
# connections.
# - Do not size this more than net.core.somaxconn
# SEE ALSO
# - net.core.netdev_max_backlog
# - net.core.somaxconn
net.ipv4.tcp_max_syn_backlog = 65535
# /proc/sys/net/core/netdev_max_backlog
# Receive Queue Size per CPU Core, number of packets.
# NOTES
# - Example server: 8 cores, each core should at least be able to
# receive 1/8 of the tcp_max_syn_backlog.
# RECOMMENDATION
# - Size this to be double the number needed; in the example, 1/4.
# SEE ALSO
# - net.ipv4.tcp_max_syn_backlog
net.core.netdev_max_backlog = 16386
# /proc/sys/net/ipv4/syn_retries
# /proc/sys/net/ipv4/synack_retries
# Maximum number of SYN and SYN+ACK retries before packet
# expires.
# NOTES
# - Reduces connection time to fail
net.ipv4.tcp_syn_retries = 1
net.ipv4.tcp_synack_retries = 1
# /proc/sys/net/ipv4/tcp_fin_timeout
# Timeout in seconds to close client connections in TIME_WAIT
# after receiving FIN packet.
# NOTES
# - Improves socket availability performance, allows for closed
# connections to be resused more quickly.
net.ipv4.tcp_fin_timeout = 5
# /proc/sys/net/ipv4/tcp_syncookies
# Disable SYN cookie flood protection.
# NOTES
# - Only disable this on systems that require a high volume of
# legal connections in a short amount of time, ie: bursts.
net.ipv4.tcp_syncookies = 0
# /proc/sys/kernel/threadsmax
# Maximum number of threads system can have, total.
# NOTES
# - Commented, may not be needed; check system.
# SEE ALSO
# - user limits.
#kernel.threads-max = 3261780
И файл настроек для лимитов пользователя:
# /etc/security/limits.d/nginx.conf
nginx soft nofile 800000
nginx hard nofile 800000
nginx soft nproc 800000
nginx hard nproc 800000