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 --повідомляє 80,443 --syn -j DROP" і так само для -D
carpii

25

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

Правда Zero простою HAProxy Reloads

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

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

Якщо коротко, рішення складається з наступних етапів:

  1. У нас є пара екземплярів хапрокси - перший активний, який отримує трафік, а другий у режимі очікування, який не отримує ніякого трафіку.
  2. Ви в будь-який час переконфігуруєте (перезавантажте) екземпляр очікування.
  3. Після готовності з новим конфігурацією ви переймете всі НОВІ з'єднання до вузла очікування, який стає новим активним . Unbounce надає bash script, який робить фліп з кількома простими 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

Я поясню свої налаштування та те, як я вирішив витончені перезавантаження:

У мене типова установка з двома вузлами, на яких працює HAproxy та кеєпалівед. Keepalived відстежує інтерфейс dummy0, тому я можу зробити "ifconfig dummy0 вниз", щоб примусити переключитися.

Справжня проблема полягає в тому, що я не знаю чому, "перезарядження haproxy" все ще скидає всі встановлені з'єднання :( я спробував "iptables flipping", запропонований 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-збережений VIP, який використовується на haproxy для прив'язки фронталів).

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

П'яте правило надсилає дублікат усіх позначених пакетів іншому HAproxy (192.168.0.2).

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

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

І останнє, але не менш важливе, майте на увазі повертаються пакети! У моєму випадку існує асиметрична маршрутизація (запити надходять до клієнта -> haproxy1 -> haproxy2 -> веб-сервер, а відповіді йдуть від webserver -> haproxy1 -> client), але це не впливає. Це чудово працює.

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

В основному, "витончене перезавантаження" працює так:

  1. Я включаю набір правил iptables і негайно бачу нові з'єднання, що йдуть до іншого HAproxy.
  2. Я стежу за "netstat -an | grep Встановлено | wc -l", щоб контролювати процес "зливу".
  3. Після того, як є лише декілька (або нульових) з'єднань, "ifconfig dummy0 down", щоб змусити переконатися у відмові, тому весь трафік буде спрямований на інший HAproxy.
  4. Я видаляю набір правил iptables
  5. (Тільки для "не випереджаючої" конфігурації збереження) "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.