Eli's Blog

1. 简介

LVS 主要由两部分组成:

  • ipvs:ip virtual server,是工作在内核空间上的一段代码,主要是实现调度的代码,它是实现传输层负载均衡的核心。
  • ipvsadm:工作在用户空间,负责为ipvs内核框架编写规则,用于定义谁是集群服务,谁是后端真实服务器

IPVS 基于 Netfilter, Netfilter 的数据包有五个挂载点Hook Point:PRE_ROUTING、INPUT、OUTPUT、FORWARD、POST_ROUTING。IPVS工作在其中的INPUT链上

img

专有名词

  • DS:Director Server,指的是前端负载均衡器。

  • RS:Real Server,指的是后端工作的服务器。

  • VIP:Virtual IP,用户请求的目标IP地址。

  • DIP:Director Server IP,前端负载均衡器的IP地址。

  • RIP:Real Server IP,后端服务器的IP地址。

  • CIP:Client IP,访问客户端的IP地址。

三种工作模式

  • NAT: Network Address Translate

    NAT模式下,请求包和响应包都需要经过LB处理。当客户端请求到达虚拟服务后,LB会对请求包做目的地址转换DNAT,将请求包的目的IP改为RS的IP;当收到RS的响应后,LB会对响应做源地址转换SNAT,将响应包的源IP改为LB的IP

  • DR: Direct Routing

    DR 模式下,客户端请求包到达LB的虚拟服务IP端口后,LB不会改写请求包的IP和端口,但会改下请求包的MAC地址(源MAC地址是DIP所在接口的MAC,目的MAC是RS所在RIP接口所在的MAC,IP首部不会发生变化),然后将数据包转发;

    数据包送到真实服务器后,判断请求报文的MAC地址是否自己的MAC地址,接收此报文,拆了MAC首部,发现目的地址是VIP后,向lo:0转发此报文 (每个RS主机上都有VIP,并且RIP配置在物理接口上,VIP配置在内置接口lo:0上), 最终达到用户空间进程.

    真实服务器用户空间进行构建响应报文,将响应报文通过lo:0接口传给物理网卡处理请求后,响应包直接回给客户端,不再经过LB

  • FULLNAT:

    FULLNAT模式,客户端感知不到RS, RS也感知不到客户端, 它们都只能看到LB. LB会对请求包和响应包都做SNAT和DNAT

三种工作模式比较:

  • 转发效率: DR > NAT > FULLNAT
  • 组网要求:
    • NAT: LB 和 RS 必须在同一个子网, 且客户端不能与LB/RS在同一子网
    • DR: LB 和 RS 必须同一子网
    • FULLNAT: 无要求
  • 端口映射:
    • NAT: 支持
    • DR: 不支持
    • FULLNAT: 不支持

负载均衡算法:

  • 轮询(Round Robin)

  • 加权轮询(Weighted Round Robin)

  • 源地址哈希(Source Hash)

  • 目标地址哈希(Destination Hash)

  • 最小连接数(Least Connections)

  • 加权最小连接数(Weighted Least Connections)

  • 最小期望延迟( Shortest Expection Delay)

2. 内核支持

1
2
3
4
5
6
7
8
9
lsmod|grep ip_vs

ls -l /lib/modules/$(uname -r)/kernel/net/netfilter/ipvs|grep -o "^[^.]*"

# 临时生效
for i in $(ls /lib/modules/$(uname -r)/kernel/net/netfilter/ipvs|grep -o "^[^.]*"); do echo $i; /sbin/modinfo -F filename $i >/dev/null 2>&1 && /sbin/modprobe $i; done

# 永久生效
ls /lib/modules/$(uname -r)/kernel/net/netfilter/ipvs|grep -o "^[^.]*" >> /etc/modules

3. ipvsadm

1
apt install ipvsadm ipset -y

管理命令:

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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
ipvsadm command [protocol] service-address
server-address [packet-forwarding-method]
[weight options]

命令:
-A, --add-service:为ipvs虚拟服务器添加一个虚拟服务,即添加一个需要被负载均衡的虚拟地址。虚拟地址需要是ip地址,端口号,协议的形式。
-E, --edit-service:修改一个虚拟服务。
-D, --delete-service:删除一个虚拟服务。
-C, --clear:清除所有虚拟服务。
-R, --restore:从标准输入获取ipvsadm命令。一般结合下边的-S使用。
-S, --save:从标准输出输出虚拟服务器的规则。可以将虚拟服务器的规则保存,在以后通过-R直接读入,以实现自动化配置。
-a, --add-server:为虚拟服务添加一个real server(RS)
-e, --edit-server:修改RS
-d, --delete-server:删除
-L, -l, --list:列出虚拟服务表中的所有虚拟服务。可以指定地址。添加-c显示连接表。
-Z, --zero:将所有数据相关的记录清零。这些记录一般用于调度策略。
--set tcp tcpfin udp:修改协议的超时时间。
--start-daemon state:设置虚拟服务器的备服务器,用来实现主备服务器冗余。(注:该功能只支持ipv4)
--stop-daemon:停止备服务器

参数:
-t, --tcp-service service-address:指定虚拟服务为tcp服务。service-address要是host[:port]的形式。端口是0表示任意端口。如果需要将端口设置为0,还需要加上-p选项(持久连接)。
-u, --udp-service service-address:使用udp服务,其他同上。
-f, --fwmark-service integer:用firewall mark取代虚拟地址来指定要被负载均衡的数据包,可以通过这个命令实现把不同地址、端口的虚拟地址整合成一个虚拟服务,可以让虚拟服务器同时截获处理去往多个不同地址的数据包。fwmark可以通过iptables命令指定。如果用在ipv6需要加上-6。
-s, --scheduler scheduling-method:指定调度算法。调度算法可以指定以下8种:rr(轮询),wrr(权重),lc(最后连接),wlc(权重),lblc(本地最后连接),lblcr(带复制的本地最后连接),dh(目的地址哈希),sh(源地址哈希),sed(最小期望延迟),nq(永不排队)
-p, --persistent [timeout]:设置持久连接,这个模式可以使来自客户的多个请求被送到同一个真实服务器,通常用于ftp或者ssl中。
-M, --netmask netmask:指定客户地址的子网掩码。用于将同属一个子网的客户的请求转发到相同服务器。
-r, --real-server server-address:为虚拟服务指定数据可以转发到的真实服务器的地址。可以添加端口号。如果没有指定端口号,则等效于使用虚拟地址的端口号。
[packet-forwarding-method]:此选项指定某个真实服务器所使用的数据转发模式。需要对每个真实服务器分别指定模式。
-g, --gatewaying:使用网关(即直接路由),此模式是默认模式。
-i, --ipip:使用ipip隧道模式。
-m, --masquerading:使用NAT模式。
-w, --weight weight:设置权重。权重是0~65535的整数。如果将某个真实服务器的权重设置为0,那么它不会收到新的连接,但是已有连接还会继续维持(这点和直接把某个真实服务器删除时不同的)。
-x, --u-threshold uthreshold:设置一个服务器可以维持的连接上限。0~65535。设置为0表示没有上限。
-y, --l-threshold lthreshold:设置一个服务器的连接下限。当服务器的连接数低于此值的时候服务器才可以重新接收连接。如果此值未设置,则当服务器的连接数连续三次低于uthreshold时服务器才可以接收到新的连接。(PS:笔者以为此设定可能是为了防止服务器在能否接收连接这两个状态上频繁变换)
--mcast-interface interface:指定使用备服务器时候的广播接口。
--syncid syncid:指定syncid,同样用于主备服务器的同步。
以下选项用于list命令:
-c, --connection:列出当前的IPVS连接。
--timeout:列出超时
--daemon:
--stats:状态信息
--rate:传输速率
--thresholds:列出阈值
--persistent-conn:坚持连接
--sor:把列表排序。
--nosort:不排序
-n, --numeric:不对ip地址进行dns查询
--exact:单位
-6:如果fwmark用的是ipv6地址需要指定此选项。

ipvs 支持三种负载均衡方式

  • NAT
  • TUN
  • DR

NAT 模式:

1
2
3
4
5
6
7
8
# 添加虚拟服务器,指定调度算法为 rr
ipvsadm -A -t 10.96.0.1:443 -s rr

# 添加真实服务器,可以有多个
ipvsadm -a -t 10.96.0.1:443 -r 192.168.80.240:6443 -m

# 查询代理规则
ipvsadm -Ln

ipvs 和 iptables区别

  • 底层数据结构:iptables 使用链表,ipvs 使用哈希表
  • 负载均衡算法:iptables 只支持随机、轮询两种负载均衡算法而 ipvs 支持的多达 8 种;
  • 操作工具:iptables 需要使用 iptables 命令行工作来定义规则,ipvs 需要使用 ipvsadm 来定义规则。

此外 ipvs 还支持 realserver 运行状况检查、连接重试、端口映射、会话保持等功能。

4. kube-proxy

通过参数--ipvs-scheduler配置负载均衡算法,默认为轮询(Round Robin):

  • rr:round robin
  • lc:least connection
  • dh:destination hashing
  • sh:source hashing
  • sed:shortest expected delay
  • nq:never queue
1
2
3
4
5
6
7
8
9
10
11
# 1. 修改配置
--proxy-mode=iptables => ipvs
--masquerade-all=true \

# 2. 重启
systemctl daemon-reload
systemctl restart kube-proxy

# 3. 查询 ipvs 是否成功开启
grep Using /var/log/kubernetes/kube-proxy.INFO
I1108 16:36:14.378518 15298 server_others.go:274] Using ipvs Proxier.

5. Dummy Interface

1
2
3
4
5
6
7
8
9
10
11
12
$ ip addr
3: kube-ipvs0: <BROADCAST,NOARP> mtu 1500 qdisc noop state DOWN group default
link/ether c6:14:f7:ad:b6:c8 brd ff:ff:ff:ff:ff:ff
inet 10.96.92.170/32 scope global kube-ipvs0
valid_lft forever preferred_lft forever
inet 10.96.0.1/32 scope global kube-ipvs0
valid_lft forever preferred_lft forever

# 状态为 down, 不进行网络包接收和发送
$ ip link
3: kube-ipvs0: <BROADCAST,NOARP> mtu 1500 qdisc noop state DOWN mode DEFAULT group default
link/ether c6:14:f7:ad:b6:c8 brd ff:ff:ff:ff:ff:ff

6. 总结

kube-proxy 启用 ipvs 模式,将在所有节点做三件事:

  • 创建一个dummy类型虚拟网卡 kube-ipvs0

  • 把 ClusterIP 地址添加到kube-ipvs0,同时添加到ipset中

  • 创建 ipvs service,其地址为ClusterIP:Port,ipvs server为所有的Endpoint地址,即Pod IP及端口

7. 问题

7.1 flannel 不正常

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
# 无法访问 kube-apiserver
$ kubectl logs -f kube-flannel-ds-ntwh4 -n kube-system
I1108 10:48:00.864770 1 main.go:520] Determining IP address of default interface
I1108 10:48:00.865795 1 main.go:533] Using interface with name ens33 and address 192.168.80.241
I1108 10:48:00.865827 1 main.go:550] Defaulting external address to interface address (192.168.80.241)
W1108 10:48:00.865861 1 client_config.go:608] Neither --kubeconfig nor --master was specified. Using the inClusterConfig. This might not work.
E1108 10:48:30.960762 1 main.go:251] Failed to create SubnetManager: error retrieving pod spec for 'kube-system/kube-flannel-ds-ntwh4': Get "https://10.96.0.1:443/api/v1/namespaces/kube-system/pods/kube-flannel-ds-ntwh4": dial tcp 10.96.0.1:443: i/o timeout

# 切换到相关主机,测试网络,无法连通
$ curl 10.96.0.1:443

# 查询网络
$ ip addr
3: kube-ipvs0: <BROADCAST,NOARP> mtu 1500 qdisc noop state DOWN group default
link/ether c6:14:f7:ad:b6:c8 brd ff:ff:ff:ff:ff:ff
inet 10.96.92.170/32 scope global kube-ipvs0
valid_lft forever preferred_lft forever
inet 10.96.0.1/32 scope global kube-ipvs0
valid_lft forever preferred_lft forever

# 尝试删除 ip, 发现删除后,自动生成;进一步发现,该设备默认down,设置IP地址,不影响通信
$ ip addr delete 10.96.0.1/32 dev kube-ipvs0

# 查询路由表,发现路由表错误
$ ip route show table local
local 10.96.0.1 dev kube-ipvs0 proto kernel scope host src 10.96.0.1

# 删除路由表
$ ip route del table local local 10.96.0.1 dev kube-ipvs0 proto kernel scope host src 10.96.0.1