Керування системними ресурсами за допомогою команди `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` | Розмір файлу core dump | Блоки по 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
“`
Вимкнення core dump у виробництві
“`bash
ulimit -c 0
“`
Core dump можуть споживати гігабайти дискового простору за лічені секунди, коли аварійно завершується процес із великим обсягом пам’яті. Їх вимкнення у виробництві є стандартною практикою, якщо ви не займаєтеся активним налагодженням. Для середовищ розробки встановіть спеціальний шлях за допомогою `sysctl kernel.core_pattern` разом із ненульовим обмеженням core.
Обмеження процесорного часу для ненадійних процесів
“`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: файли drop-in `/etc/security/limits.d/`
Для зручнішого управління, особливо в системах управління конфігурацією, таких як Ansible або Puppet, розміщуйте файли обмежень для конкретних служб у директорії drop-in:
“`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` проти 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` залишається правильним інструментом для швидких обмежень на рівні сеансу, обмежень файлових дескрипторів і керування core dump. Для комплексної ізоляції ресурсів — особливо в багатоорендних середовищах або контейнеризованих навантаженнях — 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` — історичне використання файлових дескрипторів та inode через sysstat
- Prometheus `node_exporter` — надає метрики `node_filefd_allocated` та `node_filefd_maximum` для сповіщень
Для середовищ із VPS із cPanel або іншими панелями керування багато з цих обмежень попередньо налаштовані інсталятором панелі, але їх часто потрібно збільшувати в міру зростання трафіку. Завжди перевіряйте фактичні обмеження через `/proc/<PID>/limits`, а не довіряйте документації панелі.
Наслідки для безпеки від `ulimit`
Обмеження ресурсів також є засобом контролю безпеки. Без них скомпрометований або помилковий процес може виконати fork bomb (`:(){ :|:& };:`), вичерпавши всі доступні слоти процесів і зробивши систему непридатною до використання. Консервативне обмеження `nproc` на користувача є основним засобом захисту:
“`
- hard nproc 4096
“`
Аналогічно, вимкнення core dump (`-c 0`) запобігає запису конфіденційного вмісту пам’яті — включно з ключами шифрування, паролями та токенами сеансів — на диск у файл із загальнодоступними правами читання.
Для середовищ спільного хостингу або будь-якого сервера, де кілька користувачів мають доступ до оболонки, `ulimit` є обов’язковим рівнем безпеки. На інфраструктурі Спільного веб-хостингу ці обмеження зазвичай застосовуються на рівні платформи, але адміністратори, що керують власним багатокористувацьким VPS, повинні налаштовувати їх явно.
Якщо ваш сервер виконує завершення SSL або керування сертифікатами, переконайтеся, що процес, який обробляє TLS (наприклад, Nginx, HAProxy), має достатні обмеження `nofile`, оскільки кожне TLS-з’єднання потребує кількох файлових дескрипторів. Поєднайте це з правильно налаштованими SSL-сертифікатами, щоб уникнути збоїв з’єднань, пов’язаних із сертифікатами, що посилюють проблеми з ресурсами.
Для розгортань поштових серверів Postfix і Dovecot особливо чутливі до обмежень `nofile`, оскільки кожне паралельне з’єднання електронної пошти та доступ до поштової скриньки споживає файлові дескриптори. Якщо ви керуєте власною поштовою інфраструктурою, а не використовуєте керований Хостинг електронної пошти, налаштування `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` |
| Pod Kubernetes | Контекст безпеки pod + cgroups | Керується kubelet |
| Налаштування для конкретного застосунку | Файл drop-in `limits.d/` | Параметри, специфічні для служби |
Технічний контрольний список ключових висновків
- Завжди перевіряйте застосовані обмеження через `/proc/<PID>/limits`, а не через `ulimit -a` на рівні оболонки, для запущених служб.
- Для служб systemd налаштовуйте обмеження у файлі юніта за допомогою директив `Limit*` — `limits.conf` за замовчуванням не читається systemd.
- Встановлюйте `nofile` щонайменше `65536` для будь-якої служби, що обробляє мережеві з’єднання; `131072` або більше для навантажень із високим рівнем паралелізму.
- Ніколи не встановлюйте жорстке обмеження рівним м’якому, якщо у вас немає конкретної вимоги безпеки — застосункам потрібен запас для самостійного налаштування.
- Вимикайте core dump (`LimitCORE=0`) у виробництві; вмикайте їх із контрольованим шляхом у тестовому середовищі.
- Обмеження `nproc` враховує потоки в Linux — враховуйте це при налаштуванні застосунків JVM або Go runtime.
- Налаштовуйте `fs.file-max` через `sysctl` разом із обмеженнями `nofile` на рівні процесу, щоб уникнути вичерпання `ENFILE` у всій системі.
- У контейнеризованих середовищах обмеження ядра хоста є жорсткою стелею — налаштування `ulimit` на рівні контейнера не можуть їх перевищити.
- Використовуйте cgroups v2 для застосування обмежень пам’яті та I/O; використовуйте `ulimit` для обмежень файлових дескрипторів, кількості процесів і керування core dump.
- Після будь-якої зміни обмежень у `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) для більш надійних меж безпеки. На керованій інфраструктурі ці засоби контролю зазвичай накладаються на рівні гіпервізора та середовища виконання контейнерів.
