cgroups: лимиты ресурсов
cgroups: как ограничивают CPU, память и IO.
Namespaces про видимость, cgroups про ресурсы
В прошлом уроке namespaces определяли, что процесс видит: свой список процессов, свою сеть, свою файловую систему. Но видеть лишь кусок системы не значит потреблять её ресурсы скромно. Процесс с урезанной видимостью всё ещё может занять все ядра CPU и всю память хоста.
cgroups (control groups) — механизм ядра, который ограничивает, сколько ресурсов группа процессов может потребить: процессорное время, память, дисковый ввод-вывод, число процессов. Namespaces отвечают на вопрос «что я вижу», cgroups на вопрос «сколько мне можно взять».
Иерархия групп
Процессы объединяются в дерево групп. Каждая группа — каталог в
специальной файловой системе, смонтированной в /sys/fs/cgroup. В файлах
внутри каталога лежат лимиты и текущие счётчики.
$ ls /sys/fs/cgroup
cgroup.controllers cpu.max memory.max pids.max
cgroup.procs io.max memory.current pids.current
Современная версия — cgroup v2, единая иерархия для всех контроллеров. Старая cgroup v1 с отдельным каталогом на каждый контроллер осталась в легаси: все актуальные дистрибутивы грузятся с v2 по умолчанию. Проверить, что у тебя, можно по типу файловой системы:
$ stat -fc %T /sys/fs/cgroup
cgroup2fs # v2; tmpfs означает старую v1
Файл cgroup.procs содержит PID-ы процессов группы, а файлы вида
memory.max и cpu.max задают лимиты:
$ cat /sys/fs/cgroup/memory.max
max # max значит «без лимита»
$ cat /sys/fs/cgroup/cpu.max
max 100000 # первое число — квота, второе — период в микросекундах
Как это связано с контейнерами
Флаги ограничения ресурсов у docker run под капотом создают cgroup и
пишут в неё числа. Команда --memory 512m выставляет memory.max, а
--cpus 1 настраивает cpu.max:
docker run --memory 512m --cpus 1 nginx
Проверить лимит можно из хоста. У Docker группа контейнера лежит внутри
/sys/fs/cgroup, и там видно ровно те 512 мегабайт:
$ cat /sys/fs/cgroup/system.slice/docker-<id>.scope/memory.max
536870912 # 512 * 1024 * 1024 байт
Когда процесс внутри группы пробует занять больше памяти, чем разрешает
memory.max, ядро запускает OOM-killer и убивает процесс в этой группе.
Соседи на хосте при этом не страдают: лимит сработал локально.
Ловушка первая: namespaces и cgroups это одно и то же
Их часто путают, потому что оба участвуют в изоляции контейнера. Разница в задаче. Отними namespaces, и процесс увидит все процессы и сеть хоста, но лимиты памяти останутся. Без cgroups он не увидит лишнего, зато сможет занять всю память и CPU.
$ cat /proc/self/cgroup # в какой cgroup я сейчас
0::/user.slice/user-1000.slice
$ ls -l /proc/self/ns # какие namespaces у меня
lrwxrwxrwx net -> 'net:[4026531840]'
lrwxrwxrwx pid -> 'pid:[4026531836]'
Один вывод отвечает за ресурсы, другой за видимость. Это два разных механизма ядра, работающих параллельно.
Ловушка вторая: контейнер не уронит хост по памяти
Уронит, если лимит не задан. Контейнер без --memory попадает в cgroup с
memory.max равным max, то есть без потолка. Утечка памяти внутри
такого контейнера растёт, пока OOM-killer хоста не начнёт убивать процессы,
и под удар попадут соседние контейнеры или сам хост.
docker run nginx # без лимита: memory.max = max
docker run --memory 512m nginx # с лимитом: своя группа, свой потолок
Ограничение ресурсов не появляется само. Его задаёт тот, кто запускает контейнер, через флаги или манифест.
Контейнер глазами хоста
Сложить картину помогает один факт. С точки зрения хостовой ОС контейнер — обычный процесс. Его особенности: он помещён в отдельные namespaces, ему урезали capabilities (набор привилегий процесса), и он приписан к cgroup с лимитами.
$ ps aux | grep nginx
root 4821 0.0 0.1 nginx: master process
В ps контейнерный процесс стоит рядом с остальными процессами хоста, с
обычным PID. Магии нет: namespaces прячут от него лишнее, cgroups
ограничивают ресурсы, capabilities режут права.
Что важно запомнить
cgroups (control groups) ограничивают ресурсы группы процессов: CPU, память, IO, число процессов.
Лимиты живут файлами в /sys/fs/cgroup; современная версия — cgroup v2.
docker run --memory 512m --cpus 1 под капотом создаёт cgroup и пишет в
memory.max и cpu.max.
namespaces про видимость, cgroups про ресурсы. Это разные механизмы.
Без лимита контейнер может занять всю память хоста и спровоцировать OOM у соседей. Лимит задаёт тот, кто запускает.
В следующем уроке — capabilities: как урезают привилегии процесса, чтобы root внутри контейнера не был root на хосте.
Прошли материал до конца? Отметьте урок пройденным.
Прогресс сохранён в этом браузере
Почистишь историю или откроешь с телефона, и отметки пропадут. Оставь почту, они переедут в аккаунт. Пароль придумывать не нужно.
Готово. теперь в аккаунте: прогресс виден с любого устройства. Мой прогресс