Fork me on GitHub

企业级调度器LVS

Cluster集群

     Cluster:集群,为解决某个特定问题将多台计算机组合起来形成的单个系统
• Linux Cluster类型:
​ • LB:Load Balancing,负载均衡
​ • HA:High Availiablity,高可用,SPOF(single Point Of failure)
• 分布式系统:
​ 分布式存储:云盘
​ 分布式计算:hadoop,Spark

• 系统扩展方式:
​ Scale UP:向上扩展(更换性能更好的设备)
​ Scale Out:向外扩展(Cluster)

Cluster分类

LB Cluster的实现

  1. 硬件
    F5 Big-IP
    Citrix Netscaler
    A10 A10
  2. 软件
    lvs:Linux Virtual Server,支持四层调度
    nginx:支持七层调度,阿里七层SLB使用Tengine
    haproxy:支持七层调度
    ats:apache traffic server,yahoo捐助
    perlbal:Perl 编写
    pound

基于工作的协议层次划分

  1. 传输层(通用):DPORT 器
    nginx:stream
    haproxy:mode tcp
  2. 应用层(专用):针对特定协议,自定义的请求模型分类
    proxy server:
    http:nginx, httpd, haproxy(mode http), …
    fastcgi:nginx, httpd, …
    mysql:mysql-proxy,

LVS

    LVS(Linux Virtual Server)即Linux虚拟服务器,是由章文嵩博士主导的开源负载均衡项目,目前LVS已经被集成到Linux内核模块中。该项目在Linux内核中实现了基于IP的数据请求负载均衡调度方案。

工作原理

VS: Virtual Server,负责调度
RS: Real Server,负责真正提供服务
L4:四层路由器或交换机

lvs工作原理.jpg

  1. 当用户向负载均衡调度器(Director Server)发起请求,调度器将请求发往至内核空间

  2. PREROUTING链首先会接收到用户请求,判断目标IP确定是本机IP,将数据包发往INPUT链

  3. IPVS是工作在INPUT链上的,当用户请求到达INPUT时,IPVS会将用户请求和自己已定义好的集群服务进行比对,如果用户请求的就是定义的集群服务,那么此时IPVS会强行修改数据包里的目标IP地址及端口,并将新的数据包发往POSTROUTING链

  4. POSTROUTING链接收数据包后发现目标IP地址刚好是自己的后端服务器,那么此时通过选路,将数据包最终发送给后端的服务器

LVS的组成

LVS 由2部分程序组成,包括 ipvs 和 ipvsadm。
​ ipvsadm:用户空间的命令行工具,规则管理器,用于管理集群服务及相关的RealServer;
​ ipvs:工作于内核空间的netfilter的INPUT钩子之上的框架;

lvs集群类型中的术语

  1. VS:Virtual Server,Director Server(DS) , Dispatcher(调度器),Load Balancer.指的是前端负载均衡器节点。
  2. RS:Real Server(lvs), upstream server(nginx) , backend server(haproxy).后端真实的工作服务器。
  3. CIP:Client IP.访问客户端的IP地址。
  1. VIP: Virtual serve IP.向外部直接面向用户请求,作为用户请求的目标的IP地址。
  2. DIP: Director IP,主要用于和内部主机通讯的IP地址。
  3. RIP: Real server IP.后端服务器的IP地址。

• 访问流程:CIP <--> VIP == DIP <--> RIP

LVS负载均衡调度算法

静态算法:仅根据算法本身和请求报文特征惊醒调度

  1. 轮询调度(RR)

    轮询调度(Round Robin 简称’RR’)算法就是按依次循环的方式将请求调度到不同的服务器上,该算法最大的特点就是实现简单。轮询算法假设所有的服务器处理请求的能力都一样的,调度器会将所有的请求平均分配给每个真实服务器,不管后端 RS 配置和处理能力,非常均衡地分发下去。

  1. 加权轮询(WRR)

    加权轮询(Weight Round Robin 简称’WRR’)算法主要是对轮询算法的一种优化与补充,LVS会考虑每台服务器的性能,并给每台服务器添加一个权值,如果服务器A的权值为1,服务器B的权值为2,则调度器调度到服务器B的请求会是服务器A的两倍。权值越高的服务器,处理的请求越多。

  1. 源地址散列调度(SH)

    源地址散列调度(Source Hashing 简称’SH’)算法先根据请求的源IP地址,作为散列键(Hash Key)从静态分配的散列表找出对应的服务器,若该服务器是可用的且并未超载,将请求发送到该服务器,否则返回空。它采用的散列函数与目标地址散列调度算法的相同,它的算法流程与目标地址散列调度算法的基本相似。

  1. 目标地址散列调度(DH)

    目标地址散列调度(Destination Hashing 简称’DH’)算法先根据请求的目标IP地址,作为散列键(Hash Key)从静态分配的散列表找出对应的服务器,若该服务器是可用的且并未超载,将请求发送到该服务器,否则返回空。

动态算法:额外考虑后端各RS的当前的负载状态

  1. 最少连接(lc)

    最少连接( Least Connections 简称’LC’)算法会根据后端 RS 的连接数来决定把请求分发给谁,比如 RS1 连接数比 RS2 连接数少,那么请求就优先发给 RS1

  1. 加权最小连接调度(WLC)

    加权最少连接(Weight Least Connections 简称’WLC’)算法是最小连接调度的超集,各个服务器相应的权值表示其处理性能。服务器的缺省权值为1,系统管理员可以动态地设置服务器的权值。加权最小连接调度在调度新连接时尽可能使服务器的已建立连接数和其权值成比例。调度器可以自动问询真实服务器的负载情况,并动态地调整其权值。

  1. 最短的期望的延迟(SED)

    最短的期望的延迟调度(Shortest Expected Delay 简称’SED’)算法基于WLC算法。举个例子吧,ABC三台服务器的权重分别为1、2、3 。那么如果使用WLC算法的话一个新请求进入时它可能会分给ABC中的任意一个。使用SED算法后会进行一个运算

A:(1+1)/1=2 B:(1+2)/2=3/2 C:(1+3)/3=4/3 就把请求交给得出运算结果最小的服务器。

  1. 永不排队(NQ)

    永不排队(Never Queue 简称’NQ’)算法,无需队列。如果有realserver的连接数等于0就直接分配过去,不需要在进行SED运算。

  1. 基于局部的最少连接(LBLC)

    基于局部的最少连接调度(Locality-Based Least Connections 简称’LBLC’)算法是针对请求报文的目标IP地址的 负载均衡调度,目前主要用于Cache集群系统,因为在Cache集群客户请求报文的目标IP地址是变化的。这里假设任何后端服务器都可以处理任一请求,算法的设计目标是在服务器的负载基本平衡情况下,将相同目标IP地址的请求调度到同一台服务器,来提高各台服务器的访问局部性和Cache命中率,从而提升整个集群系统的处理能力。LBLC调度算法先根据请求的目标IP地址找出该目标IP地址最近使用的服务器,若该服务器是可用的且没有超载,将请求发送到该服务器;若服务器不存在,或者该服务器超载且有服务器处于一半的工作负载,则使用’最少连接’的原则选出一个可用的服务器,将请求发送到服务器。

6.带复制的基于局部性的最少连接

    带复制的基于局部性的最少连接(Locality-Based Least Connections with Replication 简称’LBLCR’)算法也是针对目标IP地址的负载均衡,目前主要用于Cache集群系统,它与LBLC算法不同之处是它要维护从一个目标IP地址到一组服务器的映射,而LBLC算法维护从一个目标IP地址到一台服务器的映射。按’最小连接’原则从该服务器组中选出一一台服务器,若服务器没有超载,将请求发送到该服务器;若服务器超载,则按’最小连接’原则从整个集群中选出一台服务器,将该服务器加入到这个服务器组中,将请求发送到该服务器。同时,当该服务器组有一段时间没有被修改,将最忙的服务器从服务器组中删除,以降低复制的程度。

lvs集群的类型

lvs-nat:修改请求报文的目标IP,多目标IP的DNAT
lvs-dr:操纵封装新的MAC地址
lvs-tun:在原请求IP报文之外新加一个IP首部
lvs-fullnat:修改请求报文的源和目标IP

lvs-nat模式

lvs-nat.jpg

lvs-nat:
本质是多目标IP的DNAT,通过将请求报文中的目标地址和目标端口修改为某挑出的RS的RIP和PORT实现转发
(1)RIP和DIP应在同一个IP网络,且应使用私网地址;RS的网关要指向DIP
(2)请求报文和响应报文都必须经由Director转发,Director易于成为系统瓶颈
(3)支持端口映射,可修改请求报文的目标PORT
(4)VS必须是Linux系统,RS可以是任意OS系统

LVS-DR模式

lvs-dr.jpg

DR模式IP包调度过程

lvs-DR模式IP包调度过程.png

LVS-DR:

Direct Routing,直接路由,LVS默认模式,应用最广泛,通过为请求报文重新封装一个MAC首部进行转发,源MAC是DIP所在的接口的MAC,目标MAC是某挑选出的RS的RIP所在接口的MAC地址;源IP/PORT,以及目标IP/PORT均保持不变

  1. Director和各RS都配置有VIP
  2. 确保前端路由器将目标IP为VIP的请求报文发往Director
    (1) 在前端网关做静态绑定VIP和Director的MAC地址
    (2) 在RS上使用arptables工具
1
2
arptables -A IN -d $VIP -j DROP
arptables -A OUT -s $VIP -j mangle --mangle-ip-s $RIP

​ (3) 在RS上修改内核参数以限制arp通告及应答级别

1
2
/proc/sys/net/ipv4/conf/all/arp_ignore
/proc/sys/net/ipv4/conf/all/arp_announce

​ 限制响应级别:arp_ignore

  • ​ 0:默认值,表示可使用本地任意接口上配置的任意地址进行响应
  • ​ 1: 仅在请求的目标IP配置在本地主机的接收到请求报文的接口上时,才给予响应

​ 限制通告级别:arp_announce

  • ​ 0:默认值,把本机所有接口的所有信息向每个接口的网络进行通告
  • ​ 1:尽量避免将接口信息向非直接连接网络进行通告
  • ​ 2:必须避免将接口信息向非本网络进行通告
  1. RS的RIP可以使用私网地址,也可以是公网地址;RIP与DIP在同一IP网络;RIP的网关不能指向DIP,以确保响应报文不会经由Director
  2. RS和Director要在同一个物理网络
  3. 请求报文要经由Director,但响应报文不经由Director,而由RS直接发往Client
  4. 不支持端口映射(端口不能修败)
  5. RS可使用大多数OS系统

lvs-tun模式

lvs-tun.jpg

lvs-tun:

转发方式:不修改请求报文的IP首部(源IP为CIP,目标IP为VIP),而在原IP报文之外再封装一个IP首部(源IP是DIP,目标IP是RIP),将报文发往挑选出的目标RS;RS直接响应给客户端(源IP是VIP,目标IP是CIP)
(1) DIP, VIP, RIP都应该是公网地址
(2) RS的网关一般不能指向DIP
(3) 请求报文要经由Director,但响应不经由Director
(4) 不支持端口映射
(5) RS的OS须支持隧道功能

lvs-fullnat模式

lvs-fullnat:通过同时修改请求报文的源IP地址和目标IP地址进行转发
CIP –> DIP
VIP –> RIP
(1) VIP是公网地址,RIP和DIP是私网地址,且通常不在同一IP网络;因此,RIP的网关一般不会指向DIP
(2) RS收到的请求报文源地址是DIP,因此,只需响应给DIP;但Director还要将其发往Client
(3) 请求和响应报文都经由Director
(4) 支持端口映射
注意:此类型kernel默认不支持

ipvsadm

ipvsadm包构成

程序包:ipvsadm
Unit File: ipvsadm.service
主程序:/usr/sbin/ipvsadm
规则保存工具:/usr/sbin/ipvsadm-save
规则重载工具:/usr/sbin/ipvsadm-restore
配置文件:/etc/sysconfig/ipvsadm-config

ipvsadm命令

管理集群服务

​增、改:

1
ipvsadm -A|E -t|u|f service-address [-s scheduler][-p [timeout]]

删除:

1
ipvsadm -D -t|u|f service-address

service-address:
-t|u|f:
​ -t: TCP协议的端口,VIP:TCP_PORT
​ -u: UDP协议的端口,VIP:UDP_PORT
​ -f:firewall MARK,标记,一个数字
[-s scheduler]:指定集群的调度算法,默认为wlc

查看

1
ipvsadm -A|E -t|u|f service-address [-s scheduler][-p [timeout]] [-M netmask][--pe persistence_engine] [-b sched-flags]

清空定义的所有内容

1
ipvsadm –C

清空计数器

1
ipvsadm -Z [-t|u|f service-address]

查看

1
ipvsadm -L|l [options]

–numeric, -n:以数字形式输出地址和端口号(和 -L|l 连起来使用只能写在后面)
–exact:扩展信息,精确值
–connection,-c:当前IPVS连接输出
–stats:统计信息
–rate :输出速率信息

ipvs规则

1
/proc/net/ip_vs

ipvs连接

1
/proc/net/ip_vs_conn

保存:建议保存至/etc/sysconfig/ipvsadm

1
2
3
ipvsadm-save > /PATH/TO/IPVSADM_FILE
ipvsadm -S > /PATH/TO/IPVSADM_FILE
systemctl stop ipvsadm.service

重载

1
2
3
ipvsadm-restore < /PATH/FROM/IPVSADM_FILE
ipvsadm -R < /PATH/FROM/IPVSADM_FILE
systemctl restart ipvsadm.service

管理集群上的RS:增、改、删
增、改:

1
ipvsadm -a|e -t|u|f service-address -r server-address [-g|i|m][-w weight]

删:

1
ipvsadm -d -t|u|f service-address -r server-address

server-address:
rip[:port] 如省略port,不作端口映射
选项:
lvs类型:
-g: gateway, dr类型,默认
-i: ipip, tun类型
-m: masquerade, nat类型
-w weight:权重

实现lvs-nat实验

用宿主机模拟Director Server,容器模拟两个Real Server。

在宿一台主机先用docker pull镜像,做宿主机

1
[root@centos7 ~]#docker image pull busybox

配置两个容器并启动httpd服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
另开一个终端
[root@centos7 ~]#docker run --name rs1 -it --network bridge -v /vols/rsl:/data/web/html busybox
/ # httpd -h /data/web/html/
/ # netstat -ntl
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address Foreign Address State
tcp 0 0 :::80 :::* LISTEN
宿主机
[root@centos7 rsl]#vim /vols/rsl/index.html
<h1>RS1</h1>

再开一个终端
[root@centos7 ~]#docker run --name rs2 -it --network bridge -v /vols/rs2:/data/web/html busybox
/ # httpd -h /data/web/html/
/ # netstat -ntl
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address Foreign Address State
tcp 0 0 :::80 :::* LISTEN
宿主机
[root@centos7 rs2]#vim /vols/rsl/index.html
<h1>RS2</h1>

index.html的内容不一样,是为了演示调度效果

在宿主机上修改iptables forward 链的规则

1
2
3
4
5
6
7
8
[root@centos7 rs2]#which iptables
/usr/sbin/iptables
[root@centos7 rs2]#vim /usr/lib/systemd/system/docker.service
在ExecStart下面加上一行
ExecStartPost=/usr/sbin/iptables -P FORWARD ACCEPT
[root@centos7 rs2]#systemctl daemon-reload
重启会导致docker关闭,故手动添加一条规则
[root@centos7 rsl]#iptables -P FORWARD ACCEPT

在宿主机上添加ipvsadm规则

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[root@centos7 ~]#ipvsadm -A -t 192.168.146.154:80 -s wrr
[root@centos7 ~]#ipvsadm -Ln
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
-> RemoteAddress:Port Forward Weight ActiveConn InActConn
TCP 192.168.146.154:80 wrr
[root@centos7 ~]#ipvsadm -a -t 192.168.146.154:80 -r 172.17.0.2:80 -m -w 1
[root@centos7 ~]#ipvsadm -a -t 192.168.146.154:80 -r 172.17.0.3:80 -m -w 1
[root@centos7 ~]#ipvsadm -Ln
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
-> RemoteAddress:Port Forward Weight ActiveConn InActConn
TCP 192.168.146.154:80 wrr
-> 172.17.0.2:80 Masq 1 0 0
-> 172.17.0.3:80 Masq 1 0 0

在另外一个终端发请求进行模拟

1
[root@centos7 ~]#while true; do curl 192.168.146.154;sleep 0.5; done

实现lvs-DR实验

准备三台虚拟机,分别为node01、node02、node03. node01为Director Server(192.168.146.152),node02(192.168.146.154)、node03(192.168.146.139)为Real Server。

配置DS的VIP,给ens37起个别名,并且只广播给自己

1
[root@node01 ~]#ifconfig ens37:0 192.168.146.100 netmask 255.255.255.255 broadcast 192.168.146.100 up

配置RS(写RS的配置脚本)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#!/bin/bash
vip=192.168.146.100
mask='255.255.255.255'
interface="lo:0"

case $1 in
start)
iptables -F
echo 1 > /proc/sys/net/ipv4/conf/all/arp_ignore
echo 1 > /proc/sys/net/ipv4/conf/lo/arp_ignore
echo 2 > /proc/sys/net/ipv4/conf/all/arp_announce
echo 2 > /proc/sys/net/ipv4/conf/lo/arp_announce

ifconfig $interface $vip netmask $mask broadcast $vip up
route add -host $vip dev $interface
;;
stop)
ifconfig $interface down

echo 0 > /proc/sys/net/ipv4/conf/all/arp_ignore
echo 0 > /proc/sys/net/ipv4/conf/lo/arp_ignore
echo 0 > /proc/sys/net/ipv4/conf/all/arp_announce
echo 0 > /proc/sys/net/ipv4/conf/lo/arp_announce
;;
*)
echo "Usage: $(basename $0) start|stop"
exit 1
;;
esac

分别在node02、node03上运行脚本

1
2
[root@node02 ~]#bash  setkpara.sh start
[root@node03 ~]#bash setkpara.sh start

配置DS ipvsadm

1
2
3
4
5
6
7
8
9
10
[root@node01 ~]#ipvsadm -A -t 192.168.146.100:80 -s wrr
[root@node01 ~]#ipvsadm -a -t 192.168.146.100:80 -r 192.168.146.154:80 -g -w 2
[root@node01 ~]#ipvsadm -a -t 192.168.146.100:80 -r 192.168.146.139:80 -g -w 3
[root@node01 ~]#ipvsadm -Ln
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
-> RemoteAddress:Port Forward Weight ActiveConn InActConn
TCP 192.168.146.100:80 wrr
-> 192.168.146.139:80 Route 3 0 0
-> 192.168.146.154:80 Route 2 0 0

在另外一个终端发请求进行模拟

1
[root@centos7 ~]#while true ;do curl 192.168.146.100;sleep .5;done

在调度器node01查下ipvsadm

1
2
3
4
5
6
7
[root@node01 ~]#ipvsadm -ln
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
-> RemoteAddress:Port Forward Weight ActiveConn InActConn
TCP 192.168.146.100:80 wrr
-> 192.168.146.139:80 Route 3 0 90
-> 192.168.146.154:80 Route 2 0 60

调度器可以写成脚本(vs的配置脚本)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#!/bin/bash
vip='192.168.146.100'
interface='ens37:0'
mask='255.255.255.255'
port='80'
rs1='192.168.146.154'
rs2='192.168.146.139'
scheduler='wrr'
type='-g'
weight1='2'
weight2='3'

case $1 in
start)
ifconfig $interface $vip netmask $mask broadcast $vip up
iptables -F
ipvsadm -A -t ${vip}:${port} -s $scheduler
ipvsadm -a -t ${vip}:${port} -r ${rs1} $type -w ${weight1}
ipvsadm -a -t ${vip}:${port} -r ${rs2} $type -w ${weight2}
;;

stop)
ipvsadm -C
ifconfig $interface down
;;

*)
echo "Usage $(basename $0) start|stop"
exit 1
esac

FireWall Mark

    MARK target 可用于给特定的报文打标记

  • –set-mark value

    其中:value 可为0xffff格式,表示十六进制数字

  • 借助于防火墙标记来分类报文,而后基于标记定义集群服务;可将多个不同的应用使用同一个集群服务进行调度

  • 实现方法:

• 在Director主机打标记:

1
iptables -t mangle -A PREROUTING -d $vip -p $proto –m multiport --dports $port1,$port2, -j MARK --set-mark NUMBER

• 在Director主机基于标记定义集群服务:

1
ipvsadm -A -f NUMBER [options]

根据上面lvs-dr实验,在rs两台服务器上分别增加一个端口配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
[root@node02 ~]#vim /etc/httpd/conf.d/myvhosts.conf
Listen 8080

<VirtualHost *:80>
ServerName "192.168.146.154"
DocumentRoot "/www/vhost1"
<Directory "www/vhost1">
Options none
AllowOverride none
Require all granted
</Directory>
</VirtualHost>

<VirtualHost *:8080>
ServerName "192.168.146.154"
DocumentRoot "/www/vhost2"
<Directory "www/vhost2">
Options none
AllowOverride none
Require all granted
</Directory>
</VirtualHost>

[root@node02 ~]#mkdir -pv /www/vhost{1,2}
[root@node02 ~]#vim /www/vhost1/index.html
<h1>node02:80</h1>
[root@node02 ~]#vim /www/vhost2/index.html
<h1>node02:8080</h1>

另外一台做同样的修改

把上述调度器的脚本修改下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#!/bin/bash
vip='192.168.146.100'
interface='ens37:0'
mask='255.255.255.255'
port1='80'
port2='8080'
rs1='192.168.146.154'
rs2='192.168.146.139'
scheduler='wrr'
type='-g'
weight1='2'
weight2='3'
number='7'
proto='tcp'

case $1 in
start)
ifconfig $interface $vip netmask $mask broadcast $vip up
iptables -F
iptables -t mangle -A PREROUTING -d $vip -p $proto -m multiport --dports $port1,$port2 -j MARK --set-mark $number
ipvsadm -A -f ${number} -s $scheduler
ipvsadm -a -f ${number} -r ${rs1} $type -w ${weight1}
ipvsadm -a -f ${number} -r ${rs2} $type -w ${weight2}
;;

stop)
ipvsadm -C
ifconfig $interface down
;;

*)
echo "Usage $(basename $0) start|stop"
exit 1
esac

在另外一个终端发请求进行模拟

1
[root@centos7 ~]#while true; do curl 192.168.146.100;do curl 192.168.146.100:8080;sleep 0.5; done

持久连接

  1. session 绑定:对共享同一组RS的多个集群服务,需要统一进行绑定,lvs sh算法无法实现
  2. 持久连接( lvs persistence )模板:实现无论使用任何调度算法,在一段时间内(默认360s ),能够实现将来自同一个地址的请求始终发往同一个RS
1
ipvsadm -A|E -t|u|f service-address [-s scheduler][-p [timeout]]

持久连接实现方式:

  1. 每端口持久(PPC):每个端口定义为一个集群服务,每集群服务单独调度
  2. 每防火墙标记持久(PFWMC):基于防火墙标记定义集群服务;可实现将多个端口上的应用统一调度,即所谓的port Affinity
  3. 每客户端持久(PCC):基于0端口(表示所有服务)定义集群服务,即将客户端对所有应用的请求都调度至后端主机,必须定义为持久模式

---------------- The End ----------------
坚持原创技术分享,您的支持将鼓励我继续创作!