FastNetMon

Thursday, 14 August 2014

CPULIMIT, CPUUNITS и CPUS - детальное тестирование и описание параметров управляющих нагрузкой на CPU со стороне контейнеров OpenVZ

Выводы в самом конце - листайте :)

Тестовая среда: 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 грузящих контейнеров.

5 comments :

  1. >> Кого заинтересовала тулза open_vestat - могу поделиться
    Был бы признателен

    ReplyDelete
    Replies
    1. Прошу https://gist.github.com/pavel-odintsov/75ec993cf7ac8fdd0d04 :)

      Delete
  2. Отличный скрипт, только подключивает
    Argument "17899618:48" isn't numeric in subtraction (-) at ./open_vestat.pl line 108.
    Argument "23688:64" isn't numeric in subtraction (-) at ./open_vestat.pl line 121.

    ReplyDelete
    Replies
    1. В функции read_file_contents заменить строчку
      $data .= $_;

      на

      $data .= $_ . " ";

      Delete

Note: only a member of this blog may post a comment.