Назад | Перейти на главную страницу

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

Я использую сервер балансировки нагрузки 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 без единого сброшенного пакета от любого пользователя?

В соответствии с 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, пока он не достигнет нового процесса.

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

Истинное нулевое время простоя HAProxy Reloads

tl; dr используйте Linux tc (управление трафиком) и iptables для временной постановки в очередь SYN-пакетов, пока HAProxy перезагружается и имеет два идентификатора pid, подключенных к одному и тому же порту (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 за то, что поделились такими удивительными идеями.

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

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

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

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

Изменить: в моем ответе предполагается, что ядро ​​отправляет трафик только на самый последний порт, который должен быть открыт с помощью 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 поддерживают его.

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

У меня типичная установка с 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).

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

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

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

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

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

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

  1. Я включаю набор правил iptables и сразу вижу, что новые подключения идут к другому HAproxy.
  2. Я слежу за "netstat -an | grep ESTABLISHED | wc -l", чтобы контролировать процесс "слива".
  3. Как только имеется всего несколько (или ноль) подключений, «ifconfig dummy0 отключен», чтобы заставить keepalived переключиться на отказ, чтобы весь трафик шел на другой HAproxy.
  4. Я удаляю набор правил iptables
  5. (Только для конфигурации keepalive "без вытеснения") "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