Linux QOS:Тестирование различных вариантов управленя траффиком
Linux Shaper
Эта тема всегда вызывала у меня некоторые затруднения и потому я решил систематизировать свои знания.
Описание лаборатории
Для тестирования я использую лабу из 3 виртуальных машин, виртуалбокс. Базовая ОС - Мак Ос
|-------| |--------| |-------| |node-1 |-eth0-----eth1-|router0 |-eth2-----eth0-|node-2 | |-------| |--------| |-------| | eth0 | HOST
node-1
- 192.168.1.2
- Алиасами добавлены адреса 192.168.1.3-100
node-1
- 192.168.2.2
- Алиасами добавлены адреса 192.168.2.3-100
Router0
- eth1: 192.168.1.1
- eth2: 192.168.2.1
eth0 - служит для доступа к лабе и никак в тестах не участвует.
Все политики клсассы и прочее - только на транзитных интерфейсах eth1 и eth2
Базовое тестирование-ограничение скорости
Так как планируется большое число тестов и большое число данных, то первым делом нужно подготовить автоматизацию. Для генерации трафика я буду использовать iperf с переменным числом потоков, для сбора статистики - скрипт на питоне. Для визуализации - GnuPlot
Как тестируем
Тест проходит следующим образом:
- на node-1 запущены множество экземпляров iperf в режиме сервера
BASE_PORT=5000 for I in `seq 2 100` do let PORT=BASE_PORT+I echo ${PORT} iperf -s -l 32k -w 512k -u -p ${PORT} -D iperf -s -l 32k -w 512k -p ${PORT} -D done
- на node-2 запускается iperf в 30 потоков, каждый - на другой порт и на другой IP, для того что бы в дальнейших тестах проверять классы трафика и
для более равномерного распределения. sleep 30 - нужен для того что бы успеть запустить счетчики на ноде 1
В случае когда условия тестирования отличаются - я буду указывать отдельно
#!/bin/bash sleep 30 BASE_PORT=5000 for I in `seq 2 ${1}` do . IP=192.168.1.${I} let PORT=BASE_PORT+I #echo iperf -c ${IP} -b 100000M -i 1 -l 60K -t 120 -p ${PORT} -D iperf -c ${IP} -b 100000M -i 1 -l 60K -t 120 -p ${PORT} -D iperf -c ${IP} -b 100000M -i 1 -l 60K -t 120 -p ${PORT} -u -D done
- В процессе работы собираем траффик наколенном скриптом на питоне и складываем в лог, потом рисуем график используя GnuPLOT
import time import datetime as dt import sys stop_time=time.time()+300 iface='eth0' sleep_time=0.5 tx_data_file = open('/sys/class/net/'+iface+'/statistics/tx_bytes', 'r') rx_data_file = open('/sys/class/net/'+iface+'/statistics/rx_bytes', 'r') res_file = open(str(sys.argv[1]),'w') for l in rx_data_file: in_bytes_prev=int(l) for l in tx_data_file: out_bytes_prev=int(l) while True: try: rx_data_file.seek(0) tx_data_file.seek(0) for l in rx_data_file: in_bytes=int(l) for l in tx_data_file: out_bytes=int(l) in_bytes_diff=in_bytes-in_bytes_prev out_bytes_diff=out_bytes-out_bytes_prev in_bytes_prev=in_bytes out_bytes_prev=out_bytes time.sleep(sleep_time) res_file.write(str(time.time())+" "+str((in_bytes_diff)*8/sleep_time)+" "+str((out_bytes_diff)*8/sleep_time)+"\n") print str(time.time())+" "+str(in_bytes_prev)+" "+str(in_bytes)+" "+str((in_bytes_diff)*8)+" "+str(out_bytes_prev)+" "+str(out_bytes)+" "+str(out_bytes_diff)+" IN="+str((in_bytes_diff)*8/1024/1024/sleep_time)+" OUT="+str((out_bytes_diff)*8/1024/1024/sleep_time) if time.time()>stop_time: break except: break res_file.close()
Скрипт для GnuPlot - заготовка из кототорой на лету формируем awk то что надо - подставляя входной файл, имя графика и т.д.
#!/usr/bin/gnuplot -persist set terminal png size 1400,600 # Размер и формат графика. set output "/root/no_shaper.png" set title "no_shaper" # Заголовок set nokey # не знаю. Уточнить. set key top left # Расположение подписи set key box # Оформление (в рамке) подписи к графикам set xlabel "Date" # Метка по оси Х set xdata time # Описать что по оси Х время (формат ниже) set timefmt "%s" # Формат даты соответвует формату gnu date # В моем случае был удобен такой формат. set ylabel "Traffic" #set format y '%.0s%cB' set format y '%s' plot \ "/root/no_shaper.log" using 1:2 with lines title "IN, bit/s" smooth bezier, \ "/root/no_shaper.log" using 1:2 with lines title "IN, bit/s" , \ "/root/no_shaper.log" using 1:3 with lines title "OUT, bit/s" smooth bezier, \ "/root/no_shaper.log" using 1:3 with lines title "OUT, bit/s" [root@node-1 ~]# cat plot.sh #!/usr/bin/gnuplot -persist set terminal png size 1400,600 # Размер и формат графика. set output "___OUTPUT_FILE___" set title "___TITLE_OF_PLOT___" # Заголовок set nokey # не знаю. Уточнить. set key top left # Расположение подписи set key box # Оформление (в рамке) подписи к графикам set xlabel "Date" # Метка по оси Х set xdata time # Описать что по оси Х время (формат ниже) set timefmt "%s" # Формат даты соответвует формату gnu date # В моем случае был удобен такой формат. set ylabel "Traffic" #set format y '%.0s%cB' set format y '%s' plot \ "___INPUT_FILE___" using 1:2 with lines title "IN, bit/s" smooth bezier, \ "___INPUT_FILE___" using 1:2 with lines title "IN, bit/s" , \ "___INPUT_FILE___" using 1:3 with lines title "OUT, bit/s" smooth bezier, \ "___INPUT_FILE___" using 1:3 with lines title "OUT, bit/s"
Точка отсчета - без шейпера
Для того что бы получить некоторую точку отсчета (учитывая что лаба - это ВМки) делаю первый прогон без ограничения трафика.
Бесклассовые дисциплины
Я не буду рассматривать в этом разделе дисциплины без возможности ограничения трафика - по той причине что на неогруженных интерфейсах это не имеет практического смысла
RED
Отличная документация: "Подробнее с RED можно ознакомится в исходниках ядра"
TBF
Про TBF много пишут но я никогда ее не использовал. Соответственно - нужно тестировать.
Как и все бесклассовые дисциплины крепиться или к листу (leaf) или к интерфейсу.
Об этом напишу подробнее ниже.
Напомню что eth1 - интерфейс в сторону node-1
Удаляем текущую дисциплину
tc qdis del dev eth1 root
Убеждаемся что удалена (т е стоит по умолчанию pfifo_fast)
tc qdisc s dev eth1 qdisc pfifo_fast 0: root refcnt 2 bands 3 priomap 1 2 2 2 1 2 0 0 1 1 1 1 1 1 1 1 qdisc ingress ffff: parent ffff:fff1 ---------------- qdisc ingress ffff: parent ffff:fff1 ----------------
Второй раз удалить дисциплину по-умолчанию нельзя:
tc qdis del dev eth1 root RTNETLINK answers: No such file or directory
Добавляем дисциплину tbf:
tc qdisc add dev eth1 root tbf rate 180kbit latency 20ms buffer 1540
Проверяем:
tc qdisc s dev eth1 qdisc tbf 8002: root refcnt 2 rate 180000bit burst 1539b lat 20.0ms qdisc ingress ffff: parent ffff:fff1 ----------------
Описание параметров
После длительного гугления я так и не нашел внятного описания параметров. Потому попробую процитировать
- rate — ограничение скорости
- latency — максимальный "возраст"пакета в очереди
- burst — размер буфера. В байтах. (burst/buffer/maxburst - альтернативные названия что вносит некоторую путаницу)
Вот такое описание я нашел на Хабре, красным - мои вопросы
Дисциплина ТBF для своей работы использует механизм токенов. Токены генерируются системой с постоянной какой??? скоростью и помещаются в буфер(bucket). За каждый токен, вышедший из буфера с интерфейса уходит IP-пакет.А как насчет размера пакета? скорость передачи зависит от размера пакета
Если скорости передачи пакетов и генерации токенов совпадает, процесс передачи данных идет без задержки.
Если скорость передачи пакетов меньше чем скорость токенов, последние начинают накапливаться в буфере и затем могут использоваться для кратковременной передачи данных на скорости выше пороговой.
Если скорость передачи пакетов выше — токенов начинает не хватать. Пакеты данных ожидают новых токенов некоторое время Какое? думаю - пока не заполнится очередь заданая limit/latency, а затем начинают отбрасываться.
Прямым следствием из этого я вижу следующее - если задать бурст ОЧЕНЬ большим то можно ожидать всплеска траффика в начале теста.
Впрочем немного погуглив - найдено другое описание алгоритма
http://linux.die.net/man/8/tc-tbf
Результаты
Результаты (для меня) достаточно неожиданные
- Таки да - достаточно четко выставлена нужная скорость
- Явных потерь пингов я не наблюдаю
- Рост задержек явно не соответствует обещанным 20мс но и не запредельный
64 bytes from node-2 (192.168.2.2): icmp_seq=19 ttl=63 time=84.5 ms 64 bytes from node-2 (192.168.2.2): icmp_seq=20 ttl=63 time=84.6 ms 64 bytes from node-2 (192.168.2.2): icmp_seq=21 ttl=63 time=82.2 ms 64 bytes from node-2 (192.168.2.2): icmp_seq=28 ttl=63 time=86.0 ms 64 bytes from node-2 (192.168.2.2): icmp_seq=30 ttl=63 time=84.3 ms 64 bytes from node-2 (192.168.2.2): icmp_seq=37 ttl=63 time=84.1 ms
Epic Fail
tc qdisc replace dev eth1 root tbf rate 40Mbit burst 159900000000000000 latency 10s
Kernel panic
Судя по моим подсчетам - burst 159900000000000000 (который пересчитывается в байты при создании правила) израсходовал 1100Мб при том что на ВМке всего1 гиг - предположительная причина - перерасход памяти
Проверка предположения про burst
Как я писал выше, предположение что установка огромного burst вызовет всплеск трафика в начале теста требует подтверждения: настраиваю следующим образор
tc qdisc replace dev eth1 root tbf rate 40Mbit burst 159900000 latency 10s
Как можно видеть по графику - предположение полностью подтверждается, наблюдаем явный пик трафика и потом переход к "нормальной скорости"
Проверка влияния burst и latency на поведение алгоритма
Попробуем собрать более-менее полную статистику по burst и latency. Для этого я в цикле прогоняю тесты при этом в каждом следующем тесте я делаю burst=burst*N что бы обеспечить более-менее приемлемую скорость перебора. Аналогично - для latency. Все параметры теста вынесены в заголовок графика.
Выводы
Судя по тому что я вижу - TBF неплохо подходит для простых случаев; хорошо масштабируемый до скоростей порядка 100Мбит.
Задирание буферов до запредельных значений смысла не имеет как и увеличение latency.
Насколько я могу судить алгоритм достаточно точный - снимаемые с ноды значения соответвуют выставляемым на роутере.
Классовые дисциплины
Для простых случаев можно использовать и классовые дисциплины - при этом конструкции будут достаточно простые и понятные.
HTB
По-поводу HTB написано очень много - но вопросы с пониманием возникают постоянно. Потому я попробую собрать наиболее подробное и понятное описание из разных источников - такая компиляция.
Вот неплохое описание:
The Hierarchical Token Bucket (HTB) is an improved version of TBF that introduces the notion of classes. Each class is, in fact, a TBF-like qdisc, and classes are linked together as a tree, with a root and leaves. HTB introduces a number of features to improved the management of bandwidth, such as a the priority between classes, a way to borrow bandwidth from another class, or the possibility to plug another qdisc as an exit point (a SFQ for example).
Т.е. для начала считаем что HTB - это расширенная TBF с возможностью займа полосы и подключения к выходу других дисциплин.
Пробная настройка
У HTB много малопонятных параметров - я постараюсь описать их по возможности подробно.
# tc qdics add dev eth0 root handle 1: htb default 20
default 20 — задаем класс по-умолчанию. В нем будут обрабатываться пакеты, не попавшие в другие классы дисциплины htb. Если его не указать, то будет назначен “default 0” и весь неклассифицированный (непопавший под фильтры) трафик будет отправляться со скоростью интерфейса.
# tc class add dev eth0 parent 1: classid 1:1 htb rate 90kbps ceil 90kbps
прикрепляем к root qdisc класс с идентификатором 1:1. Тем самым ограничиваем скорость на интерфейсе до 90Кбайт/c.
classid 1:1 — идентификатор класса. <PRE> * rate 90kbps — устанавливаем нижний порог пропускной способности для класса. * ceil 90kbps — устанавливаем верхний порог пропускной способности для класса. <PRE> # tc class add dev eth0 parent 1:1 classid 1:10 htb rate 20kbps ceil 70kbps
создаем класс 1:10, дочерний классу 1:1. Затем в него фильтром будет направляться исходящий почтовый трафик.
rate 20kbps — устанавливаем гарантированный нижний порог пропускной способности для класса.
ceil 70kbps — устанавливаем верхний порог пропускной способности для класса. В случае если у родительского класса будет свободна полоса пропускания (наличие “лишних” токенов), class 1:10 сможет временно поднять скорость передачи данных, вплоть до указанного предела в 70Кбайт/c.
- tc class add dev eth0 parent 1:1 classid 1:20 htb rate 70kbps ceil 90kbps
Создаем класс по умолчанию. В него будет попадать весь остальной трафик. Точно также, параметрами rate и ceil, задаем расширение пропускной способности в случае отсутствия уже почтового трафика. # tc filter add dev eth0 parent 1: protocol ip prio 1 u32 match ip dport 25 0xffff flowid 1:10 фильтр на базе u32, направляющий пакеты исходящие на 25й порт в класс 1:10. Кстати, в документации указано, что по факту в HTB шейпинг трафика происходит только в краевых классах, в нашем случае 1:10 и 1:20. Указание параметров ограничения полосы пропускания в остальных классах HTB нужно лишь для функционирования системы заимствования между классами. При добавлении класса также возможно указать параметр prio. Он задает приоритет класса (0 — макс.приоритет). Классы с меньшим приоритетом не обрабатываются пока есть данные в более приоритетных классах.HFSC
* http://ace-host.stuart.id.au/russell/files/tc/doc/sch_hfsc.txt * http://linux-ip.net/articles/hfsc.en/Источники
* http://habrahabr.ru/post/138463/ * http://habrahabr.ru/post/138562/ * http://habrahabr.ru/post/133076/ * http://www.opennet.ru/base/net/linux_traffic_qos.txt.html * http://ace-host.stuart.id.au/russell/files/tc/doc/sch_hfsc.txt * http://wiki.linuxwall.info/doku.php/en:ressources:dossiers:networking:traffic_control#tbf_-_token_bucket_filter * http://xgu.ru/wiki/QoS_%D0%B2_Linux