采用磁悬浮技术的高可用性负载均衡器

2020-06-11 01:05:12

我们运行许多后端服务,为我们的客户控制面板、API和我们边缘可用的功能提供支持。我们拥有并运营后端服务的物理基础设施。我们需要一种有效的方法在服务之间路由任意TCP和UDP流量,也需要从这些数据中心外部路由任意TCP和UDP流量。

以前,这些后端服务的所有流量在到达可用实例之前都会经过多层有状态TCP代理和NAT。此解决方案运行了几年,但随着我们的发展,它给我们的服务和运营团队带来了许多问题。我们的服务团队需要处理可用性下降的问题,而我们的运营团队在需要对负载均衡服务器进行维护时需要付出很大的努力。

考虑到我们的有状态TCP代理和NAT解决方案的经验,我们有几个目标来替代负载平衡服务,同时保留在我们自己的基础设施上:

通过将决策路由到目的服务器来保留源IP。这使我们能够支持需要客户端IP地址作为其操作一部分的服务器,而无需X-Forwarded-For标头或代理TCP扩展等变通方法。

支持后端跨多个机架和子网的架构。这可以防止现有网络设备无法路由的解决方案。

允许运营团队在零停机时间内执行维护。我们应该能够随时删除负载均衡器,而不会导致服务的任何连接重置或停机。

使用常见且经过良好测试的Linux工具和功能。Linux中有很多非常酷的网络特性,我们可以进行试验,但我们希望为那些主要不使用这些负载均衡器的操作员优化一些最不令人惊讶的行为。

负载平衡器之间没有显式连接同步。我们发现,负载均衡器之间的通信显著增加了系统复杂性,从而增加了出错的机会。

允许从先前的负载平衡器实现分阶段部署。我们应该能够在两个实现之间迁移特定服务的流量,以发现问题并获得对系统的信心。

以前,当流量到达我们的后端数据中心时,我们的路由器会挑选数据包并将其转发到它所知道的L4负载均衡器服务器之一。这些L4负载均衡器将确定流量用于什么服务,然后将流量转发到服务的L7服务器之一。

此架构在正常运行期间运行良好。但是,只要负载平衡器集发生更改,问题就会很快浮出水面。我们的路由器会将流量转发到新集,并且流量很可能会到达与以前不同的负载均衡器。由于每个负载平衡器维护其自己的连接状态,因此它将无法为这些新的正在进行的连接转发流量。然后,这些连接将被重置,这可能会给我们的客户带来错误。

在正常运行期间,我们的新架构具有与以前设计类似的行为。我们的路由器将选择L4负载均衡器,然后将流量转发到服务的L7服务器。

当负载均衡器集合发生变化时,会发生重大变化。因为我们的负载均衡器现在是无状态的,所以路由器选择将流量转发到哪个负载均衡器并不重要,它们最终将到达相同的后端服务器。

我们的负载均衡器服务器使用BGP向数据中心的路由器通告服务IP地址,与以前的解决方案相同。我们的路由器根据一种称为等价多路径路由(ECMP)的路由策略来选择哪些负载均衡器将接收数据包。

ECMP对数据包中的信息进行散列处理,以选择该数据包的路径。路由器使用的散列函数通常固定在固件中。选择不良散列函数或选择不良输入的路由器可能会造成网络和服务器负载失衡,或破坏协议层的假设。

我们与网络团队合作,确保在我们的路由器上配置ECMP,使其仅根据数据包的5元组(协议、源地址和端口以及目的地址和端口)进行散列。

为了进行维护,我们的运营商可以撤回BGP会话,流量将透明地转移到其他负载均衡器。但是,如果负载平衡器突然变得不可用(例如内核死机或电源故障),则在BGP保持连接机制失败和路由器终止会话之前会有一段短暂的延迟。

在路由器和负载均衡器之间使用双向转发检测(BFD)协议,路由器可以在更短的延迟后终止BGP会话。不同的路由器对BFD有不同的限制和限制,这使得它很难在频繁使用L2链路聚合和VXLAN的环境中使用。

我们正在继续与我们的网络团队合作,使用他们最熟悉的工具和配置来寻找解决方案,以减少终止BGP会话的时间。

为了确保所有负载均衡器都将流量发送到相同的后端,我们决定使用磁悬浮连接调度器。MagLev是一个一致的散列调度器,它对来自每个数据包的5元组信息(协议、源地址和端口以及目的地址和端口)进行散列,以确定后端服务器。

由于是一致的散列,每个负载均衡器都会为数据包选择相同的后端服务器,而不需要保持任何连接状态。这允许我们透明地在负载均衡器之间移动流量,而不需要它们之间显式的连接同步。

在可能的情况下,我们希望使用普通而可靠的Linux功能。自本世纪初以来,Linux已经实现了一个功能强大的第4层负载均衡器,即IP虚拟服务器(IPVS)。IPVS从Linux4.18开始就支持磁悬浮调度器。

我们的负载均衡器和应用服务器分布在多个机架和子网中。为了路由来自负载均衡器的流量,我们选择使用Foo-over-UDP封装。

在foo-over-UDP封装中,在原始数据包周围添加新的IP和UDP报头。当这些数据包到达目的服务器时,Linux内核会删除外部IP和UDP报头,并将内部有效负载重新插入网络堆栈进行处理,就好像数据包最初是在该服务器上接收的一样。

与其他封装方法(如IPIP、GUE和Geneve)相比,我们觉得Foo-over-UDP在功能和灵活性之间取得了很好的平衡。直接服务器返回(其中应用程序服务器直接回复客户端并绕过负载均衡器)作为封装的副产品实现。没有与封装相关联的状态,每台服务器只需要一个封装接口即可从所有负载平衡器接收流量。

#加载IPV和FOU所需的内核模块。$modProbe IP_VS&;&;modProbe IP_VS_mh&;&;modProbe Fou#在负载均衡器和#一个应用服务器之间创建一条隧道。IP就是机器';#网络上的真实IP。$IP link add name lbtun1 type ipip\remote 192.0.2.1 local 192.0.2.2 ttl 2\encap fou encap-SPORT auto encap-dport 5555#通知内核有关可能在此处通告的VIP的信息。$IP route add table local local 198.51.100.0/24\dev lo proto kernel#为隧道提供此计算机本地的IP地址。#此计算机上发往此IP地址的通信将#沿隧道向下发送。并且它应该使用#MagLev调度程序。$ipvsadm-A-t 198.51.100.1:80-s mh#告诉IPV此服务的后端。$ipvsadm-a-t 198.51.100.1:80-r 203.0.113.1:80。

#可能需要加载内核模块。$modproto fou#设置IPIP接收器。#ipproto 4=IPIP(不是IPv4)$ip for add port 5555 ipproto 4#启动隧道。$ip link set dev tunl0 up#在隧道接口上禁用反向路径筛选。$sysctl-w net.ipv4.conf.tunl0.rp_filter=0$sysctl-w net.ipv4.conf.all.rp_filter=0。

IPVS不支持将基于UDP的Foo-over-UDP作为数据包转发方法。为了解决这个限制,我们创建了虚拟接口来实现foo-over-UDP封装。然后,我们可以使用IPVS的直接数据包转发方法和内核路由表来选择特定的接口。

Linux通常被配置为忽略到达与用于应答的接口不同的接口的数据包。由于数据包现在将到达虚拟Tunl0&34;接口,我们需要在此接口上禁用反向路径过滤。内核使用命名接口和所有接口中较高的值,因此您可能需要减少";所有";并调整其他接口。

我们从互联网接受的最大IPv4数据包大小或最大传输单位(MTU)是1500字节。为了确保我们在封装期间不会对这些数据包进行分段,我们将内部MTU从默认值增加,以适应IP和UDP报头。

团队低估了在我们所有的设备机架上更换MTU的复杂性。我们必须调整所有路由器和交换机的MTU,调整绑定和VXLAN接口的MTU,最后调整我们的FOO-Over-UDP封装。即使经过精心策划的推广,我们仍然发现了交换机和服务器堆栈中与MTU相关的错误,其中许多错误首先表现为网络其他部分的问题。

我们编写了一个运行在每个负载均衡器上的GO代理,它与跟踪服务位置的控制平面层同步。然后,代理根据活动服务和可用的后端服务器配置系统。

为了配置IPV和路由表,我们正在使用基于NetLink Go包构建的包。我们重新开源了我们今天构建的IPVS NetLink包,它支持查询、创建和更新IPVS虚拟服务器、目的地和统计数据。

不幸的是,iptables没有正式的编程接口,所以我们必须改为执行iptables二进制文件。代理计算一组理想的iptables链和规则,然后将其与活动规则进行协调。

*filter-A input-d 198.51.100.0/24-m COMMENT--COMMENT\";LEIF:nhAi5v93jwQYcJuK";-j LEIFR-LB-A LEIFR-LB-d 198.51.100.1/32-p TCP-m COMMENT--COMMENT\";LEIF:G4qtNUVFCkLCu4yt";-m multiport-dports 80-j Leiport。-j Accept-A Leiur-GQ4OKHRLCJYOWIN9-s 172.16.0.0/12-m COMMENT--COMMENT\";LEIF:0XxZ2OwlQWzIYFTD";-j Accept。

规则的iptables输出可能与理想规则提供的输入有很大不同。为了避免在比较中解析整个iptables规则,我们将规则的散列(包括链中的位置)存储为iptables注释。然后,我们可以将注释与理想规则进行比较,以确定是否需要采取任何操作。在共享的链(如输入)上,代理忽略非托管规则。

我们使用这里介绍的网络负载均衡作为Kubernetes的负载均衡。控制器为请求负载均衡IP的Kubernetes服务分配虚拟IP地址。这些IP由代理在IPVS中配置。流量将定向到群集节点的子集,由Kube-Proxy处理,除非外部流量策略设置为";Local";,在这种情况下,流量将发送到运行工作负载的特定后端。

这允许我们拥有内部Kubernetes集群,更好地复制云提供商上托管集群的负载均衡器行为。运行Kubernetes的服务(如入口控制器、API网关和数据库)可以访问负载均衡流量的正确客户端IP地址。

继续密切关注IPV和替代方案的未来发展,包括nftlb和Express data path(XDP)以及eBPF。

迁移到nftables。单一的优先级和缺乏iptables的可编程接口使得它不适合将自动规则与操作员添加的规则一起包含。我们希望随着更多的项目和运营转移到nftable,我们将能够在不造成运营盲点的情况下切换到nftable。

由于BGP保留计时器,负载均衡器故障可能会导致临时中断。我们希望改进我们处理BGP会话故障的方式。

研究使用轻量级隧道来减少负载均衡器节点上需要的foo-over-UDP接口数。

使用Linux实现多层负载均衡。文森特·伯纳特(2018)。描述使用IPVS和IPIP的网络负载平衡器。

介绍GitHub负载均衡器。乔·威廉姆斯,西奥·朱利安(2017)。描述了类似的拆分第4层和第7层架构,使用一致的哈希和UDP上的Foo-over-UDP。他们似乎对IPV有一些限制,这些限制似乎已经解决了。

FOO OVER UDP。乔纳森·科比特(2014)。描述IPIP和当时刚刚介绍的foo-over-UDP的基础知识。

UDP封装、FOU、GUE和RCO。汤姆·赫伯特(2015)。介绍不同的UDP封装选项。

BFD(双向转发检测):有效吗?值得吗?汤姆学校(2009)。讨论使用通用协议的BFD,以及它在哪里会成为问题。

负载平衡磁悬浮可用性