Приоритеты и ограничения: nice, ionice, cgroups

Рейтинг: 52.3% · 11 голосов
Подробный курс по диагностике и производительности Linux для админов и DevOps: с чего начать, методология USE, логи (journalctl, dmesg), процессы, CPU, память и OOM, диск и IO (iostat), сеть (ss, tcpdump), системные вызовы и strace, ltrace, perf, флеймграфы, ftrace, eBPF и bpftrace, lsof, мониторинг. Разбор каждого инструмента с чтением вывода.
Ответить
Аватара пользователя
Dmitry_SRE
Сообщения: 47
Зарегистрирован: 11 май 2026, 05:31

Приоритеты и ограничения: nice, ionice, cgroups

Сообщение Dmitry_SRE »

Оглавление курса (47)
  1. С чего начать диагностику Linux: методология вместо паники
  2. Первые 60 секунд: экспресс-диагностика нагруженного сервера
  3. Методологии диагностики: USE, RED и здравый смысл
  4. Логи systemd через journalctl: где искать причину
  5. Где лежат логи Linux: /var/log, dmesg и rsyslog
  6. Источники правды: load average, /proc и /sys
  7. Процессы Linux: ps, pstree и состояния процессов
  8. Интерактивный мониторинг: top и htop
  9. Метрики процессов во времени: pidstat
  10. Приоритеты и ограничения: nice, ionice, cgroups (вы здесь)
  11. Сигналы и зависшие процессы: kill, и что делать с D-state
  12. Загрузка CPU: user, system, iowait, steal и контекст-свитчи
  13. Диагностика CPU по ядрам: vmstat и mpstat
  14. Профилирование CPU: perf top и поиск пожирателя
  15. Частоты, троттлинг и NUMA: turbostat и numastat
  16. Память Linux: RSS, VSZ, page cache и миф о нехватке памяти
  17. Сколько памяти занято: free, /proc/meminfo и vmstat
  18. Поиск утечек и пожирателей памяти: smem, pmap, smaps
  19. Swap и подкачка: swapon, swappiness, когда своп - это боль
  20. OOM killer: кто и за что убил процесс
  21. Память ядра и slab: slabtop и куда уходит RAM
  22. Подсистема ввода-вывода: путь запроса от приложения до диска
  23. Диагностика диска: iostat и чтение await, %util
  24. Кто грузит диск: iotop и атрибуция io процессам
  25. Файловые системы: df, du, иноды и куда делось место
  26. Глубокая диагностика блочного слоя: blktrace и biolatency
  27. Кеш страниц и грязные данные: dirty pages, fsync, drop_caches
  28. Сетевой стек Linux: путь пакета и где возникают задержки
  29. Сокеты и соединения: ss и netstat
  30. Захват трафика: tcpdump для диагностики сети
  31. Задержки и потери в сети: ping, mtr, диагностика латентности
  32. Сеть как источник проблем: nftables, conntrack, дропы
  33. Что такое системный вызов и зачем его трассировать
  34. strace: трассировка системных вызовов на практике
  35. strace в бою: почему программа висит, падает или тормозит
  36. ltrace: трассировка вызовов библиотек
  37. Накладные расходы трассировки и безопасные альтернативы
  38. perf: универсальный профайлер Linux
  39. Флеймграфы: визуализация профиля производительности
  40. Трассировка ядра: ftrace и trace-cmd
  41. eBPF: революция в наблюдаемости Linux
  42. Инструменты eBPF на практике: BCC и bpftrace
  43. lsof: открытые файлы, дескрипторы, кто держит файл и порт
  44. Разбор кейса: приложение тормозит - пошаговая диагностика
  45. Непрерывный мониторинг: sar, node_exporter, Grafana, Zabbix
  46. Диагностика в контейнерах: cgroups, лимиты, Docker и Kubernetes
  47. Карта инструментов и сквозной разбор инцидента производительности
Знакомая картина: сервер еле дышит, в топе висит какой-нибудь rsync, ночной бэкап или сборка проекта, и она сжирает все. Первая мысль новичка - kill -9. Но это грубо: процесс может быть нужным, он просто слишком жадный. А что если не убивать, а притормозить? Сказать ядру: "пусть работает, но в последнюю очередь, не мешая базе и веб-серверу". Именно для этого есть приоритеты и лимиты. В этом уроке разберем, как управлять приоритетом процесса в linux по CPU и диску через nice и ionice, и как жестко ограничить аппетиты через cgroups - тот самый механизм, на котором стоят Docker, Kubernetes и systemd. Все примеры актуальны на 2026 год: современное ядро (6.x), cgroups v2 по умолчанию, systemd.

Два разных инструмента: вежливость и потолок

Сразу разложим по полочкам, иначе будет каша. Есть два принципиально разных подхода.
  • Вежливость (nice, ionice) - меняет порядок в очереди. Работает ТОЛЬКО когда есть конкуренция за ресурс. На простаивающей системе вежливый процесс заберет все. Жесткого потолка не дает.
  • Потолок (cgroups: cpu.max, memory.max, io.max) - реальный лимит. "Не больше пол-ядра и 512 МБ, и точка". Работает всегда, даже если система простаивает.
Запомни это разделение - половина граблей в теме именно из-за того, что люди ждут от nice потолка, а от cgroups вежливости.

Изображение

Приоритет CPU: nice и renice в linux

Начнем с самого простого - nice. Представь очередь в столовой: планировщик ядра (на современных ядрах это EEVDF, пришедший на смену CFS в ядре 6.6) решает, кого пустить к процессору следующим. Значение nice - твоя вежливость в этой очереди. Диапазон от -20 до 19. Чем БОЛЬШЕ число, тем процесс вежливее (nice = "хороший, уступчивый"), тем меньше доля CPU при конкуренции. Чем меньше (вплоть до -20) - тем он наглее и прожорливее.

Важно понять: nice не выключает процесс и не дает ему ровно "20% CPU". Если система простаивает, даже nice 19 получит все ядра. Приоритет работает только при конкуренции. Тогда ядро делит время пропорционально весу: каждая единица nice меняет вес примерно в 1.25 раза, так что разница между nice 0 и nice 19 - это разница долей CPU в десятки раз.

Запустить программу с пониженным приоритетом:

Код: Выделить всё

nice -n 10 ./heavy_script.sh
Поменять приоритет уже работающему процессу - renice (по PID):

Код: Выделить всё

renice -n 15 -p 24917
Повысить приоритет (уйти в минус) может только root - это защита, чтобы юзеры не растаскивали систему:

Код: Выделить всё

sudo renice -n -5 -p 24917
Можно ренайсить все процессы пользователя сразу через -u, или группу через -g - удобно прижать разом всю пользовательскую сессию.

Где смотреть текущий nice? В выводе top это колонка NI, рядом - PR (реальный приоритет ядра). Или через ps:

Код: Выделить всё

ps -o pid,ni,pri,comm -p 24917
  PID  NI PRI COMMAND
24917  15   4 heavy_script.sh
Читаем: NI = 15 - мы сами поставили высокую вежливость. PRI - внутренний приоритет планировщика; не путай его с NI, шкала тут другая. Для новичка достаточно смотреть на NI: 0 - обычный процесс, положительное - притормозили, отрицательное - дали фору. В top у PR ты увидишь число от 0 до 39 для обычных задач, а у realtime-задач - rt. Норма для фоновой задачи вроде бэкапа - nice 10..19.

Приоритет диска: ionice

nice управляет процессором, но часто тормозит не CPU, а диск. Бэкап читает терабайты, и интерактивная база начинает лагать на ровном месте. Тут нужен ionice - приоритет ввода-вывода. Важный нюанс 2026 года: ionice работает не на любом дисковом планировщике. Приоритеты ввода-вывода поддерживают BFQ и mq-deadline, а вот планировщик none (типичный дефолт для NVMe) их полностью игнорирует. Старый CFQ, на котором ionice исторически и появился, из ядра давно выпилен (еще в районе 5.0), его наследник - BFQ.

Три класса, задаются флагом -c:
  • Realtime (-c 1) - получает диск первым, может задушить все остальное. Только root, использовать осторожно.
  • Best-effort (-c 2) - обычный класс по умолчанию, внутри уровни приоритета -n от 0 (главный) до 7 (последний).
  • Idle (-c 3) - диск только когда никто другой его не просит. Идеально для бэкапов: уровень -n тут не задается, класс сам по себе самый низкий.
Запустить бэкап так, чтобы он не мешал никому:

Код: Выделить всё

sudo ionice -c 3 nice -n 19 tar -czf /backup/data.tgz /var/data
Тут мы скрутили и диск (idle), и CPU (nice 19) - получился максимально незаметный фоновый процесс. Посмотреть класс работающего процесса:

Код: Выделить всё

ionice -p 24917
idle
Для best-effort увидишь что-то вроде "best-effort: prio 4". Если видишь "idle" - процесс не отберет диск у твоей базы, это то, что нужно для тяжелой фоновой задачи.

Перед тем как полагаться на ionice, проверь планировщик диска - это и есть главный подводный камень темы:

Код: Выделить всё

cat /sys/block/nvme0n1/queue/scheduler
[none] mq-deadline kyber bfq
В квадратных скобках - активный. Тут стоит none, значит ionice бесполезен. Включить BFQ на лету:

Код: Выделить всё

echo bfq | sudo tee /sys/block/nvme0n1/queue/scheduler
Чтобы переживало ребут - через udev-правило или параметр ядра. На быстрых NVMe none нередко оставляют осознанно (меньше накладных расходов на CPU), и тогда вместо ionice применяют лимиты пропускной способности через cgroups (io.max), о которых ниже.

cgroups v2: настоящие лимиты ресурсов в linux

nice и ionice - это про вежливость, а не про жесткий потолок. Если нужно сказать "этому процессу НЕ БОЛЬШЕ 50% ядра и 512 МБ памяти" - это cgroups (control groups). Это фундамент: именно через cgroups Docker, Kubernetes и systemd навешивают лимиты на контейнеры и сервисы. На современных дистрибутивах (2026) cgroups v2 - дефолт: единое дерево в /sys/fs/cgroup без разнесенных подпапок cpu, memory, blkio как было в v1.

Идея простая: группа процессов = папка. В папке лежат файлы-ручки, в которые пишешь лимит. Самые важные:
  • cpu.max - квота CPU. Формат "квота период" в микросекундах. "50000 100000" значит 50 мс из каждых 100 мс = пол-ядра. Значение "max 100000" - без лимита.
  • cpu.weight - относительный вес при конкуренции (1..10000, дефолт 100). Это и есть cgroup-аналог nice: вежливость, а не потолок.
  • memory.max - жесткий потолок памяти. Превысил и не смог освободить - срабатывает OOM-killer внутри группы.
  • memory.high - мягкий порог: память не режется насмерть, но процесс притормаживают и заставляют активно освобождать (reclaim, своп).
  • io.max - лимит диска по устройству (major:minor), например rbps/wbps - байт в секунду на чтение/запись, riops/wiops - операций в секунду.
  • io.weight - относительный вес ввода-вывода (только при активном BFQ).
Руками файлы трогают редко - удобнее через systemd, потому что он сам создает cgroup для каждого сервиса. Ограничить запущенный сервис на лету:

Код: Выделить всё

sudo systemctl set-property nginx.service CPUQuota=50% MemoryMax=512M
Применяется сразу, без рестарта, и переживает перезагрузку (пишется drop-in в /etc/systemd/system.control). Нужно только до ребута - добавь --runtime. Под капотом systemd транслирует CPUQuota=50% в cpu.max = "50000 100000", MemoryMax - в memory.max, CPUWeight - в cpu.weight, IOWeight - в io.weight. Самый быстрый способ загнать в лимит вообще любую команду:

Код: Выделить всё

sudo systemd-run --scope -p CPUQuota=30% -p MemoryMax=300M ./heavy_script.sh
Смотреть, кто сколько ест по группам - systemd-cgtop, это как top, но по cgroup:

Код: Выделить всё

sudo systemd-cgtop
Control Group               Tasks  %CPU  Memory  Input/s Output/s
/                             412  88.3    5.1G        -        -
/system.slice/mysql.service    38  61.2    2.3G        -        -
/system.slice/nginx.service    12  12.0  118.4M        -        -
Читаем сверху вниз: mysql забирает 61% CPU и 2.3 ГБ. Если это норма для нагрузки - ок; если сервис фоновый, а ест столько - вот кандидат на лимит. Чтобы видеть Input/s и Output/s по диску, нужен включенный io-аккаунтинг (IOAccounting=yes), иначе будут прочерки.

Диагностика throttling и лимиты процесса (ulimit)

Поставил CPUQuota - и сервис вдруг стал медленным и дерганым? Скорее всего его душит throttling: задача уперлась в квоту, и ядро тормозит ее до конца периода (по умолчанию 100 мс). Это видно в файле cpu.stat внутри cgroup:

Код: Выделить всё

cat /sys/fs/cgroup/system.slice/nginx.service/cpu.stat
usage_usec 9234112
user_usec 7120044
system_usec 2114068
nr_periods 8423
nr_throttled 5901
throttled_usec 271044000
Ключевое - nr_throttled относительно nr_periods. Тут 5901 из 8423 периодов процесс упирался в потолок - это огромный throttling, лимит слишком тесный, надо поднимать CPUQuota. Грубое правило: если nr_throttled / nr_periods больше ~5-10%, throttling уже бьет по отзывчивости, особенно для latency-чувствительных сервисов. throttled_usec - сколько суммарно микросекунд процесс простоял в наказание. Аналогично у памяти есть memory.events с полями high, max и oom - туда смотрят, когда сервис подозрительно медленный из-за постоянного reclaim или его периодически прибивает OOM.

Отдельная история - ulimit: лимиты на один процесс и его потомков (открытые файлы, размер памяти, число процессов). Классика - too many open files:

Код: Выделить всё

ulimit -n
1024
1024 дескриптора - для нагруженного веб-сервера мало, отсюда падения под нагрузкой. Для systemd-сервиса это правится не через limits.conf (его systemd игнорирует), а директивой LimitNOFILE= в юните. Для интерактивных сессий и не-systemd запусков - /etc/security/limits.conf или limits.d. Посмотреть реальные лимиты живого процесса:

Код: Выделить всё

cat /proc/24917/limits
Колонка "Soft Limit" - текущий, "Hard Limit" - потолок, выше которого без root не прыгнуть.

Грабли и заблуждения
  • nice не "выделяет проценты CPU". На свободной системе nice 19 заберет все ядра. Жесткий потолок - только cgroups (CPUQuota / cpu.max).
  • ionice молча не работает на планировщике none (частый дефолт для NVMe). Приоритеты понимают только BFQ и mq-deadline. Проверь cat /sys/block/.../queue/scheduler перед тем как рассчитывать на ionice.
  • Старого CFQ в современном ядре уже нет, его место занял BFQ. Гайды, которые говорят "включите CFQ для ionice", устарели.
  • Слишком тесный CPUQuota хуже его отсутствия: получишь throttling и рваные тормоза вместо плавного замедления. Всегда сверяйся с cpu.stat. Часто правильнее не жесткий CPUQuota, а относительный CPUWeight - тогда сервис притормаживается только при реальной конкуренции.
  • memory.max - это жестко: процесс не "затормозит", а получит OOM внутри своей группы. Для мягкого давления бери memory.high (MemoryHigh).
  • limits.conf не действует на systemd-сервисы - правь LimitNOFILE в юните. Очень частая ловушка.
  • На Astra Linux и RED OS systemd и cgroups v2 на месте, все описанное работает. Если /sys/fs/cgroup выглядит иначе (старое дерево с подпапками cpu, memory, blkio) - система на cgroups v1, имена файлов другие (cpu.cfs_quota_us и т.п.).
Мини-лаба
  • Запусти нагрузку: yes > /dev/null & и посмотри в top - оно ест 100% ядра. Сделай ему renice -n 19 -p PID и запусти рядом второй yes без nice - увидишь в top, как они делят CPU резко неравномерно. Останови, запусти только один с nice 19 - он снова заберет все ядро (конкуренции нет).
  • Загони ту же нагрузку в лимит: sudo systemd-run --scope -p CPUQuota=20% yes > /dev/null - теперь больше 20% ядра не возьмет, как ни старайся.
  • Найди cgroup этого scope в /sys/fs/cgroup (имя вида run-rXXXX.scope) и читай его cpu.stat в цикле - убедись, что nr_throttled растет. Проверь scheduler своего диска: cat /sys/block/.../queue/scheduler. Не забудь убить yes после опытов: pkill yes.
Контрольные вопросы
  • Чем принципиально отличается nice (или cpu.weight) от CPUQuota в cgroups? Когда какой инструмент брать?
  • Каким классом ionice запустить ночной бэкап, чтобы он не мешал базе, и что обязательно проверить, прежде чем на ionice полагаться?
  • По каким двум полям cpu.stat понять, что сервис душит throttling, и какой порог уже тревожный?
  • Что произойдет с процессом при превышении memory.max и чем это отличается от memory.high?
Что запомнить

nice и ionice - это вежливость: работают только при конкуренции за ресурс и не дают жесткого потолка. cgroups v2 - это настоящие лимиты (cpu.max, memory.max, io.max) и относительные веса (cpu.weight, io.weight), и именно на них стоят Docker, Kubernetes и systemd. Притормозить жадный процесс, не убивая, проще всего через renice, ionice -c 3 (если планировщик это поддерживает) или systemctl set-property с CPUQuota/CPUWeight. А если после лимита все затормозило - первым делом смотри nr_throttled в cpu.stat: тесный лимит лечится не сильнее, а аккуратнее.
👍3 ❤️2 🔥 😄 🤔
Аватара пользователя
solidityguru
Сообщения: 1
Зарегистрирован: 13 май 2026, 23:05

Re: Приоритеты и ограничения: nice, ionice, cgroups

Сообщение solidityguru »

Спасибо, наконец дошло, почему мой nice 19 на тестовом сервере все равно сжирал все ядро - там же просто никто больше за CPU не дрался. На бою при конкуренции реально помогло.
👍 ❤️ 🔥1 😄 🤔
Аватара пользователя
jackie24
Сообщения: 1
Зарегистрирован: 13 май 2026, 01:10

Re: Приоритеты и ограничения: nice, ionice, cgroups

Сообщение jackie24 »

А у меня ionice -c 3 будто не работал, бэкап все равно клал диск. Глянул scheduler - стоит none на nvme. Переключил на bfq и стало норм, но прочитал тут, что на быстром nvme иногда лучше вообще io.max через cgroups крутить. Попробую оба.
👍 ❤️1 🔥 😄 🤔
Ответить
← Предыдущая глава
Метрики процессов во времени: pidstat
Следующая глава →
Сигналы и зависшие процессы: kill, и что делать с D-state

Все главы курса «Диагностика и производительность Linux: логи, strace, perf и eBPF»

Поделиться темой: ✈ Telegram VK

Вернуться в «Диагностика и производительность Linux: логи, strace, perf и eBPF»

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и 1 гость