第 10-1 章 Linux LVS 服务配置
网站架构中,负载均衡技术是实现网站架构伸缩性的主要手段之一。所谓 "伸缩性",是指可以不断向集群中添加新的服务器来提升性能、缓解不断增加的并发用户访问压力。
负载均衡有好几种方式:http URL 重定向、DNS 的 A 记录负载均衡、反向代理负载均衡、IP 负载均衡和链路层负载。本文所述为 LVS,它的 VS/NAT 和 VS/TUN 模式是 IP 负载均衡的优秀代表,而它的 VS/DR 模式则是链路层负载均衡的优秀代表。
10.1、LVS 简介
LVS 中文官方手册:http://www.linuxvirtualserver.org/zh/index.html 这个手册对于了解 lvs 的背景知识很有帮助。
LVS 英文官方手册:http://www.linuxvirtualserver.org/Documents.html 这个手册比较全面,对于了解和学习 lvs 的原理、配置很有帮助。
LVS 是章文嵩开发的一个国产开源负载均衡软件。LVS 最初是他在大学期间的玩具,随着后来使用的用户越来越多,LVS 也越来越完善,最终集成到了 Linux 的内核中。有不少开源牛人都为 LVS 开发过辅助工具和辅助组件,最出名的就是 Alexandre 为 LVS 编写的 Keepalived,它最初专门用于监控 LVS,后来加入了通过 VRRP 实现高可用的功能。
LVS 的全称是 Linux virtual server,即 Linux 虚拟服务器。之所以是虚拟服务器,是因为 LVS 自身是个负载均衡器(director),不直接处理请求,而是将请求转发至位于它后端真正的服务器 realserver 上。
LVS 是四层(传输层 tcp/udp)、七层(应用层)的负载均衡工具,只不过大众一般都使用它的四层负载均衡功能 ipvs,而七层的内容分发负载工具 ktcpvs(kernel tcp virtual server)不怎么完善,使用的人并不多。
ipvs 是集成在内核中的框架,可以通过用户空间的程序 ipvsadm
工具来管理,该工具可以定义一些规则来管理内核中的 ipvs。就像 iptables 和 netfilter 的关系一样。
10.2、LVS-ipvs 三种模式的工作原理
首先要解释的是 LVS 相关的几种 IP:
VIP
:virtual IP,LVS 服务器上接收外网数据包的网卡 IP 地址。DIP
:director IP,LVS 服务器上转发数据包到 realserver 的网卡 IP 地址。RIP
:realserver(常简称为 RS)上接收 Director 转发数据包的 IP,即提供服务的服务器 IP。CIP
:客户端的 IP。
LVS 的三种工作模式:
- 通过网络地址转换(NAT)将一组服务器构成一个高性能的、高可用的虚拟服务器,是 VS/NAT 技术。
- 在分析 VS/NAT 的缺点和网络服务的非对称性的基础上,提出了通过 IP 隧道实现虚拟服务器的方法 VS/TUN(Virtual Server via IP Tunneling)。
- 通过直接路由实现虚拟服务器的方法 VS/DR(Virtual Server via Direct Routing)。
10.2.1、VS/NAT 模式
-
客户端发送的请求到达 Director 后,Director 根据负载均衡算法改写目标地址为后端某个 RIP(web 服务器池中主机之一)并转发给该后端主机,就像 NAT 一样。
-
当后端主机处理完请求后,后端主机将响应数据交给 Director,并由 director 改写源地址为 VIP 后传输给客户端。
大多数商品化的 IP 负载均衡硬件都是使用此方法,如 Cisco 的 LocalDirector、F5 的 Big/IP。
VS/NAT 模式下:
- RIP 和 DIP 一般处于同一私有网段中。但并非必须,只要它们能通信即可。
- 各 RealServer 的网关指向 DIP,这样能保证将响应数据交给 Director。
- VS/NAT 模式的最大缺点是 Director 负责所有进出数据:不仅处理客户端发起的请求,还负责将响应传输给客户端。而响应数据一般比请求数据大得多,调度器 Director 容易出现瓶颈。(也就是像 7 层负载的处理方式一样,但却没有 7 层负载那么 "多功能")
- 这种模式配置起来最简单。
10.2.2、VS/TUN 模式
采用 NAT 技术时,由于请求和响应报文都必须经过调度器地址重写,当客户请求越来越多时,调度器的处理能力将成为瓶颈。
为了解决这个问题,调度器把请求报文通过 IP 隧道转发至真实服务器,而真实服务器将响应直接返回给客户,所以调度器只处理请求报文。
由于一般网络服务响应报文比请求报文大许多,采用 VS/TUN 技术后,调度器得到极大的解放,集群系统的最大吞吐量可以提高 10 倍。
VS/TUN 模式的工作原理:
- IP 隧道技术又称为 IP 封装技术,它可以将带有源和目标 IP 地址的数据报文使用新的源和目标 IP 进行第二次封装,这样这个报文就可以发送到一个指定的目标主机上;
- VS/TUN 模式下,调度器和后端服务器组之间使用 IP 隧道技术。当客户端发送的请求(CIP-->VIP)被 director 接收后,director 修改该报文,加上 IP 隧道两端的 IP 地址作为新的源和目标地址,并将请求转发给后端被选中的一个目标;
- 当后端服务器接收到报文后,首先解封报文得到原有的 CIP-->VIP,该后端服务器发现自身的 tun 接口上配置了 VIP,因此接受该数据包。
- 当请求处理完成后,结果将不会重新交给 director,而是直接返回给客户端。此时响应数据包的源 IP 为 VIP,目标 IP 为 CIP。
采用 VS/TUN 模式时的基本属性和要求:
- RealServer 的 RIP 和 director 的 DIP 不用处于同一物理网络中,且 RIP 必须可以和公网通信。也就是说集群节点可以跨互联网实现。
- realserver 的 tun 接口上需要配置 VIP 地址,以便接收 director 转发过来的数据包,以及作为响应报文的源 IP。
- director 给 realserver 时需要借助隧道,隧道外层的 IP 头部的源 IP 是 DIP,目标 IP 是 RIP。而 realsever 响应给客户端的 IP 头部是根据隧道内层的 IP 头分析得到的,源 IP 是 VIP,目标 IP 是 CIP。这样客户端就无法区分这个 VIP 到底是 director 的还是服务器组中的。
- director 只处理入站请求,响应请求由 realserver 完成。
一般来说,VS/TUN 模式会用来负载调度缓存服务器组,这些缓存服务器一般放置在不同网络环境,可以就近返回数据给客户端。在请求对象不能在 Cache 服务器本地命中的情况下,Cache 服务器要向源服务器发请求,将结果取回,最后将结果返回给客户。
10.2.3、VS/DR 模式
VS/TUN 模式下,调度器对数据包的处理是使用 IP 隧道技术进行二次封装。VS/DR 模式和 VS/TUN 模式很类似,只不过调度器对数据包的处理是改写数据帧的目标 MAC 地址,通过链路层来负载均衡。
VS/DR 通过改写请求报文的目标 MAC 地址,将请求发送到真实服务器,而真实服务器将响应直接返回给客户。同 VS/TUN 技术一样,VS/DR 技术可极大地提高集群系统的伸缩性。这种方法没有 IP 隧道的开销,对集群中的真实服务器也没有必须支持 IP 隧道协议的要求,但是要求调度器与真实服务器都有一块网卡连在同一物理网段上,以便使用 MAC 地址通信转发数据包。
VS/DR 模式的工作原理:
- 客户端发送的请求被 director 接收后,director 根据负载均衡算法,改写数据帧的目标 MAC 地址为后端某 RS 的 MAC 地址,并将该数据包转发给该 RS(实际上是往整个局域网发送,但只有该 MAC 地址的 RS 才不会丢弃)。
- RS 接收到数据包后,发现数据包的目标 IP 地址为 VIP,而 RS 本身已经将 VIP 配置在了某个接口上,因此 RS 会接收下这个数据包并进行处理。
- 处理完毕后,RS 直接将响应报文响应给客户端。此时数据包源 IP 为 VIP,目标 IP 为 CIP。
采用 VS/DR 模式时的基本属性和要求:
- RealServer 的 RIP 和 director 的 DIP 必须处于同一网段中,以便使用 MAC 地址进行通信。
- realserver 上必须配置 VIP 地址,以便接收 director 转发过来的数据包,以及作为响应报文的源 IP。
- realsever 响应给客户端的数据包的源和目标 IP 为 VIP --> CIP。
- director 只处理入站请求,响应请求由 realserver 完成。
10.2.4、lvs-ipvs 的三种模式比较
三种模式的比较如图所示:
- 在性能上,VS/DR 和 VS/TUN 远高于 VS/NAT,因为调度器只处于从客户到服务器的半连接中,按照半连接的 TCP 有限状态机进行状态迁移,极大程度上减轻了调度器的压力(真正建立 TCP 连接的是 RS 和 Client)。
- VS/DR 性能又稍高于 VS/TUN,因为少了隧道的开销。但是,VS/DR 和 VS/TUN 的主要区别是 VS/TUN 可以跨网络实现后端服务器负载均衡(也可以局域网内),而 VS/DR 只能和 director 在局域网内进行负载均衡。
10.3、VS/TUN 和 VS/DR 模式中的 ARP 问题
参见 VS/TUN 和 VS/DR 的 arp 问题,非常详细地分析了 ARP、arp_ignore 和 arp_announce 相关原理和设置方法。此处简单说明为何需要设置 arp 抑制以及设置 arp 抑制的方法。
当一个目标 IP 地址为 VIP 的数据包进入 Director 时,Director 局域网(包括隧道)内会进行 ARP 广播,以找出 VIP 地址的 MAC 地址在哪台主机上。
Director 和各 RS 都配置了 VIP。Director 和 RS 之间的连接设备 L(可能是交换机、路由器,也可能是直连时 Director 的内核) 发送 ARP 广播后,Director 和 RS 都会收到这个广播包,且都认为这个广播包找的就是自己,于是都回应给设备 L,这样设备 L 上的 ARP 缓存表中的条目 VIP <--> vip_MAC
就不断被覆盖直到最后一个回应。
这样一来,设备 L 将把客户端的数据包发送给最后一个回应的主机,这台主机的 VIP 可能是 Director 上的,也可能是某个 RS 上的。在一定时间内,设备 L 收到目标 IP 为 VIP 的数据包都会发送给该主机。但设备 L 会定时发送 ARP 广播包,这样一来 ARP 缓存表中的 VIP 对应的 MAC 地址可能会换成另一台主机。
因此,必须要保证设备 L 只保存 Director 上 VIP 对应的 MAC 地址,即只允许 Director 才对设备 L 的 ARP 广播进行回应。也就是说,所有 RS 上的 VIP 必须隐藏起来。
一般通过将 Real Server 上的 VIP 设置在 lo 接口的别名接口上(如 lo:0),并设置 arp_ignore=1
和 arp_announce=2
的方式来隐藏 RS 上的 VIP。至于 VIP 为何要设置在 lo 接口上以及为何要这样设置这两个 arp 参数,请参见 VS/TUN 和 VS/DR 的 arp 问题,内容非常详细。
echo 1 >/proc/sys/net/ipv4/conf/all/arp_ignore
echo 2 >/proc/sys/net/ipv4/conf/all/arp_announce
或者:
sysctl -w net.ipv4.conf.all.arp_ignore=1
sysctl -w net.ipv4.conf.all.arp_announce=2
或者将 arp 参数设置到内核参数配置文件中以让其永久生效:
echo "net.ipv4.conf.all.arp_ignore=1" >>/etc/sysctl.conf
echo "net.ipv4.conf.all.arp_announce=2" >>/etc/sysctl.conf
sysctl -p
在网上几乎所有文章还设置了 lo 接口上的 arp 参数:
echo 1 >/proc/sys/net/ipv4/conf/lo/arp_ignore
echo 2 >/proc/sys/net/ipv4/conf/lo/arp_announce
- 但这没有任何意义,因为 lo 接口不受 arp 参数的影响。
应该在配置 VIP 之前就设置 arp 参数,以防止配置 VIP 后、设置 arp 抑制之前被外界主机发现。
10.4、LVS 负载均衡的调度算法
LVS 的调度算法,详细内容见官方手册:http://www.linuxvirtualserver.org/zh/lvs4.html 。
IPVS 在内核中的负载均衡调度是以连接为粒度的。在 HTTP 协议(非持久)中,每次从 WEB 服务器上获取资源都需要建立一个 TCP 连接,同一用户的不同请求会被调度到不同的服务器上,所以这种细粒度的调度在一定程度上可以避免单个用户访问的突发性引起服务器间的负载不平衡。
LVS 分为两种调度方式:静态调度和动态反馈调度。
- 静态调度方式是指不管 RS 的繁忙程度,根据调度算法计算后轮到谁就调度谁。
- 例如两台 realserver,一开始双方都在处理 500 个连接,下一个请求到来前,server1 只断开了 10 个,而 server2 断开了 490 个,但是此时轮到了 server1,就会调度 server1 而不管繁忙的程度。
- 动态调度方式是指根据 RS 的繁忙程度反馈,计算出下一个连接应该调度谁。
- 动态反馈负载均衡算法考虑服务器的实时负载和响应情况,不断调整服务器间处理请求的比例,来避免有些服务器超载时依然收到大量请求,从而提高整个系统的吞吐率。
在内核中的连接调度算法上,IPVS 已实现了以下八种调度算法:默认的算法为 wlc。
- 静态调度:
- 轮叫调度(Round-Robin Scheduling,rr)
- 加权轮叫调度(Weighted Round-Robin Scheduling,wrr),按照权重比例作为轮询标准
- 目标地址散列调度(Destination Hashing Scheduling,dh),目标地址哈希,对于同一目标 IP 的请求总是发往同一服务器
- 源地址散列调度(Source Hashing Scheduling,sh),源地址哈希,在一定时间内,只要是来自同一个客户端的请求,就发送至同一个 realserver
- 动态反馈调度:
- 最小连接调度(Least-Connection Scheduling,lc),调度器需要记录各个服务器已建立连接的数目,当一个请求被调度到某服务器,其连接数加 1;当连接中止或超时,其连接数减 1。当各个服务器的处理能力不同时,该算法不理想。
- 加权最小连接调度(Weighted Least-Connection Scheduling,wlc)
- 基于本地的最少连接(Locality-Based Least Connections Scheduling,lblc),目前该算法主要用于 cache 集群系统。
- 带复制的基于局部性最少连接(Locality-Based Least Connections with Replication Scheduling,lblcr),目前主要用于 Cache 集群系统。
10.5、ipvsadm 命令
ipvsadm 是 ipvs 的命令行管理工具,可以定义、删除、查看 virtual service 和 Real Server 的属性。
使用操作系统自带的 rpm 包进行安装:
[root@yidam ~]# cat /etc/redhat-release
CentOS Linux release 7.9.2009 (AltArch)
[root@yidam ~]#
[root@yidam ~]# yum info ipvsadm
Loaded plugins: fastestmirror
Loading mirror speeds from cached hostfile
Available Packages
Name : ipvsadm
Arch : aarch64
Version : 1.27
Release : 8.el7
Size : 44 k
Repo : base/aarch64
Summary : Utility to administer the Linux Virtual Server
URL : https://kernel.org/pub/linux/utils/kernel/ipvsadm/
License : GPLv2+
Description : ipvsadm is used to setup, maintain, and 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:
: - two transport layer (layer-4) protocols (TCP and UDP)
: - three packet-forwarding methods (NAT, tunneling, and direct routing)
: - eight load balancing algorithms (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)
[root@yidam ~]# yum install ipvsadm.aarch64 -y
使用最新的 ipvsadm 源码安装,下载地址 https://mirrors.edge.kernel.org/pub/linux/utils/kernel/ipvsadm/:
[root@yidam ~]# wget --no-check-certificate https://mirrors.edge.kernel.org/pub/linux/utils/kernel/ipvsadm/ipvsadm-1.31.tar.xz
[root@yidam ~]# yum groups install "Development Tools" -y
[root@yidam ~]# yum install libnl3-devel.aarch64 popt-devel -y
[root@yidam ~]# tar xf ipvsadm-1.31.tar.xz
[root@yidam ~]# cd ipvsadm-1.31/
[root@yidam ipvsadm-1.31]# make && make install
- 编译安装后自动执行下面的工作,一般其他软件都要手动执行这些动作,给 ipvsadm 点个赞:
if [ ! -d /sbin ]; then mkdir -p /sbin; fi
install -m 0755 ipvsadm /sbin
install -m 0755 ipvsadm-save /sbin
install -m 0755 ipvsadm-restore /sbin
[ -d /usr/man/man8 ] || mkdir -p /usr/man/man8
install -m 0644 ipvsadm.8 /usr/man/man8
install -m 0644 ipvsadm-save.8 /usr/man/man8
install -m 0644 ipvsadm-restore.8 /usr/man/man8
[ -d /etc/rc.d/init.d ] || mkdir -p /etc/rc.d/init.d
install -m 0755 ipvsadm.sh /etc/rc.d/init.d/ipvsadm
10.6、ipvsadm 语法
使用 ipvsadm --help
可以查看使用方法。ipvs 的更多功能以及 ipvsadm 的更详细用法,请 man ipvsadm
# ipvsadm的选项中,大写选项管理虚拟服务virtual service,小写选项管理关联了虚拟服务的真实服务器RealServer,"-L"和"-l"除外,它们同义。
(1).Manage virtual services:
Add: -A -t|u|f service-address [-s scheduler]
-t: tcp协议的集群
-u: udp协议的集群
service-address格式为IP:PORT
-f: firewall-mark防火墙标记
service-address:a num for mark
-s: 调度算法
Modify: -E -t|u|f service-address [-s scheduler]
# 和-A使用方法一样
Delete: -D -t|u|f service-address
# 和-A使用方法一样
# Example: ipvsadm -A -t 172.16.10.20:80 -s rr (对外的地址,也就是VIP)
(2).Manage realservers in virtual service:
Add: -a -t|u|f service-address -r server-address [-g|i|m] [-w weight]
-t|u|f service-address: 指定Real server所绑定的virtual service
-r server-address: 某RS地址,在NAT模型中,可IP:PORT实现端口映射,即端口无需等于VIP对应的port
-g|i|m: 指定lvs的类型,有三种:
-g: gataway即DR类型(默认的模型)
-i: --ipip,即TUN类型
-m: masquerade地址伪装即NAT
-w: 指定权重(需要调度算法支持权重)
Modify: -e和-a用法一样
Delete: -d -t|u|f service-address -r server-address表示从哪个virtual service中删除哪个realserver
# Examples: ipvsadm -a -t 172.16.10.20:80 -r 192.168.100.9 -m
# Examples: ipvsadm -a -t 172.16.10.20:80 -r 192.168.100.10 -m
(3).view:
-L/-l: 列出状态信息,配合以下选项用于显示更精确数据
-n: 只显示数字格式,不反解IP地址和端口
--stats: 显示统计信息
--rate: 显示速率信息(每秒的值)
--timeout: 显示tcp/tcpfin/udp的会话超时时间长度
--daemon: 显示进程状态和多播端口(不太用)
--sort: 对-n列出来的进行排序(按协议、IP、端口号升序排序)
-c: 显示当前ipvs的连接状况(不能和stats选项同用)
(4).others:
-Z: 清空统计数据
-C: 删除一个或所有virtual service,连同与之绑定的real server也删除
-S: 保存规则 ipvsadm -S > /path/to/somefile 或者使用ipvsadm-save > /path/to/somefile
-R: 载入规则 ipvsadm -R < /path/to/somefile 或者使用ipvsadm-restore < /path/to/somefile
service ipvsadm save
service ipvsadm restore
10.7、实现 VS/NAT 模式的负载均衡
实验环境如下:
在开始操作前,先回顾下 VS/NAT 模式的相关内容:
请求过程:CIP-->VIP--ip_forward-->DIP-->RIP
响应过程:RIP-->DIP--ip_forward-->VIP-->CIP
由于 director 接收到 CIP 发送的数据包后,需要转发给 Real Server 进行处理,但 Real Server 的 RIP 和 DIP 是同一网段的,因此 Director 必须将 VIP 接口上收到的数据包转发给 DIP,也就是说 Director 需要开启 ip_forward 功能。
当 RS 处理完成后,响应数据需要先转发给 Director,但因为响应数据的目标地址为 CIP,因此需要将 RS 的网关指向 Director 的 DIP,这样 CIP 目的的数据包才能保证交给 director 进行处理并返回给客户端。
因此,如下操作 Director:假设该实验中的 VS/NAT 采用 wrr 调度算法。
[root@director ~]# echo 1 >/proc/sys/net/ipv4/ip_forward
[root@director ~]# ipvsadm -A -t 192.168.122.202:80 -s wrr
[root@director ~]# ipvsadm -a -t 192.168.122.202:80 -r 172.16.4.203 -m -w 2
[root@director ~]# ipvsadm -a -t 192.168.122.202:80 -r 172.16.4.204 -m -w 3
如下操作各 RS:
# realserver1
[root@realserver1 ~]# yum install httpd -y
[root@realserver1 ~]# echo "This is page from RS1:172.16.4.203" > /var/www/html/index.html
[root@realserver1 ~]# service httpd start
Redirecting to /bin/systemctl start httpd.service
[root@realserver1 ~]# route del default
[root@realserver1 ~]# route add default gw 172.16.4.202
# realserver2
[root@realserver2 ~]# yum install httpd -y
[root@realserver2 ~]# echo "This is page from RS2:172.16.4.204" > /var/www/html/index.html
[root@realserver2 ~]# service httpd start
Redirecting to /bin/systemctl start httpd.service
[root@realserver2 ~]# route del default
[root@realserver2 ~]# route add default gw 172.16.4.202
使用 linux 客户端(192.168.122.8) 测试 http://192.168.122.202:80:
[root@yidam ~]# curl http://192.168.122.202
<h1>This is page from RS1:172.16.4.203</h1>
[root@yidam ~]# curl http://192.168.122.202
<h1>This is page from RS2:172.16.4.204</h1>
[root@yidam ~]# curl http://192.168.122.202
<h1>This is page from RS2:172.16.4.204</h1>
[root@yidam ~]# curl http://192.168.122.202
<h1>This is page from RS1:172.16.4.203</h1>
[root@yidam ~]# curl http://192.168.122.202
<h1>This is page from RS2:172.16.4.204</h1>
[root@yidam ~]# curl http://192.168.122.202
<h1>This is page from RS1:172.16.4.203</h1>
[root@yidam ~]# curl http://192.168.122.202
<h1>This is page from RS2:172.16.4.204</h1>
[root@yidam ~]# curl http://192.168.122.202
<h1>This is page from RS2:172.16.4.204</h1>
查看 director 主机统计数据:
[root@director ~]# ipvsadm -ln --stats
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Conns InPkts OutPkts InBytes OutBytes
-> RemoteAddress:Port
TCP 192.168.122.202:80 14 115 78 14586 10292
-> 172.16.4.203:80 5 35 24 3440 3279
-> 172.16.4.204:80 9 80 54 11146 7013
[root@director ~]#
注意事项:
- VS/NAT 模式下,realserver 上不要配置跟 vip 同网段的 ip 地址,否则会出现请求异常的情况。
- 测试客户端最好使用 linux 系统,使用 curl 命令请求 vip 地址,得到的结果更接近期望值,方便分析。
10.8、实现 VS/DR 模式的负载均衡
先说明一下 VS/DR 模式下,Director 的功能:
- 在 VS/DR 模式下,TCP 连接是客户端和 RS 之间建立的,Director 只是负责改造、转发建立 TCP 连接时的数据包给后端 RealServer;
- 当 TCP 连接建立完成后,就有了客户端和服务端(RS)的概念,这时客户端将直接和 RS 进行数据通信,而 Director 已经退出舞台,不再负责改造、转发请求数据包,直到关闭连接。
- 也就是说,Director 改造、转发的数据包只有客户端发送的和 tcp 连接建立、关闭相关的 syn、ack 和 fin 包,其它数据包和它无关(网络状况良好的情况下,共转发 syn+ack、fin+ack 4 个包)。
- 可以想象,这样的 Director 相比 NAT 模式,性能高的不是一点点。
请求应答过程:
- 当客户端发起
http://VIP
的请求时,数据包的源和目标地址为CIP-->VIP
。这个数据包最终会到达 Director。 - 当 Director 收到请求数据包后,将根据调度算法选择一台后端 RealServer 作为调度的目标。于是修改数据包的目标 MAC 地址为该 RealServer 的 RIP 所在 MAC 地址。数据包将转发给该 RS,此时数据包的源和目标 IP 地址仍然为
CIP-->VIP
,但源和目标 MAC 地址为DIP_MAC-->RIP_MAC
。- 需要注意的是,Director 虽然要将数据包交给 RS,但交出去的这个数据包是修改过目标 MAC 地址的数据包。也就是说,交给 RIP 的数据包不是原包,因此在 Director 上不会经过 ip_forward 转发,而是修改 MAC 后根据路由决策直接路由到 RIP。
- 因此,VS/DR 模式下,Director 无需开启 ip_forward 功能,这一点很容易出现误解,其实如果搭建过 keepalived+lvs 的 DR,就可以发现 keepalived 管理 ipvs 的时候,并没有开启 ip_forward。
- 被调度到的 RS 收到 Director 转发的数据包后,发现目标 IP 地址已经配置在自身内核,因此会收下该数据包。之后会处理请求,并构建响应数据。
- RS 将响应数据响应给客户端,该响应数据包的源和目标 IP 地址为
VIP-->CIP
。- 由于 RS 上的 VIP 一般配置在 lo 的别名接口上,它无法和外界直接通信,因此数据包最终会从普通网卡上流出,如 RIP 所在接口。
- 根据 TCP/IP 协议,响应数据包的源 MAC 地址也将是普通网卡(如 RIP 所在接口)的 MAC 地址。这一细节在后面配置 CIP、VIP、RIP 不同网段时会引出一种特殊问题,详情见后文。
10.8.1、CIP、VIP、RIP 同网段实验
CIP、VIP 和 RIP 同网段时,配置非常简单,几乎无需考虑额外的路由问题,也都无需分析数据包流向问题。
实验环境如下:
配置 Director:
[root@director ~]# ipvsadm -C
[root@director ~]# ipvsadm -A -t 192.168.122.9:80 -s wrr
[root@director ~]# ipvsadm -a -t 192.168.122.9:80 -r 192.168.122.203 -g -w 2
[root@director ~]# ipvsadm -a -t 192.168.122.9:80 -r 192.168.122.204 -g -w 1
配置所有 realservers:
# 1.提供web服务和测试页面
yum -y install httpd
echo "This is page from RS1:172.16.4.203" > /var/www/html/index.html # 在RS1上操作
echo "This is page from RS2:172.16.4.204" > /var/www/html/index.html # 在RS2上操作
service httpd start
# 2.设置arp参数
echo 1 >/proc/sys/net/ipv4/conf/all/arp_ignore
echo 2 >/proc/sys/net/ipv4/conf/all/arp_announce
# 3.设置VIP
ifconfig lo:0 192.168.122.9 netmask 255.255.255.255 up
route add -host 192.168.122.9 dev lo:0
- DR 模型中一定要配 lo:0 回环口的路由,不然内核不能用 lo:0 上的 vip 进行封装报文。
- 注意即使配了 arp_announce、arp_ignore,一样要将 lo:0 配成 32 位掩码,抑制 arp feedback,而且前面两个内核参数要提前改,因为先配了 ip 就会广播。
- 学习 iptables 的章节时,我们就知道地址是属于内核的,不是属于接口的,请求内核的地址从哪个接口进来默认从哪个接口出去,不过路由表才是真正具有权威的。
经验分享:
-
实验过程中,我第一次没有在 Director 上设置
eth0:0
用作 VIP,而是直接使用 eth0 当 VIP,结果一旦 RealServers 配置了 lo:0 作为 VIP 时,就会跟 Director 上的 eth0 VIP 地址冲突。最终导致 Director 和 RealServers 之间无法通信,即便设置了 arp_announce、arp_ignore,也没有意义,因为地址是属于内核的,这两个参数只是让内核从哪个口如何响应 arp 罢了。 -
实验过程中如果跟我一样 Director 中只设置了一个 IP 地址,而且作为了 VIP,那么实验过程中你会得到
No route to host
错误。 -
为了有一个更直观的理解,把上述拓扑图实验环境配置好,通过客户端(192.168.122.8) 去 curl http://192.168.122.9:80 两次,然后在 Director 的 eth0 接口上抓包,如下:
[root@director ~]# tcpdump -i eth0 not tcp port 22 -nn tcpdump: verbose output suppressed, use -v or -vv for full protocol decode listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes 12:27:10.889114 IP 192.168.122.8.55976 > 192.168.122.9.80: Flags [S], seq 4149950767, win 29200, options [mss 1460,sackOK,TS val 1289129575 ecr 0,nop,wscale 7], length 0 12:27:10.889175 ARP, Request who-has 192.168.122.203 tell 192.168.122.202, length 28 12:27:10.889700 ARP, Reply 192.168.122.203 is-at 52:54:00:0e:05:54, length 28 12:27:10.889729 IP 192.168.122.8.55976 > 192.168.122.9.80: Flags [S], seq 4149950767, win 29200, options [mss 1460,sackOK,TS val 1289129575 ecr 0,nop,wscale 7], length 0 12:27:10.890167 IP 192.168.122.8.55976 > 192.168.122.9.80: Flags [.], ack 1449142928, win 229, options [nop,nop,TS val 1289129576 ecr 4290791627], length 0 12:27:10.890203 IP 192.168.122.8.55976 > 192.168.122.9.80: Flags [.], ack 1, win 229, options [nop,nop,TS val 1289129576 ecr 4290791627], length 0 12:27:10.890250 IP 192.168.122.8.55976 > 192.168.122.9.80: Flags [P.], seq 0:77, ack 1, win 229, options [nop,nop,TS val 1289129576 ecr 4290791627], length 77: HTTP: GET / HTTP/1.1 12:27:10.890258 IP 192.168.122.8.55976 > 192.168.122.9.80: Flags [P.], seq 0:77, ack 1, win 229, options [nop,nop,TS val 1289129576 ecr 4290791627], length 77: HTTP: GET / HTTP/1.1 12:27:10.890999 IP 192.168.122.8.55976 > 192.168.122.9.80: Flags [.], ack 286, win 237, options [nop,nop,TS val 1289129577 ecr 4290791628], length 0 12:27:10.891042 IP 192.168.122.8.55976 > 192.168.122.9.80: Flags [.], ack 286, win 237, options [nop,nop,TS val 1289129577 ecr 4290791628], length 0 12:27:10.891353 IP 192.168.122.8.55976 > 192.168.122.9.80: Flags [F.], seq 77, ack 286, win 237, options [nop,nop,TS val 1289129577 ecr 4290791628], length 0 12:27:10.891389 IP 192.168.122.8.55976 > 192.168.122.9.80: Flags [F.], seq 77, ack 286, win 237, options [nop,nop,TS val 1289129577 ecr 4290791628], length 0 12:27:10.891820 IP 192.168.122.8.55976 > 192.168.122.9.80: Flags [.], ack 287, win 237, options [nop,nop,TS val 1289129578 ecr 4290791628], length 0 12:27:10.891851 IP 192.168.122.8.55976 > 192.168.122.9.80: Flags [.], ack 287, win 237, options [nop,nop,TS val 1289129578 ecr 4290791628], length 0 12:27:11.352836 IP 192.168.122.8.55978 > 192.168.122.9.80: Flags [S], seq 1099517050, win 29200, options [mss 1460,sackOK,TS val 1289130039 ecr 0,nop,wscale 7], length 0 12:27:11.352899 ARP, Request who-has 192.168.122.204 tell 192.168.122.202, length 28 12:27:11.353344 ARP, Reply 192.168.122.204 is-at 52:54:00:52:1a:d4, length 28 12:27:11.353373 IP 192.168.122.8.55978 > 192.168.122.9.80: Flags [S], seq 1099517050, win 29200, options [mss 1460,sackOK,TS val 1289130039 ecr 0,nop,wscale 7], length 0 12:27:11.353758 IP 192.168.122.8.55978 > 192.168.122.9.80: Flags [.], ack 322887858, win 229, options [nop,nop,TS val 1289130040 ecr 3849403757], length 0 12:27:11.353796 IP 192.168.122.8.55978 > 192.168.122.9.80: Flags [.], ack 1, win 229, options [nop,nop,TS val 1289130040 ecr 3849403757], length 0 12:27:11.353825 IP 192.168.122.8.55978 > 192.168.122.9.80: Flags [P.], seq 0:77, ack 1, win 229, options [nop,nop,TS val 1289130040 ecr 3849403757], length 77: HTTP: GET / HTTP/1.1 12:27:11.353833 IP 192.168.122.8.55978 > 192.168.122.9.80: Flags [P.], seq 0:77, ack 1, win 229, options [nop,nop,TS val 1289130040 ecr 3849403757], length 77: HTTP: GET / HTTP/1.1 12:27:11.354754 IP 192.168.122.8.55978 > 192.168.122.9.80: Flags [.], ack 286, win 237, options [nop,nop,TS val 1289130041 ecr 3849403758], length 0 12:27:11.354799 IP 192.168.122.8.55978 > 192.168.122.9.80: Flags [.], ack 286, win 237, options [nop,nop,TS val 1289130041 ecr 3849403758], length 0 12:27:11.354950 IP 192.168.122.8.55978 > 192.168.122.9.80: Flags [F.], seq 77, ack 286, win 237, options [nop,nop,TS val 1289130041 ecr 3849403758], length 0 12:27:11.354982 IP 192.168.122.8.55978 > 192.168.122.9.80: Flags [F.], seq 77, ack 286, win 237, options [nop,nop,TS val 1289130041 ecr 3849403758], length 0 12:27:11.355422 IP 192.168.122.8.55978 > 192.168.122.9.80: Flags [.], ack 287, win 237, options [nop,nop,TS val 1289130042 ecr 3849403758], length 0 12:27:11.355455 IP 192.168.122.8.55978 > 192.168.122.9.80: Flags [.], ack 287, win 237, options [nop,nop,TS val 1289130042 ecr 3849403758], length 0 ^C 61 packets captured 61 packets received by filter 0 packets dropped by kernel [root@director ~]#
- 两次请求的最开始都是 Director arp 广播寻找 RealServers,是 192.168.122.202 找 192.168.122.203/204,不是 VIP 192.168.122.9 找 192.168.122.203/204,不然会跟 RealServers 上的 VIP 冲突。所以 Director 上必须配置除 VIP 外的自己的地址。
- 为什么 VS/NAT 上 Director 没有配置
eth0:0
呢?因为这种模式 RealServers 没有 VIP,而且寻址逻辑完全不同。 - 另外通过这两次请求的 Director arp 寻址,正好可以感受到 ipvsadm 修改 MAC 地址的精髓。
10.8.2、CIP、VIP、RIP 不同网段实验
考虑实际的生产环境,由于公网地址有限,一般只给 web server 的负载均衡系统分配一个公网地址。这个公网地址可能直接分配给 VIP,这样 CIP、VIP、RIP 两两都处于不同网段;还可能分配给路由器或防火墙,然后 VIP 和 RIP 都是用私网地址,这样 VIP/RIP 同网段,但它们和 CIP 不同网段。
不管公网地址分配给谁,CIP、RIP 总是不同网段的。这时需要考虑以下几个问题:
- RS 将响应数据响应给客户端,该响应数据包的源和目标 IP 地址为
VIP-->CIP
,但因为是从 RIP 所在网卡流出的,这个响应数据包的源 MAC 地址为 MAC_RIP; - 由于 RIP 和 CIP 不同网段,因此 RS 需要借助某个路由设备来转发这个数据包;
- 由于 RS 上的 VIP 被隐藏,DIP/RIP 所在网段的所有主机(包括这个路由设备)上缓存的关于 VIP 的 arp 记录都是 Director 上的(即
VIP<-->MAC_V
),即使重新发起 arp 请求,也只能获取到这样的对应关系。- 因此,响应数据包的源 IP、源 MAC 的对应关系和路由设备 arp 缓存中记录不一致,这个响应数据包是 "非法" 数据包。
- 这个路由设备发现数据包的源 MAC 地址、源 IP 和它 arp 缓存中的记录不一致,它会丢弃这个数据包吗?分两种情况:
- 如果这个数据包被路由到普通的路由设备上,路由设备默认不会检查源地址是否合法。这种检查称为 "reverse path filter"(反向路径过滤)功能。默认 Linux 主机会做反向检查,Linux 上控制该功能的参数为 rp_filter。
- 如果这个数据包被路由到 Director 所在主机(Linux)上,默认会丢弃这个数据包,因为源 IP 地址正好在本机上。但可以为内核打上 forward_shared 补丁,以便使用 forward_shared 和 rp_filter 参数来开启转发源 IP 地址为自身地址数据包的功能。转发时,需要设置转发接口为 VIP 所在接口,这样数据包的源 MAC 地址和 VIP 相互对应,之后的路由过程会一路顺畅。
根据第四点的 2 种情况,Director 与 RS 之间的路由器有如下两个选择:
-
第一种情况设置很简单,但 Linux 充当路由设备关闭源 IP 地址检查功能后,将更容易受到 DDos 攻击。
-
第二种情况可以忽略,不仅要重新编译内核,更重要的是 VS/DR 模式的优点本就在于让 Director 专注于调度工作来实现高性能,而不应该让其帮助转发。
因此,本文将以第一种情况来做实验测试。
以下是 Linux内核文档 中关于rp_filter变量的描述:
rp_filter - INTEGER
0 - No source validation.
1 - Strict mode as defined in RFC3704 Strict Reverse Path
Each incoming packet is tested against the FIB and if the interface
is not the best reverse path the packet check will fail.
By default failed packets are discarded.
2 - Loose mode as defined in RFC3704 Loose Reverse Path
Each incoming packet's source address is also tested against the FIB
and if the source address is not reachable via any interface
the packet check will fail.
Current recommended practice in RFC3704 is to enable strict mode
to prevent IP spoofing from DDos attacks. If using asymmetric routing
or other complicated routing, then loose mode is recommended.
The max value from conf/{all,interface}/rp_filter is used
when doing source validation on the {interface}.
Default value is 0. Note that some distributions enable it
in startup scripts.
- 0:不开启源地址校验。
- 1:开启严格的反向路径校验。对每个进来的数据包,校验其反向路径是否是最佳路径(通过特定接口)。如果反向路径不是最佳路径,则直接丢弃该数据包。
- 2:开启松散的反向路径校验。对每个进来的数据包,校验其源地址是否可达,即反向路径是否能通(通过任意接口),如果反向路径不通,则直接丢弃该数据包。这个值只是为了防止转发无效或冒充的源 IP 地址,如 192.16.10 这样的不完整 IP。
- 取 /proc/sys/net/ipv4/conf/{all,interface}/rp_filter 中的最大值生效。注意,对 lo 接口设置该参数是无意义的。
为了更好地解决实际生产问题,我专门就自己的工作环境设计了一张拓扑图,如果有需要,我会以此设计方案作为生产环境,如下图:
配置 lvs 比较简单,几条命令就可以了,真正的难点在网络架构如何设计,这里并没有加上 HA 高可用,所以相对来说还比较简单。把这个实验搞懂,再加个 keepalived 也不在话下。
Director 配置:
[root@director ~]# ipvsadm -C
[root@director ~]# ipvsadm -A -t 192.168.122.9:80 -s wrr
[root@director ~]# ipvsadm -a -t 192.168.122.9:80 -r 172.16.4.203 -g -w 2
[root@director ~]# ipvsadm -a -t 192.168.122.9:80 -r 172.16.4.204 -g -w 1
# 添加 vip,我这里使用 ip add 添加的,跟 ifconfig eth0:0 一样,都是临时生效,生产上一定要写在配置文件中
[root@director ~]# ip add add 192.168.122.9/24 dev eth0
所有 RealServers 配置:
# 1.提供web服务和测试页面
yum -y install httpd
echo "This is page from RS1:172.16.4.203" > /var/www/html/index.html # 在RS1上操作
echo "This is page from RS2:172.16.4.204" > /var/www/html/index.html # 在RS2上操作
service httpd start
# 2.设置arp参数
echo 1 >/proc/sys/net/ipv4/conf/all/arp_ignore
echo 2 >/proc/sys/net/ipv4/conf/all/arp_announce
# 3.设置VIP
ifconfig lo:0 192.168.122.9 netmask 255.255.255.255 up
route add -host 192.168.122.9 dev lo:0
# 4.一定要注意,网关不能指向director,而是要指向router,我在网络配置文件中写好了,按照上面拓扑去设置
此时,如果通过客户端(192.168.122.1) 去请求 vip(192.168.122.9),结果是不通的,这是 VS/DR 模式下跨网段的特殊性,重点在这个 router 上,理论上面分析得很详细,如下设置 router 即可完成配置:
[root@router ~]# echo 1 > /proc/sys/net/ipv4/ip_forward
[root@router ~]# echo 2 > /proc/sys/net/ipv4/conf/all/rp_filter
- 这个 router 是 linux 主机,所以必须配置 rp_filter,上一小节是同网段,不需要走三层路由,所以可以不用设置 rp_filter。
客户端再测试,没有问题:
[root@yidam ~]# curl 192.168.122.9
<h1>This is page from RS1:172.16.4.203</h1>
[root@yidam ~]# curl 192.168.122.9
<h1>This is page from RS1:172.16.4.203</h1>
[root@yidam ~]# curl 192.168.122.9
<h1>This is page from RS2:172.16.4.204</h1>
[root@yidam ~]# curl 192.168.122.9
<h1>This is page from RS1:172.16.4.203</h1>
[root@yidam ~]# curl 192.168.122.9
<h1>This is page from RS1:172.16.4.203</h1>
[root@yidam ~]# curl 192.168.122.9
<h1>This is page from RS2:172.16.4.204</h1>
[root@yidam ~]#