open atlas
↑ К треку
Linux: операционная система LIN · 08 · 03

cgroups и ограничения ресурсов

cgroups v2 ограничивают CPU и память для групп процессов; systemd-слайсы используют MemoryMax и CPUQuota. rlimits (ulimit) ограничивают ресурсы одного процесса. Механизмы дополняют друг друга: rlimits — на процесс, cgroups — на группу.

LIN Senior ◷ 22 min
Уровень
ОсновыJuniorMiddleSenior

Процесс, который утекает по памяти или крутит CPU на 100%, вредит не только себе — он морит голодом всё остальное на хосте. В ядре Linux есть два дополняющих механизма для ограничения использования ресурсов. rlimits — потолки на процесс: количество открытых файлов, число дочерних процессов, размер стека. cgroups (control groups) — потолки на группу: набор процессов с общим бюджетом CPU и памяти. systemd использует cgroups v2 для реализации каждой границы сервиса. OOM killer (следующий урок) — это то, что случается, когда ни один из механизмов не остановил процесс.

Цель

После этого урока ты сможешь читать и устанавливать rlimits через ulimit, определять самые частые ошибки rlimit, ориентироваться в файловой системе cgroups v2, устанавливать CPUQuota и MemoryMax для systemd-сервиса, и объяснять ключевое различие между rlimits (на процесс) и cgroups (на группу).

1

rlimits: потолки ресурсов на процесс, применяемые ядром.

Каждый процесс наследует набор rlimits от родителя. У каждого лимита есть мягкое значение (текущий применяемый потолок, который процесс может понижать, но не поднимать выше жёсткого) и жёсткое значение (потолок для мягкого — только root может его поднять).

# Просмотр rlimits текущей оболочки:
ulimit -a
# core file size          (blocks, -c) 0
# max locked memory       (kbytes, -l) 8192
# open files                      (-n) 1024    ← RLIMIT_NOFILE мягкий
# max user processes              (-u) 63296   ← RLIMIT_NPROC

# Два лимита, которые чаще всего встречаются в продакшене:

# RLIMIT_NOFILE: максимальное количество открытых файловых дескрипторов
# По умолчанию мягкий: 1024. По умолчанию жёсткий: 1048576 (Ubuntu).
# При превышении: "Too many open files" (errno EMFILE)
# Node.js или Java-сервер под нагрузкой нуждается в гораздо большем.

# RLIMIT_NPROC: максимальное число процессов/потоков пользователя
# При превышении fork() возвращает EAGAIN: "Resource temporarily unavailable"
# Fork-бомбы останавливаются здесь.
2

Установка rlimits: ulimit для сессии, /etc/security/limits.conf для постоянства.

# Поднять лимит открытых файлов для текущей оболочки и её детей:
ulimit -n 65536
ulimit -n
# 65536

# Это влияет только на текущую сессию.
# Для сервиса — в /etc/security/limits.conf:
# *         soft  nofile  65536
# *         hard  nofile  1048576
# www-data  soft  nproc   4096

# Для systemd-сервисов — предпочтительнее LimitNOFILE в unit-файле:
# [Service]
# LimitNOFILE=65536
# Это правильный способ — limits.conf обходится для сервисов без PAM.

# Проверить реальные лимиты работающего процесса (PID 1234):
cat /proc/1234/limits
# Limit                     Soft Limit   Hard Limit   Units
# Max open files            65536        1048576      files
# Max processes             63296        63296        processes
3

cgroups v2: единая иерархия.

cgroups v2 (по умолчанию на Ubuntu 22.04+) использует единую файловую иерархию в /sys/fs/cgroup. Каждый процесс принадлежит ровно одной cgroup. systemd автоматически отображает сервисы на cgroups.

# Просмотр иерархии cgroup:
systemd-cgls
# Control group /:
# -.slice
#   ├─system.slice
#   │ ├─nginx.service
#   │ │ └─987 nginx: worker process
#   └─user.slice

# cgroup текущего процесса:
cat /proc/$$/cgroup
# 0::/user.slice/user-1000.slice/session-1.scope

# Доступные контроллеры:
cat /sys/fs/cgroup/cgroup.controllers
# cpuset cpu io memory hugetlb pids rdma misc

# Текущее использование памяти cgroup:
cat /sys/fs/cgroup/system.slice/nginx.service/memory.current
# 45678592   (байты — около 44 МБ)

# Использование CPU:
cat /sys/fs/cgroup/system.slice/nginx.service/cpu.stat
# usage_usec 123456789
4

CPUQuota и MemoryMax в systemd: установка лимитов для сервиса.

systemd транслирует директивы unit-файла в записи cgroup. Напрямую писать в /sys/fs/cgroup почти никогда не нужно.

# Установить лимит памяти и квоту CPU для сервиса:
sudo systemctl edit nginx
# Добавить:
# [Service]
# MemoryMax=512M
# CPUQuota=50%

# MemoryMax: cgroup memory.max — жёсткий потолок. При превышении
# ядро вызывает локальный OOM killer cgroup (убивает процесс в этой cgroup).
# НЕ системный OOM killer.

# CPUQuota=50%: cgroup получает не более 50% одного ядра CPU за период
# (период по умолчанию 100мс). На 4-ядерной машине 50% = одно ядро на полной
# скорости, НЕ 50% от всех 4 ядер.

sudo systemctl daemon-reload
sudo systemctl restart nginx

# Проверить применение лимитов:
systemctl show nginx | grep -E 'MemoryMax|CPUQuota'
# MemoryMax=536870912
# CPUQuotaPerSecUSec=500000

# Из cgroup напрямую:
cat /sys/fs/cgroup/system.slice/nginx.service/memory.max
# 536870912
cat /sys/fs/cgroup/system.slice/nginx.service/cpu.max
# 50000 100000   (50мс бюджета за 100мс период = 50%)

# Временные лимиты (без изменения unit-файла, для тестирования):
sudo systemctl set-property nginx.service MemoryMax=256M
Разбор примера

Диагностика “too many open files” в продакшн Node.js-сервисе.

# Симптом: в логах "EMFILE: too many open files"

# Шаг 1: найти PID сервиса
systemctl show node-api --property MainPID
# MainPID=7823

# Шаг 2: проверить его реальные rlimits
cat /proc/7823/limits | grep 'open files'
# Max open files     1024     1048576     files
# ↑ мягкий=1024 — проблема

# Шаг 3: сколько FD сейчас открыто
ls /proc/7823/fd | wc -l
# 1019   ← почти у лимита 1024

# Шаг 4: исправление — в unit-файле systemd
sudo systemctl edit node-api
# [Service]
# LimitNOFILE=65536

sudo systemctl daemon-reload
sudo systemctl restart node-api

# Проверка:
cat /proc/$(systemctl show node-api --property MainPID --value)/limits | grep 'open files'
# Max open files     65536    1048576    files
Почему это работает

На macOS cgroups нет — это функциональность ядра Linux. macOS использует элементы управления ресурсами launchd и BSD setrlimit(). Команда ulimit работает в оболочках macOS и устанавливает те же POSIX rlimits (RLIMIT_NOFILE, RLIMIT_NPROC и т.д.), но конфигурируются они иначе: через launchctl limit или plist-ключи SoftResourceLimits/HardResourceLimits. Мягкий лимит RLIMIT_NOFILE по умолчанию на macOS исторически составлял 256 — ещё ниже, чем 1024 на Linux, поэтому “too many open files” — очень распространённая боль при разработке на macOS.

Частая ошибка

Установка LimitNOFILE в /etc/security/limits.conf для systemd-сервиса ничего не даст. limits.conf обрабатывается модулем PAM pam_limits во время сессий входа в систему. Сервисы, запускаемые systemd, не проходят через PAM — они наследуют лимиты от systemd, который читает LimitNOFILE из unit-файла. Исправление всегда в секции [Service] unit-файла (или в drop-in через systemctl edit). Многие операторы тратят время на редактирование limits.conf и перезапуск сервиса, обнаруживая что rlimit не изменился. Всегда проверяй через cat /proc/<pid>/limits, а не ulimit -a в оболочке.

Проверь себя
Викторина

У systemd-сервиса в unit-файле стоит MemoryMax=256M. Использование памяти сервиса вырастает выше 256 МБ. Что произойдёт, и то же ли это что системный OOM killer?

Итог

rlimits — потолки ресурсов на процесс: RLIMIT_NOFILE ограничивает открытые файловые дескрипторы (по умолчанию 1024 — мало для продакшн-сервисов), RLIMIT_NPROC — количество процессов пользователя. Устанавливай через ulimit в сессии или LimitNOFILE в unit-файле systemd (не через limits.conf — systemd его обходит). cgroups v2 организует процессы в иерархию под /sys/fs/cgroup и применяет групповые бюджеты: cpu.max реализует CPUQuota, memory.max реализует MemoryMax. При превышении cgroup MemoryMax срабатывает локальный OOM kill cgroup — отдельно от системного OOM killer, который срабатывает только при исчерпании памяти всей системы. systemd автоматически отображает каждый сервис в собственную cgroup; используй systemctl edit для установки MemoryMax и CPUQuota. Всегда проверяй лимиты через cat /proc/<pid>/limits, а не через ulimit в оболочке.

Практика

Начни сверху. Задачи идут от простого к сложному: вспомнить факт, применить к случаю, затем senior-уровень. Открой, попробуй, потом открой ответ.

вспомнитьприменитьуглубить0 из 4 завершено

Что-то непонятно?

Задай вопрос по этому уроку. Вопросы анонимны и попадают напрямую автору — урок станет лучше.

Примени это

Примени этот урок в реальном проекте.

хоткеи развернуть
поиск
K
пред. пьеса
k
след. пьеса
j
тиры
t
это меню
?
sources2
expand
  1. 01
  2. 02

Trademarks belong to their respective owners. Editorial reference only.