HAProxy优雅重新加载零丢包

我正在运行一个HAProxy负载平衡服务器来平衡负载到多个Apache服务器。 我需要重新加载HAProxy在任何给定的时间,以改变负载平衡algorithm。

这一切都工作正常,除了我不得不重新加载服务器,而不会丢失一个数据包(目前重新加载99.76%平均成功,每秒1000个请求5秒)。 我已经做了许多小时的研究,并且已经find了下面的命令来“正常地重新载入”HAProxy服务器:

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

然而,这与旧的service haproxy reload相比,效果不大或没有影响,平均下降了0.24%。

是否有任何方法重新加载HAProxyconfiguration文件没有从任何用户丢弃一个单一的数据包?

根据https://github.com/aws/opsworks-cookbooks/pull/40和http://www.mail-archive.com/[email protected]/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在精心testing的基础上分享了更复杂的方法。 博客文章是一个深入的潜水,值得花时间投资,充分感谢它。

真正的零停机HAProxy重新加载

tl; dr使用Linux tc(stream量控制)和iptables在HAProxy重新加载时临时排队SYN数据包,并且有两个pid连接到相同的端口( SO_REUSEPORT )。

我很不习惯在ServerFault上重新发布整篇文章; 不过,这里有一些摘录来激起你的兴趣:

通过延迟SYN数据包进入我们在每台机器上运行的HAProxy负载均衡器,我们能够在HAProxy重新加载期间对stream量产生最小的影响,这使我们能够在SOA内添加,移除和更改服务后端,而不用担心会显着影响用户stream量。

 # 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翻转 (文章实际上是对Yelp解决scheme的Unbounce响应)。 它比接受的答案更清洁,因为不需要丢弃任何可能导致长时间重新加载的问题的数据包。

简而言之,解决scheme包含以下步骤:

  1. 让我们有一对haproxy实例 – 第一个接收stream量的活动,第二个处于待机状态,不接收任何stream量。
  2. 您可以随时重新configuration(重新加载)备用实例。
  3. 一旦待机准备好新的configuration,您将所有的新连接转移到备用节点,成为新的活动 。 Unbounce提供了bash脚本,它用less量简单的iptable命令进行翻转 。
  4. 有一刻你有两个活动的实例。 你需要等待,直到打开连接到旧的活动将停止。 时间取决于您的服务行为和保持活动的设置。
  5. 交通到旧的主动站,成为新的待机 – 你回到了第1步。

此外,该解决scheme可用于任何types的服务(nginx,Apache等),并且更容错,因为您可以在联机之前testing待机configuration。

如果你在一个支持SO_REUSEPORT的内核上,那么这个问题就不应该发生。

haproxy重启时的过程是:

1)打开端口时尝试设置SO_REUSEPORT( https://github.com/haproxy/haproxy/blob/3cd0ae963e958d5d5fb838e120f1b0e9361a92f8/src/proto_tcp.c#L792-L798

2)尝试打开端口(将通过SO_REUSEPORT成功)

3)如果没有成功,发信号通知旧的进程closures它的端口,等待10ms再试一遍。 ( https://github.com/haproxy/haproxy/blob/3cd0ae963e958d5d5fb838e120f1b0e9361a92f8/src/haproxy.c#L1554-L1577

它在Linux 3.9内核中首次得到支持,但一些发行版已经支持它。 例如,2.6.32-417.el6的EL6内核支持它。

我会解释我的设置,以及我如何解决优雅的重新加载:

我有一个典型的设置与2个节点运行HAproxy和keepalived。 Keepalived轨道接口dummy0,所以我可以做一个“ifconfig dummy0下”强制切换。

真正的问题是,我不知道为什么,一个“haproxy重新加载”仍然掉落所有ESTABLISHED连接:(我尝试了由gertas提出的“iptables翻转”,但我发现一些问题,因为它执行目的地的NAT IP地址,这在某些情况下不是合适的解决scheme。

相反,我决定使用CONNMARK肮脏的黑客来标记属于NEW连接的数据包,然后将这些标记的数据包redirect到另一个节点。

这是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 

前两条规则标记属于新stream的数据包(123.123.123.123是在haproxy上使用的keepalived VIP来绑定前端)。

第三和第四条规则标记包FIN / RST包。 (我不知道为什么,TEE目标“忽略”FIN / RST包)。

第五条规则将所有标记的数据包复制到另一个HAproxy(192.168.0.2)。

第六条规则丢弃属于新stream的数据包,以防止到达其原始目的地。

请记住在接口上禁用rp_filter,否则内核将丢弃这些火星包。

最后但并非最不重要,请注意返回的数据包! 在我的情况下,有不对称的路由(请求来到客户端 – > haproxy1 – > haproxy2 – > web服务器,并回答从web服务器 – > haproxy1 – >客户端),但它不会影响。 它工作正常。

我知道最优雅的解决scheme是使用iproute2做转移,但它只适用于第一个SYN数据包。 当它收到ACK(三次握手的第三个数据包)时,它没有标记:(我不能花太多的时间来调查,只要我看到它与TEE目标一起工作,就把它留在那里。当然,可以随意用iproute2来试试。

基本上,“优雅的重装”是这样工作的:

  1. 我启用iptables规则集,并立即看到去其他HAproxy的新连接。
  2. 我一直关注“netstat -an | grep ESTABLISHED | wc -l”来监督“排水”过程。
  3. 一旦只有几个(或零)连接,“ifconfig dummy0 down”强制保持故障切换,所有stream量将转到另一个HAproxy。
  4. 我删除了iptables规则集
  5. (仅限于“抢占”keepaliveconfiguration)“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