Pull to refresh

Балансировка входящих соединений на iptables

Reading time3 min
Views26K
Предположим, что у вас есть некий сервис, принимающий входящие соединения и возникла задача балансировки нагрузки и/или отказоустойчивости.

В общем виде схема выглядит так:
клиент ----> балансировщик ---> бэкенд (сервис)


Готовых балансировщиков под конкретные нужды множество. Например, 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


Заключение


С одной стороны наверное нет веских причин делать именно так, но с другой можно их придумать.
  1. iptables работает на уровне ядра и скорее всего будет быстрее любого другого балансировщика
  2. iptables надежнее. Например, не факт, что когда между балансировщиками произойдет failover, тот же haproxy успешно стартанет, т.к. для старта ему нужно гораздо больше условий. Например, IP-адрес 10.0.0.1 должен быть перехвачен и только после этого можно будет запускать haproxy на 25-м порту. Например, кто-то за это время испортил его файл конфигурации и он вообще не стартанет.
Tags:
Hubs:
+35
Comments16

Articles

Change theme settings