1. group表介绍
1.1 group表类型
- Indirect:执行该group中一个已定义的bucket, 该组仅支持一个bucket。 允许多个流表项或组表项指向一个公共的组(例如IP转发的下一跳)。 这是最简单的group类型,交换机通常比较支持这种类型的group。
- All:执行该group中所有的bucket。这种类型的group用来进行multicast和broadcast。为每个bucket克隆一份数据包,然后分别执行每个bucket中的actions。
- Select:执行该group中的一个bucket。基于一种选择算法(用户定义的哈希算法或者轮询算法)选择group中的一个bucket对数据包执行actions。这种选择算法应该尽量支持负载均衡并且为每个bucket提供一个权重用于分配。当一个bucket指定的端口down掉,交换机应该将选择限制在剩下的正常的bucket中而不是丢掉,这是为了减少链路中断。
- Fast failover:执行第一个活动的bucket。 每个action bucket都与控制其活动性的特定端口和/或组相关联。 按照group定义的顺序评估bucket,并选择与活动端口/组关联的第一个bucket。 这个group类型使交换机可以更改转发行为而无需往返于控制器。 如果没有bucket,则丢弃数据包。
1.2 操作
# 查看group表
ovs-ofctl -O OpenFlow13 dump-groups br0
# 创建group表
# 类型为all
ovs-ofctl -O OpenFlow13 add-group br0 group_id=1,type=all,bucket=output:1,bucket=output:2,bucket=output:3
# 类型为select
ovs-ofctl -O OpenFlow13 add-group br0 group_id=2,type=select,bucket=output:1,bucket=output:2,bucket=output:3
# 类型为select,指定hash方法(5元组,OpenFlow1.5+)
ovs-ofctl -O OpenFlow15 add-group br0 group_id=3,type=select,selection_method=hash,fields=ip_src,bucket=output:2,bucket=output:3
# 删除group表
ovs-ofctl -O OpenFlow13 del-groups br0 group_id=2
# 创建流表
ovs-ofctl -O OpenFlow13 add-flow br0 in_port=1,actions=group:2
1.3 实验
1.3.1 准备基础环境
ip netns add ns1
ip link add dev tap1 type veth peer name tap2
ip link set dev tap1 netns ns1
ip link set tap2 up
ip netns exec ns1 ip addr add dev tap1 192.168.1.1/24
ip netns exec ns1 ip link set dev tap1 up
# ip netns exec ns1 ip route add default dev tap1 双向nat不需要网关
alias ovs-ofctl="ovs-ofctl -O openflow13"
ovs-vsctl add-br ovs-lb
ovs-vsctl add-port ovs-lb eth1
ovs-vsctl add-port ovs-lb tap2
ip a a 10.0.1.7/24 dev ovs-lb 方便ssh可以加上
ip link set dev ovs-lb up
nginx-ep1和nginx-ep2用两台主机做了测试的nginx
# curl 10.0.1.17
10.0.1.17
# curl 10.0.1.27
10.0.1.27
查看ovs状态
# ovs-vsctl show
bf91c4a2-33f6-481e-b44e-05eb968a60cd
Bridge ovs-lb
Port ovs-lb
Interface ovs-lb
type: internal
Port "tap2"
Interface "tap2"
Port "eth1"
Interface "eth1"
ovs_version: "2.11.0"
# ovs-ofctl dump-flows ovs-lb
cookie=0x0, duration=1398.915s, table=0, n_packets=464, n_bytes=35518, priority=0 actions=NORMAL
1.3.2 ovs流表创建
# load加载的是tap2设备mac,如果访问的是VIP,返回tap2设备mac
ovs-ofctl add-flow ovs-lb "table=0,priority=2,nw_dst=192.168.1.200,arp actions=move:NXM_OF_ETH_SRC[]->NXM_OF_ETH_DST[],load:0x72de72263211->NXM_OF_ETH_SRC[],load:0x2->NXM_OF_ARP_OP[],move:NXM_NX_ARP_SHA[]->NXM_NX_ARP_THA[],load:0x72de72263211->NXM_NX_ARP_SHA[],move:NXM_OF_ARP_TPA[]->NXM_NX_REG3[],move:NXM_OF_ARP_SPA[]->NXM_OF_ARP_TPA[],move:NXM_NX_REG3[]->NXM_OF_ARP_SPA[],IN_PORT"
ovs-ofctl add-flow ovs-lb "table=0,priority=2,nw_dst=192.168.1.200,ip actions=resubmit(,11)"
# 从外面回来的流量先过一遍zone=100,snat表
ovs-ofctl add-flow ovs-lb "table=0,priority=2,in_port=eth1,ip actions=ct(table=1,zone=100,nat)"
ovs-ofctl add-flow ovs-lb "table=0,priority=0,actions=NORMAL"
# 从外面回来的流量再过一边zone=0,dnat表
ovs-ofctl add-flow ovs-lb "table=1,priority=0,ip actions=ct(table=92,nat)"
# 初始没有被ct模块处理过的数据包
ovs-ofctl add-flow ovs-lb "table=11, priority=200, ct_state=-trk, ip, in_port=tap2, actions=ct(table=11)"
# 经过ct模块处理过的数据包匹配状态为+new+trk,进入group1进行负载dnat
ovs-ofctl add-flow ovs-lb "table=11,priority=200,ct_state=+new+trk,tcp,nw_dst=192.168.1.200,tp_dst=80 actions=group:1"
# 长连接中后续报文不需要再过group1的hash,直接nat表匹配转发即可。
ovs-ofctl add-flow ovs-lb "table=11,priority=200,ct_state=+est+trk,tcp,nw_dst=192.168.1.200,tp_dst=80 actions=ct(table=90,nat)"
#
ovs-ofctl add-group ovs-lb "group_id=1,type=select,selection_method=hash,fields(ip_src,tcp_src),bucket=weight:100,actions=ct(commit,table=90,nat(dst=10.0.1.17:80)),bucket=weight:100,actions=ct(commit,table=90,nat(dst=10.0.1.27:80))"
# 修改目的mac为后端nginx服务真实mac
ovs-ofctl add-flow ovs-lb "table=90,priority=10,ip,nw_dst=10.0.1.17 actions=mod_dl_dst:00:0c:29:9b:93:21,resubmit(,91)"
ovs-ofctl add-flow ovs-lb "table=90,priority=10,ip,nw_dst=10.0.1.27 actions=mod_dl_dst:00:0c:29:4e:86:d9,resubmit(,91)"
ovs-ofctl add-flow ovs-lb "table=90,priority=0,actions=resubmit(,91)"
# dnat后进行snat,分表分zone同时做snat和dnat才能生效。
ovs-ofctl add-flow ovs-lb "table=91,priority=10,ip,nw_src=192.168.1.1 actions=ct(commit,table=92,zone=100,nat(src=10.0.1.7))"
ovs-ofctl add-flow ovs-lb "table=91,priority=0,actions=resubmit(,92)"
# nat转回后mac转向真正1.1客户端mac。
ovs-ofctl add-flow ovs-lb "table=92,priority=10,ip,nw_dst=192.168.1.1 actions=mod_dl_dst:92:c7:20:8d:00:97,resubmit(,93)"
ovs-ofctl add-flow ovs-lb "table=92,priority=0,actions=resubmit(,93)"
ovs-ofctl add-flow ovs-lb "table=93 actions=NORMAL"
1.3.3 测试
[root@kvm-server1 ~]# ip netns
ns1 (id: 0)
[root@kvm-server1 ~]# ip netns exec ns1 bash
[root@kvm-server1 ~]# ip a
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
7: tap1@if6: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
link/ether d6:3d:3e:97:4c:1f brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 192.168.1.1/24 scope global tap1
valid_lft forever preferred_lft forever
inet6 fe80::d43d:3eff:fe97:4c1f/64 scope link
valid_lft forever preferred_lft forever
[root@kvm-server1 ~]# ip r
192.168.1.0/24 dev tap1 proto kernel scope link src 192.168.1.1
[root@kvm-server1 ~]# curl 192.168.1.200
10.0.0.17
[root@kvm-server1 ~]# curl 192.168.1.200
10.0.0.17
[root@kvm-server1 ~]# curl 192.168.1.200
10.0.0.27
[root@kvm-server1 ~]# curl 192.168.1.200
10.0.0.27
1.3.4 流量转发叙述
- 客户端192.168.1.1 去ping vip 192.168.1.200,首先发起arp广播请求。
- 数据通过tap2口到达ovs,匹配table=0的第一条,收到arp响应,arp响应的mac地址是tap2的mac地址。
- 客户端收到arp响应之后,构建tcp报文,建立三次握手。流量到达ovs匹配第二条流表resubmit到11表。
- 在11表匹配状态-trk,通过nf conntrak in 经过ct模块处理,状态转为+trk+new,然后原来数据报文生命周期结束,新克隆的数据继续流转到11表
- 在11表中匹配ct_state=+new+trk这条流表,然后执行group1中的操作(基于源ip、源端口的hash结果执行其中一个bucket做dnat)。
- 此时流量已经做了dnat,在流转到90表,对目的mac进行修改为后端real server的mac。
- 然后流量流转到91表,在91表中分zone在做一次snat
- 流转到92表,93表normal正常流转发出从eth1口出。后端nginx收到之后回复synack
- 流量从eth1口收到synack,匹配table=0 in_port=eth1 这条流表,进行一次snat转回。
- 然后流量流转到1表在进行一次dnat转回。
- 流转到92表,匹配转回的目的地址192.168.1.1,修改目的mac为真实客户端mac。
- 然后流量根据报文目的mac normal处理回到客户端。