CGroups (Control Groups)
Последнее изменение этой страницы: 16:11, 20 декабря 2015.
Веб-сайт |
{{ |
---|
cgroups (англ. control group) — механизм ядра Linux, который ограничивает и изолирует вычислительные ресурсы (процессорные, сетевые, ресурсы памяти, ресурсы ввода-вывода) для групп процессов. Механизм позволяет образовывать иерархические группы процессов с заданными ресурсными свойствами и обеспечивает программное управление ими.
Содержание
История
Разработка была начата инженерами Google Полом Менэджем (Paul Menage) и Рохитом Сетом (Rohit Seth) в 2006 году и первоначально называлась «контейнеры процессов» (англ. process containers). В 2007 году проект был переименован в сgroups (от англ. control groups) по причине неоднозначности значения термина «контейнер» в ядре Linux.
Начиная с версии 2.6.24 ядра Linux технология включена в официальные версии ядра. С этого момента разработка значительно активизировалась, в механизм добавлено много дополнительных возможностей, механизм существенным образом используется в технологии инициализации systemd, а также является ключевым элементом в реализации системы виртуализации на уровне операционной системы LXC.
Возможности
Механизм предоставляет следующие возможности:
- ограничение ресурсов (англ. resource limiting): использование памяти, в том числе виртуальной;
- приоритезацию: разным группам можно выделить разное количество процессорного ресурса и пропускной способности подсистемы ввода-вывода;
- учёт: подсчёт затрат тех либо иных ресурсов группой;
- изоляцию: разделение пространств имён для групп таким образом, что одной группе недоступны процессы, сетевые соединения и файлы другой;
- управление: приостановку (freezing) групп, создание контрольных точек (checkpointing) и их перезагрузку.
Подключение
Для управления cgroups используется «виртуальная» файловая система (ФС) с идентификатором типа cgroup
, подключаемая, как правило, к директориям иерархии /sys/fs/cgroup
. Каждое такое подключение соответствует отдельной и независимой иерархии групп управления.
Проверить наличие активных иерархий можно следующей командой:
$ grep -E -- \\\<cgroup\\\> < /proc/mounts
cgroup /sys/fs/cgroup tmpfs rw,relatime 0 0
rg42 /sys/fs/cgroup/rg42 cgroup rw,relatime,memory 0 0
$
В данном примере, в системе активна единственная иерархия, управляемая через файлы директории /sys/fs/cgroup/rg42
, и позволяющая ограничивать использование памяти процессами, — на что указывает параметр memory
.
Создать такую иерархию можно командами mkdir
и mount
, подобно:
# mkdir -- /sys/fs/cgroup/rg42 # mount -t cgroup -o memory -- rg42 /sys/fs/cgroup/rg42 #
(При необходимости управлять или учитывать использование также и других ресурсов в рамках данной иерархии, после -o
следует перечислить все соответствующие подсистемы, например: -o blkio,cpuacct,memory
.)
Перед этим, однако, следует удостовериться в наличии директории /sys/fs/cgroup
и, при необходимости, создать ее, подобно:
# mount -t tmpfs -o size=64M -- cgroup /sys/fs/cgroup #
Отметим, что использование Cgroups для ограничения используемой памяти предполагает некоторые накладные расходы независимо от фактического использования данной функции. Чтобы их избежать, поддержка подсистемы memory
по-умолчанию отключена; ее включение требует явного указания параметра cgroup_enable=memory
в командной строке ядра. (Для изменения последней, в свою очередь, необходима перезагрузка системы.)
Информацию о поддерживаемых используемой сборкой ядра и доступных непосредственно в текущий момент параметрах Cgroups можно найти в файле /proc/cgroups
. Для решаемой задачи, в поле enabled
для подсистемы memory
должно присутствовать ненулевое значение.
Напомним также, что копия действующей командной строки ядра отражается в файл /proc/cmdline
(должен присутствовать параметр cgroup_enable=memory
); поддерживаемые типы ФС перечислены в файле /proc/filesystems
(должен присутствовать тип cgroup
.)
Примеры
Заморозка процессов
В cgroups существует возможность создания "специального изолятора" для процессов выполнение которых требуется немедленно приостановить. Подсистема, отвечающая за это, называется freezer
. Пользоваться ей очень просто:
# cgcreate -t roman:roman -g freezer:/perl
Запускаем процесс съедающий все ядра процессора в группе perl подсистемы freezer:
$ cgexec -g freezer:/perl perl -Mthreads -we 'print "My PID: $$\n";
for(1..4){threads->create(sub { while(1){} })} while(1){};'
My PID: 16604
Результат:
%Cpu0 :100.0 us
%Cpu1 : 93.5 us
%Cpu2 : 96.8 us
%Cpu3 :100.0 us
Останавливаем группу:
# cgset -r freezer.state=FROZEN /perl
Использование процессора сразу пришло в норму:
%Cpu0 : 6.5 us
%Cpu1 : 6.7 us
%Cpu2 : 9.7 us
%Cpu3 : 20.0 us
Проверяем состояние процесса:
$ ps -o pid,state,time,comm -p 16604
PID S TIME COMMAND
16604 D 00:11:03 perl
Процесс остановлен. Для "разморозки" нужно выполнить:
$ cgset -r freezer.state=THAWED /perl
Подсистема freezer выгодно отличается от обычного SIGSTOP следующими моментами:
- Поддерживается наследование на уровне процессов. Все дочерние процессы порождённые процессом находящимся в групее при необходимости будут заморожены одновременно с ним.
- Поддерживается иерархичность структуры. При заморозке родительской группы - одновременно будут "заморожены" все дочерние группы.
- Сигнал STOP не может быть перехвачен процессом которому он послан, но может быть замечен его родителем, либо процессом который ведёт его отладку (
ptrace
). Это может привести к неожиданным и негативным последствиям. Заморозка процесса в отличии от посылки SIGSTOP не может быть отслежена родителем/отладчиком, потому не приводит к таким последствиям.
Привязка к ядрам процессора
Создадим тестовую контрольную группу в иерархии cpu_control
:
# mkdir perl
# cd perl/
Удостоверимся, что в ней нет ни одной задачи:
# cat tasks
Проводим предварительну настройку группы, указываем доступные ядра процессора и узлы памяти:
# echo 3 > cpuset.cpus
# echo 0 > cpuset.mems
Настройка задаёт выполнение всех задач в группе на 3м (счёт с нуля) ядре процессора и на нулевом узле памяти.
Смотрим текущую загрузку всех ядер процессора (кусочек вывода top):
%Cpu0 : 3,3 us
%Cpu1 : 3,2 us
%Cpu2 : 6,7 us
%Cpu3 : 9,7 us
Запускаем тестовый однострочник гарантированно загружающий все ядра:
$ perl -Mthreads -e 'print "My PID: $$\n";
for(1..3){threads->create(sub{while(1){}})}; while(1){};'
My PID: 384
Все 4 ядра процессора ожидаемо загружены:
%Cpu0 :100,0 us
%Cpu1 : 96,8 us
%Cpu2 :100,0 us
%Cpu3 : 96,9 us
Помещаем тестовый процесс в созданную нами группу:
# echo 384 > cgroup.procs
Это действие автоматически добавляет процесс с PID 384
и все его потоки в контрольную группу.
Проверяем полученный результат:
# cat tasks
384
385
386
387
Все порождённые процессом потоки действительно добавились в файл tasks
автоматически. Загрузка CPU:
%Cpu0 : 3,1 us
%Cpu1 : 6,5 us
%Cpu2 : 0,0 us
%Cpu3 :100,0 us
Ограничение ресурсов
Ограничение процессорного времени. Создадим контрольную группу perl
утилитой cgcreate
:
# cgcreate -t roman:roman -g 'cpu:/perl'
Параметр -t
задаёт пользователя и группу, имеющих доступ к файлу tasks
, а значит способного управлять составом данной группы (см. man cgcreate
). По сути эта команда эквивалентна mkdir <root>/perl && chown roman:roman <root>/perl/tasks
. Важно отметить, что на права доступа к файлу cgroup.procs эта команда не влияет. Зададим выполнение тестовой контрольной группе на 3-ем ядре процессора:
# cgset -r cpuset.mems=0 /perl
# cgset -r cpuset.cpus=3 /perl
Опять запустим тестовый многопоточный однострочник, но сделаем это через утилиту cgexec
, тем самым сразу поместив его в нужную группу:
$ cgexec -g cpu:/perl perl -Mthreads -e 'print "My PID: $$\n";
for(1..3){threads->create(sub { while(1){} })} while(1){};'
My PID: 8209
Процесс с PID 8209
запустившись сразу попал в контрольную группу perl
, после чего породил 3 потока унаследовавших родительскую группу. Потому все все 4 потока (включая родительский) оказались в нашей контрольной группе, о чём свидетельствует загрузка только одного ядра CPU:
%Cpu0 : 3,3 us
%Cpu1 : 6,7 us
%Cpu2 : 0,0 us
%Cpu3 :100,0 us
Введём ограничение использования процессорного времени для группы perl
. Сперва просмотрим текущие лимиты:
# cgget -r cpu.cfs_period_us -r cpu.cfs_quota_us /perl
/perl:
cpu.cfs_period_us: 100000
cpu.cfs_quota_us: -1
Что бы разрешить задачам из нашей контрольной группы выполняться не более 60% времени в течение контрольного периода, нужно соответственно установить значение cpu.cfs_quota_us
в 60 мс:
# cgset -r cpu.cfs_quota_us=60000 /perl
Проверяем результат:
%Cpu0 : 4,1 us
%Cpu1 : 2,7 us
%Cpu2 : 5,0 us
%Cpu3 : 60,7 us
Ограничение по памяти. Создаём контрольную группу:
# cgcreate -t roman:roman -g memory:/perl
Задаём жёсткий лимит по потреблению физической памяти в 5 Мб:
# cgset -r memory.limit_in_bytes=$(( 1024 * 1024 * 5 )) /perl
Такой же лимит потреблению физической памяти + swap (у меня swap просто отключен):
# cgset -r memory.memsw.limit_in_bytes=$(( 1024 * 1024 * 5 )) /perl
Проверяем:
# cgget -r memory.limit_in_bytes -r memory.memsw.limit_in_bytes /perl
/perl:
memory.limit_in_bytes: 5242880
memory.memsw.limit_in_bytes: 5242880
Запускаем тестового "пожирателя памяти" через cgexec
, сразу же помещая его в контрольную группу и смотрим что будет (скрипт каждую секунду выделяет примерно 1 Мб памяти):
$ cgexec -g memory:/perl perl -e 'print "My PID: $$\n";
while(1){$x.="x"x(1024**2); print "Len: ",length($x),"\n"; sleep 1}'
My PID: 8882
Len: 1048576
Len: 2097152
Len: 3145728
Killed
Смотрим что в /var/log/messages
:
kernel: [347004.116334] perl invoked oom-killer: gfp_mask=0xd0, order=0, oom_score_adj=0
kernel: [347004.116345] perl cpuset=/ mems_allowed=0
kernel: [347004.116354] CPU: 0 PID: 8882 Comm: perl Not tainted 3.13.3 #5
/* Описание Hardware и большой Call Trace пропущены */
kernel: [347004.116519] Task in /perl killed as a result of limit of /perl
kernel: [347004.116525] memory: usage 5120kB, limit 5120kB, failcnt 79
kernel: [347004.116529] memory+swap: usage 5120kB, limit 5120kB, failcnt 0
/* Продолжение служебной информации относительно действий OOM Killer'а */
Достигнут ожидаемый результат, о чём во всех подробностях ядро рассказало в логе. Для проверки можно запустить два процесса параллельно в разных терминалах. В таком случае первый приблизившийся к суммарному пределу (т.е. примерно половине от доступной памяти) будет убит, после чего второй процесс продолжит выполняться пока в одиночку не использует всю доступную память после чего так же наступит неминуемая гибель.
ISSN 2542-0356
Следуй за Полисом
Оставайся в курсе последних событий
Лицензия
Если не указано иное, содержание этой страницы доступно по лицензии Creative Commons «Attribution-NonCommercial-NoDerivatives» 4.0, а примеры кода – по лицензии Apache 2.0. Подробнее см. Условия использования.