OpenVZ часть 6. Управление ресурсами контейнеров

OpenVZ. Все статьи цикла

Ресурсы предоставляемые гостевым окружениям могут быть нескольких видов;

гарантированные — это тот объем ресурсов который контейнер получит в любых условиях.

негарантированные — это свободные ресурсы хоста которые могут быть отданы контейнеру если он в них нуждается. Но, они так же могут быть отобраны в любой момент.

лимитированные — это максимальная планка за которую контейнер не может выйти.

Лимитирование ресурсов защищает контейнеры друг от друга а дополнительное выделение свободных ресурсов повышает производительность активных контейнеров в моменты простоя хоста.

Лимиты CPU

Тут есть три параметра; 

cpulimit — это значение определяет максимум в процентах от всей мощности CPU. Например, значение 10 от мощности процессора в 2.0 Ghz будет означать 200 Mhz. 

cpuunits — эта величина является гарантированной мощностью. Определяется она в неких виртуальных единицах(юнитах) рассчитываемых OpenVZ по особой формуле. Посмотреть сколько этих юнитов доступно на вашем хосте можно следующим образом;

# vzcpucheck

Current CPU utilization: 1000 Power of the node: 138295

1000 это то, что хост система резервирует за собой а 138295 это доступное количество ресурсов. Если это число(138295) разделить на 100 то получим один процент мощности CPU в юнитах.

cpus — это значение определяет количество ядер на которых будут обрабатываться контейнер.

Каждый из параметров может использоваться по отдельности или вместе для более точного описания пределов.

Детальнее объясню на примере;

# vzctl set 101 --cpus 1 --cpuunits 6914 --cpulimit 10 --save

Во первых мы презентуем нашему контейнеру одно ядро (—cpus 1), от этого ядра, гарантированно будет выдаваться 5%(—cpuunits 6914) мощности, а максимальная планка определена 10% (—cpulimit 10). Зазор между гарантированными 5% и максимальными 10% является не гарантируемыми ресурсами которые будут выданы только в случае не полной загрузки процессора.

Предоставленные в этом примере расчеты, справедливы для моего конкретного двух ядерного процессора с частотой 2794 Mhz и должны быть пересчитаны исходя из ваших мощностей.

Стоит отметить, что эти значения записываются в конфигурационный файл контейнера и следуют за ним, в случае миграции с хоста на хост. Соответственно, если конфигурация процессоров отличается то и производительность контейнеров будет отличаться.

Лимиты памяти

Начиная с RHEL 6 и производных, в OpenVZ были внесены значительные изменения в работе с памятью. На смену старым, требующим расчетов и не эффективным средствам, пришел механизм vSwap. Теперь существует только два параметра которые значительно облегчают жизнь.

ram — задает гарантированный объем оперативной памяти.

swap — это дополнительный объем оперативной памяти, который будет имулировать настойщий файл подкачки. Для этого будет выполненно искуственное замедление памяти.

Поясню на примере;

# vzctl set 101 --ram 512M --swap 1G --save

Здесь, мы гарантируем контейнеру 512 Мб физической памяти. при ее нехватке его страницы будут помещаться в vSwap максимальный объем которого так же 512 Мб(1G — 512M). vSwap это негарантированный ресурс. Он будет выделена только если на хосте будет свободная память. Реальный, дисковый файл подкачки будет задействован только в случае исчерпания всей физической памяти.

Данное решение является более производительным и безопасным. Во первых не используется диск(в идеале) а во вторых при превышении порога выделенной памяти(ram), не происходит отстреливание процессов как это делается в LXC.

Дисковые операции

Как ограничить объем дискового пространства уже понятно из раздела про снимки состояния. Здесь же мы поговорим о приоритетах и дисковых операциях ввода\вывода(i\o).

Приоритеты(ioprio) — доступны с версии ядра 2.6.18-028stab021. Позволяют распределить доступ к диску, в случае полной загрузки между несколькими контейнерами. Всего их 7(от 0 до 7). Соответственно 0 — наименьший приоритет а 7 — наивысший. По умолчанию, все контейнеры имеют приоритет 4. Например, если первый контейнер имеет приоритет 0 а второй 7 и нагрузка на дисковую подсистему будет 100%, то второму экземпляру ОС будет позволено обращаться к диску, примерно в три раза больше чем первому.

Установить приоритет можно следующим образом;

# vzctl set 101 --ioprio 0 --save

Более точным и предсказуемым лимитом является ограничение пропускной способности диска для конкретного контейнера(iolimit). Тут все просто;

# vzctl set 101 --iolimit 5M --save

--iolimit 5M  означает, что максимальная, суммарная скорость чтения и записи с диска и на диск будет ограниченна 5 МБ в секунду.

Вот что показал тест до установки лимита;

# time -p dd if=/var/test.iso of=/dev/null bs=1024k

563+1 records in 563+1 records out 590448640 bytes (590 MB) copied, 45.6611 s, 12.9 MB/s real 45.74 user 0.00 sys 3.38

Этот же тест после установки лимита в 5 Мб;

# time -p dd if=/var/xen2.iso of=/dev/null bs=1024k

563+1 records in 563+1 records out 590448640 bytes (590 MB) copied, 108.484 s, 5.4 MB/s real 108.50 user 0.00 sys 1.40

Тест показал среднюю скорость в 5.4 потому, что ограничение срабатывает не сразу. Дело в том, что первые блоки пишутся с максимальной скоростью и приблизительно за несколько секунд упадет до установленного лимита. По этому, средний показатель немного выше.

Ну, и на последок, чтоб уже совсем зажать дисковую подсистему контейнера можно установить лимит на число операций ввода и вывода в секунду — IOps.

# vzctl set 101 --iopslimit 10 --save

И вот результат теста;

# time -p dd if=/var/xen2.iso of=/dev/null bs=1024k

563+1 records in 563+1 records out 590448640 bytes (590 MB) copied, 848.602 s, 696 kB/s real 849.19 user 0.00 sys 3.58

Сетевой ввод\вывод

Взаимодействие контейнера с сетью проходит через сетевой стек хост системы и по этой причине, регулировка полосы пропускания и а так же приоритизация трафика производится с помощью его механизмов. В Linux для этих целей используется tc(Traffic Control).

Управление очередями трафика с помощью tc довольна сложная тема которую не как не покрыть за 15 минут одним абзацем. Существует множество многостраничных документов посвященных этому механизму. Я покажу как это сделать на практике в частном случае OpenVZ контейнеров и постараюсь дать минимальные теоретические основы для понимания того как это работает.

Далее, будет использоваться терминология tc.

Прежде чем пакеты попадают в «трубу» под названием в сетевой интерфейс они обрабатываются с помощью специальных дисциплин(qdisc). Всего их несколько. Каждая из них описывает особый алгоритм обработки пакетов. Один интерфейс, может обрабатываться только одной дисциплиной.

Посмотреть какие дисциплины назначены на ваших интерфейсах можно следующим образом;

# tc qdisc

qdisc pfifo_fast 0: dev eth0 root refcnt 2 bands 3 priomap 1 2 2 2 1 2 0 0 1 1 1 1 1 1 1 1
qdisc pfifo_fast 0: dev eth1 root refcnt 2 bands 3 priomap 1 2 2 2 1 2 0 0 1 1 1 1 1 1 1 1

pfifo_fast — это дисциплина по умолчанию. Работает по принципу FIFO — первым пришел, первым ушел.

Нам же необходимо шейпить трафик по этому будет использоваться дисциплина cbq (Class Based Queueing) как для входящего трафика так и для исходящего.

Лимит на входящий трафик.

Прежде чем попасть в контейнер, трафик проходит через виртуальный интерфейс хост системы venet0. По этому, настраивать дисциплину cbq мы будем на этом интерфейсе.

# tc qdisc add dev venet0 root handle 1: cbq avpkt 1000 bandwidth 1000mbit

Здесь qdisc add dev venet0 означает что мы устанавливаем дисциплину для интерфейса venet0 аroot handle 1: что это корневая дисциплина. Далее указываются ее параметры avpkt 1000(средний размер пакетов) и bandwidth 1000mbit(реальная ширина канала). Параметры avpkt и bandwidth необходимо указывать для того что бы cbq могла корректно строить очереди пакетов.

Сама по себе дисциплина определяет алгоритм который будет применяться к пакетам. Теперь необходимо определить классы. Класс — это своего рода канал с определенными характеристиками. Таких каналов можно описывать множество для различных нужд.

# tc class add dev venet0 parent 1: classid 1:1 cbq rate 10mbit allot 1500 prio 5 bounded isolated

class add dev venet0 означает, что мы описываем новый класс в рамках корневой дисциплины parent 1:. classid 1:1 это идентификатор класса, следующий должен иметь classid 1:2. rate 10mbit задает максимальную пропускную способность. allot 1500 это объем данных который можно отправить с этого канала за раз. prio 5 это приоритет. Чем значение ниже, тем выше приоритет обработки трафика этого класса. bounded означает, что занимать полосу других классов нельзя а
isolated заприщает заем полосы у этого класса. То есть все жестко. Канал гарантирован.

Теперь когда канал описан, необходимо создать фильтр который опишет, какие пакеты должны попадать в этот канал.

# tc filter add dev venet0 parent 1: protocol ip prio 16 u32 match ip dst 192.168.0.15 flowid 1:1

Тут мы указываем ip нашего контейнера, входящий трафик которого мы хотим пустить по описанному выше каналу. prio 16 это уже приоритезация внутри канала.

В общем правила для ограничения входящего канала, для одного контейнера выглядит следующим образом;

# tc qdisc add dev venet0 root handle 1: cbq avpkt 1000 bandwidth 1000mbit
# tc class add dev venet0 parent 1: classid 1:1 cbq rate 10mbit allot 1500 prio 5 bounded isolated
# tc filter add dev venet0 parent 1: protocol ip prio 16 u32 match ip dst 192.168.0.15 flowid 1:1

Вот так вот!

Лимит на исходящий трафик

Для исходящего трафика все примерно так же, только интерфейс нужно использовать физический ethX или мост brX

# tc qdisc del dev br0 root
# tc qdisc add dev br0 root handle 1: cbq avpkt 1000 bandwidth 100mbit
# tc class add dev br0 parent 1: classid 1:1 cbq rate 1mbit allot 1500 prio 5 bounded isolated
# tc filter add dev br0 parent 1: protocol ip prio 16 u32 match ip src 192.168.0.15 flowid 1:1

Официальную инструкцию можно найти на оф. сайте. А детальный перевод документации по tc, тут