Балансировка каналов — два провайдера, AS, BGP, NAT

Спасибо Хабру, много полезного тут для себя нашел. Думаю, пора «отдавать долги».
Хочу описать алгоритм, который работает больше года на моем шлюзе для балансировки каналов (Гбит трафика, 8k клиентов, 2 провайдера, AS на 1k адресов, большинство клиентов за NAT). Возможно, кому-то пригодится. Во всяком случае, ничего похожего не встречал и когда специально искал — не нашел. Так что полностью мое детище.
Все, что попадалось на просторах Интернета, позволяло резервировать один из каналов. И исходящий регулировать — описаний много. А вот регулировать входящий трафик (т.е. обеспечить равномерную загрузку нескольких каналов) — не попадалось.
Конечно, указанный алгоритм нельзя считать универсальным, подойдет только в подходящих условиях.

Итак, исходные:
— Шлюз на Linux (Debian 6). Используется пакет quagga (бывший zebra).
— Два провайдера (пусть будут ТТК и РТК). Каждый дает канал определенной толщины, «лишнее» режет.
— AS на 1k адресов (пусть будет 1.1.144.0/22). AS0000.
— Большинство клиентов имеют серые адреса (пусть будет 192.168.0.0/16), «клиентские» сети 192.168.1-99.0/24, на шлюзе натятся.
— Небольшая часть клиентов имеют белые адреса в пространстве моей AS.

Задача:
Обеспечить равномерную загрузку каналов ТТК и РТК входящим трафиком для исключения перегрузки каналов.


Упрощение.
Не буду тут говорить о настройках шейпера. Будем считать что это уже настроено и работает. При этом, шейпится общий канал, без учета ТТК / РТК.
Не будем балансировать исходящий. В большинстве случаях это не актуально (исходящего много меньше), да и решается довольно просто.

Теория.
1. BGP позволяет управлять вероятностью (предпочтением). Т.е. указать предпочтительный входящий маршрут для определенной сети. Используется для этого искусственное «удлинение» маршрута — при анонсировании своего маршрута соседу можно несколько раз повторить номер своей AS. При этом, каждому соседу можно «удлинять» маршрут по-разному. Предпочтительным оказывается более короткий маршрут.
2. BGP позволяет делать описания отдельных частей AS. Т.е. в RIPE наша AS описана как 1.1.144.0/22, никто не мешает дополнительно в BGP описать (т.е. анонсировать) 1.1.144.0/24, 1.1.145.0/24, 1.1.146.0/24 и 1.1.147.0/24.
Совет — не убирайте анонс всей AS (1.1.144.0/22). Некоторые шлюзы не принимают маршруты с маской 24. Лучше немного удлинить общий маршрут.
3. Напомню алгоритм выбора маршрута при BGP-маршрутизации из нескольких имеющихся.
— Выбирается маршрут с большей маской. Если не выбрано, то далее.
— Выбирается более короткий маршрут (меньше промежуточных AS). Если не выбрано, то далее.
— Выбирается маршрут, объявленный раньше (считается более надежный). Если не выбрано, то далее.
— Однозначный псевдо-случайный выбор.

Капля дегтя.
К сожалению, «управление предпочтением» совсем не значит «управление вероятностью». На деле оборачивается тем, что почти весь трафик начинает идти по предпочитаемому маршруту. Т.е. используя удлинения маршрутов, плавно регулировать потоки не получится. Это скорее «переключатель», чем «регулятор».

Идея.
Большинство наших клиентов имеют серые IP. Соответственно, на нашем шлюзе натятся. И это основной по объему трафик. Ну и как их натить (т.е. какой внешний IP задать), — в нашей власти.
Всю нашу AS можно разбить на 4 части (1.1.144.0/24, 1.1.145.0/24, 1.1.146.0/24 и 1.1.147.0/24) и использовать их по-разному. Например, первые две для клиентов с «белыми» IP, третья — предпочтение РТК и четвертая — предпочтение ТТК. Именно так сделано у меня.
На уровне iptables принимать решение какие адреса использовать для NAT.
Если адрес клиента 192.168.1-N.0/24, то для NAT использовать 1.1.146.0/24. Иначе — 1.1.147.0/24.
Таким образом, изменяя N, можно плавно балансировать входящий трафик двух каналов.

Реализация.
Прошу считать эту реализацию просто как пример. Далеко не все тут оптимально, кому-то удобнее делать на перле / питоне, кому-то удобнее организовать единым демоном. Главная цель этого примера — показать возможность реализации идеи. Ну и ее работоспособность.

1. Для проверки принадлежности IP клиента к 192.168.1-N.0/24, в iptables, использовать модуль ipset, в правилах далее набор «rtk».
Цепочка «NAT_AS», которая у меня натит:
:NAT_AS — [0:0]
#Для старых соединений, чтобы не «бросать» их с одного внешнего на другой
-A NAT_AS -m state -s 192.168.0.0/16 --state ESTABLISHED,RELATED -j SNAT --to-source 91.235.146.0-91.235.147.255 --persistent
#Для новых соединений выбираем как натить.
#РТК
-A NAT_AS -m state -m set --set rtk src --state NEW -j SNAT --to-source 1.1.146.0-1.1.146.255 --persistent
#ТТК
-A NAT_AS -m state -m set! --set rtk src --state NEW -j SNAT --to-source 1.1.147.0-1.1.147.255 --persistent

Обратите внимание, -j SNAT используется с параметром --persistent. Это чтобы клиент использовал постоянный внешний IP. Без этого у клиента могут быть проблемы на многих сервисах в интернете.
Ну и где-то в NAT / POSTROUTING
# eth1, eth3 — интерфейсы которые «смотрят» на ТТК и РТК
-A POSTROUTING -s 192.168.0.0/16 -o eth1 -j NAT_AS
-A POSTROUTING -s 192.168.0.0/16 -o eth3 -j NAT_AS


2. Подготавливаем набор ipset «rtk»
Файл, в котором я храню все параметры (используется в нескольких скриптах)
cat param_rtk_set:
#Подсети клиентов, которыми будем управлять
export rtk_start=1
export rtk_min=1
export rtk_max=99

#Максимальные трафики (что дал провайдер, точнее что от него можно получить без потерь). Подбирается.
# РТК
export shp_rtk_max=547
# ТТК
export shp_ttk_max=535

#Относительная точность регулирования
export scale=50

#файл, где хранится текущее значение N. Лучше где-то на tmpfs.
export f_set_end=/lib/init/rw/rtk_set_end

Непосредственно создание и обновление набора rtk. Можно (и нужно) запускать регулярно.
cat create_rtk_set
#! /bin/sh

# Если не существует, то создается набор rtk
/usr/sbin/ipset -N rtk nethash -q
# Временный набор. Чтобы «на горячюю» не изменять рабочий набор
/usr/sbin/ipset -N temp_rtk nethash -q
/usr/sbin/ipset -F temp_rtk -q

. ./param_rtk_set

# В некоторых версиях sh, переменную надо объявить до цикла или условия. Чтобы потом использовать.
rtk_set_end=0

# Если это первый запуск (нет старого значения N, то создадим среднее от min и max. И сохраним.
if [ -f $f_set_end ]; then
read rtk_set_end < $f_set_end
else
rtk_set_end=$(($rtk_min + $rtk_max))
rtk_set_end=$(($rtk_set_end / 2))
echo $rtk_set_end > $f_set_end
fi

# заполним временный набор
net=$rtk_start
while [ $net -lt $rtk_set_end ]; do
/usr/sbin/ipset -A temp_rtk 192.168.${net}.0/24 -q
net=$(($net + 1))
done

# скопируем временный набор в рабочий
/usr/sbin/ipset -W temp_rtk rtk
# удалим временный набор
/usr/sbin/ipset -X temp_rtk -q


ОК, «управляющее воздействие» есть. Т.е. изменяя N (у меня значение хранится в /lib/init/rw/rtk_set_end), можно плавно изменять соотношения входящих трафиков ТТК и РТК. Теперь осталось настроить автоматику.

Автоматизация.
cat rtk-ttk:
# Получим текущие значения счетчиков на интерфейсах
ttk=$(/sbin/ifconfig eth1 | grep -Eo «RX bytes:[0-9]*» | grep -Eo "[0-9]*")
if [ "$ttk" = "" ]; then
echo «No TTK ifconfig»
exit
fi
rtk=$(/sbin/ifconfig eth3 | grep -Eo «RX bytes:[0-9]*» | grep -Eo "[0-9]*")
if [ "$rtk" = "" ]; then
echo «No RTK ifconfig»
exit
fi

# Каталог, где будем хранить прошлые значения
work_dir="/lib/init/rw/"

# Найдем разницу, сохраним текущее значение, если текущее меньше прошлого, то на выход.
read ttk_old < ${work_dir}shp_ttk_old
read rtk_old < ${work_dir}shp_rtk_old

echo $ttk > ${work_dir}shp_ttk_old
echo $rtk > ${work_dir}shp_rtk_old

if [ $ttk -le $ttk_old ]; then
echo «TTK RX smoll»
exit
fi

if [ $rtk -le $rtk_old ]; then
echo «TTK RX smoll»
exit
fi

ttk_cur=$(($ttk — $ttk_old + 1))
rtk_cur=$(($rtk — $rtk_old + 1))

# прочитаем параметры
. ./param_rtk_set

# Максимальное изменение N за одну итерацию. Подбирается.
max_delta=5

# Найдем отклонение.
p=$( echo «scale=10; $scale * ($shp_rtk_max / $shp_ttk_max) / ($rtk_cur / $ttk_cur) + 100.5» | /usr/bin/bc )
p=${p%%.*}
p=$(( $p — 100 ))

# Увеличиваем N
n_for=1
while [ $scale -lt $p ]; do
#echo "$p add"
p=$(( $p — 1 ))
n_for=$(( $n_for + 1 ))
if [ $max_delta -lt $n_for ]; then
break
fi
./add_rtk
done

# Уменьшаем N
n_for=1
while [ $p -lt $scale ]; do
#echo "$p del"
p=$(( 1 + $p ))
n_for=$(( $n_for + 1 ))
if [ $max_delta -lt $n_for ]; then
break
fi
./del_rtk
done

# применим новое значение N
./create_rtk_set


Осталось сделать скрипты add_rtk для увеличения N и del_rtk для уменьшения. Скрипты эти должны читать текущее N из /lib/init/rw/rtk_set_end, уменьшать / увеличивать, проверять вхождение в интервал [min — max] и сохранять. Не буду их приводить, это просто.

Настройка BGP.
Для того, чтобы все изложенное могло управлять входящим трафиком, надо подготовить BGP.

Пример моего bgp.conf (естественно, настоящие IP и номера изменены под исходные данные:
!
hostname AS0000
password ****
enable password ****
log file /var/log/quagga/bgpd.log
!
router bgp 0000
no synchronization
bgp router-id [наш любой внешний IP]
network 1.1.144.0/22
network 1.1.144.0/24
network 1.1.145.0/24
network 1.1.146.0/24
network 1.1.147.0/24
!
neighbor [IP шлюза РТК] remote-as [AS РТК (только номер, например 12345)]
neighbor [IP шлюза РТК] update-source [наш внешний IP РТК]
neighbor [IP шлюза РТК] route-map MY-OUT-RTK out
neighbor [IP шлюза РТК] route-map INTER_NET in
!
neighbor [IP шлюза ТТК] remote-as [AS ТТК (только номер, например 12345)]
neighbor [IP шлюза ТТК] update-source [наш внешний IP ТТК]
neighbor [IP шлюза ТТК] route-map MY-OUT-TTK out
neighbor [IP шлюза ТТК] route-map INTER_NET in
!
ip prefix-list upstream-out seq 10 permit 1.1.144.0/22
!
ip prefix-list up144 seq 10 permit 1.1.144.0/24
!
ip prefix-list up145 seq 10 permit 1.1.145.0/24
!
ip prefix-list up146 seq 10 permit 1.1.146.0/24
!
ip prefix-list up147 seq 10 permit 1.1.147.0/24
!
!=========================
!--- MY-OUT-TTK
route-map MY-OUT-TTK permit 10
match ip address prefix-list up144
!set as-path prepend 0000 0000
!
route-map MY-OUT-TTK permit 20
match ip address prefix-list up145
!set as-path prepend 0000 0000
!
route-map MY-OUT-TTK permit 30
match ip address prefix-list up146
set as-path prepend 0000
!
route-map MY-OUT-TTK permit 40
!match ip address prefix-list up147
!set as-path prepend 0000 0000
!
!route-map MY-OUT-TTK deny 200
!=========================
!
route-map MY-OUT-TTK permit 100
match ip address prefix-list upstream-out
!set as-path prepend 0000 0000 0000
!
route-map MY-OUT-TTK deny 200
!
!--- конец MY-OUT-TTK
!=========================
!--- MY-OUT-RTK
route-map MY-OUT-RTK permit 10
match ip address prefix-list up144
set as-path prepend 0000
!
route-map MY-OUT-RTK permit 20
match ip address prefix-list up145
set as-path prepend 0000
!
route-map MY-OUT-RTK permit 30
match ip address prefix-list up146
!set as-path prepend 0000 0000 0000
!
route-map MY-OUT-RTK permit 40
match ip address prefix-list up147
set as-path prepend 0000
!
!=========================
!
route-map MY-OUT-RTK permit 100
match ip address prefix-list upstream-out
set as-path prepend 0000 0000
!
route-map MY-OUT-RTK deny 200
!
!--- конец MY-OUT-RTK
!=========================
!---- Local nets
ip prefix-list local_ seq 15 permit 192.168.0.0/16
ip prefix-list local_ seq 18 permit 0.0.0.0/8
ip prefix-list local_ seq 19 permit 127.0.0.0/8
ip prefix-list local_ seq 20 permit 10.0.0.0/8
ip prefix-list local_ seq 21 permit 172.16.0.0/12
ip prefix-list local_ seq 22 permit 169.254.0.0/16
ip prefix-list local_ seq 23 permit 224.0.0.0/4
ip prefix-list local_ seq 24 permit 240.0.0.0/4
! Тут надо добавить свои «белые» сети
!
route-map INTER_NET deny 10
match ip address prefix-list local_
!
!
route-map INTER_NET permit 200
set local-preference 500
!
line vty
!

Напомню, что здесь «0000» — номер моей AS, «1.1.» — начало моих IP. Все остальное подписано.

Настройка, тюнинг.
Во-первых, надо настроить все «set as-path prepend» в bgp.conf.
Задача — чтобы если все натить исходящим 1.1.146.0/24 (это условно, конечно), то получить большой перекос в сторону РТК.
И наоборот, если все натить исходящим 1.1.147.0/24, то получить сильный перекос в сторону ТТК.

Во-вторых, надо указать максимально-доступные трафики от ТТК и РТК в файле param_rtk_set (shp_rtk_max и shp_ttk_max). Не рекомендую указывать значение «из договора». Укажите максимальное, которое вы когда-либо получали. Имейте ввиду, что этим вы задаете желаемое соотношение входящих трафиков.

Третье.
Указать желаемую точность регулирования (значение «scale» в параметрах). Чем больше, тем меньше «мертвая зона», тем сильнее управляющее воздействие (сильнее изменяется N). Слишком большое значение может вызвать «биения» (резонанс).

Четвертое.
Укажите максимальное изменение N за одну итерацию. Слишком большое значение может вызвать «биения», т.е. резонанс. Происходит это от того, что реакция на управляющее воздействие появляется не сразу, а с некоторой задержкой. Ну а слишком маленькое значение не позволит успевать за жизненными реалиями.

Ну вот и все. Осталось сделать задание в cron, для выполнения rtk-ttk каждую минуту. Или сделать какой-то демон, которые будет запускать rtk-ttk периодически.

Добавлю, что указанный алгоритм работает у меня больше года. Иногда приходится вмешиваться — поправлять настройки BGP (set as-path prepend). Что-то в Интернете меняется, приходится реагировать.

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

Комментарии 27

    0
    А что будет, если один из шлюзов/провайдеров уйдёт в нирвану?
      +2
      Значит BGP демон не сможет с ним связаться и анонсировать ему наши сети. Следовательно, весь входящий пойдет по оставшемуся каналу.
      Ну а для исходящих надо или мониторить эти 2 канала с автоматическим изменением default маршрута, или настраивать BGP так, чтобы он изменял свои локальные маршруты.
      У меня используется 1й вариант — моя AS не пропускает транзитный трафик, достаточно обойтись статическими маршрутами.
      В итоге, каждые 5 минут проверяется доступность 8.8.8.8 через оба канала (мониторить сами шлюзы смысла нет — проблема может быть дальше). Если оба доступны, то:
      ip route change default scope global nexthop via [IP шлюза 1 пров] dev [eth 1 пров] weight 1 nexthop via [IP шлюза 2 пров] dev [eth 2 пров] weight 1
      Если один, то:
      ip route change default via [IP шлюза оставшегося прова] dev [eth оставшегося прова]

      В результате, при пропадании одного из каналов, в течении 5 минут, происходит переключение исходящего маршрута на оставшийся канал. Входящий анонсируемый маршрут остается один — оставшегося канала.
      N достигает минимума или максимума, таким и остается. Но на работу это уже не сказывается. Ну, натиться все будут одним диапазоном, и что?
      Единственный минус — оставшийся канал оказывается перегружен. Ведет это к большим потерям. Но, это форс-мажор, авария… И обычно не так долго.

      Такой мониторинг работает у меня давно (больше года). Время от времени пропадает то 1 канал, то другой. Все отрабатывает нормально, еще и сообщение на мыло присылает.

      При восстановлении канала все быстро приходит в норму.
        0
        при такой смене дефолт-роута может сложиться ситуация, что открытые соединения изза коннтрака пойдут по новому умолчательному маршруту, но будут снатиться по прежнему на старый адрес.
          0
          И бог с ними!
          Главное, чтобы существовал анонс для этого «старого адреса» на «живой» маршрут. При этом, длина этого маршрута не имеет значения — он остался один.
          Смотрите.
          Клиент натится адресом ВнешнийАдресКлиента.
          Пока живы оба канала, анонсируются два маршрута на этот ВнешнийАдресКлиента. Один чуть длиннее другого. Следовательно, трафик этому клиенту пойдет по короткому маршруту.
          Если в живых остается один канал, то и анонсируется только один маршрут (на оставшийся канал). Следовательно, весь трафик этому клиенту пойдет только по живому каналу. При этом все остальное не имеет значения.

          Это особенность работы BGP — автоматическое поддерживание «живых» маршрутов. И автоматическая очистка от «мертвых».
            0
            UP. Замечу, что хотя полное анонсирование своего маршрута на весь Интернет, — дело долгое (до 6 часов), информация до ближайших маршрутизаторов доходит быстро (по моим наблюдениям 5-10 минут). И это оказывает решающее значение. Даже если какой-то хост в далекой Америке (например) отправит пакет по уже «мертвому» маршруту, то где-то вблизи от меня этот пакет переправится по «живому» пути.
              0
              т.е в любом случае соединения успею порваться :(
                0
                Вероятно да. Конечно, если соединение не имеет большого тайм-аута. Перестроение маршрутов даже ближайших соседей займет какое-то время. Поэтому часть трафика уйдет «в пустоту».
              0
              а если все тоже самое, но без бгп ?)
                0
                Хм… Чтобы принимать трафик на одни и те-же адреса через несколько каналов — это надо иметь автономную систему (AS). Т.е. независимые от провайдера адреса. Никакой провайдер не станет маршрутизировать свои адреса через другого.
                Ну, а где AS, там и BGP.

                ИМХО, балансировать входящий без равноправности адресов в разных каналах — это создавать большие проблемы клиентам. Придется постоянно менять внешний адрес клиента, что далеко не гуд.
                Или менять «N» изредка. До получения AS так и делали, но это «N» менялось вручную по мере надобности. Подобрать оптимального просто не получалось. В результате в течении суток то один канал переполнялся, то другой. А это — потери. Опять «не гуд».
                  0
                  не, я имел ввиду обычный фейловер на случай падения основного канала — как быть с открытыми соединениями. рвать принудительно через conntrack -D при смене маршрута по-умолчанию?
                    0
                    А зачем? Если ответа нет, то по тайм-ауту сами отвалятся. Только IP стек надо настроить — уменьшить этот тайм-аут до адекватных 30 минут (например). А лучше 5 минут. По-умолчанию там что-то около недели.

                    Но это уже другой разговор — оптимизация IP стека на шлюзе.
                      0
                      5 минут ?) а если цель — обеспечить воип с мин простоями?
                        0
                        Кратковременное прерывание все равно будет, от этого никак не уйти. Это если «оборвался» как раз канал, по которому идет основной трафик клиента. Иначе вообще никаких прерываний.
                        Чтобы уменьшить время прерывания — AS и BGP. Т.е. сохраняется внешний адрес клиента независимо от входящего/исходящего канала.
                        Другой вариант — bond на 2 провайдера. Но это не реально. Если 2 подключения к одному, это еще как-то возможно. А к разным… Им это надо?
        –2
        А чем это решение лучше/хуже настройки bond-интерфейса? И не получится ли так, что для одного и того же адресата исходящие пакеты отправляются по разным каналам в разные моменты времени?
          +1
          у бонд-интерфейса 1 ип-адрес, нужна поддержка со стороны «другого конца проводов»
            +2
            2 разных провайдера никогда не дадут создать такое подключение.
              +1
              И не получится ли так, что для одного и того же адресата исходящие пакеты отправляются по разным каналам в разные моменты времени?

              Более того, и исходящие от клиента, и входящие к клиенту, могут идти одновременно по обоим каналам (т.е. часть по одному, часть по другому). И это нормальное явление в Интернете.
                0
                Пусть себе отправляются — какая разница? :) Все равно в конечном итоге приедут куда надо.
                0
                никто не мешает дополнительно в BGP описать (т.е. анонсировать) 1.1.144.0/24, 1.1.145.0/24, 1.1.146.0/24 и 1.1.147.0/24

                Вот Вы совсем не самурай. Самураи так не делают, т.к. это увеличивает и так раздутый full view и, как следствие, повышает энтропию вселенной, приближая тепловую смерть.
                Правильно брать у обеих аплинков физику с запасом и канал по burstable billing.
                  0
                  К сожалению, нахожусь я недалеко от Тихого Океана, цены за трафик у нас не маленькие и всего два провайдера с достаточными мощностями.
                  Так что особо выбирать и кочевряжиться не получится. И переплачивать в два раза — тоже «не фонтан».

                  А про «full view» согласен… Но куда деваться? На сколько знаю, мелкие узлы его режут. Т.е. мои /24 просто проигнорируют.
                • НЛО прилетело и опубликовало эту надпись здесь
                    0
                    Мне повезло на 50%. У одного провайдера сразу были возможны препенды, второй разрешил после недели переписки.
                      0
                      PS. И действительно, у вышестоящих провайдеров были разные local-pref от пиров. Пришлось выравнивать своими «set as-path prepend». Но получилось. ;)
                      Не зря писал
                      Во-первых, надо настроить все «set as-path prepend» в bgp.conf.
                      Задача — чтобы если все натить исходящим 1.1.146.0/24 (это условно, конечно), то получить большой перекос в сторону РТК.
                      И наоборот, если все натить исходящим 1.1.147.0/24, то получить сильный перекос в сторону ТТК.

                      И с этим довольно долго помучился…
                      • НЛО прилетело и опубликовало эту надпись здесь
                          0
                          Если честно — не знаю. Работает, и не заморачиваюсь.
                          Основной трафик идет издалека, и он регулируется. Так что если от кого-то трафик будет идти только по одному каналу (как аплинк приказал, а такое скорее всего имеется), то основной массой трафика выравнивается.
                          Кстати, а и не всегда выравнивается! Ночью, когда трафика мало — диапазона регулировки часто не хватает. И между каналами идет перекос. Но это ночью, каналы почти пустуют, так что не страшно. Главная задача — не допустить переполнения каналов при эффективном их использовании, и задача эта успешно выполняется.

                          Хм… Вот и объяснение, почему ночью плохая балансировка (т.е. отклонение от заданного соотношения). А я все голову ломал…
                            0
                            PS. Думаю, найдется много AS, чей трафик указанным методом не выровнять. Но общая масса, особенно «в час пик», — выравнивается. И это для меня главное.
                        0
                        А ещё можно почитать есть ли у провов public community list, проверить там с каким localpref у них сидят префиксы от клиентов, а с каким — от транзитников. И потом от себя задавать на разные префиксы разные community чтоб приоритизировать траффик у прова. Это, конечно, берёт время, но результат будет на лицо. Prepend-ы это далеко не наше всё. Но, хотя, если у вас так работает, то заморачиваться нет смысла.

                        Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                        Самое читаемое