在前面的文章中,笔者介绍了 Linux Netfilter 基础框架,并利用 iptables 工具对网络流量进行了干预。今天笔者来简单聊一聊 ipvs(IP Virtual Server) 技术。理解其基本原理,将对我们后续学习各种负载均衡技术,网络架构等提供加速度。
ipvs 概念
说起 ipvs 就不得不提章文嵩博士,因为 LVS(Linux Virtual Server)项目最初是由他开发并维护的,本文的很多内容都摘录于此。官方对 ipvs 的介绍如下:
IPVS (IP Virtual Server) implements transport-layer load balancing inside the Linux kernel, so called Layer-4 switching. IPVS running on a host acts as a load balancer at the front of a cluster of real servers, it can direct requests for TCP/UDP based services to the real servers, and makes services of the real servers to appear as a virtual service on a single IP address.
Status: The ipvs 1.2.1 is the latest stable version, it is in the official kernel 2.6.10 released on December 24, 2004.
……
……
从中我们可以知道:
- 从 Linux Kernel 2.4.x 开始,ipvs 已内置到了 Linux 内核当中,目前最新的稳定版本是 2004 年发布的 1.2.1;
- ipvs 基于传输层(TCP/UDP)实现负载均衡能力,所以是四层交换;
- ipvs 服务所运行的机器,扮演着负载均衡器的角色。它拦截网络流量,并利用相关算法,将流量路由到后端某台真实的服务器上;
- ……
其实,ipvs 不仅能做负载均衡,转发报文,还具备设置链路权重、动态重定向等功能。
了解了 ipvs 基本概念之后,我们来看看其底层的基本逻辑。其实,它也是基于 Linux Netfilter 框架实现的。
ipvs 基础架构
打开 Linux kernel 中的 ipvs 核心源码,将会看到如下介绍:
An implementation of the IP virtual server support for the LINUX operating system. IPVS is now implemented as a module over the Netfilter framework. IPVS can be used to build a high-performance and highly available server based on a cluster of servers.
ipvs 是 Netfilter 框架上的一个模块,可用来构建高可用、高性能的服务器集群。笔者对这块的源码也不熟悉,只能简单的捋一捋其基本逻辑。下图来自孟凡杰等老师合著的《Kubernetes生产化实践之路》,笔者对其就行了重新绘制:
从图上可以看到,ipvs 只实现了 Netfilter 框架中的三个钩子,分别是:Input,Forward 和 Output,也就是加工发往本机的数据包,转发数据包以及加工从本机进程处理后的数据包。
从源码层面来看,与三个钩子相对应的函数主要有:ip_vs_remote_request
, ip_vs_reply
ip_vs_forward_icmp
, ip_vs_local_request
, ip_vs_local_reply
。
不同的内核版本可能有差别,下面的代码来自 Linux Kernel 5.8:
// Linux 5.8 net/netfilter/ipvs/ip_vs_core.c
static const struct nf_hook_ops ip_vs_ops[] = {
/* After packet filtering, change source only for VS/NAT */
{
.hook = ip_vs_reply4,
.pf = NFPROTO_IPV4,
.hooknum = NF_INET_LOCAL_IN,
.priority = NF_IP_PRI_NAT_SRC - 2,
},
/* After packet filtering, forward packet through VS/DR, VS/TUN,
* or VS/NAT(change destination), so that filtering rules can be
* applied to IPVS. */
{
.hook = ip_vs_remote_request4,
.pf = NFPROTO_IPV4,
.hooknum = NF_INET_LOCAL_IN,
.priority = NF_IP_PRI_NAT_SRC - 1,
},
/* Before ip_vs_in, change source only for VS/NAT */
{
.hook = ip_vs_local_reply4,
.pf = NFPROTO_IPV4,
.hooknum = NF_INET_LOCAL_OUT,
.priority = NF_IP_PRI_NAT_DST + 1,
},
/* After mangle, schedule and forward local requests */
{
.hook = ip_vs_local_request4,
.pf = NFPROTO_IPV4,
.hooknum = NF_INET_LOCAL_OUT,
.priority = NF_IP_PRI_NAT_DST + 2,
},
/* After packet filtering (but before ip_vs_out_icmp), catch icmp
* destined for 0.0.0.0/0, which is for incoming IPVS connections */
{
.hook = ip_vs_forward_icmp,
.pf = NFPROTO_IPV4,
.hooknum = NF_INET_FORWARD,
.priority = 99,
},
/* After packet filtering, change source only for VS/NAT */
{
.hook = ip_vs_reply4,
.pf = NFPROTO_IPV4,
.hooknum = NF_INET_FORWARD,
.priority = 100,
},
// ...
// ...
};
那么这些函数干了啥呢?其实,上面的注释已经给出一部分信息。
-
Input Hook:
ip_vs_remote_request
,ip_vs_reply
1)
ip_vs_remote_request
对应的核心函数为ip_vs_in
,用于处理外部客户端进入 ipvs 系统的报文,如果没有可用连接,则创建;2)
ip_vs_reply
对应的核心函数为ip_vs_out
,用于处理系统回复给外部客户端的报文,比如修改源 IP 或目标 IP 地址等。/* * It is hooked at the NF_INET_FORWARD and NF_INET_LOCAL_IN chain, used only for VS/NAT. * Check if packet is reply for established ip_vs_conn. */ static unsigned int ip_vs_reply4(void *priv, struct sk_buff *skb, const struct nf_hook_state *state) { return ip_vs_out(net_ipvs(state->net), state->hook, skb, AF_INET); }
-
Forward Hook:
ip_vs_forward_icmp
,ip_vs_reply
1)
ip_vs_forward_icmp
的核心函数为ip_vs_in_icmp
,用于处理外部客户端进入 ipvs 系统的 ICMP 报文,并将其转发到后端真实的服务器上。ICMP 全称为 Internet Control Message Protocol,互联网控制报文协议,是网络层的一个协议,其主要功能包括:确认 IP 包是否成功送达目的主机,通知传输过程中 IP 包被废弃的原因,改善网络设置等。简单来说,ICMP 就是为 TCP/IP 网络上的设备,服务以及路由器提供可用性信息,一个典型的应用是
ping
命令。/* * It is hooked at the NF_INET_FORWARD chain, in order to catch ICMP * related packets destined for 0.0.0.0/0. * When fwmark-based virtual service is used, such as transparent * cache cluster, TCP packets can be marked and routed to ip_vs_in, * but ICMP destined for 0.0.0.0/0 cannot not be easily marked and * sent to ip_vs_in_icmp. So, catch them at the NF_INET_FORWARD chain * and send them to ip_vs_in_icmp. */ static unsigned int ip_vs_forward_icmp(void *priv, struct sk_buff *skb, const struct nf_hook_state *state) { int r; struct netns_ipvs *ipvs = net_ipvs(state->net); if (ip_hdr(skb)->protocol != IPPROTO_ICMP) return NF_ACCEPT; /* ipvs enabled in this netns ? */ if (unlikely(sysctl_backup_only(ipvs) || !ipvs->enable)) return NF_ACCEPT; return ip_vs_in_icmp(ipvs, skb, &r, state->hook); }
2)
ip_vs_reply
在上面已经介绍过,对于后端真实服务器返回的数据报文,ipvs 系统需要将其源 IP 地址修改为 ipvs 系统所在机器的虚拟 IP(即 SNAT)。 -
Output Hook:
ip_vs_local_request
,ip_vs_local_reply
1)
ip_vs_local_request
对应的核心函数是前面介绍的ip_vs_in
,主要处理由本机应用服务发给 ipvs 系统的报文,调度并发送信息等;2)
ip_vs_local_reply
对应的核心函数也是前面介绍的ip_vs_out
,只不过这里处理的是 ipvs 本机回复的消息,一般用于 NAT/Masq 转发模式,以及 NAT 地址的修改等。MASQUERADE:地址伪装,可以理解为动态的 SNAT。通过它可以将源地址绑定到某个网卡上,因为这个网卡的 IP 可能是动态变化的,此时用 SNAT 就不好实现。
AI 回复:MASQUERADE 转发模式用于网络地址转换(NAT)的设置,允许私有网络中的计算机通过共享的公共 IP 地址访问互联网。MASQUERADE 是一种特殊的 NAT 转发模式,它动态地修改数据包的源 IP 地址,使其伪装成 NAT 网关的 IP 地址,从而实现网络连接的转发。
到这里,我们了解到了 ipvs 在 Netfilter 框架中的基本逻辑,简单来说,当一个请求到 ipvs 系统后,会被内核拦截,在 ipvs 系统处理后(即上面提到的钩子),再发往后端真实的服务器,后端服务器回复的消息也 可能 会走 ipvs,经过转换后,再转发给原请求客户端。
下面我们就通过一个应用实例来体验一下,ipvs 作为负载均衡器的功能。
ipvs 使用
ipvsadm
与 iptables 类似,ipvs 下也有用户态操控工具,即 ipvsadm
:
# 如果机器上没有该命令,则需要手动安装,以 debian 系列为例:
sudo apt install ipvsadm
运行 ipvsadm --help
可以查看使用方式:
demon@demon-debian12:~$ sudo ipvsadm --help
ipvsadm v1.31 2019/12/24 (compiled with popt and IPVS v1.2.1)
Usage:
ipvsadm -A|E virtual-service [-s scheduler] [-p [timeout]] [-M netmask] [--pe persistence_engine] [-b sched-flags]
ipvsadm -D virtual-service
ipvsadm -C
ipvsadm -R
ipvsadm -S [-n]
ipvsadm -a|e virtual-service -r server-address [options]
ipvsadm -d virtual-service -r server-address
ipvsadm -L|l [virtual-service] [options]
ipvsadm -Z [virtual-service]
ipvsadm --set tcp tcpfin udp
ipvsadm --start-daemon {master|backup} [daemon-options]
ipvsadm --stop-daemon {master|backup}
ipvsadm -h
Commands:
Either long or short options are allowed.
--add-service -A add virtual service with options
--edit-service -E edit virtual service with options
--delete-service -D delete virtual service
--clear -C clear the whole table
--restore -R restore rules from stdin
--save -S save rules to stdout
--add-server -a add real server with options
--edit-server -e edit real server with options
--delete-server -d delete real server
--list -L|-l list the table
--zero -Z zero counters in a service or all services
--set tcp tcpfin udp set connection timeout values
--start-daemon start connection sync daemon
--stop-daemon stop connection sync daemon
--help -h display this help message
# ...
# ...
运行 man ipvsadm
则可以看到更多详细介绍:
NAME
ipvsadm - Linux Virtual Server administration
SYNOPSIS
ipvsadm -A|E virtual-service [-s scheduler]
[-p [timeout]] [-M netmask] [-b sched-flags]
ipvsadm -D virtual-service
ipvsadm -C
ipvsadm -R
...
...
DESCRIPTION
Ipvsadm(8) is used to set up, maintain or inspect the virtual server table in the Linux kernel. The Linux Virtual Server can be used to build scalable network services based on a cluster of two or more nodes. The active node of the cluster redirects service requests to a collection of server hosts that will actually perform the services. Supported features include three protocols (TCP, UDP and SCTP), three packet-forwarding methods (NAT, tunneling, and direct routing), and eight load balancing algorithms (round robin, weighted round robin, least-connection, weighted least-connection, locality-based least-connec‐tion, locality-based least-connection with replication, destination-hashing, and source-hashing).
The command has two basic formats for execution:
ipvsadm COMMAND virtual-service
[scheduling-method] [persistence options]
ipvsadm command virtual-service
server-address [packet-forwarding-method]
[weight options]
The first format manipulates a virtual service and the algorithm for assigning service requests to real servers. Optionally, a persistent timeout and network mask for the granularity of a persistent service and a persistence engine may be specified. The second format manipulates a real server that is associated with an existing virtual service. When specifying a real server, the packet-forwarding method and the weight of the real server, relative to other real servers for the virtual service, may be specified, otherwise defaults will be used.
COMMANDS
ipvsadm(8) recognises the commands described below. Upper-case commands maintain virtual services. Lower-case commands maintain real servers that are associated with a virtual service.
-A, --add-service
Add a virtual service. A service address is uniquely defined by a triplet: IP address, port number, and protocol. Alternatively, a virtual service may be defined by a firewall-mark.
-E, --edit-service
Edit a virtual service.
-D, --delete-service
Delete a virtual service, along with any associated real servers.
...
...
virtual-service
Specifies the virtual service based on protocol/addr/port or firewall mark.
-t, --tcp-service service-address
Use TCP service. The service-address is of the form host[:port]. Host may be one of a plain IP address or a hostname. Port may be either a plain port number or the service name of port. The Port may be omitted, in which case zero will be used. A Port of zero is only valid if the service is persistent as the -p|--persistent option, in which case it is a wild-card port, that is connections will be accepted to any port.
-u, --udp-service service-address
Use UDP service. See the -t|--tcp-service for the description of the service-address.
--sctp-service service-address
Use SCTP service. See the -t|--tcp-service for the description of the service-address.
...
...
PARAMETERS
The commands above accept or require zero or more of the following parameters.
...
...
-r, --real-server server-address
Real server that an associated request for service may be assigned to. The server-address is the host address of a real server, and may plus port. Host can be either a plain IP address or a hostname. Port can be either a plain port number or the service name of port. In the case of the masquerading method, the host address is usually an RFC 1918 private IP address, and the port can be different from that of the associated service. With the tunneling and direct routing methods, port must be equal to that of the service address. For normal services, the port specified in the service address will be used if port is not specified. For fwmark services, port may be omitted, in which case the destination port on the real server will be the destination port of the request sent to the virtual service.
...
...
从这么多内容中,我们可以了解到哪些细节?
- 添加 real-server 和 virtual-service 一般是分开的;
- 虚拟服务支持三种协议:TCP,UDP 和 SCTP;
- 支持三种数据包转发模式:NAT, tunneling, and direct routing
- 支持多种负载均衡算法:round robin, weighted round robin, least-connection, weighted least-connection, locality-based least-connection, locality-based least-connection with replication, destination-hashing, and source-hashing
- ……
转发模式以及负载均衡算法,将在下一小节中梳理,这里先跳过,而关于 SCTP 协议,AI 的回复是:
SCTP(Stream Control Transmission Protocol)是一种传输层网络协议,旨在提供可靠的、有序的、多流的传输服务。与传输控制协议(TCP)和用户数据报协议(UDP)相比,SCTP 提供了一些额外的功能和特性,使其适用于一些特定的应用场景。
下面是一些 SCTP 网络协议的特点和功能:
1、可靠性:SCTP 提供可靠的传输,通过使用确认和重传机制来确保数据的完整性和可靠性。它还支持选择性确认和故障恢复机制。
2、有序性:SCTP 可以确保数据按照发送顺序被接收,这对于某些应用场景非常重要,例如实时多媒体流传输。
3、多流传输:SCTP 具有多流传输的能力,它可以同时传输多个独立的数据流。每个数据流都有自己的序列号,从而可以实现更高的并发性和吞吐量。
4、流量控制:SCTP 支持流量控制机制,可以根据网络条件和接收方的处理能力来调整数据发送速率,以避免网络拥塞和资源浪费。
5、心跳机制:SCTP 提供了心跳机制,用于监测连接的状态和可用性。通过定期交换心跳消息,可以及时检测到连接故障,并采取相应的恢复措施。
6、多宿主:SCTP 允许一个端点绑定到多个 IP 地址,从而实现多宿主的冗余和负载均衡。
7、安全性:SCTP 支持消息完整性检查和身份验证,可以通过加密和认证机制来保护数据的安全性。
SCTP 主要应用于对可靠性和顺序性要求较高的应用,例如实时通信、语音和视频传输、远程桌面等。它也被广泛用于传输信令和控制信息,如电话信令(如SIP)和无线电网络协议(如DIAMETER)。需要注意的是,SCTP 并不像 TCP 那样广泛被操作系统和应用程序所支持,但在一些特定的领域和应用中,SCTP 提供了一种有价值的传输协议选择。
实战:部署 LVS 集群
为了能看到实际效果,笔者将使用 ipvs 部署一个集群,需要三台 Linux 机器(通过 VMware 虚拟化而来):
-
192.168.10.125(ubuntu-22.04 Arm64):用作负载均衡调度器,安装 ipvs 服务
-
192.168.10.131(centos-stream-9 Arm64):real server 实例 1,部署 nginx 服务,暴露 80 端口
-
192.168.10.132(debian-12 Arm64):real server 实例 2,部署 nginx 服务,暴露 80 端口
安装过程,笔者这里主要参考了《跟阿铭学Linux(第3版)》,为了能正常演示,作为负载均衡调度器的机器(即 192.168.10.125)上还需要安装一个“外网”网卡,专门用于外部访问,笔者这里设置的 IP 为 192.168.20.128:
三台机器安装好之后,再执行以下步骤:
1)在 virtual service 服务器(即 192.168.10.125)上安装 ipvsadm 工具;
2)在三台机器上清除 iptables 规则并保存(笔者认为这里是为了清除干扰,如果之前没有特殊配置,可以不用执行):
iptables -F;
iptables -t nat -F;
service iptables save;
3)在两台 real server 上安装 nginx 服务,并暴露 80 端口。笔者为提高部署效率,使用了 docker,脚本如下:
docker run --name "nginx-ipvs" \
-p "80:80" \
-v "./default.conf:/etc/nginx/conf.d/default.conf" \
-d nginx:1.22.1-alpine
为了调试方便,default.conf 中的配置内容如下所示:
server {
listen 80;
location / {
default_type text/plain;
return 200
'srv : $server_addr:$server_port\nhost: $hostname\nuri : $request_method $host $request_uri\ndate: $time_iso8601\n';
}
}
4)修改两台 real server 上的默认网关,原来的网关是:192.168.10.2,将其调整为 192.168.10.125,脚本中的 <device>
需要调整为具体的网卡名称,比如 ens160:
sudo ip route del default
sudo ip route add default via 192.168.10.125 dev <device>
上面的方式在机器重启后自动失效,这里只是为了演示,生产环境需要将其写入到对应文件中,比如 centos 9 中的位置是 /etc/NetworkManager/system-connections/<device>-nmconnection
:
5)在 virtual service 机器上配置 ipvs,脚本如下:
#!/bin/bash
# 开启路由转发
echo 1 > /proc/sys/net/ipv4/ip_forward
# 关闭 ICMP 重定向
echo 0 > /proc/sys/net/ipv4/conf/all/send_redirects
echo 0 > /proc/sys/net/ipv4/conf/default/send_redirects
echo 0 > /proc/sys/net/ipv4/conf/ens160/send_redirects
echo 0 > /proc/sys/net/ipv4/conf/ens256/send_redirects
# 设置 NAT 防火墙
# 清除 nat 表中的规则
iptables -t nat -F
# 删除 nat 表中的自定义链
iptables -t nat -X
# 在 nat 表的 POSTROUTING 链上新增一条规则:将源地址为 192.168.10.0/24 的数据包的源地址替换为出口网卡的 IP 地址,实现网络地址转换。
iptables -t nat -A POSTROUTING -s 192.168.10.0/24 -j MASQUERADE
# 添加 ipvs 规则
# 清除虚拟服务表
ipvsadm -C
# 增加一个虚拟服务 192.168.20.128:80,并指定调度算法为 wrr,即 weighted round robin
ipvsadm -A -t 192.168.20.128:80 -s wrr
# 将两台真实服务器添加到虚拟服务表中,-m 表示 masquerading,即 NAT 模式,而 -w 1 则表示设置的权重为 1
ipvsadm -a -t 192.168.20.128:80 -r 192.168.10.131:80 -m -w 1
ipvsadm -a -t 192.168.20.128:80 -r 192.168.10.132:80 -m -w 4
由于前面已经列出了 ipvsadm 工具的大部分命令,所以这里不再赘述,只提一下 -w
所设置的权重:
-w, --weight weight
Weight is an integer specifying the capacity of a server relative to the others in the pool. The valid values of weight are 0 through to 2147483647. The default is 1. Quiescent servers are specified with a weight of zero. A quiescent server will receive no new jobs but still serve the existing jobs, for all scheduling algorithms distributed with the Linux Virtual Server. Setting a quiescent server may be useful if the server is overloaded or needs to be taken out of service for maintenance.
这里的权重是指当前服务器与其他服务器的相对值。比如上面的示例中,笔者给两台 real server 分别设置的值是 1 和4,也就是说当有 10 个请求发送到负载均衡器,那么 20% 会流到 192.168.10.131:80,而另外的 80% 会流到 192.168.10.132:80,以下是测试结果:
➜ ~ >curl http://192.168.20.128
srv : 172.17.0.2:80
host: cf395fed4a22
uri : GET 192.168.20.128 /
date: 2023-06-23T15:02:01+00:00
➜ ~ >
➜ ~ >curl http://192.168.20.128
srv : 172.17.0.2:80
host: cf395fed4a22
uri : GET 192.168.20.128 /
date: 2023-06-23T15:03:45+00:00
➜ ~ >
➜ ~ >curl http://192.168.20.128
srv : 172.17.0.2:80
host: cf395fed4a22
uri : GET 192.168.20.128 /
date: 2023-06-23T15:03:45+00:00
➜ ~ >
➜ ~ >curl http://192.168.20.128
srv : 172.17.0.2:80
host: cf395fed4a22
uri : GET 192.168.20.128 /
date: 2023-06-23T15:04:11+00:00
➜ ~ >
➜ ~ >curl http://192.168.20.128
srv : 172.17.0.2:80
host: 3c4d8610e11d
uri : GET 192.168.20.128 /
date: 2023-06-23T15:14:47+00:00
➜ ~ >
5 次请求,只要 1 次返回的 host 不相同,读者可以继续调整上面的权重值反复验证。此时在 virtual service 上查看 ipvs,输出内容如下所示:
ubuntu-2204-dev% sudo ipvsadm -l
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
-> RemoteAddress:Port Forward Weight ActiveConn InActConn
TCP 192.168.20.128:http wrr
-> 192.168.10.131:http Masq 1 0 1
-> 192.168.10.132:http Masq 4 0 5
ubuntu-2204-dev%
另外,脚本中包含几个 关闭 ICMP 重定向 的命令,这是什么意思呢?前面简单提了一下 ICMP 协议,而重定向是 ICMP 的一种控制报文,由网关发出,用于要求主机或路由器改变数据报文的传输路径。我们来看维基百科的解释就明白了:
ICMP 重定向是路由器将路由信息传达给主机的机制。这种类型的报文通知主机更新它的路由信息(请求主机改变其路由)。如果一个主机 H1 在通信时将数据报发送给了路由器 R1,而 R1 将这个数据报转发给了另一个路由器 R2,且主机 H1 到路由器 R2 之间有一条直连的路径(也就是说,此主机和路由器 R2 处于同一以太网段上),那么路由器 R1 会发送一条重定向报文给主机 H1,来通知它到路由器 R2 可用路径里有一条更短、更优化的路径。主机 H1 在接收到这个重定向报文之后应该改变其路由至这个优化版本的路由信息,来将抵达这个目的地的数据报直接发送到路由器R2。
ipvs 下的负载均衡技术
最后,梳理一下 ipvs 负载均衡技术的基本原理,由于章文嵩博士在 LVS 官网已经做了比较详细的介绍,所以笔者在这里只做一个简单的总结。
负载均衡架构
1、VS/NAT
VS/NAT(Virtual Server via Network Address Translation),即利用网络地址转换,将请求转发到不同的服务器上。在之前的 Linux Netfilter 文章中,笔者介绍了 nat 表,和这里的 NAT 是一个意思。
该模式下,负载均衡器负责改写请求报文的源地址,目标地址以及端口等。当客户端请求通过 VIP 到达负载均衡器(virtual service)后,负载均衡器根据相关负载均衡算法,从多台真实服务器(real server)中选出一台,并将请求中的目标地址和端口改成对应的值,然后通过交换机将报文发送过去。当真实服务器处理完请求后,响应报文又会回到负载均衡器,负载均衡器再将报文的源地址和端口改回来,返回给客户端。
上一小节中的演示案例,就是 VS/NAT 模式。从上面的架构图,我们可以推测出来,当请求量多了以后,负载均衡器将会成为瓶颈,因为数据的输入和输出都要从这里走一遍。
基于此,便有了下面两种改进的方案,IP Tunneling 和 Direct Routing。
2、VS/TUN
IP Tunneling 是什么,就是将一个 IP 报文封装起来,放进另一个 IP 报文中,我们日常用的 VPN 就是 IP 隧道技术的典型案例。
如上图所示,当客户端请求到达负载均衡器后(目标地址是 VIP),负载均衡器从多台真实服务器中选择一台,然后在请求报文上再封装一层真实服务器的 IP 地址,并发送出去。 服务器收到报文后,先将报文解封获得原来目标地址为 VIP 的报文,服务器发现 VIP 地址被配置在本地的 IP 隧道设备上,所以就处理这个请求。 真实服务器处理完请求后,会根据源目标地址(真实客户端地址,而非 VIP),将响应报文返回。注意,此时真实服务器会根据路由表找一条路,而不是沿着负载均衡器原路返回。
从响应报文的返回路径,就能看出,此时负载均衡器可以支撑的并发请求大大提升。很多时候,请求报文都比较小,而响应报文则比较大。现在响应报文不再通过负载均衡器,前面提到的瓶颈问题也就解决了。
因为将 IP 报文封装起来,所以该技术又被称为 IP encapsulation,即 IP 封装技术。
3、VS/DR
最后一种,Direct Routing 模式,即直接路由模式,跟上面的 IP Tunneling 有点像:
与 IP Tunneling 不同的是,这里没有报文封装,而是直接修改请求报文:负载均衡器收到客户端请求报文后,会根据负载均衡算法从多台真实服务器中选择一台,然后将请求报文的目标 MAC 地址改成该真实服务器的 MAC 地址,接着把请求发送到服务器组的局域网内。这就是为什么图中要求它们在一个物理网段内,并且 VIP 会配置在每一台真实服务器上。
通过 MAC 地址,真实服务器拿到请求报文后继续处理,由于源地址还是客户端的地址,所以响应报文也是直接返回给客户端,而不是走负载均衡器。
需要特殊说明的是,在 TUN 和 DR 两种模式下,负载均衡器并未收到响应报文,所以 TCP 状态机走的是半连接,具体细节笔者将在后续文章中进一步梳理。
官网对这三种架构的支撑能力进行了对比,笔者将其引用到这里:
这里的实验数据已经是 20 年前的了,现在的设备性能远超当时的水平,所以支撑能力要以实际测试结果为准,表格中的数字主要是作为可伸缩性的一个相对参考。
负载均衡算法
在前面的实战案例中,笔者指定的算法为 wrr
,即带权重的轮询调度。其实章文嵩博士在LVS集群的负载调度一文中给出了 LVS 支持的 8 种调度算法:
- rr(Round-Robin Scheduling):轮询调度,即依次请求后端的真实服务器,这里假设后端服务器的性能都相同;
- wrr(Weighted Round-Robin Scheduling):带权重的轮询调度,依据后端服务器的配置,为其配置不同的权重;
- lc(Least-Connection Scheduling):最小连接调度,计算每台真实服务器当前的连接数,选择连接数最少的一台;
- wlc(Weighted Least-Connection Scheduling):带权重的最小连接调度,在上面的基础上,根据权重计算真实服务器的连接数;
- lblc(Locality-Based Least Connections Scheduling):基于局部性的最少连接调度,用于缓存集群中,即根据目标 IP 找到之前用过的那台服务器;
- lblcr(Locality-Based Least Connections with Replication Scheduling):带复制的基于局部性最少连接调度,它与 lblc 算法的不同之处是:它要维护从一个目标 IP 地址到一组服务器的映射,而 lblc 算法是维护从一个目标 IP 地址到一台服务器的映射;
- dh(Destination Hashing Scheduling):目标地址散列调度,静态散列算法,根据目标 ip 找到对应的服务器;
- sh(Source Hashing Scheduling):源地址散列调度,同 dh,只不过用的源 ip 地址。
由于官网介绍的非常详细,笔者这里就一笔带过。一般情况下,我们主要使用前面 4 种算法。另外,通过 man ipvsadm
可以看到,目前支持的算法不止这 8 种,还有 sed,sq,fo,ovf,mh 等,感兴趣的小伙伴可以进一步研究。
小结
笔者在 Linux Netfilter 框架的基础上,尝试着把 ipvs 技术的基本逻辑梳理清楚:当流量到达内核后,在 Netfilter 中配置的钩子将会生效。日常工作中,我们主要使用 ipvsadm/iptables 这两个用户态的命令行工具配置钩子中需要执行的动作。
笔者通过三台机器演示了 NAT 模式下的负载均衡,使用的是 wrr
负载均衡算法。官网除了详细分析 8 种负载均衡算法外,还提出了动态反馈负载均衡算法,即负载均衡器与各个真实服务器进行交互,获取它们的真实负载,然后基于这些数据,动态的分配权重。笔者觉得,不管哪种复杂的负载均衡器,其原理估计也不会逃过文章中的分析,建议小伙伴们去看看。
最后,ipvs 是比较偏底层的技术,工作中我们基本上不会直接使用它,而是使用一些成熟的工具和框架,比如 keepalived。keepalived 底层使用的就是 ipvs 技术,但比 ipvs 更可靠,当有一台 real server 宕机,keepalived 可以识别并跳过,但直接使用 ipvs 则不行。
这不正是我们要学习底层技术的原因吗?万丈高楼,始于一砖一瓦。
关于 ipvs 还有一些问题和细节没有搞清楚,笔者将在后续的文章中继续分析,下回见。