HAProxy 优雅重载,零数据包丢失

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 配置文件,并且不丢失任何用户的数据包?

答案1

根据https://github.com/aws/opsworks-cookbooks/pull/40因此http://www.mail-archive.com/[电子邮件保护]/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 直到到达新进程。

答案2

Yelp 分享了一种基于细致测试的更复杂的方法。这篇博客文章深入探讨,值得花时间去充分领会。

真正的零停机 HAProxy 重新加载

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

我不太愿意重新发布有关 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 分享如此精彩的见解。

答案3

还有另一种更简单的方法可以真正实现零停机重新加载 haproxy - 它被命名为iptables 翻转(这篇文章实际上是 Unbounce 对 Yelp 解决方案的回应)。它比公认的答案更简洁,因为不需要丢弃任何可能导致长时间重新加载问题的数据包。

简而言之,该解决方案包括以下步骤:

  1. 我们有一对 haproxy 实例 - 第一个处于活动状态,接收流量,第二个处于待机状态,不接收任何流量。
  2. 您可以随时重新配置(重新加载)备用实例。
  3. 一旦备用节点准备好新的配置,你就将所有新连接转移到备用节点,这将新的活跃.Unbounce 提供iptablebash 脚本使用几个简单的命令进行翻转
  4. 目前有两个活动实例。您需要等待打开的连接才能旧活动将停止。时间取决于您的服务行为和保持活动设置。
  5. 交通旧活动停止,变成新待机- 您已返回步骤 1。

此外,该解决方案可以适用于任何类型的服务(nginx,apache等),并且具有更强的容错能力,因为您可以在上线之前测试备用配置。

答案4

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

我有一个典型的设置,其中 2 个节点运行 HAproxy 和 keepalived。Keepalived 跟踪接口 dummy0,因此我可以执行“ifconfig dummy0 down”来强制切换。

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

相反,我决定使用 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 是 haproxy 上用于绑定前端的 keepalived VIP)。

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

第五条规则将所有标记数据包的副本发送到另一个 HAproxy(192.168.0.2)。

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

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

最后但并非最不重要的一点是,请注意返回的数据包! 在我的例子中,存在不对称路由(请求到达客户端 -> haproxy1 -> haproxy2 -> web 服务器,回复从 web 服务器 -> haproxy1 -> 客户端),但它不会产生影响。 它工作正常。

我知道最优雅的解决方案是使用 iproute2 进行转移,但它只对第一个 SYN 数据包有效。当它收到 ACK(三次握手的第 3 个数据包)时,它没有标记它 :( 我无法花太多时间进行调查,当我看到它与 TEE 目标一起工作时,它就把它留在那里了。当然,您可以随意尝试使用 iproute2。

基本上,“优雅重新加载”的工作原理如下:

  1. 我启用 iptables 规则集并立即看到新连接转至其他 HAproxy。
  2. 我密切关注“netstat -an | grep ESTABLISHED | wc -l”来监督“排水”过程。
  3. 一旦只剩下几个(或零个)连接,“ifconfig dummy0 down”将强制 keepalived 进行故障转移,因此所有流量将转至另一个 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

相关内容