Изящная перезагрузка HAProxy с нулевой потерей пакетов


42

Я использую сервер балансировки нагрузки HAProxy для балансировки нагрузки на несколько серверов Apache. Мне нужно перезагрузить HAProxy в любой момент времени, чтобы изменить алгоритм балансировки нагрузки.

Все это прекрасно работает, за исключением того факта, что я должен перезагрузить сервер, не теряя ни одного пакета (в данный момент перезагрузка дает мне в среднем 99,76% успеха, при 1000 запросов в секунду в течение 5 секунд). Я провел много часов по этому поводу и нашел следующую команду для «изящной перезагрузки» HAProxy-сервера:

haproxy -D -f /etc/haproxy/haproxy.cfg -p /var/run/haproxy.pid -sf $(cat /var/run/haproxy.pid)

Тем не менее, это мало или совсем не влияет по сравнению с простым старым service haproxy reload, оно все еще падает в среднем на 0,24%.

Есть ли способ перезагрузить конфигурационный файл HAProxy без единого отброшенного пакета от любого пользователя?


6
Если вам нужна такая надежность, лучшим решением было бы запустить более одного экземпляра HAproxy, где вы можете вывести один из них из строя для перезагрузки, поставить его обратно и повторить для других.
yoonix

Ответы:


32

Согласно https://github.com/aws/opsworks-cookbooks/pull/40 и, следовательно, http://www.mail-archive.com/haproxy@formilux.org/msg06885.html вы можете:

iptables -I INPUT -p tcp --dport $PORT --syn -j DROP
sleep 1
service haproxy restart
iptables -D INPUT -p tcp --dport $PORT --syn -j DROP

Это приводит к удалению SYN перед перезапуском, так что клиенты будут повторно отправлять этот SYN, пока он не достигнет нового процесса.



обе эти команды дали мне это: iptables v1.4.14: invalid port/service --syn 'указано`
Дмитрий БД

5
@DmitriDB, который вы должны заменить $PORTреальным портом, haproxyкоторый прослушивает. Если HAProxy прослушивает несколько портов, записи заменить --dport $PORTс --dports $PORTS_SEPARATED_BY_COMMAS, например, --dports 80,443.
pepoluan

1
iptables 1.4.7 (Centos 6.7) - вы также должны указать -m mulitport, если хотите использовать --dports. Таким образом, его "iptables -I INPUT -p tcp -m multiport --dports 80,443 --syn -j DROP" и аналогично для -D
carpii

25

Yelp поделился более сложным подходом, основанным на тщательном тестировании. Статья в блоге - это глубокое погружение, которое стоит потраченных времени, чтобы полностью ее оценить.

True Zero Downtime HAProxy перезагружается

tl; dr использует Linux tc (управление трафиком) и iptables для временной очереди пакетов SYN, пока HAProxy перезагружается и имеет два pids, подключенных к одному и тому же порту ( SO_REUSEPORT).

Мне неудобно переиздавать всю статью о ServerFault; тем не менее, вот несколько выдержек, чтобы разжечь ваш интерес:

Задерживая SYN-пакеты, поступающие в наши балансировщики нагрузки HAProxy, которые работают на каждой машине, мы можем минимально влиять на трафик во время перезагрузок HAProxy, что позволяет нам добавлять, удалять и изменять служебные бэкэнды в нашей SOA, не опасаясь значительного влияния на пользовательский трафик.

# plug_manipulation.sh
nl-qdisc-add --dev=lo --parent=1:4 --id=40: --update plug --buffer
service haproxy reload
nl-qdisc-add --dev=lo --parent=1:4 --id=40: --update plug --release-indefinite

# setup_iptables.sh
iptables -t mangle -I OUTPUT -p tcp -s 169.254.255.254 --syn -j MARK --set-mark 1

# setup_qdisc.sh
## Set up the queuing discipline
tc qdisc add dev lo root handle 1: prio bands 4
tc qdisc add dev lo parent 1:1 handle 10: pfifo limit 1000
tc qdisc add dev lo parent 1:2 handle 20: pfifo limit 1000
tc qdisc add dev lo parent 1:3 handle 30: pfifo limit 1000

## Create a plug qdisc with 1 meg of buffer
nl-qdisc-add --dev=lo --parent=1:4 --id=40: plug --limit 1048576
## Release the plug
nl-qdisc-add --dev=lo --parent=1:4 --id=40: --update plug --release-indefinite

## Set up the filter, any packet marked with “1” will be
## directed to the plug
tc filter add dev lo protocol ip parent 1:0 prio 1 handle 1 fw classid 1:4

Суть: https://gist.github.com/jolynch/97e3505a1e92e35de2c0

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


Отличная ссылка! Но, возможно, вы хотели бы обобщить это здесь в случае истечения срока действия ссылки. Это единственная причина отсутствия голосов.
Мэтт

@Matt добавил некоторые выдержки и примеры кода
Стив Янсен

8

Есть еще один гораздо более простой способ перезагрузить haproxy с истинным нулевым временем простоя - он называется iptables flipping (статья на самом деле является ответом Unbounce на решение Yelp). Он чище, чем принятый ответ, так как нет необходимости отбрасывать какие-либо пакеты, что может вызвать проблемы при длительных перезагрузках.

Вкратце, решение состоит из следующих шагов:

  1. Давайте создадим пару экземпляров haproxy - первый активный, который получает трафик, и второй в режиме ожидания, который не получает трафик.
  2. Вы переконфигурируете (перезагружаете) резервный экземпляр в любое время.
  3. Как только резервный режим готов с новой конфигурацией, вы перенаправляете все НОВЫЕ соединения на резервный узел, который становится новым активным . Unbounce предоставляет скрипт bash, который выполняет переворот с помощью нескольких простых iptableкоманд .
  4. На мгновение у вас есть два активных экземпляра. Вам нужно подождать, пока не прекратятся открытые соединения со старым активным . Время зависит от вашего поведения службы и настроек поддержки активности.
  5. Трафик к старым активным остановкам, который становится новым резервным - вы вернулись к шагу 1.

Более того, решение может быть адаптировано к любому виду услуг (nginx, apache и т. Д.) И является более отказоустойчивым, так как вы можете протестировать резервную конфигурацию перед ее подключением к сети.


4

Редактировать: Мой ответ предполагает, что ядро ​​отправляет трафик только на самый последний порт, который открывается с помощью SO_REUSEPORT, тогда как фактически отправляет трафик всем процессам, как описано в одном из комментариев. Другими словами, танец iptables все еще необходим. :(

Если вы работаете с ядром, поддерживающим SO_REUSEPORT, такой проблемы быть не должно.

Процесс, который haproxy выполняет при перезапуске:

1) Попробуйте установить SO_REUSEPORT при открытии порта ( https://github.com/haproxy/haproxy/blob/3cd0ae963e958d5d5fb838e120f1b0e9361a92f8/src/proto_tcp.c#L792-L798 )

2) Попробуйте открыть порт (будет успешно с SO_REUSEPORT)

3) Если это не удалось, подайте сигнал старому процессу о закрытии порта, подождите 10 мс и попробуйте снова. ( https://github.com/haproxy/haproxy/blob/3cd0ae963e958d5d5fb838e120f1b0e9361a92f8/src/haproxy.c#L1554-L1577 )

Впервые он был поддержан в ядре Linux 3.9, но некоторые дистрибутивы поддержали его. Например, ядра EL6 из 2.6.32-417.el6 поддерживают это.


Это произойдет SO_REUSEPORTпо определенному сценарию, особенно в условиях интенсивного движения. Когда SYN отправляется старому процессу haproxy и в тот же момент он закрывает сокет прослушивания, что приводит к RST. См. Статью Yelp, упомянутую в другом ответе выше.
Гертас

4
Это отстой ... Просто, чтобы подвести итог проблемы, Linux распределяет новые соединения между всеми процессами, прослушивающими конкретный порт, когда используется SO_REUSEPORT, поэтому существует короткое время, когда старый процесс все еще будет получать соединения, помещенные в его очередь.
Джейсон Стаббс

2

Я объясню мои настройки и как я решил изящные перезагрузки:

У меня есть типичная установка с 2 узлами под управлением HAproxy и keepalived. Keepalived отслеживает интерфейс dummy0, поэтому я могу выполнить команду ifconfig dummy0 down для принудительного переключения.

Реальная проблема в том, что, я не знаю, почему, «перезагрузка haproxy» все еще отбрасывает все УСТАНОВЛЕННЫЕ соединения :( Я попробовал «переворачивание iptables», предложенное gertas, но я нашел некоторые проблемы, потому что он выполняет NAT в месте назначения IP-адрес, который не подходит для некоторых сценариев.

Вместо этого я решил использовать грязный хак CONNMARK, чтобы пометить пакеты, принадлежащие НОВЫМ соединениям, а затем перенаправить эти помеченные пакеты на другой узел.

Вот набор правил iptables:

iptables -t mangle -A PREROUTING -i eth1 -d 123.123.123.123/32 -m conntrack --ctstate NEW -j CONNMARK --set-mark 1
iptables -t mangle -A PREROUTING -j CONNMARK --restore-mark
iptables -t mangle -A PREROUTING -i eth1 -p tcp --tcp-flags FIN FIN -j MARK --set-mark 2
iptables -t mangle -A PREROUTING -i eth1 -p tcp --tcp-flags RST RST -j MARK --set-mark 2
iptables -t mangle -A PREROUTING -i eth1 -m mark ! --mark 0 -j TEE --gateway 192.168.0.2
iptables -t mangle -A PREROUTING -i eth1 -m mark --mark 1 -j DROP

Первые два правила помечают пакеты, принадлежащие новым потокам (123.123.123.123 - это VIP с поддержкой активности, используемый на haproxy для привязки внешних интерфейсов).

Третье и четвертое правила помечают пакеты FIN / RST. (Я не знаю почему, цель TEE "игнорирует" пакеты FIN / RST).

Пятое правило отправляет дубликат всех отмеченных пакетов другому HAproxy (192.168.0.2).

Шестое правило отбрасывает пакеты, принадлежащие новым потокам, чтобы предотвратить достижение их исходного назначения.

Не забудьте отключить rp_filter на интерфейсах, иначе ядро ​​отбросит эти марсианские пакеты.

И последнее по порядку, но не по значению, не забывайте возвращать пакеты В моем случае есть асимметричная маршрутизация (запросы приходят к клиенту -> haproxy1 -> haproxy2 -> веб-сервер, а ответы идут от веб-сервера -> haproxy1 -> клиент), но это не влияет. Работает нормально.

Я знаю, что самым элегантным решением было бы использовать iproute2 для переадресации, но он работал только для первого пакета SYN. Когда он получил ACK (3-й пакет 3-стороннего рукопожатия), он не пометил его :( Я не мог тратить много времени на исследование, как только я увидел, что он работает с целью TEE, он оставил его там. Конечно, не стесняйтесь попробовать это с iproute2.

По сути, «изящная перезагрузка» работает так:

  1. Я включаю набор правил iptables и сразу вижу новые соединения, идущие к другому HAproxy.
  2. Я слежу за «netstat -an | grep ESTABLISHED | wc -l», чтобы контролировать процесс «слива».
  3. Если есть только несколько (или ноль) соединений, «ifconfig dummy0 down», чтобы принудительно поддерживать keepalive в состоянии отработки отказа, поэтому весь трафик будет идти к другому HAproxy.
  4. Я удаляю набор правил iptables
  5. (Только для "keepalive config" без прерывания) "ifconfig dummy0 up".

Набор правил IPtables можно легко интегрировать в скрипт запуска / остановки:

#!/bin/sh

case $1 in
start)
        echo Redirection for new sessions is enabled

#       echo 0 > /proc/sys/net/ipv4/tcp_fwmark_accept
        for f in /proc/sys/net/ipv4/conf/*/rp_filter; do echo 0 > $f; done
        iptables -t mangle -A PREROUTING -i eth1 ! -d 123.123.123.123 -m conntrack --ctstate NEW -j CONNMARK --set-mark 1
        iptables -t mangle -A PREROUTING -j CONNMARK --restore-mark
        iptables -t mangle -A PREROUTING -i eth1 -p tcp --tcp-flags FIN FIN -j MARK --set-mark 2
        iptables -t mangle -A PREROUTING -i eth1 -p tcp --tcp-flags RST RST -j MARK --set-mark 2
        iptables -t mangle -A PREROUTING -i eth1 -m mark ! --mark 0 -j TEE --gateway 192.168.0.2
        iptables -t mangle -A PREROUTING -i eth1 -m mark --mark 1 -j DROP
        ;;
stop)
        iptables -t mangle -D PREROUTING -i eth1 -m mark --mark 1 -j DROP
        iptables -t mangle -D PREROUTING -i eth1 -m mark ! --mark 0 -j TEE --gateway 192.168.0.2
        iptables -t mangle -D PREROUTING -i eth1 -p tcp --tcp-flags RST RST -j MARK --set-mark 2
        iptables -t mangle -D PREROUTING -i eth1 -p tcp --tcp-flags FIN FIN -j MARK --set-mark 2
        iptables -t mangle -D PREROUTING -j CONNMARK --restore-mark
        iptables -t mangle -D PREROUTING -i eth1 ! -d 123.123.123.123 -m conntrack --ctstate NEW -j CONNMARK --set-mark 1

        echo Redirection for new sessions is disabled
        ;;
esac
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.