Выводы в самом конце - листайте :)
Тестовая среда: 2.6.32-042stab090.5, в контейнерах: Debian-7-x86_64 minimal. Процессор: 3.4 Ghz, число ядер: 8
На ноде выставлен режим максимальной частоты:
echo performance | tee /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor
На машине 8 ядер
Два контейнера - 101 и 102, им по дефалту выдано: CPUUNITS="1000" CPUS/CPULIMIT не использованы.
Изучение поведения шедулера OpenVZ:
1) Запускаем stress --cpu 8 на первом контейнере:
1 [||||||||||||||||||||||||||||||||||||||||||||||||||||||||100.0%]
2 [||||||||||||||||||||||||||||||||||||||||||||||||||||||||100.0%]
3 [||||||||||||||||||||||||||||||||||||||||||||||||||||||||100.0%]
4 [||||||||||||||||||||||||||||||||||||||||||||||||||||||||100.0%]
5 [||||||||||||||||||||||||||||||||||||||||||||||||||||||||100.0%]
6 [||||||||||||||||||||||||||||||||||||||||||||||||||||||||100.0%]
7 [||||||||||||||||||||||||||||||||||||||||||||||||||||||||100.0%]
8 [||||||||||||||||||||||||||||||||||||||||||||||||||||||||100.0%]
То есть машина получила все доступные ресурсы.
2) Включаем ту же самую команду на второй машине (то есть теперь она включена на обоих). Получаем тоже самое:
1 [||||||||||||||||||||||||||||||||||||||||||||||||||||||||100.0%]
2 [||||||||||||||||||||||||||||||||||||||||||||||||||||||||100.0%]
3 [||||||||||||||||||||||||||||||||||||||||||||||||||||||||100.0%]
4 [||||||||||||||||||||||||||||||||||||||||||||||||||||||||100.0%]
5 [||||||||||||||||||||||||||||||||||||||||||||||||||||||||100.0%]
6 [||||||||||||||||||||||||||||||||||||||||||||||||||||||||100.0%]
7 [||||||||||||||||||||||||||||||||||||||||||||||||||||||||100.0%]
8 [||||||||||||||||||||||||||||||||||||||||||||||||||||||||100.0%]
Но теперь каждый контейнер грузит отдельно ядро не более чем на 50%.
open_vestat показывает следующее:
101: cpu: 50.1 %
102: cpu: 49.8 %
3) Меняем лимиты на 100:
vzctl set 101 --cpuunits 100 --save; vzctl set 102 --cpuunits 100 --save;
И 10000:
vzctl set 101 --cpuunits 10000 --save; vzctl set 102 --cpuunits 10000 --save;
Ничего при этом не меняется! Все ок, важно лишь отношение этих величин
4) Ставим неравные лимиты:
vzctl set 101 --cpuunits 100 --save; vzctl set 102 --cpuunits 200 --save;
И сразу приоритет перешел к машине 101:
101: cpu: 37.2 %
102: cpu: 62.7 %
Схема нагрузки на процы ноды:
1 [||||||||||||||||||||||||||||||||||||||||||||||||||||||||100.0%]
2 [||||||||||||||||||||||||||||||||||||||||||||||||||||||||100.0%]
3 [||||||||||||||||||||||||||||||||||||||||||||||||||||||||100.0%]
4 [||||||||||||||||||||||||||||||||||||||||||||||||||||||||100.0%]
5 [||||||||||||||||||||||||||||||||||||||||||||||||||||||||100.0%]
6 [||||||||||||||||||||||||||||||||||||||||||||||||||||||||100.0%]
7 [||||||||||||||||||||||||||||||||||||||||||||||||||||||||100.0%]
8 [||||||||||||||||||||||||||||||||||||||||||||||||||||||||100.0%]
5) Теперь выдаем каждой машине ровно по 1 логическому ядру, тем самым ограничивая пиковую нагрузку на ядра:
vzctl set 101 --cpus 1 --cpuunits 1000 --save; vzctl set 102 --cpuunits 1000 --cpus 1 --save;
Картина мгновенно меняется - ограничение на число ядер, которое может выедать машина включилось в действие.
1 [ 0.0%]
2 [||||||||||||||||||||||||||||||||||||||||||||||||||||||||100.0%]
3 [ 0.0%]
4 [||||||||||||||||||||||||||||||||||||||||||||||||||||||||100.0%]
5 [ 0.0%]
6 [| 0.7%]
7 [ 0.0%]
8 [ 0.0%]
Разделение как и прежде идеальное:
101: cpu: 49.8 %
102: cpu: 49.8 %
Мегагерцы также выдаются как и ранее:
vzctl exec 101 "cat /proc/cpuinfo|grep Mhz -i"; vzctl exec 102 "cat /proc/cpuinfo|grep Mhz -i";
cpu MHz : 3401.000
cpu MHz : 3401.000
6) Теперь выдаем каждой машине по 2 ядра:
vzctl set 101 --cpus 2 --cpuunits 1000 --save; vzctl set 102 --cpuunits 1000 --cpus 2 --save;
Разделение продолжает быть идеальным:
101: cpu: 49.8 %
102: cpu: 49.8 %
Картина нагрузки следующая:
1 [||||||||||||||||||||||||||||||||||||||||||||||||||||||||100.0%]
2 [||||||||||||||||||||||||||||||||||||||||||||||||||||||||100.0%]
3 [||||||||||||||||||||||||||||||||||||||||||||||||||||||||100.0%]
4 [|| 1.3%]
5 [ 0.0%]
6 [ 0.0%]
7 [ 0.0%]
8 [|||||||||||||||||||||||||||||||||||||||||||||||||||||||||99.3%]
7) Вводим параметр cpulimit.
Для машины с 1 логическим процессором поидее поменяться нчиего не должно.
vzctl set 101 --cpus 1 --cpulimit 100 --save; vzctl set 102 --cpulimit 100 --cpus 1 --save;
Ну в общем ничего и не поменялось:
1 [||||||||||||||||||||||||||||||||||||||||||||||||||||||||100.0%]
2 [ 0.0%]
3 [||||||||||||||||||||||||||||||||||||||||||||||||||||||||100.0%]
4 [ 0.0%]
5 [ 0.0%]
6 [ 0.0%]
7 [ 0.0%]
8 [| 0.7%]
8) Срезаем пиковую нагрузку до 50:
vzctl set 101 --cpus 1 --cpulimit 50 --save; vzctl set 102 --cpulimit 50 --cpus 1 --save;
Несмотря на наличие доступных ресурсов - машины обрезало строго по половине ядра.
1 [||||||||||||||||||||||||||||||| 49.3%]
2 [ 0.0%]
3 [||||||||||||||||||||||||||||||| 49.7%]
4 [ 0.0%]
5 [ 0.0%]
6 [ 0.0%]
7 [ 0.0%]
8 [| 1.3%]
И мегагерцы поменялись:
vzctl exec 101 "cat /proc/cpuinfo|grep Mhz -i"; vzctl exec 102 "cat /proc/cpuinfo|grep Mhz -i";
cpu MHz : 1700.500
cpu MHz : 1700.500
9) Срезаем нагрузку до 10:
vzctl set 101 --cpus 1 --cpulimit 10 --save; vzctl set 102 --cpulimit 10 --cpus 1 --save;
1 [||||||| 9.9%]
5 [ 0.0%]
2 [ 0.0%]
6 [ 0.0%]
3 [ 0.0%]
7 [ 0.0%]
4 [||||||| 10.5%]
8 [| 0.7%]
И тут все работает идеально.
10) отключаем один контейнер, второй оставляем с ограничнием в 10%:
1 [||||||| 9.9%]
5 [|| 1.3%]
2 [ 0.0%]
6 [ 0.0%]
3 [ 0.0%]
7 [ 0.0%]
4 [ 0.0%]
8 [ 0.0%]
И тут все ок!
11) Что будет если сделать --cpulimit 200 при выданном одном процессоре для контейнера? Да ничего плохого - больше 100% он не съест :)
1 [ 0.0%]
5 [| 0.7%]
2 [ 0.0%]
6 [ 0.0%]
3 [||||||||||||||||||||||||||||||||||||||||||||||||||||||||100.0%]
7 [ 0.0%]
4 [||||||||||||||||||||||||||||||||||||||||||||||||||||||||100.0%]
8 [ 0.0%]
12) Выдаем двум контейнера по два проца, но при этом жестко ограничиваем нагрузку 100% одного ядра:
vzctl set 101 --cpus 2 --cpulimit 100 --save; vzctl set 102 --cpulimit 100 --cpus 2 --save;
В итоге каждая машина получит по 2 ядра, но по 1.7 Ghz (половина мощности ядра аппаратной ноды):
vzctl exec 101 "cat /proc/cpuinfo|grep Mhz -i"; vzctl exec 102 "cat /proc/cpuinfo|grep Mhz -i";
cpu MHz : 1700.500
cpu MHz : 1700.500
cpu MHz : 1700.500
cpu MHz : 1700.500
Схема нагрузки будет вот такая:
1 [ 0.0%]
5 [| 0.7%]
2 [ 0.0%]
6 [ 0.0%]
3 [||||||||||||||||||||||||||||||||||||||||||||||||||||||||100.0%]
7 [ 0.0%]
4 [||||||||||||||||||||||||||||||||||||||||||||||||||||||||100.0%]
8 [ 0.0%]
13) Выдаем полную нагрузку по обоим ядрам:
vzctl set 101 --cpus 2 --cpulimit 200 --save; vzctl set 102 --cpulimit 200 --cpus 2 --save;
Схема нагрузки меняется мгновенно:
1 [||||||||||||||||||||||||||||||||||||||||||||||||||||||||100.0%]
5 [|| 1.3%]
2 [||||||||||||||||||||||||||||||||||||||||||||||||||||||||100.0%]
6 [ 0.0%]
3 [||||||||||||||||||||||||||||||||||||||||||||||||||||||||100.0%]
7 [ 0.0%]
4 [|||||||||||||||||||||||||||||||||||||||||||||||||||||||||99.3%]
8 [ 0.0%]
14) А если дать одному контейнеру приоритет в два раза:
vzctl set 101 --cpus 2 --cpulimit 200 --cpuunits 100 --save; vzctl set 102 --cpulimit 200 --cpus 2 --cpuunits 200 --save;
То ничего не поменяется, так как нету конкуренции за ресурс - у ноды достаточно ресурсов.
15) Устроим небольшой оверселл по ядрам:
set 101 --cpus 6 --cpulimit 600 --cpuunits 100 --save; vzctl set 102 --cpulimit 600 --cpus 6 --cpuunits 200 --save;
1 [||||||||||||||||||||||||||||||||||||||||||||||||||||||||100.0%]
5 [||||||||||||||||||||||||||||||||||||||||||||||||||||||||100.0%]
2 [||||||||||||||||||||||||||||||||||||||||||||||||||||||||100.0%]
6 [||||||||||||||||||||||||||||||||||||||||||||||||||||||||100.0%]
3 [||||||||||||||||||||||||||||||||||||||||||||||||||||||||100.0%]
7 [|||||||||||||||||||||||||||||||||||||||||||||||||||||||||99.3%]
4 [||||||||||||||||||||||||||||||||||||||||||||||||||||||||100.0%]
8 [||||||||||||||||||||||||||||||||||||||||||||||||||||||||100.0%]
И при этом процессор поделится 1 к 2:
102: cpu: 66.5 %
101: cpu: 33.3 %
16) Теперь рассмотрим интересный кейс - что будет какой контейнер позволит получить больше ресурсов. Контейнер с полным доступом к одному ядру или контейнер с двумя ядрами, но с лимитом суммарной нагрузки 50я % на каждое.
vzctl set 101 --cpus 2 --cpulimit 100 --cpuunits 100 --save; vzctl set 102 --cpulimit 100 --cpus 1 --cpuunits 100 --save;
1 [||||||||||||||||||||||||||||||||||||||||||||||||||||||||100.0%]
5 [ 0.0%]
2 [ 0.0%]
6 [ 0.0%]
3 [ 0.0%]
7 [||||||||||||||||||||||||||||||||||||||||||||||||||||||||100.0%]
4 [ 0.0%]
8 [|| 1.3%]
Разделение при этом будет следующее:
102: cpu: 49.8 %
101: cpu: 49.8 %
Но схема с полным доступом к одному ядру будет предпочтительнее при использовании ПО, которое плохо распараллеливается.
17) Рассмотрим кейс, когда контейнер с большим числом ядер получает меньший вес cpuunits, чем контейнер с меньшим числом ядер
vzctl set 101 --cpus 10 --cpulimit 1000 --cpuunits 100 --save; vzctl set 102 --cpulimit 500 --cpus 5 --cpuunits 200 --save;
Получим полную прогрузку машины:
1 [|||||||||||||||||||||||||||||||||||||||||||||||||||||||||97.4%]
5 [||||||||||||||||||||||||||||||||||||||||||||||||||||||||100.0%]
2 [||||||||||||||||||||||||||||||||||||||||||||||||||||||||100.0%]
6 [||||||||||||||||||||||||||||||||||||||||||||||||||||||||100.0%]
3 [||||||||||||||||||||||||||||||||||||||||||||||||||||||||100.0%]
7 [||||||||||||||||||||||||||||||||||||||||||||||||||||||||100.0%]
4 [||||||||||||||||||||||||||||||||||||||||||||||||||||||||100.0%]
8 [||||||||||||||||||||||||||||||||||||||||||||||||||||||||100.0%]
Приоритет возьмет свое и вторая машина получи ресурсов больше:
101: cpu: 43.4 %
102: cpu: 56.4 %
Но тут нельзя сказать, что получит она ресурсов вдвое больше.
18) Этот пункт я добавил уже после публикации статьи, он исследует кейс поведения, как поведут себя контейнеры с одинаковым CPUUNITS, но разным числом процессоров, выданных контейнеру:
vzctl set 101 --cpus 8 --cpulimit 800 --cpuunits 100 --save; vzctl set 102 --cpulimit 200 --cpus 2 --cpuunits 100 --save;
А поведут они себя вот так (для чистоты эксперимента я запустил по 16 потоков утилиты stress в контейнерах):
101: cpu: 75.0 %
102: cpu: 24.8 %
То есть, контейнер, которому дали большую процессорную мощность получит приоритет в 3-4 раза, как и ожидается. Но стоит обратить внимание, что очень сложно выдать приоритет контейнеру, у которого меньше ресурсов даже силами CPUUNITS.
Иными словами, довольно сложно дать приоритет контейнеру с 1 ядром, когда на ноде есть контейнеры с большим числом выданных ядер.
CPUS
CPUS - задает число логичеcких процессоров, которые доступны контейнеру. Стоит понимать, что если выдать контейнеру 1 ядро, то его производительность будет жестко ограничена мощностью 1 ядра хост-сервера. Даже если на хост-серверер будут свободные ядра - контейнер их использовать не сможет. Это стоит учитывать всегда. Если данный параметр не указан или установлен в нуль - это означает, что контейнер может использовать все ядра хост-сервера.
Также стоит понимать, что логический процессор - это абстракция, в реальности процессы каждый раз выполняются на том ядре хост-сервера, у которого есть ресурсы.
Также параметр CPUS имеет важное значение при использовании параметра CPULIMIT.
CPULIMIT
CPULIMIT - задает максимальную суммарную нагрузку ядер контейнера. Максимально возможная нагрузка контейнера определяется так: N * 100, где N = числу ядер контейнера. То есть, если у контейнера 8 логических процессоров, то максимальное значение CPULIMIT может достигать 800.
В случае 1 процессора, выданного контейнеру, все просто - 50% означает 50% нагрузки на единственное ядро, то есть максимальная нагрузка на процессор ограничивается половиной ядра хост-сервера.
Если процессоров несколько, то все резко усложняется. Например, если задать CPULIMIT=100 для контейнера с двумя процессорами, то контейнер сможет нагружать каждое из выданных ему ядер не более чем на 50%.
Если же нужно полностью дать контейнеру, скажем, 4 ядра, то ему нужно выдать (4*100) CPULIMIT 400.
Именно этот параметр отображается внутри контейнера как частота виртуального процессора (Mhz /proc/cpuinfo). Частота рассчитывается вот так: HWN_FREQ * CPULIMIT / (100 * N_CPU), где N_CPU - число процессоров, выданных контейнеру. А HWN_FREQ - тактовая максимальная частота 1 ядра ноды. Например, в случае, когда у контейнера 2 ядра и CPULIMIT=100 мы получим: 1/2 HWN_FREQ (1.7 Ghz для моей тестовой ноды с процессором 3.4Ghz)
Лимит этот жесткий, то есть если на аппаратной ноде есть ресурсы, контейнер их не получит. Стоит обращать на это внимание!
CPUUNITS
CPUUNITS - задает отношение выделяемого процессорного времени одному контейнеру к другому. То есть, если задать для двух контейнеров 100 и 200, то второй получит вдвое больше ресурсов. CPUUNITS идеально работает когда контейнеру выдано 1 ядро. Но в случае многопроцессорных контейнеров и серьезной нагрузки на аппаратную ноду эти отношения перестают выполняться честно и второй контейнер будет получать больше ресурсов, чем первый, но не в два раза. Это объясняется особенности шедулера CFQ. Поэтому с разделением ресурсов между контейнерами с большим числом логических процессоров есть довольно серьезные проблемы.
Кого заинтересовала тулза open_vestat - могу
поделиться, это скрипт обвязка cgroup на Perl, считает потребление cpu/blkio.time/blkio.sectors и отрисовывает топ10 грузящих контейнеров.