Управление системными ресурсами с помощью команды `ulimit` в Linux
Команда `ulimit` — это встроенная утилита оболочки в системах Unix и Linux, которая устанавливает ограничения ресурсов для отдельных процессов и пользователей, предотвращая исчерпание системных ресурсов — таких как процессорное время, память, открытые файловые дескрипторы и количество процессов — одним процессом или пользователем. Она работает на уровне ядра через системный вызов `setrlimit()`, что делает её одним из наиболее прямых и малозатратных механизмов управления ресурсами, доступных системным администраторам.
Для любого сервера, выполняющего производственные нагрузки — будь то высоконагруженное веб-приложение, СУБД или стек контейнеризированных микросервисов — неправильно настроенные или отсутствующие параметры `ulimit` являются одной из главных причин каскадных сбоев, неуправляемых процессов и полных отказов системы. Правильная настройка этих ограничений — не опция, а основа инфраструктурной гигиены.
Как работает `ulimit` на низком уровне
Когда процесс оболочки вызывает `ulimit`, он обращается к системным вызовам `getrlimit()` и `setrlimit()`, определённым в стандарте POSIX. Каждое ограничение представлено парой значений: мягкий лимит и жёсткий лимит. Они хранятся для каждого процесса в дескрипторе процесса ядра и наследуются дочерними процессами во время `fork()`.
Эту модель наследования важно понимать. Если вы задаёте значения `ulimit` в сеансе оболочки, каждый процесс, порождённый из этой оболочки, — включая демоны, запущенные через init-скрипты, — наследует эти ограничения. Напротив, ограничения, заданные в `/etc/security/limits.conf`, применяются во время входа через PAM, а не во время выполнения, то есть они вступают в силу только для новых сеансов входа, но не для уже запущенных служб.
Мягкие и жёсткие лимиты
| Свойство | Мягкий лимит | Жёсткий лимит |
|---|---|---|
| — | — | — |
| Кто может повысить | Любой непривилегированный пользователь (до жёсткого лимита) | Только root (`CAP_SYS_RESOURCE`) |
| Кто может понизить | Любой пользователь | Любой пользователь (необратимо без root) |
| Применение | Применяется ядром | Является потолком для мягкого лимита |
| Типичный сценарий использования | Операционная граница в повседневной работе | Абсолютный максимум для политики безопасности |
| Флаг в `ulimit` | `-S` | `-H` |
Распространённая операционная ошибка — установка жёсткого лимита равным мягкому. Это лишает процесс возможности временно повышать собственные ограничения, что некоторые приложения (например, отдельные реализации JVM и СУБД) делают вполне законно во время запуска.
Полный справочник: флаги ресурсов `ulimit`
| Флаг | Ресурс | Единица | Типичное производственное значение |
|---|---|---|---|
| — | — | — | — |
| `-t` | Процессорное время | Секунды | `unlimited` для демонов |
| `-f` | Максимальный размер файла | Блоки по 512 байт | `unlimited` или конкретное ограничение |
| `-d` | Размер сегмента данных (кучи) | KB | `unlimited` для Java-приложений |
| `-s` | Размер стека | KB | `8192` (по умолчанию) |
| `-c` | Размер файла дампа ядра | Блоки по 512 байт | `0` (отключено в продакшене) |
| `-m` | Максимальный размер резидентного набора | KB | Редко применяется (используйте cgroups) |
| `-v` | Виртуальная память (адресное пространство) | KB | `unlimited` для большинства служб |
| `-n` | Открытые файловые дескрипторы | Количество | `65536` и выше для нагруженных серверов |
| `-u` | Максимальное количество процессов пользователя | Количество | `4096`–`65536` в зависимости от роли |
| `-l` | Заблокированная память (mlock) | KB | Высокое значение для Redis, Elasticsearch |
| `-i` | Ожидающие сигналы | Количество | Системного значения по умолчанию обычно достаточно |
| `-q` | Байты очереди сообщений POSIX | Байты | Системное значение по умолчанию |
| `-r` | Приоритет планирования реального времени | Приоритет | `0` если нет RT-нагрузок |
| `-e` | Максимальный приоритет планирования (nice) | Значение nice | Системное значение по умолчанию |
Практическое использование `ulimit` в реальных условиях
Просмотр текущих ограничений
“`bash
ulimit -a # All soft limits for the current shell
ulimit -aH # All hard limits for the current shell
“`
Для проверки ограничений конкретного запущенного процесса (PID) читайте данные непосредственно из файловой системы proc — это авторитетный источник, который обходит отчётность на уровне оболочки:
“`bash
cat /proc/<PID>/limits
“`
Это незаменимо при диагностике службы, запущенной через systemd или init-скрипт, где `ulimit -a` на уровне оболочки не отражает фактических ограничений процесса.
Установка мягких и жёстких лимитов
“`bash
Set soft limit for open file descriptors
ulimit -Sn 65536
Set hard limit for open file descriptors
ulimit -Hn 131072
Set both simultaneously (soft = hard = value)
ulimit -n 65536
“`
Отключение дампов ядра в продакшене
“`bash
ulimit -c 0
“`
Дампы ядра могут занять гигабайты дискового пространства за считанные секунды при сбое процесса с большим объёмом памяти. Их отключение в продакшене является стандартной практикой, если вы не занимаетесь активной отладкой. Для сред разработки задайте выделенный путь с помощью `sysctl kernel.core_pattern` вместе с ненулевым лимитом дампа ядра.
Ограничение процессорного времени для ненадёжных процессов
“`bash
ulimit -t 30
“`
Это отправляет `SIGXCPU` процессу при достижении мягкого лимита процессорного времени и `SIGKILL` при достижении жёсткого лимита. Особенно полезно в средах общего хостинга или при выполнении пользовательских скриптов.
Увеличение лимита открытых файловых дескрипторов для высококонкурентных служб
Nginx, HAProxy, PostgreSQL и Redis требуют большого количества открытых файловых дескрипторов под нагрузкой. Системное значение по умолчанию — 1024 — опасно мало для продакшена:
“`bash
ulimit -n 65536
“`
Однако это влияет только на текущий сеанс оболочки. Для постоянной настройки используйте методы, описанные в следующем разделе.
Сохранение настроек `ulimit` между перезапусками
Метод 1: `/etc/security/limits.conf`
Это стандартный подход на основе PAM для постоянных ограничений на уровне пользователя:
“`
/etc/security/limits.conf
<domain> <type> <item> <value>
- soft nofile 65536
- hard nofile 131072
nginx soft nproc 4096
nginx hard nproc 8192
postgres soft nofile 65536
postgres hard nofile 65536
postgres soft memlock unlimited
postgres hard memlock unlimited
“`
Символ подстановки `*` применяется ко всем пользователям, но не применяется к root. Для root требуется явная запись:
“`
root soft nofile 65536
root hard nofile 131072
“`
Убедитесь, что модуль PAM загружен. Проверьте, содержит ли `/etc/pam.d/common-session` (Debian/Ubuntu) или `/etc/pam.d/system-auth` (RHEL/CentOS) следующую строку:
“`
session required pam_limits.so
“`
Метод 2: дополнительные файлы `/etc/security/limits.d/`
Для более удобного управления, особенно в системах управления конфигурацией, таких как Ansible или Puppet, размещайте файлы ограничений для конкретных служб в дополнительном каталоге:
“`bash
/etc/security/limits.d/99-nginx.conf
nginx soft nofile 65536
nginx hard nofile 131072
“`
Файлы в этом каталоге обрабатываются после `limits.conf` и переопределяют его, что делает их идеальными для настройки под конкретное приложение без изменения базовой конфигурации.
Метод 3: юниты служб systemd (современный стандарт)
Для служб, управляемых systemd, — а это большинство современных дистрибутивов Linux — `limits.conf` не применяется по умолчанию. systemd управляет собственными ограничениями ресурсов для каждого юнита службы:
“`ini
/etc/systemd/system/nginx.service.d/limits.conf
[Service]
LimitNOFILE=65536
LimitNPROC=4096
LimitCORE=0
LimitMEMLOCK=infinity
“`
После редактирования перезагрузите и перезапустите:
“`bash
systemctl daemon-reload
systemctl restart nginx
“`
Проверьте применённые ограничения:
“`bash
cat /proc/$(systemctl show -p MainPID nginx | cut -d= -f2)/limits
“`
Это наиболее надёжный метод для производственных служб и должен быть подходом по умолчанию на любой системе с systemd (Ubuntu 16.04+, CentOS 7+, Debian 8+).
Метод 4: файлы профиля оболочки
Для ограничений пользовательского сеанса, применяемых в интерактивном режиме, добавьте команды `ulimit` в `/etc/profile` (общесистемный) или `~/.bashrc` / `~/.profile` (для конкретного пользователя). Этот подход подходит для рабочих станций разработчиков, но не подходит для процессов-демонов.
Профили конфигурации `ulimit` по ролям
Различные роли серверов требуют принципиально разных профилей ограничений ресурсов. Применение универсальных значений по умолчанию ко всем типам серверов — распространённая причина трудно диагностируемых сбоев.
Веб-сервер (Nginx / Apache)
“`
nofile: 65536–131072 # High concurrency requires many open sockets + files
nproc: 4096 # Worker processes + threads
core: 0 # Disable core dumps in production
“`
Реляционная СУБД (PostgreSQL / MySQL)
“`
nofile: 65536 # Many concurrent connections = many file descriptors
memlock: unlimited # Required for shared memory and huge pages
nproc: 4096
stack: 8192 KB
core: 0
“`
Java-сервер приложений (Tomcat / Spring Boot)
“`
nofile: 65536
nproc: 65536 # JVM thread-per-connection models spawn many threads
data: unlimited # JVM heap is allocated from the data segment
stack: 512 KB # Reduce stack size to fit more threads in memory
“`
Redis / хранилище данных в памяти
“`
nofile: 65536
memlock: unlimited # Prevents swapping of memory-mapped data
“`
Критические подводные камни и граничные случаи
Лимит `nproc` учитывает потоки, а не только процессы. В Linux потоки реализованы как лёгкие процессы (`clone()` с общей памятью). Java-приложение с 500 потоками учитывается как 500 единиц в лимите `nproc`. Это удивляет многих администраторов, которые устанавливают консервативные значения `nproc` и затем недоумевают, почему их JVM падает с ошибкой `OutOfMemoryError: unable to create new native thread`.
`ulimit -v` ограничивает виртуальное адресное пространство, а не физическую RAM. Многие администраторы устанавливают `-v`, полагая, что ограничивают использование памяти. На самом деле они ограничивают виртуальное адресное пространство, которое включает файлы, отображённые в память, разделяемые библиотеки и метапространство JVM. Слишком низкое значение приведёт к ошибкам `mmap()` и непонятным ошибкам приложений.
`ulimit` не применяется ретроактивно. Изменение ограничений в `limits.conf` или файле юнита systemd не влияет на уже запущенные процессы. Для вступления новых ограничений в силу необходимо перезапустить службу.
Контейнерные среды обходят `ulimit` неожиданным образом. В Docker значения `ulimit` по умолчанию задаются на уровне демона (`/etc/docker/daemon.json`) и могут быть переопределены для каждого контейнера с помощью `–ulimit`. Однако ограничения контейнера ограничены лимитами ядра хоста. Установка `nofile=1048576` в контейнере при значении `nofile=65536` на хосте молча откатится до лимита хоста.
Общесистемный потолок `nofile` отделён от ограничений на уровне процесса. Параметр ядра `fs.file-max` (задаётся через `sysctl`) управляет общим количеством файловых дескрипторов во всей системе. Даже если `nofile` на уровне процесса установлен высоким, достижение `fs.file-max` вызовет ошибки `ENFILE` во всей системе. Проверяйте и настраивайте оба параметра:
“`bash
sysctl fs.file-max
sysctl -w fs.file-max=2097152
“`
`ulimit` vs. cgroups: выбор подходящего инструмента
| Возможность | `ulimit` / `setrlimit` | cgroups v2 |
|---|---|---|
| — | — | — |
| Область действия | На уровне процесса (наследуется дочерними) | На уровне группы процессов |
| Ограничение памяти | Только виртуальное адресное пространство (`-v`) | Принудительное ограничение фактического RSS + swap |
| Ограничение CPU | Бюджет процессорного времени (`-t`) | Контроллер полосы пропускания CPU (точный %) |
| Ограничение I/O | Не поддерживается | Весовые и скоростные ограничения блочного I/O |
| Ограничение сети | Не поддерживается | Требует интеграции tc + cgroup |
| Постоянство | Через PAM или systemd | Через срезы systemd или cgroupfs |
| Совместимость с контейнерами | Ограниченная | Нативная (Docker, Kubernetes используют cgroups) |
| Гранулярность | Грубая | Тонкая настройка |
`ulimit` остаётся правильным инструментом для быстрых ограничений на уровне сеанса, лимитов файловых дескрипторов и управления дампами ядра. Для комплексной изоляции ресурсов — особенно в многопользовательских средах или контейнеризированных нагрузках — cgroups v2 является более совершенным механизмом. В хорошо настроенной среде VPS Хостинга или Выделенного сервера оба механизма, как правило, используются совместно: `ulimit` для защитных барьеров на уровне процесса и cgroups для совокупных бюджетов ресурсов.
Мониторинг и проверка ограничений ресурсов
Проактивный мониторинг предотвращает превращение сбоев, связанных с лимитами, в производственные инциденты.
Проверка текущего использования файловых дескрипторов в масштабе системы:
“`bash
cat /proc/sys/fs/file-nr
Output: <allocated> <unused> <max>
“`
Поиск процессов, приближающихся к лимиту `nofile`:
“`bash
for pid in /proc/[0-9]*; do
pid_num=${pid##*/}
limit=$(awk '/Max open files/{print $4}' /proc/$pid_num/limits 2>/dev/null)
current=$(ls /proc/$pid_num/fd 2>/dev/null | wc -l)
[ -n "$limit" ] && [ "$limit" != "unlimited" ] &&
awk -v c=$current -v l=$limit -v p=$pid_num
'BEGIN{if(c/l>0.8) printf "PID %s: %d/%d (%.0f%%)n",p,c,l,c/l*100}'
done
“`
Инструменты для постоянного мониторинга:
- `lsof -u <username>` — список всех открытых файлов для пользователя
- `ss -s` — статистика сокетов (коррелирует с нагрузкой на `nofile`)
- `htop` с отображением дерева процессов — визуализация количества процессов на пользователя
- `sar -v` — исторические данные об использовании файловых дескрипторов и инодов через sysstat
- Prometheus `node_exporter` — предоставляет метрики `node_filefd_allocated` и `node_filefd_maximum` для оповещений
Для сред, работающих на VPS с cPanel или других панелях управления, многие из этих ограничений предварительно настраиваются установщиком панели, однако по мере роста трафика их часто необходимо увеличивать. Всегда проверяйте фактические ограничения через `/proc/<PID>/limits`, не полагаясь на документацию панели.
Аспекты безопасности `ulimit`
Ограничения ресурсов также являются средством контроля безопасности. Без них скомпрометированный или содержащий ошибки процесс может выполнить форк-бомбу (`:(){ :|:& };:`), исчерпав все доступные слоты процессов и сделав систему неотзывчивой. Консервативный лимит `nproc` на пользователя является основным средством защиты:
“`
- hard nproc 4096
“`
Аналогично, отключение дампов ядра (`-c 0`) предотвращает запись конфиденциального содержимого памяти — включая ключи шифрования, пароли и токены сеансов — в файл с общедоступными правами чтения на диске.
Для сред общего хостинга или любого сервера, где несколько пользователей имеют доступ к оболочке, `ulimit` является обязательным уровнем безопасности. На инфраструктуре Общего веб-хостинга эти ограничения, как правило, применяются на уровне платформы, однако администраторы, управляющие собственным многопользовательским VPS, должны настраивать их явно.
Если ваш сервер выполняет терминирование SSL или управление сертификатами, убедитесь, что процесс, обрабатывающий TLS (например, Nginx, HAProxy), имеет достаточные лимиты `nofile`, поскольку каждое TLS-соединение требует нескольких файловых дескрипторов. Сочетайте это с правильно настроенными SSL-сертификатами, чтобы избежать усугубления проблем с ресурсами из-за ошибок, связанных с сертификатами.
При развёртывании почтовых серверов Postfix и Dovecot особенно чувствительны к лимитам `nofile`, поскольку каждое одновременное почтовое соединение и обращение к почтовому ящику потребляет файловые дескрипторы. Если вы управляете собственной почтовой инфраструктурой, а не используете управляемый Email Хостинг, установка `nofile` не менее 65536 для почтового пользователя является обязательным условием на любом умеренно нагруженном сервере.
Матрица решений: что и где настраивать
| Сценарий | Рекомендуемый метод | Ключевые параметры |
|---|---|---|
| — | — | — |
| Интерактивные пользовательские сеансы | `/etc/security/limits.conf` | `nofile`, `nproc`, `core` |
| Служба под управлением systemd | Секция `[Service]` юнита systemd | `LimitNOFILE`, `LimitNPROC`, `LimitCORE` |
| Контейнер Docker | Флаг `–ulimit` или `daemon.json` | `nofile`, `nproc` |
| Разовое тестирование в оболочке | Команда `ulimit` напрямую | Любой флаг |
| Многопользовательский общий сервер | `limits.conf` + применение PAM | `nproc`, `nofile`, `fsize`, `cpu` |
| Под Kubernetes | Контекст безопасности пода + cgroups | Управляется kubelet |
| Настройка под конкретное приложение | Дополнительный файл `limits.d/` | Параметры, специфичные для службы |
Технический контрольный список ключевых выводов
- Всегда проверяйте применённые ограничения через `/proc/<PID>/limits`, а не через `ulimit -a` на уровне оболочки, для запущенных служб.
- Для служб systemd настраивайте ограничения в файле юнита с помощью директив `Limit*` — `limits.conf` по умолчанию не читается systemd.
- Устанавливайте `nofile` не менее `65536` для любой службы, обрабатывающей сетевые соединения; `131072` и выше для высококонкурентных нагрузок.
- Никогда не устанавливайте жёсткий лимит равным мягкому, если у вас нет конкретного требования безопасности — приложениям нужен запас для самостоятельной регулировки.
- Отключайте дампы ядра (`LimitCORE=0`) в продакшене; включайте их с контролируемым путём в тестовой среде.
- Лимит `nproc` учитывает потоки в Linux — учитывайте это при настройке JVM или приложений на Go runtime.
- Настраивайте `fs.file-max` через `sysctl` совместно с ограничениями `nofile` на уровне процесса, чтобы избежать исчерпания `ENFILE` в масштабе системы.
- В контейнерных средах лимиты ядра хоста являются жёстким потолком — настройки `ulimit` на уровне контейнера не могут их превышать.
- Используйте cgroups v2 для ограничения памяти и I/O; используйте `ulimit` для лимитов файловых дескрипторов, количества процессов и управления дампами ядра.
- После любого изменения ограничений в `limits.conf` или файлах юнитов systemd перезапустите затронутую службу и проверьте результат с помощью `/proc/<PID>/limits`.
Часто задаваемые вопросы
Применяется ли `ulimit` к процессам root?
Символ подстановки `*` в `/etc/security/limits.conf` явно исключает root. Процессы root также обходят применение жёстких лимитов для большинства типов ресурсов — root может повышать собственные жёсткие лимиты. Чтобы применить ограничения к root, добавьте явную запись `root` в `limits.conf`, хотя многие системные службы, работающие от имени root, будут игнорировать ограничения, применённые через PAM, если они запущены вне сеанса входа.
Почему моё изменение `limits.conf` не влияет на запущенную службу?
`limits.conf` применяется PAM во время входа в систему. Службы, запущенные через systemd, SysVinit или Upstart, не проходят через PAM и поэтому не наследуют настройки `limits.conf`. Настраивайте ограничения непосредственно в файле юнита systemd с помощью `LimitNOFILE` и связанных директив, затем выполните `systemctl daemon-reload && systemctl restart <service>`.
Каково максимальное значение, которое можно установить для `nofile`?
Максимум на уровне процесса ограничен параметром ядра `fs.nr_open` (по умолчанию: 1 048 576 на большинстве ядер). Общесистемный итог ограничен `fs.file-max`. Вы можете увеличить `fs.nr_open` через `sysctl`, но значения выше 1 048 576 требуют перекомпиляции ядра на старых версиях. На практике 524 288 или 1 048 576 покрывает практически все производственные сценарии использования.
Как проверить, достиг ли процесс своей границы `ulimit`?
Проверьте журнал ядра с помощью `dmesg | grep -i "ulimit|RLIMIT|too many open|cannot allocate"`. Журналы приложений, как правило, будут показывать `EMFILE` (слишком много открытых файлов), `ENOMEM` (ошибка выделения памяти) или `EAGAIN` (ресурс временно недоступен). Сопоставьте с `/proc/<PID>/limits` и текущим количеством дескрипторов через `ls /proc/<PID>/fd | wc -l`.
Достаточно ли `ulimit` для изоляции ресурсов в многопользовательской среде?
Нет. `ulimit` обеспечивает защитные барьеры на уровне процесса и пользователя, но не применяет ограничения пропускной способности памяти, дискового I/O или пропускной способности сети. Для настоящей многопользовательской изоляции сочетайте `ulimit` с контроллерами ресурсов cgroups v2 и рассмотрите изоляцию пространств имён (пространства имён пользователей, пространства имён PID) для более строгих границ безопасности. На управляемой инфраструктуре эти средства контроля, как правило, применяются на уровне гипервизора и среды выполнения контейнеров.
