ПАВЕЛ ЗАКЛЯКОВ
На пути повышения надёжности и скорости:
Linux bonding
– Bond?....James Bond?
– No, Linux Bonding.
В данной статье описывается частный случай объединения двух каналов Ethernet в один c помощью Linux bonding (на базе Red Hat v.7.3) как с целью повышения пропускной способности, так и надёжности сетевого соединения.
Года два назад, просматривая одну электронную доску объявлений в разделе б/у сетевого оборудования, я наткнулся на заметку о продаже 16-портового коммутатора «Switch Surecom EP-716X c поддержкой виртуальной сети и транка на базе портов» плюс прочие документированные возможности. Цена была низкой, а желание повозиться с настройками велико. Поэтому я поспешил приобрести его.
Рисунок 1. Фотография коммутатора Surecom EP-716X
Ситуация на рынке год от года не меняется, скорости передачи данных неизменно растут, выходят новые версии и т. д. Несмотря на рост скоростей в сетях с выделенным сервером, узким местом все равно остаётся точка соединения сервера с локальной сетью. Если взять наиболее часто используемый вариант, то используется сеть Fast Ethernet и сетевые адаптеры с пропускной способностью 100 Мбит/с. В результате если к серверу обращается одновременно 20 компьютеров, то пропускная способность падает до 5 Мбит/c, а за вычетом служебной информации реальная пропускная способность оказывается ещё меньше. Выход из данной ситуации может быть только один – увеличивать пропускную способность со стороны сервера. Если на сервере в качестве делимого взять не 100, а 1000 Мбит/c, то в теории средняя скорость передачи на один компьютер должна увеличиться в 10 раз. Характеристики повсеместно продаваемого сетевого оборудования указывают на тот факт, что развитие идёт именно в этом направлении. Однако на данном пути есть несколько препятствий, не позволяющих делать переход массовым и незаметным.
Первый недостаток – необходимость покупки дорогих коммутаторов с поддержкой нескольких или одного порта по стандарту IEEE 802.3ab (1000Base-T). При этом старый коммутатор оказывается не у дел.
Второй недостаток – необходимость замены кабельной инфраструктуры. Кабели повсеместно используемых на сегодня категорий 5 и 5e не подойдут по полосе пропускания, соответственно их придётся также менять.
Третий недостаток – сочетание сетевых карт стандарта IEEE 802.3ab (1000Base-T) и шины PCI. С некоторой натяжкой можно считать, что цена данных устройств относительно невелика. А вот пропускная способность шины PCI 2.0 может оказаться для нас недостаточной: 33 МГц * 32 бита = 1056 Мбит/c [1]. Хотя, 1056 > 1000 следует понимать, что различные накладные расходы, особенно для карт стоимостью менее $25, приведут к тому, что реальная пропускная способность окажется ниже 1000 Мбит/c. При большой загрузке сети это приведёт к тому, что буфер не будет успевать обрабатываться, возникнут потери пакетов. Как следствие – повторная передача и ещё большее снижение пропускной способности. Также не следует упускать из виду, что многие серверы часто имеют не одну сетевую карту, а несколько.
Если согласно спецификации PCI 2.1 (и выше) вместо тактовой частоты шины в 33 МГц использовать 66 МГц, то подобных эффектов удастся избежать, однако в этом случае возможны проблемы c другим оборудованием, не поддерживающим частоту шины 66 МГц. Использование PCI-X или 64-разрядной шины PCI (даже при тактовой частоте 32 МГц) решает проблему технически, но часто делает её финансово непосильной. Материнские платы с поддержкой 64-разрядной шины PCI уже относятся к серверному классу и стоят соответствующе. «Склепать дешёвый сервер на коленке» не получится.
Единственный недорогой выход для данной ситуации – это использование нескольких обычных сетевых карт. Затраты при этом минимальны: $5 + один вечер и результат усилий (удвоение пропускной способности) налицо. По этому пути я и пошёл.
Первое, что мне бросилось в глаза в описании, – это была фраза: «Support Port-Base Trunking», интуитивно поняв, что это то, что мне нужно, я залез на веб-страницу настройки и выставил одну транкинговую группу из 1-го и 9-го портов.
Рисунок 2. Порты 1 и 9 находятся в режиме port trunking
(По непонятным для меня причинам, настройку транкинга портов с помощью протокола telnet разработчики почему-то не сделали.) Далее я установил вторую карточку на компьютер, подключаемый к коммутатору в качестве сервера, соединил всё хозяйство патчкордами, и в результате у меня получилась следующая схема соединений.
Рисунок 3. Схема соединений
Понимая, что само по себе железо работать не будет, я занялся поисками литературы и настройкой программного обеспечения. Основным документом, который частично помог мне разобраться с проблемой, оказался Linux Ethernet Bonding Driver mini-howto из комплектации ядра(v.2.4.27), находящийся по следующему пути: /usr/src/linux/Documentation/networking/bonding.txt. В сети этот документ можно найти по адресу [2]. Желая иметь монолитное ядро, я отказался от рекомендаций компилировать Linux bonding в виде модуля. Всё бы хорошо, но объединение каналов почему-то не работало как надо. При отключении кабелей от сетевых интерфейсов ядро Linux продолжало думать, что всё в порядке. Убедившись в правильности совета, я скомпилировал ядро повторно, выбрав «Bonding driver support» в качестве модуля в разделе «Network device support».
Рисунок 4. Выбор «Bonding driver support» в качестве модуля
После компиляции ядра я зашёл в директорию, где находится исходный файл ifenslave.с:
# cd /usr/src/linux/Documentation/networking
и скомпилировал из него утилиту ifenslave (которая «подключает» к общему интерфейсу bond0(bond1, ...) другие подчинённые интерфейсы) командой:
# gcc -Wall -Wstrict-prototypes -O -I/usr/src/linux/include ifenslave.c -o ifenslave
Получившийся файл я скопировал в директорию /sbin:
# cp ifenslave /sbin/ifenslave
Для организации работы с модулем и передачи ему параметров необходимо добавить следующие строчки в файл /etc/modules.conf:
alias bond0 bonding
options bond0 miimon=100 mode=0 downdelay=2000 updelay=5000
Параметры, естественно, можно задать другие, но об этом чуть позже. Чтобы было удобно воспользоваться стандартной командой:
/etc/rc.d/init.d/network (start|stop|restart...)
а не вводить каждый раз параметры вручную через ifconfig, следует создать следующие файлы (либо внести в них изменения, если файлы уже имеются):
/etc/sysconfig/network-scripts/ifcfg-bond0
DEVICE=bond0
IPADDR=192.168.0.1
NETMASK=255.255.255.0
NETWORK=192.168.0.0
BROADCAST=192.168.0.255
ONBOOT=yes
BOOTPROTO=none
USERCTL=no
/etc/sysconfig/network-scripts/ifcfg-eth0
DEVICE=eth0
USERCTL=no
ONBOOT=yes
MASTER=bond0
SLAVE=yes
BOOTPROTO=none
/etc/sysconfig/network-scripts/ifcfg-eth1
DEVICE=eth1
USERCTL=no
ONBOOT=yes
MASTER=bond0
SLAVE=yes
BOOTPROTO=none
В случае возникновения проблем с настройкой нестандартных конфигураций рекомендую обратиться к первоисточнику [2].
Теория. О режимах работы
Смысл организации объединённых каналов обычно очень прост: либо нужно повышение пропускной способности, либо увеличение надёжности, либо и то, и другое вместе. Несмотря на то, что идеология построения объединённых каналов относительно прозрачна, существует несколько различных режимов совместной работы.
- Round robin, rr, 0. Круговой, основанный на последовательном использовании подчинённых интерфейсов для передачи через них пакетов в том порядке, как они были подключены. В этом режиме интерфейсы наиболее равномерно загружены.
- XOR, 2. Режим, в котором подчинённый интерфейс для передачи конкретного пакета выбирается по формуле «(src hw addr XOR dst hw addr) % slave», где «src hw addr» и «dst hw addr» есть MAC-адреса отправителя и получателя, приведённые к целочисленному виду, а slave – число работающих подчинённых интерфейсов. Определение интерфейса для отправки пакетов напрямую зависит от адреса получателя. При такой схеме выбора происходит примерно равномерное распределение оппонентов информационного обмена по интерфейсам с их «виртуальной» взаимной привязкой друг к другу.
- Active-backup, 1. Режим горячей замены. В каждый момент времени работает только один интерфейс. Если рабочий канал выходит из строя, то начинает использоваться какой-либо один резервный интерфейс (из соподчинённых главному) взамен «отказавшего». Данный режим используется в основном в системах повышенной надёжности.
Диагностика: «жив ли link?»
Когда все связи активны и система работает, как это от неё ожидается, может сложиться впечатление, что диагностика не нужна. Однако не используя её постоянно в рабочем режиме, сложно поймать тот момент, когда что-то перестало работать. Соответственно, можно пропустить мгновение, когда нужно начать более активно использовать другие соподчинённые интерфейсы, полностью отказавшись от сломанного. Если этого не сделать вовремя, то будет большой процент потерь пакетов, далее увеличение числа повторных передач и как следствие падение пропускной способности ниже, чем если бы работал только один интерфейс без настройки всяких linux bonding.
Существует два способа диагностирования (мониторинга) связанности:
- с помощью статуса Media Independent Interface (MII);
- c помощью ARP-запросов и ответов.
Несмотря на то что теоретически эти способы можно использовать вместе, на практике они не совмещаются. Видимо, разработчики этой части ядра сочли такую функциональность излишней. Расмотрим эти способы подробнее.
В первом случае небольшая схема на сетевой плате или часть основной микросхемы отслеживают состояние подключенности и режима работы сети. Какая скорость – 10 или 100 Мбит/c? Полный ли дуплекс? Есть ли связь вообще или нет? Эти данные с сетевой платы можно снимать и использовать в благих целях. Для применения этого режима диагностики ваша карта должна поддерживать функцию мониторинга MII-статуса, и эта функция должна быть включена. Часть ядра, отвечающая за bonding, исходя из этой информации, может принимать решение о целесообразности использования того или иного сетевого интерфейса для передачи данных. Дополнительно можно настроить периодичность, с которой осуществляется проверка. Рекомендуется использовать значение в 100 мс. Сильно уменьшать время не следует, так как это будет в значительной мере загружать систему. Увеличивать тоже особого смысла нет – может привести к росту числа повторных передач пакетов.
Второй способ более медленный, зато более надёжный и может работать с древними сетевыми картами, не поддерживающими мониторинг на основе MII-статуса.
Для этого способа используется какой-либо отдельный IP-адрес рабочего компьютера или адрес шлюза, которые всегда должны быть в сети в рабочем состоянии. Периодически на этот адрес посылаются ARP-запросы, на которые согласно требованиям, предъявляемым к хостам [5], должны отсылаться ARP-ответы. Если вдруг запросы перестали уходить или перестали приходить ответы – значит пропала связанность и канал следует исключить из использования во избежание потери пакетов. Простой пример конфигурации сети, где этот метод сработает лучше, чем мониторинг MII-статуса, представлен на рисунке 5. Например, во время переезда внутри здания некоторое устройство, ранее сообщавшееся с сервером Linux напрямую двумя каналами Ethernet, оказалось сильно отнесено (более чем на 180 метров). Благодаря особенностям организации сети, Ethernet стало невозможным использование столь длинного сегмента без ретрансляторов, в результате чего было принято решение о применении двух коммутаторов посередине с целью обеспечения связи устройства с сервером. Сервер как работал в режиме объединения каналов с диагностикой в виде мониторинга MII-статуса, так и продолжает работать. Теперь, если произойдёт сбой на участке, отмеченном красным крестиком (между коммутатором 1 и устройством) (см. рис. 5), сервер об этом не узнает. Так как связь на участке «eth0 – коммутатор 1» есть, сервер будет продолжать посылать по данному каналу пакеты, которые будут успешно теряться на коммутаторе.
Если в данной схеме использовать мониторинг по ARP-запросам и ответам, то, не получив ответа от устройства, сервер поймёт, что связность в этом направлении нарушена и откажется от использования испорченного канала.
Рисунок 5. Пример, где мониторинг MII статуса неприменим
Параметры, передаваемые модулю
Чуть выше без пояснений были выбраны следующие параметры: miimon=100 mode=0 downdelay=2000 updelay=5000. Давайте рассмотрим более подробно эти и другие параметры, с помощью которых можно менять режим работы.
mode
Параметр, выбирающий режим работы, возможные значения: 0,1,2 для Round robin, XOR, active backup соответственно.
miimon
Число мс (тип integer) – как часто производится мониторинг MII статуса. 0 – мониторинг отключен. Рекомендуемое значение 100 мс.
downdelay
Задержка в мс (тип integer) с того момента, как было обнаружено, что связь пропала, до того, как данный канал перестанет использоваться. Задержка нужна, чтобы отфильтровывать кратковременные сбои. Например, после переключения патчкорда в течение нескольких секунд совсем не обязательно терять связь на минуту или больше. По умолчанию используется 0 – нет задержки.
updelay
То же самое, что и downdelay, но определяющее задержку по включению, на тот случай, если какой-нибудь switch или hub «не до конца включились». Напряжение на порт подано, а пакеты ещё корректно не принимаются. Если в этот момент передать данные – они потеряются, а если немного подождать, то этого не случится.
arp_interval
С какой периодичностью в мс (тип integer) осуществлять ARP-мониторинг. Значение 0 означает что ARP-мониторинг выключен.
arp_ip_target
IP-адрес, по которому осуществлять ARP-мониторинг (в случае если arp_interval>0). Если с данного адреса нет ARP-ответов, то значит канал, по которому посылались запросы, не работает.
Linux bonding на практике
Как оказалось, практика расходится с теорией, и не всё у меня работало так, как хотелось. Проводимые испытания и результаты были следующими:
Была создана схема, указанная на рисунке 3, все связи в ней целые.
Запускаем на 192.168.0.5: tcpdump icmp
Запускаем на 192.168.0.1: ping 192.168.0.2 -I bond0
Запускаем на 192.168.0.1 на другой консоли: tcpdump icmp
Пакеты проходят нормально. Отсоединяем сетевой шнур от eth0, и в /var/log/messages появляются строки:
Sep 22 17:55:19 LINUX2 kernel: bonding: bond0: link status down for idle interface eth0, disabling it in 2000 ms.
Sep 22 17:55:21 LINUX2 kernel: bonding: bond0: link status definitely down for interface eth0, disabling it
cat /proc/net/bonding/bond0 выводит:
Bonding Mode: load balancing (round-robin)
MII Status: up
MII Polling Interval (ms): 100
Up Delay (ms): 5000
Down Delay (ms): 2000
Slave Interface: eth0
MII Status: down
Link Failure Count: 1
Permanent HW addr: 00:50:22:e1:9e:3e
Slave Interface: eth1
MII Status: up
Link Failure Count: 0
Permanent HW addr: 00:02:44:8b:c9:c1
|
далее вставляем кабель обратно и наблюдаем в /var/log/messages появление строк:
Sep 22 18:03:23 LINUX2 kernel: eth0: link up, 100Mbps, full-duplex, lpa 0x45E1
Sep 22 18:03:23 LINUX2 kernel: bonding: bond0: link status up for interface eth0, enabling it in 5000 ms.
Sep 22 18:03:28 LINUX2 kernel: bonding: bond0: link status definitely up for interface eth0
|
/proc/net/bonding/bond0 соответственно для eth0 отображает:
Повторим тест, но на этот раз отсоединяем кабель от eth1. По идее должно быть всё то же самое, но, увы. С 192.168.0.1 ICMP-запросы уходят и доходят до 192.168.0.5, далее на них отсылается ответ, о чём свидетельствует tcpdump, запущенный на обеих машинах, а вот до 192.168.0.1 ответы уже не доходят, теряясь где-то внутри коммутатора. Через некоторое время устаревает arp-таблица, и пакеты вообще перестают уходить.
Переставляем патчкорды на коммутаторе местами и повторяем ситуацию, ситуация с eth0 и eth1 меняется также местами, из чего делаем вывод о плохой работоспособности конкретного экземпляра Switch Surecom EP-716X. Как результат тоже неплохо.
Попытки найти прошивку новее, чем v.1.3, как и выйти на контакт с кем-то из техподдержки Surecom, не увенчались успехом.
Однако это ещё не конец статьи. Мной было замечено, что если два кабеля от eth0 и eth1 воткнуть в обычные порты этого или любого другого коммутатора либо отключить port trunking у экземпляра выше, то объединение каналов работает, но с небольшими оговорками.
В [2] рассмотрен такой случай, когда используется несколько коммутаторов или один без поддержки port trunking, однако там используется режим повышенной надёжности, и одна из карточек стоит в горячем резерве (Active backup, mode=1). При этом в одно и то же время обе карточки не работают, чтобы не смущать коммутатор своими одинаковыми MAC-адресами. Точнее, на одной из них установлен флаг NOARP. И, естественно, ни о каком увеличении пропускной способности в 2 раза речь не идёт.
Испытания на обычном коммутаторе показали следующую картину. Пакеты посылаются по обоим интерфейсам поочерёдно. Обратно приходят также на два интерфейса вразнобой. Выдвигаю гипотезу, что приходящий пакет коммутатором посылается на последний работавший интерфейс. Если отключить один провод от сетевой карты, то три исходящих пакета теряются, а далее связь восстанавливается. Если отключить провод от другой сетевой карты, ситуация почти аналогична – либо также теряются первые три исходящих пакета, либо ничего не теряется. Объяснить это можно тем, что подчинённые интерфейсы eth0 и eth1 не совсем равнозначны для bond0. Чем раньше подчинённый интерфейс был подключён к общему, тем выше у него приоритет. Если отключить самый приоритетный (в объединённом канале может быть более двух подчинённых), то роль первенства переходит другому интерфейсу, и тогда теряется часть пакетов. Если отключить менее приоритетный, то всё нормально. Каких-либо дополнительных проблем мною замечено не было. Возможно, сказалась маленькая нагрузка моей тестовой сети, состоящей из четырех компьютеров, хотя, думается, что и при большем числе компьютеров всё будет работать так же.
Ещё интересное наблюдение было сделано по результатам анализа tcpdump в разных точках. На 192.168.0.1 был запущен ping, посылающий раз в секунду ICMP-запросы на адрес 192.168.0.2.
tcpdump icmp -I eth0 на 192.168.0.1 перехватил следующее:
20:16:52.578951 192.168.0.1 > 192.168.0.2: icmp: echo request (DF)
20:16:54.578941 192.168.0.1 > 192.168.0.2: icmp: echo request (DF)
20:16:56.578950 192.168.0.1 > 192.168.0.2: icmp: echo request (DF)
20:16:58.578947 192.168.0.1 > 192.168.0.2: icmp: echo request (DF)
20:17:00.578945 192.168.0.1 > 192.168.0.2: icmp: echo request (DF)
tcpdump icmp -I eth1 на 192.168.0.1 перехватил следующее:
20:16:53.578947 192.168.0.1 > 192.168.0.2: icmp: echo request (DF)
20:16:55.578954 192.168.0.1 > 192.168.0.2: icmp: echo request (DF)
20:16:57.578951 192.168.0.1 > 192.168.0.2: icmp: echo request (DF)
20:16:59.578942 192.168.0.1 > 192.168.0.2: icmp: echo request (DF)
20:17:01.578948 192.168.0.1 > 192.168.0.2: icmp: echo request (DF)
tcpdump icmp -I bond0 на 192.168.0.1 перехватил следующее:
20:16:52.578933 192.168.0.1 > 192.168.0.2: icmp: echo request (DF)
20:16:52.579111 192.168.0.2 > 192.168.0.1: icmp: echo reply
20:16:53.578926 192.168.0.1 > 192.168.0.2: icmp: echo request (DF)
20:16:53.579106 192.168.0.2 > 192.168.0.1: icmp: echo reply
20:16:54.578921 192.168.0.1 > 192.168.0.2: icmp: echo request (DF)
20:16:54.579130 192.168.0.2 > 192.168.0.1: icmp: echo reply
20:16:55.578933 192.168.0.1 > 192.168.0.2: icmp: echo request (DF)
20:16:55.579142 192.168.0.2 > 192.168.0.1: icmp: echo reply
20:16:56.578928 192.168.0.1 > 192.168.0.2: icmp: echo request (DF)
20:16:56.579117 192.168.0.2 > 192.168.0.1: icmp: echo reply
...
|
Как легко заметить, исходящие пакеты чередуются с выбором интерфейса. В 52, 54, 56, 58 секунд они уходят с интерфейса eth0, а в 53, 55, 57, 59 – с интерфейса eth1. При этом на интерфейсе bond0 их можно наблюдать вместе в 52, 53, 54, 55 и т. д. секунд показывающими, что утилита ping действительно посылает раз в секунду ICMP-запросы. Также забавно то, что на интерфейсе bond0 можно наблюдать и ICMP-ответы на посланные запросы, в то время как на интерфейсах eth0 и eth1 их почему-то не видно, хотя они реально приходят. Если включить мониторинг портов на коммутаторе или поставить в разрыв провода мост, то там видны все пакеты.
Я задал этот вопрос (почему не видны приходящие ответы на интерфейсах eth0 и eth1) Willy Tarreau <willy@w.ods.org>, упомянутому в [2]. Он ответил, что это происходит потому, что модуль bonding работает на очень низком уровне, ниже того места, где в обычном режиме цепляется tcpdump.
Так как со стороны Linux, скорее всего, я настроил всё правильно, то думаю, что проделанная работа кому-нибудь пригодится. Возможно, окажись у меня под рукой другой коммутатор, всё бы сразу заработало как надо. Если у кого-то объединение портов работает нормально с другим оборудованием (поддерживающим port trunking), буду рад об этом услышать. Также мне интересно узнать, какие могут возникнуть проблемы с обычными коммутаторами (не поддерживающими объединение портов) и почему у меня они не возникли.
Замечание 1. Никто не мешает организовать несколько непересекающихся групп объединяемых каналов. При этом необходимо загрузить модуль несколько раз, то есть в /etc/modules.conf прописать две или более строчек вместо одной.
alias bond0 bonding
alias bond1 bonding
Ну и потом, при обращении к каждому модулю (обратите внимание, что используются разные модули, а не один и тот же под псевдонимами) передать каждому свои параметры.
options bond0 miimon=100
options bond1 -o bonding1 miimon=100
Замечание 2. Как я понял в процессе написания статьи, терминов, обозначающих объединение каналов, много. Узнать, почему их много и чем они отличаются, мне не удалось. Складывается впечатление, что каждая фирма хочет быть законодателем мод. Всего мне встретилось 5 вариантов:
- Aggregate link – в терминологии Intel;
- Port trunking – в терминологии D-link, 3Com, Surecom;
- Sun Trunking – в терминологии SUN;
- EtherChannel – в терминологии CISCO;
- Bonding – в терминологии Linux, FreeBSD.
Ещё мне встретился термин «load balancing», но это немного другое, никакого отношения к объединённым каналам не имеет. Сетевые карты не работают в паре на канальном уровне и имеют различные сетевые адреса, при этом могут быть установлены на разных компьютерах.
Замечание 3. Идя по пути экономии, технологию объединения каналов при желании можно использовать для Gigabit Ethernet. Скорее всего, объединять будет выгоднее, чем иметь один канал на 10 Гбит/c.
Литература, ссылки:
- С.Мюллер Модернизация и ремонт ПК, 14-е издание.: Пер. с англ. – М.: Издательский дом «Вильямс», 2003, стр. 294.
- Linux Ethernet Bonding Driver mini-howto: http://www.kernel.org/pub/linux/kernel/people/marcelo/linux-2.4/Documentation/networking/bonding.txt
- Реализация механизма Cisco Fast EtherChannel для FreeBSD: http://avdor.irkutsk.ru/faq/article.php?show_id=308
- Bundling network interfaces with ng_one2many: http://unixpages.org/one2many_tut.html, http://bsdvault.net/sections.php?op=viewarticle&artid=98
- RFC 1127: A Perspective on the Host Requirements RFCs: ftp://ftp.rfc-editor.org/in-notes/rfc1127.txt