我正在运行一个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文件没有从任何用户丢弃一个单一的数据包?
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包含以下步骤:
iptable
命令进行翻转 。 此外,该解决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来试试。
基本上,“优雅的重装”是这样工作的:
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