Чому застосунок упирається в ліміти файлових дескрипторів
Кожне з'єднання, відкритий файл, сокет або канал — це файловий дескриптор, узятий з таблиці процесу. Таблиця не нескінченна: її розмір задає м'який і жорсткий поріг, який успадковується від батьківського процесу під час старту.
systemd задає своє значення для служби, оболонка — своє через ulimit, і робочі процеси часто отримують не той ліміт, що налаштований «на папері».
Поки сервіс працює у спокійному режимі, вільні дескриптори не закінчуються — ліміт видно лише під навантаженням. Кожне вхідне з'єднання, звернення до кешу, запис у журнал або чергу займає ще один.
Якщо з'єднання не закриваються вчасно — немає обмеження на час утримання, втрачені відгалуження при помилках, — виникає витік дескрипторів: лічильник зростає монотонно і не повертається до базового рівня.
У логах це виглядає однаково: помилка «занадто багато відкритих файлів», відмова на прийомі нового з'єднання, при цьому процес живий і метрики здоров'я відповідають.
Назовні — відмова сервісу під піковим навантаженням: балансувальник бачить відкритий порт, а запити не обслуговуються. Клієнти йдуть у повторні спроби й добивають залишок вільних дескрипторів.
Пік небезпечний тим, що незакриті з'єднання та повтори додаються: кожне нове з'єднання вимагає дескриптор, а звільняються вони повільніше, ніж надходять. Розбираємо, як це закривається — від аудиту лімітів до контролю витоків.
Що дають налаштовані ліміти: менше відмов і простоїв
Неналаштований ліміт проявляється не під час старту, а на піку: робочий процес упирається в soft-ліміт, нові з'єднання відбиваються, у логах зростає частка помилок — при цьому сервіс відповідає на перевірку доступності й виглядає живим.
Діагностика йде в бік застосунку та мережі, хоча причина в одному параметрі, який ніхто не виставляв під реальний профіль навантаження. Розбираємо, що змінюється, коли він виставлений.
Ліміти задаються явно — окремо soft і hard, прив'язані до самого сервісу, а не до інтерактивної сесії адміністратора. У systemd це LimitNOFILE в unit-файлі, для процесів поза менеджером служб — правила в limits.conf.
Інакше після перезапуску вузол повернеться до значення за замовчуванням, і інцидент повториться на тому самому місці.
Ефект вимірюється за трьома осями. Зниження відмов видно за часткою невдалих з'єднань і кодом повернення на прийомі — вона прямує до нуля в межах перевіреного запасу.
Час відновлення сервісу після піку скорочується: не потрібно чекати, поки звільняться дескриптори, які процеси тримають в очікуванні.
Стійкість під навантаженням перестає залежати від удачі — черга з'єднань не зростає лавиноподібно, деградація відбувається плавно й знімається сама.
Передбачуваність роботи дає те, що ліміт описаний у репозиторії й розкочується на кожен вузол однаково: новий інстанс у кластері поводиться як інші.
Запас підтверджуємо навантажувальним прогоном із профілем, близьким до пікового, і тримаємо поріг вище за розрахунковий максимум на зрозумілий коефіцієнт. Запобігання простоям тримається саме на цьому запасі, а не на цілодобовому чергуванні.
Варіанти налаштування лімітів: що підходить під навантаження
Ліміт дескрипторів задається не в одному місці: у процесу є м'який і жорсткий ліміт, у системи — загальна стеля, а служби та користувацькі сесії отримують значення різними шляхами.
Порядок застосування залежить від того, хто запускає процес: оболонка при вході, менеджер служб або батьківський процес, який передав значення у спадок.
Тому налаштування під навантаження — це вибір рівня, а не пошук «правильного файлу».
Правка на одному рівні не діє на процеси, що стартують інакше, і ліміт мовчки залишається попереднім: застосунок упирається у відмову під час відкриття з'єднань, хоча в налаштуваннях усе виглядає коректно.
| Рівень налаштування | Коли підходить | Обмеження |
|---|---|---|
| Системна стеля | Загальний запас дескрипторів для всіх процесів вузла | Задає верхню межу, але не розподіляє її між службами |
| М'який ліміт процесу | Застосунок сам піднімає ліміт на старті | Не може перевищити жорсткий ліміт |
| Жорсткий ліміт процесу | Верхня межа, вище якої процес не підніметься | Застосовується лише до нових процесів або після перезапуску |
| Ліміти для служб | Демони, що стартують без входу користувача | Не успадковуються інтерактивними сесіями та ручними запусками |
| Ліміти для користувацьких сесій | Налагодження й ручний запуск з оболонки | Не діють на служби, що стартують до входу |
Перевіряють результат за фактичним значенням у працюючому процесі, а не за файлом налаштувань: перезапуск служби й нова сесія дають різну картину. Для навантаженого профілю піднімають і системну стелю — інакше служба упреться в загальний ліміт раніше, ніж у власний.
Як відбувається впровадження: від аудиту до передачі регламентів
Ліміти файлових дескрипторів успадковуються процесом у момент старту, тому правка конфігурації «на живу» нічого не змінює, а виставлене з великим запасом значення не вирішує проблему — воно лише відкладає відмову й маскує витік. Роботу ведемо у фіксованому порядку: спочатку замір і схема, потім зміни.
- Аудит конфігурації. Збираємо фактичні значення за всіма шарами: обмеження ядра, системні пороги, параметри служби, яка запускає застосунок, і обмеження користувацької сесії. Розбіжність між шарами — типова причина, чому процес бачить не те, що прописали у файлі.
- Замір поточного стану. Дивимося, скільки дескрипторів процес тримає у спокої та на піку, як зростає число відкритих з'єднань і файлів, що залишилося в журналах після минулих відмов. Це дає відправну точку, а не здогад.
- Схема налаштування. Визначаємо, де саме правимо, які процеси отримують винятки, як значення застосується і до служби, і до ручного запуску. Розділяємо м'який і жорсткий пороги, фіксуємо запас щодо пікового споживання.
- Налаштування. Вносимо правки в конфігурацію та в опис служби, перезапускаємо за планом, а не в момент збою, перевіряємо, що нове значення справді підхоплено процесом.
- Перевірка під навантаженням. Відтворюємо робочий профіль: піковий трафік, сплеск з'єднань, відмова суміжного вузла. Окремо дивимося поведінку застосунку, коли він наближається до порогу.
- Спостережуваність. Налаштовуємо метрики за числом відкритих дескрипторів і з'єднань та оповіщення за швидкістю зростання — тоді витік видно задовго до того, як процес упреться в стелю.
- Передача регламентів. Віддаємо документ із цільовими значеннями, порядком застосування на новому вузлі, чек-лист перевірки після змін і таблицю «симптом → що дивитися».
Після передачі команда замовника може сама розкочувати ліміти на нових серверах і розуміти, коли зростання числа дескрипторів — це норма, а коли сигнал проблеми.
Конфігурації, схеми та документація за підсумками робіт
Налаштування лімітів не вважається завершеним, поки воно живе лише в історії команд на конкретному сервері.
Ми залишаємо вашій команді набір артефактів, за якими інженер відтворить ту саму конфігурацію на іншому стенді, пояснить вибрані значення через пів року й зрозуміє, де шукати причину, якщо лічильники знову упруться в стелю.
- Конфігураційні файли з роздільними м'якими та жорсткими значеннями для системних служб і користувацьких сесій, з коментарями: звідки взявся запас і що станеться при його зниженні.
- Профілі запуску застосунків — де саме піднімається ліміт: в описі служби, в обгортці запуску або в оточенні контейнера, щоб значення не губилося під час перезапуску й деплою.
- Схема інфраструктури з ролями вузлів і позначкою, на яких машинах ліміти підвищені, а на яких залишені базовими й чому.
- Політики доступу: які ролі можуть змінювати параметри ядра й перезапускати служби, як це пов'язано з правами на конфігурацію.
- Регламент обслуговування: як перевірити фактичне значення в працюючого процесу, які лічильники дивитися при зростанні навантаження і з якою періодичністю.
- Розрахунок цільових значень: пікові одночасні з'єднання на процес, число обробників, службові з'єднання та запас на відновлення.
- Чек-лист приймання після застосування та порядок відкату до попередніх значень, якщо нова конфігурація дасть неочікуваний ефект.
Плюс пам'ятка для чергової зміни: типові симптоми впирання в ліміт і перша лінія дій до ескалації. Усе це передається у вашому репозиторії конфігурацій, а не окремими листами.
Кейс: прибрали відмови сервісу під піковим навантаженням
Як було
Сервіс на піку переставав приймати нові з'єднання: у журналах — відмова відкрити файл або сокет, назовні — сплеск помилок і обриви з'єднань.
Черговий бачив симптом, а не причину: метрики стежили за кодами відповідей, але не за числом відкритих дескрипторів, тому кожен розбір починався з ручного перебору журналів. Від першого сигналу до відновлення минали десятки хвилин, і весь цей час частина запитів губилася.
Що змінили й що отримали
Спочатку зняли базову лінію: скільки дескрипторів сервіс тримає на піку і скільки відкриває на один запит.
Виявилося, що стеля м'якого ліміту вибиралася повністю вже при штатній роботі — запасу не було, а сам ліміт задається на процес, тому при кількох робочих процесах стеля вибирається кратно швидше, ніж очікуєш.
Підняли м'який і жорсткий ліміти для процесу, окремо — загальносистемну стелю, заклавши запас на зростання, і прибрали витік: з'єднання повертаються в перевикористання, а не залишаються відкритими до перезапуску.
Спостережуваність перевели на причину. Число відкритих дескрипторів та його частка від ліміту виводяться на графік поруч із трафіком у Prometheus і на загальних панелях, оповіщення спрацьовує на підході до стелі, а не після відмови.
Після змін пік проходить без відмов цього класу: зайнятість ліміту тримається в межах двох третин замість впирання в ліміт, час діагностики скоротився з годин до хвилин, ручний перезапуск сервісу під навантаженням більше не потрібен.
Чим системний ліміт відрізняється від ліміту сервісу
Ліміт файлових дескрипторів задається на двох різних рівнях, і плутати їх — джерело більшості «містичних» відмов. Системний ліміт — сумарна стеля для всієї машини, його тримає ядро.
Ліміт процесу й служби — скільки дескрипторів може відкрити конкретний процес; він завжди нижчий за системний і успадковується під час запуску.
Пріоритет визначається тим, хто породив процес: дочірній процес не читає конфігурацію заново, а отримує значення від батьківського, тому підсумок залежить від ланцюжка запуску.
| Рівень обмеження | Де задається | На що впливає | Що перекриває |
|---|---|---|---|
| Системний | параметри ядра | сумарно по хосту | стеля для всіх служб |
| Служба | опис юніта в systemd | лише цей сервіс | значення користувацьких профілів |
| Користувацька сесія | limits.conf через PAM | інтерактивні входи | м'який ліміт сесії |
| Процес | ulimit під час запуску | поточний і дочірні процеси | нічого вище жорсткого ліміту |
Ключовий нюанс: служби, які піднімає менеджер служб, не проходять через PAM, тому правки для користувацьких сесій їх не зачіпають — звідси класична розбіжність «у терміналі ліміт один, під службою інший».
У межах одного рівня м'який ліміт — робоче значення, жорсткий — верхня межа: м'який можна підняти, але не вище жорсткого, а жорсткий не вище системного.
Фактичне значення дивимося за /proc/<pid>/limits у конкретного процесу, а не за файлами конфігурації. Порядок застосування: системна стеля → параметри юніта або батьківського процесу → успадкування дочірніми процесами → значення, з яким процес реально працює.
Часті технічні сумніви при налаштуванні лімітів
Розбір лімітів майже завжди впирається не в питання «піднімати чи ні», а в застереження інженерної команди: чим це загрожує працюючому сервісу і як потім довести, що стало краще. Нижче — те, що спливає на погодженні найчастіше.
Окремо варто поведінка під навантаженням: при середньому трафіку запас виглядає надлишковим, а на піку той самий процес упирається в ліміт, бо частину дескрипторів тримають з'єднання й тимчасові файли. Тому розрахунок ведемо від пікового профілю, а не від добового середнього.
Чи доведеться перезапускати сервіс?
Не завжди, але планувати вікно потрібно. Частина параметрів застосовується до працюючого процесу, частина — лише при старті або новому вході в сесію, а системні ліміти змінюються на рівні ядра.
Порядок такий: спочатку правимо описи служб і системні значення, потім перезапускаємо по одному вузлу за раз, щоб не втрачати доступність.
Чи не замаскує новий ліміт реальний витік?
Ризик є, і він головний. Якщо дескриптори не закриваються в обробниках, високий ліміт просто відкладе відмову. Тому перед зміною знімаємо знімок відкритих дескрипторів на кількох процесах: зростання, прив'язане до числа запитів, — це витік, рівний профіль — нестача запасу. Діагностику робимо до правок, а не після.
Як переконатися, що запас справді потрібен?
За моніторингом: збираємо число відкритих дескрипторів за процесами, частку використання від ліміту та лічильник відмов відкриття.
Оповіщення ставимо на 75–80% від ліміту, щоб був запас до деградації, і звіряємо його з історією викликів — так видно, чи зростає споживання з часом, чи впирається в сезонні піки.
Чи не конфліктує це з поточною конфігурацією?
Ліміти можуть задаватися в кількох місцях — загальносистемно, в описі служби, у контейнерній платформі. Застосовується не найбільший, а заданий пізніше за ланцюжком запуску, тому значення зводимо до одного джерела й фіксуємо в документації. Інакше після перезапуску поведінка виявиться не тією, що перевіряли на стенді.
Обговоримо задачу й повернемося з оцінкою та планом робіт
Оцінка налаштування лімітів файлових дескрипторів рідко потребує довгого обстеження — але склад робіт і план впровадження напряму залежать від того, наскільки повно описаний контур. Нижче — вхідні дані, яких достатньо, щоб повернутися зі строками та порядком змін.
- Склад сервісів: які процеси тримають відкриті з'єднання та файли — вебсервери, воркери черг, проксі, бази, — і скільки екземплярів працює на одному вузлі.
- Характер навантаження: пікове число одночасних з'єднань, добовий профіль, як поводиться система в момент піку.
- Поточні налаштування: м'який і жорсткий ліміт для користувача й процесу, обмеження в юніті служби, загальносистемний ліміт на відкриті файли.
- Симптоми відмов: тексти помилок із логів, у який момент з'являються, що саме падає й перезапускається.
- Спосіб запуску: хто ініціює процеси — служба, супервізор або контейнер, — і чи передаються ліміти всередину.
- Що вже змінювали: які значення правили і чому результат не влаштував.
- Обмеження оточення: контейнери, оркестратор, чи є права на зміну системних параметрів.
З цим набором обговорюємо задачу й фіксуємо домовленості: які значення ставимо, як перевіряємо під навантаженням і в якому порядку відкочуємо.







