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 2014年

Answers:


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'指定`
Dmitri DB

5
您应该将@DmitriDB替换为正在监听$PORT的实际端口haproxy。如果haproxy在多个端口上侦听,请用替换--dport $PORT--dports $PORTS_SEPARATED_BY_COMMAS,例如--dports 80,443
pepoluan 2014年

1
iptables 1.4.7(Centos 6.7)-如果要使用--dports,则还必须指定-m mulitport。因此,其“ iptables -I INPUT -p tcp -m multiport --dports 80,443 --syn -j DROP”,以及-D
carpii,2015年

25

Yelp分享了一种基于细致测试的更为复杂的方法。博客文章深入探讨,值得花时间投入以充分欣赏它。

真正的零停机HAProxy重新加载

tl; dr使用Linux tc(流量控制)和iptables在HAProxy重新加载时使SYN数据包暂时排队,并且两个pid附加到同一端口(SO_REUSEPORT)。

我不愿意在ServerFault上重新发布整篇文章;不过,这里有一些摘录可以激起您的兴趣:

通过延迟进入在每台计算机上运行的HAProxy负载均衡器中的SYN数据包,我们可以在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,分享如此惊人的见解。


优秀的链接!但也许您想在这里总结一下,以防链接过期。这是不投票的唯一原因。
马特2015年

@Matt添加了一些摘录和代码示例
Steve Jansen

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)如果未成功,请通知旧进程关闭其端口,等待10ms,然后重试。(https://github.com/haproxy/haproxy/blob/3cd0ae963e958d5d5fb838e120f1b0e9361a92f8/src/haproxy.c#L1554-L1577

它最初是在Linux 3.9内核中受支持的,但是一些发行版已经对其进行了反向移植。例如,2.6.32-417.el6中的EL6内核支持它。


这将SO_REUSEPORT在某些特定情况下发生-特别是在交通繁忙的情况下。当SYN发送到旧的haproxy进程时,同时关闭监听套接字,这将导致RST。请参阅上面其他答案中提到的Yelp文章。
gertas 2015年

4
真令人毛骨悚然...总结一下这个问题,当使用SO_REUSEPORT时,Linux会在侦听特定端口的所有进程之间分配新的连接,因此在很短的时间内,旧进程仍会将连接放入其队列中。
杰森·斯塔布斯

2

我将解释我的设置以及如何解决优雅的重载:

我有一个典型的安装程序,其中有2个节点运行HAproxy并保持活动状态。Keepalived跟踪接口dummy0,因此我可以执行“ ifconfig dummy0 down”来强制切换。

真正的问题是,我不知道为什么,“ haproxy reload”仍然会丢弃所有已建立的连接:(我尝试了gertas提出的“ iptables fliping”,但是我发现了一些问题,因为它在目标上执行了NAT IP地址,在某些情况下不是合适的解决方案。

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

这是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是在代理服务器上用于绑定前端的keepalived VIP)。

第三和第四条规则将数据包标记为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已建立| wc -l”以监督“排水”过程。
  3. 一旦只有几个(或零个)连接,请“ ifconfig dummy0 down”以强制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
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.