租户创建vpc,租户创建虚拟机,租户创建数据库实现,返回访问的vip,vip主主模式,vip流量尽量负载均衡,同一session保持到固定的LB节点,而且虚拟机迁移时到vip不断流,迁移这个session还保持在同一个LB节点,要求租户流程隔离,LB运行于物理机,LB节点可扩展,后面是k8s pod,数据库实例运行于k8s pod中。
第0步,部署完后,调用neutron-server api添加dpvs节点信息,neutron-server存数据库,然后广播所有ovs-agent,ovs-agent创建到dpvs的vxlan tunnel。
第1步,web调用k8s api创建mysql实现
第2步,k8s给web返回mysql所有ip和port
第3步,web调用neutron-server api创建vip
第4步,neutron-server给web返回vip ip,并且广播所有ovs-agent给此vip添加流表,web给用户展示vip ip和mysql实现vip port
第5步,web把vip_ip,vip_port,vpc_vxlan, k8s_ip,k8s_port提交给所有dpvs节点,dpvs进行配置
删除过程,web先通知dpvs删除,再通知neutron和k8s删除。
新的dpvs节点上线,web给neutron-server api添加dpvs节点,然后同步所有vip和k8s信息给dpvs
openstack第一要实现LB节点故障探测并且快速切换,第二要实现vip流量负载到LB节点之间,考虑过neutron-server实现,server和agent之间rpc太慢,故障切换时间不可保证,用neutron-openvswitch-agent实现,agent信息有限只能根据vpc/vip/vm实现流量负载均衡,而且没有好的故障探测机制,即使探测到故障再修改流表切换LB节点时间也太久。最后决定用ovs实现,ovs是转发面,越靠近转发面故障切换越快,并且实现了bfd功能,而且用group table能达到报文级别负载均衡。LB改造dpvs实现vxlan收发,并用vni隔离租户流量,不同租户vip有可能一样,不同租户虚拟机ip有可能一样,计算节点和dpvs节点full mesh建立vxlan tunnel,tunnel上运行bfd协议。ovs实现给虚拟机访问vip arp代答,ovs group table实现访问vip的流量走不同tunnel,group type用select,hash算法保证同一session的流量固定发往同一个dpvs节点。
Open vSwitch 2.4 and later by default hashes the source and destination Ethernet address,
VLAN ID, Ethernet type, IPv4/v6 source and destination address and protocol, and for TCP and SCTP only,
the source and destination ports.
The hash is "symmetric", meaning that exchanging source and destination addresses does not change the bucket selection.
详见ovs代码pick_default_select_group,bfd把port down后会自动切换到其它bucket出去
ovs-vsctl set interface vxlan-0ad08026 bfd:enable=true
Port "vxlan-0ad08026"
Interface "vxlan-0ad08026"
type: vxlan
options: {df_default="true", egress_pkt_mark="0", in_key=flow, local_ip="10.208.128.20", out_key=flow, remote_ip="10.208.128.38"}
bfd_status: {diagnostic="No Diagnostic", flap_count="1", forwarding="true", remote_diagnostic="No Diagnostic", remote_state=up, state=up}
需要和dpvs配合运行bfd,bfd远远好于发icmp包探测这样的monitor。
在br-tun table 20中增加一条priority更大的流表,让dst mac是vip的查group table,区别就是action从strip_vlan,set_tunnel:0x29,output:1177变为trip_vlan,set_tunnel:0x29,group:41,原来从一个tunnel口出去,现在到group,group中有好几个tunnel口,根据hash结果选一个tunnel口出去。
#原来的流
cookie=0xb37f4b0e192fa003, duration=26723512.305s, table=20, n_packets=1377429016, n_bytes=239779004594, idle_age=0, hard_age=65534, priority=2,dl_vlan=67,dl_dst=fa:16:3e:df:0a:dd actions=strip_vlan,set_tunnel:0x29,output:1177
#group流
cookie=0xb37f4b0e192fa003, duration=26723512.305s, table=20, n_packets=1377429016, n_bytes=239779004594, idle_age=0, hard_age=65534, priority=3,dl_vlan=67,dl_dst=fa:16:3e:df:0a:dd actions=strip_vlan,set_tunnel:0x29,group:41
group id是vip所在vxlan id,一个vpc对应一个group,一个dpvs节点对应group中一个bucket。
ovs-ofctl -O OpenFlow13 add-group br-tun group_id=41,type=select,bucket=watch_port:vxlan-0ad08026,output:vxlan-0ad08026,bucket=watch_port:vxlan-0ad08038,output:vxlan-0ad08038
增加api用于创建和删除dpvs节点,存储数据库,然后广播所有ovs-agent。vip利用port的api,device_owner标志为NEUTRON_VIP_PORT,这种类型的port广播所有ovs-agent。
ovs-agent启动时从neutron-server pull所有dpvs节点然后建立tunnel,再主动从server pull所有vip port,下流表。
当用户创建/删除vip时,server向所有agent广播创建/删除vip port,agent创建/删除流表。创建时br-tun table 20加一条流表,如果group不存则增加group。删除时br-tun table 20删除这条流表,如果是这个vpc的最后一个group则删除group。
当用户创建/删除dpvs时,server向所有agent广播创建/删除dpvs,agent创建/删除流表。创建时添加vxlan port,然后遍历group表在每个表中添加一个bucket,删除时遍历group表删除dpvs节点对应的bucket。
python代码写得烂,请大家不要见笑。
https://github.com/huiweics/neutron/tree/openflow_group_ecmp_lbgithub.com
最终采用的hash配置是selection_method=hash,fields\(ip_src,ip_dst,ip_proto\)。
测试环境就两个dpvs节点6和7。
测试脚本
i="1"
while [ $i -lt 10 ]
do
ovs-vsctl add-port br-int vxlan_net0_ip"$i" -- set Interface vxlan_net0_ip"$i" type=internal
ovs-vsctl set port vxlan_net0_ip"$i" tag=30
i=$[$i+1]
done
i="1"
while [ $i -lt 10 ]
do
openstack port create --network vxlan_net0 --mac-address fa:16:3e:ec:85:0"$i" --fixed-ip subnet=vxlan_subnet0,ip-address=192.168.200."$i"0 vxlan_net0_ip"$i"
i=$[$i+1]
done
i="1"
while [ $i -lt 10 ]
do
ip netns add vxlan_net0_ip"$i"
ip link set dev vxlan_net0_ip"$i" netns vxlan_net0_ip"$i"
ip netns exec vxlan_net0_ip"$i" ip link set dev vxlan_net0_ip"$i" address fa:16:3e:ec:85:0"$i"
ip netns exec vxlan_net0_ip"$i" ip addr add dev vxlan_net0_ip"$i" 192.168.200."$i"0/24
ip netns exec vxlan_net0_ip"$i" ip link set dev vxlan_net0_ip"$i" up
i=$[$i+1]
done
i="1"
while [ $i -lt 10 ]
do
ip netns exec vxlan_net0_ip"$i" curl http://192.168.200.5:3306/
i=$[$i+1]
done
i="1"
while [ $i -lt 10 ]
do
ip netns exec vxlan_net0_ip"$i" nc -nzv -u 192.168.200.5 3307
i=$[$i+1]
done
i="1"
while [ $i -lt 10 ]
do
ip netns exec vxlan_net0_ip"$i" curl http://192.168.200.6:3306/
i=$[$i+1]
done
i="1"
while [ $i -lt 10 ]
do
ip netns exec vxlan_net0_ip"$i" nc -nzv -u 192.168.200.6 3307
i=$[$i+1]
done
只ip proto变化,12/18才散列开。只dst_ip从192.168.200.5变华为192.168.200.5,9/18散列开。6出现15次,7出现21次,考虑到ip的相似性,hash结果还不错。
性能方面,首包送用户态,进行hash计算,然后给datapath安装流表,后续只在datapath转发,只要datapath安装的流表不老化就不再需要重新hash计算,对长连接大量数据传送性能没有影响,对短的连接影响比较大,短连接在任何场景都需要重新安装流表,相比较于安装流表,hash计算影响也比较小。
故障时间方面,如果ovs和dpvs之间的tunnel bfd检测到down了,那么ovs会把报文转发到另一个dpvs节。udp不受影响,直接到了另一个dpvs节点,就是不知道dpvs节点能不能转发到同一个rs。tcp如果处于等syn+ack状态,重传一次syn包就行了,最多64s,由net.ipv4.tcp_syn_retries控制。tcp如果连接已经建立好了,重传数据,时间会很久,由net.ipv4.tcp_retries1和net.ipv4.tcp_retries2控制。这是内核机制,总的来说用户态可以通过socket调用设置timeout参数,设置小一点能快速恢复故障。
openstack port create --device-owner "network:network_vip" --network 79fe1480-adc2-4633-b31c-c1236ac9b757 vip_port
vip_port不支持多个ip,不能没有ip(--no-fixed-ip),创建后不支持修改device-owner。network 只能有一个segment。
重启先用OFPGroupDescStatsRequest获取所有group table,有的不再重复下,多的clean_stale_flow时删除。group不相同的增加或者删除bucket。
增加api用于创建和删除dpvs节点,存储数据库,然后广播所有ovs-agent,dpvs的ip不再作为ovs-agent的配置项。
当用户创建/删除dpvs时,server向所有agent广播创建/删除dpvs,agent创建/删除流表。创建时添加vxlan port,然后遍历group表在每个表中添加一个bucket,删除时遍历group表删除dpvs节点对应的bucket,然后删除vxlan口。
cfg.StrOpt('of_interface', default='native',choices=['ovs-ofctl', 'native'])
默认是native,先实现native方式给ovs下流表,后面再实现ovs-ofctl给ovs下发流表