Предположим, что у вас есть некий сервис, принимающий входящие соединения и возникла задача балансировки нагрузки и/или отказоустойчивости.
В общем виде схема выглядит так:
клиент ----> балансировщик ---> бэкенд (сервис)
Готовых балансировщиков под конкретные нужды множество. Например, nginx — отличный балансировщик для веб-приложений, haproxy для tcp-соединений.
- Вы не ищете легких путей
- Вам скучно и захотелось чего-нибудь новенького
- Только iptables, только хардкор
На самом деле все зависит от конкретной ситуации, поэтому давайте я просто напишу рецепт и по ходу дела вы решите для себя сами где он может пригодиться.
Рецепт
Для балансировки будем использовать модули statistic, condition и маркировку соединений.
Не все эти модули есть по-умолчанию. В старых версиях linux вместо statistic ищите nth, а condition так возможно придется поставить руками с сайта проекта
Теперь предположим, что ваш сервис — это smtp-сервер.
Внешний ip-адрес пусть будет 10.0.0.1
Внутренние ip-адреса сервисов будут 192.168.0.1 и 192.168.0.2
Создадим в таблице mangle следующий набор правил.
*mangle :MAILMARK - [0:0] -A MAILMARK -j CONNMARK --restore-mark -A MAILMARK -m mark --mark 0x0 -m statistic --mode nth --every 2 -m condition ! --condition mail1up -j MARK --set-mark 1 -A MAILMARK -m mark --mark 0x0 -m condition ! --condition mail2up -j MARK --set-mark 2 -A MAILMARK -m mark --mark 0x0 -m condition ! --condition mail1up -j MARK --set-mark 1 -A MAILMARK -j CONNMARK --save-mark -A PREROUTING -d 10.0.0.1 -p tcp --dport 25 -j MAILMARK COMMIT
а в таблице nat следующий:
-A PREROUTING -d 10.0.0.1 -p tcp --dport 25 -i bond0.204 -m mark --mark 1 -j DNAT --to-destination 192.168.0.1 -A PREROUTING -d 10.0.0.1 -p tcp --dport 25 -i bond0.204 -m mark --mark 2 -j DNAT --to-destination 192.168.0.2
Вот собственно и все, теперь осталось пояснить как это все работает.
Сначала любой входящий пакет на внешний адрес 10.0.0.1 на 25 порт проходит через таблицу mangle и правило маркировки
-A PREROUTING -d 10.0.0.1 -p tcp --dport 25 -j MAILMARK
Для маркировки создана отдельная цепочка MAILMARK, которая работает следующим образом.
Если соединение уже было промаркировано, то маркируем пакет тем же значением, что и соединение.
-A MAILMARK -j CONNMARK --restore-mark
Таким образом пакеты одного соединения в последствии попадут на один бэкенд сервиса.
Если пакет не промаркирован, то маркируем его следующим правилом.
-A MAILMARK -m mark --mark 0x0 -m statistic --mode nth --every 2 -m condition ! --condition mail1up -j MARK --set-mark 1
-m mark --mark 0x0
условие того, что пакет еще не промаркирован
-m statistic --mode nth --every 2
маркируем каждый второй пакет. Т.е. мы балансируем нагрузку очень просто 50/50, но это не единственная возможность (см. random и statistic)
-m condition ! --condition mail1up
условие того, что бэкенд живой
-j MARK --set-mark 1
маркируем пакет меткой со значением 1
Если пакет остался не промаркирован, то маркируем его меткой 2, при условии, что бэкенд живой
-A MAILMARK -m mark --mark 0x0 -m condition ! --condition mail2up -j MARK --set-mark 2
Если пакет все еще остался не промаркирован, то видимо второй бэкенд мертвый, а первая метка не поставилась, т.к. нам либо не повезло с 50/50, либо первый бэкенд тоже умер. На случай если первый бэкенд все же живой маркируем пакет меткой 1
-A MAILMARK -m mark --mark 0x0 -m condition ! --condition mail1up -j MARK --set-mark 1
Последним правилом запоминаем для текущего соединения проставленную метку
-A MAILMARK -j CONNMARK --save-mark
После того, как пакет промаркирован, он будет транслирован на выбранный бэкенд правилами, которые были добавлены в таблицу nat
Осталось осветить еще один момент, связанный с тем, как iptables поймет, что бэкенд живой.
Да никак, это должны сделать вы сами и положить в файлы /proc/net/nf_condition/mail1up и /proc/net/nf_condition/mail2up 0 или 1, в зависимости от того жив бэкенд или мертв.
По-умолчанию при старте iptables в этих файлах будет 0.
Правила, которые приведены, рассчитаны на то, что 0 означает, что бэкенд живой.
Например, для проверки smtp сервера я использую следующий bash-скрипт
#!/bin/bash
get () {
response=$(echo "QUIT" | nc -w 5 $1 25 | head -1 | awk '{print $1}')
if [ "$response" == "220" ]
then
echo "0"
else
echo "1"
fi
}
echo $(get 192.168.0.1) > /proc/net/nf_condition/mail1up
echo $(get 192.168.0.2) > /proc/net/nf_condition/mail2up
Заключение
С одной стороны наверное нет веских причин делать именно так, но с другой можно их придумать.
- iptables работает на уровне ядра и скорее всего будет быстрее любого другого балансировщика
- iptables надежнее. Например, не факт, что когда между балансировщиками произойдет failover, тот же haproxy успешно стартанет, т.к. для старта ему нужно гораздо больше условий. Например, IP-адрес 10.0.0.1 должен быть перехвачен и только после этого можно будет запускать haproxy на 25-м порту. Например, кто-то за это время испортил его файл конфигурации и он вообще не стартанет.
Автор: gmlexx