Neutron 懂得(14):Neutron ML2 + Linux bridge + VxLAN 组网
学习 Neutron 系列文章:
(2)Neutron OpenvSwitch + VLAN 虚拟网络
(3)Neutron OpenvSwitch + GRE/VxLAN 虚拟网络
(4)Neutron OVS OpenFlow 流表 和 L2 Population
(9)Neutron FWaas 和 Nova Security Group
(10)Neutron VPNaas
(11)Neutron DVR
(12)Neutron VRRP
1. 为什么要使用 Linux bridge 而不使用 Open vSwitch
1.1 为什么要用 Linux bridge 替代 Open vSwitch?
我们都知道,OpenStack 社区官方的安装文档的步骤都是以 Open vSwitch 为例子的(Liberty 版本之前都是使用 neutron-plugin-openvswitch-agent, 但是 Liberty 版本转为使用 neutron-plugin-linuxbridge-agent, 不知道这背后究竟发生了什么)。那还有人使用 Linux bridge 吗?答案是有的,据我所知,国内厂商比如海云捷迅就推荐私有云中的两种配置:Linux bridge + VLAN 以及 Linux bridge + VxLAN。国外的 Rackspace 也已经使用Linux bridge 替代 Open vSwitch,请参见下图:
本段以 Rackspace 为例,说明他们使用 Linux bridge 的理由:
- Kernetl panics 1.10
- ovs-switched segfaults 1.11
- 广播风暴
- Data corruption 2.01
- 稳定性和可靠性要求:Linux bridge 有十几年的使用历史,非常成熟。
- 易于问题诊断 (troubleshooting)
- 社区也支持
- 还是可以使用 Overlay 网络 VxLAN 9需要 Linux 内核 3.9 版本或者以上)
- Neutron DVR 还不支持 Linux bridge
- 不支持 GRE
- 一些 OVS 提供但是 Neutorn 不支持的功能
(4)长期来看,随着稳定性的进一步提高,Open vSwitch 会在生产环境中成为主流。
1.2 Linux bridge 和 Open vSwitch 的功能对比
可以看出:
(1)OVS 将各种功能都原生地实现在其中,这在起始阶段不可避免地带来潜在的稳定性和可调试性问题
(2)Linux bridge 依赖各种其他模块来实现各种功能,而这些模块的发布时间往往都已经很长,稳定性较高
(3)两者在核心功能上没有什么差距,只是在集中管控和性能优化这一块OVS有一些新的功能或者优化。但是,从测试结果看,两者的性能没有明显差异:
总之,目前,除了 SDN 对集中管控的需求,Linux bridge 是个较好的选择。
2. 使用 Linux bridge + VXLAN 组网
2.1 配置
在控制、网络和计算节点上修改 /etc/neutron/plugins/ml2/ml2_conf.ini 中的如下配置项:
[ml2] type_drivers = flat,vlan,gre,vxlan tenant_network_types = vxlan mechanism_drivers = linuxbridge [ml2_type_vxlan] vni_ranges = 1001:2000 [vxlan] local_ip = 10.0.0.13 enable_vxlan = true [securitygroup] firewall_driver = neutron.agent.linux.iptables_firewall.IptablesFirewallDriver [agent] tunnel_types = vxlan
2.2 组网
2.2.1 配置步骤
- 创建三个网络,每个网络都启动DHCP,每个网络创建一个子网
- 创建一个 router,连接其中两个网络的若干子网
- 创建多个虚机
2.2.2 租户网络的构成
计算节点1上有三个虚机,分布在两个网络上:
(1)两个 Linux bridge
root@compute1:~# brctl show bridge name bridge id STP enabled interfaces brq18fc2ba1-05 8000.9292780d149d no tap5d8a021b-64 tapf64dcdd0-0e vxlan-1074 brq243661e7-f7 8000.66239c87f16c no tap818682fe-a6 vxlan-1027
(2)VXLAN 在端口 8472 上创建了一个 UDP Socket
root@compute1:~# netstat -lu Active Internet connections (only servers) Proto Recv-Q Send-Q Local Address Foreign Address State udp 0 0 192.168.122.1:domain *:* udp 0 0 *:bootps *:* udp 768 0 *:8472 *:*
(3)创建了两个 vxlan network interface
root@compute1:~# ip -d link show dev vxlan-1074 14: vxlan-1074: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue master brq18fc2ba1-05 state UNKNOWN mode DEFAULT group default link/ether 92:92:78:0d:14:9d brd ff:ff:ff:ff:ff:ff promiscuity 1 vxlan id 1074 group 224.0.0.1 dev eth1 port 32768 61000 ageing 300 root@compute1:~# ip -d link show dev vxlan-1027 12: vxlan-1027: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue master brq243661e7-f7 state UNKNOWN mode DEFAULT group default link/ether 66:23:9c:87:f1:6c brd ff:ff:ff:ff:ff:ff promiscuity 1 vxlan id 1027 group 224.0.0.1 dev eth1 port 32768 61000 ageing 300
(4)vxlan interface 使用网卡 eth1
网络节点上:
(1)一个 qrouter network namespace
(2)三个 dhcp network namespace,每个 enable DHCP 的网络各一个
(3)三个 Linux bridge,每个 network 一个
(4)三个 vxlan network interface(ip link),每个 network 一个 bridge, 每个 linux bridge 上只有一个 vxlan interface
root@controller:~/s1# ip -d link show dev vxlan-1027 11: vxlan-1027: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue master brq243661e7-f7 state UNKNOWN mode DEFAULT group default link/ether a6:6b:39:67:60:68 brd ff:ff:ff:ff:ff:ff promiscuity 1 vxlan id 1027 group 224.0.0.1 dev eth1 port 32768 61000 ageing 300 root@controller:~/s1# ip -d link show dev vxlan-1074 5: vxlan-1074: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue master brq18fc2ba1-05 state UNKNOWN mode DEFAULT group default link/ether 8a:e2:2f:2d:e7:48 brd ff:ff:ff:ff:ff:ff promiscuity 1 vxlan id 1074 group 224.0.0.1 dev eth1 port 32768 61000 ageing 300 root@controller:~/s1# ip -d link show dev vxlan-1054 8: vxlan-1054: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue master brq1e7052dc-7f state UNKNOWN mode DEFAULT group default link/ether 5e:c7:d4:85:23:fc brd ff:ff:ff:ff:ff:ff promiscuity 1 vxlan id 1054 group 224.0.0.1 dev eth1 port 32768 61000 ageing 300
(5)vxlan 在 8472 端口上监听 UDP 请求
(6)vxlan interface 的广播网卡是 eth1
2.2.3 网络流量走向
注:红色线条为计算节点1上的vm3访问 DHCP Agent 来获取 IP 地址;蓝色线条为同一个网段的 VM3 和 VM2 互访
3. vxlan + linux bridge 组网的实现
3.1 vxlan interface
3.1.1 创建 vxlan interface
Neutron Linux bridge agent 使用 "ip link add type vxlan" 命令来创建 vxlan interface。这是该命令的帮助信息:
Command: ['ip link add type vxlan help'] Usage: ... vxlan id VNI [ { group | remote } ADDR ] [ local ADDR ] [ ttl TTL ] [ tos TOS ] [ dev PHYS_DEV ] [ port MIN MAX ] [ [no]learning ] [ [no]proxy ] [ [no]rsc ] [ [no]l2miss ] [ [no]l3miss ] Where: VNI := 0-16777215 #network 的 segemntation_id ADDR := { IP_ADDRESS | any } #IP 地址 TOS := { NUMBER | inherit } #可以由 tos 配置项指定 TTL := { 1..255 | inherit } #可以由 ttl 配置项指定,不设置时默认为1
以 vxlan id 1027 group 224.0.0.1 dev eth1 port 32768 61000 ageing 300 为例,它使用如下的配置项:
(1)id 1027,这是 vxlan interface id,其值为 neutron network 的 provider:segmentation_id 属性。
(2)group 224.0.0.1。表示这个是一个多播 VXLAN interface,使用 Neutron 默认的多播组 224.0.0.1,你可以使用 vxlan_group 配置项指定其它的多播组。
(3)dev eth1 是 UDP出去的网卡,由配置项 local_ip 指定。
(4)port 32768 61000 是 linux vxlan 实现的 UDP 源端口号范围,这个不可以配置。
/* The below used as the min/max for the UDP port range */ >> +#define VXLAN_SRC_PORT_MIN 32768 >> +#define VXLAN_SRC_PORT_MAX 61000
在计算该端口的时候,可以考虑每个虚机的特定属性,来实现底层转发网络上多条转发路径上的负载均衡。
+/* Compute source port for outgoing packet. >> + * Currently we use the flow hash. >> + */ >> +static u16 get_src_port(struct sk_buff *skb) >> +{ >> + unsigned int range = (VXLAN_SRC_PORT_MAX - VXLAN_SRC_PORT_MIN) + 1; >> + u32 hash = OVS_CB(skb)->flow->hash; >> + >> + return (__force u16)(((u64) hash * range) >> 32) + VXLAN_SRC_PORT_MIN; >> +}
(5)ageing 300,这是 unreachable-vtep-aging-timer,单位是秒。其含义是,经过学习得到的远端 VTEP 后面的虚机的 MAC 地址过期时长。
创建好以后,需要设置其 IP 地址或者加入一个bridge。Neutron 的方案是将它加入到一个 linux bridge 上。然后,将其设置为 up (使用 ip link set up 命令)。
3.1.2 VXLAN interface 的功能
该 interface 会:
(1)创建并连接到一个 UDP 端口 8472 的 socket。IANA 标准化组织规定的端口是 4789,而 Linux 内核为了后向兼容需要而使用的端口是 8472.
/* UDP port for VXLAN traffic. 62 * The IANA assigned port is 4789, but the Linux default is 8472 63 * for compatibility with early adopters. 64 */
err = sock_create(AF_INET, SOCK_DGRAM, 0, &vxlan_port->vxlan_rcv_socket); kernel_bind(vxlan_port->vxlan_rcv_socket, (struct sockaddr *)&sin, sizeof(struct sockaddr_in)); udp_sk(vxlan_port->vxlan_rcv_socket->sk)->encap_rcv = vxlan_rcv;
(2)在支持多播的情况下,加入一个多播组
(3)虚机经过 linux bridge 由 vxlan 出去的流量,由 vxlan interface 封装 VXLAN 头,然后使用 UDP 由指定网卡发出
src_port = udp_flow_src_port(net, skb, 0, 0, true); #计算源 UDP 端口 md.vni = htonl(be64_to_cpu(tun_key->tun_id) << 8); md.gbp = vxlan_ext_gbp(skb); vxflags = vxlan_port->exts |(tun_key->tun_flags & TUNNEL_CSUM ? VXLAN_F_UDP_CSUM : 0); err = vxlan_xmit_skb(rt, sk, skb, fl.saddr, tun_key->ipv4_dst, #经过 UDP socket 发出 tun_key->ipv4_tos, tun_key->ipv4_ttl, df, src_port, dst_port, &md, false, vxflags);
(4)进来的 vxlan 流量,首先到达 UDP 端口,再交给 vxlan 钩子函数,由 vxlan 做解包处理后,经过 vxlan interface 通过 linux bridge 转发给虚机
/* Called with rcu_read_lock and BH disabled. */ >> +static int vxlan_rcv(struct sock *sk, struct sk_buff *skb)
注:
(1)以上示例代码都是 OVS 对 vxlan 的实现,linux 内核的实现原理其实也差不多。
(2)其实更准确地说以上这些都是Linux vxlan 内核模块的功能,只不过都是通过 vxlan interface 来体现给用户的。
3.1.3 默认情况下的 Linux vxlan interface 的 FDB 表(forwarding table)
默认情况下,VXLAN 通过多播学习来得到 fdb 表项即(VM-MAC, VTEP-IP)。这个成本是蛮高的。你可以使用 bridge 命令来查看 FDB,该命令由 iproute2 包提供。fdb 表的一个示例:
root@controller:~/s1# bridge fdb show dev vxlan-1074 00:00:00:00:00:00 dst 224.0.0.1 via eth1 self permanent #目的组为 224.0.0.1 的广播和多播包包经过 eth1 fa:16:3e:b4:9b:7c dst 10.0.0.13 self #MAC 地址为 fa:16:3e:b4:9b:7c 的虚机在 10.0.0.13 VTEP 后面 fa:16:3e:b4:9b:7c vlan 0 8a:e2:2f:2d:e7:48 vlan 0 permanent
该表内的表项的过期时间由 aging 参数决定,默认为5分钟。
3.2 使用 Neutorn l2population - fdb 更新
3.2.1 vSphere 和 LInux 实现的 vxlan fdb 表
fdb 表需要帮助解决两个问题:
(1)远端 VM 所在的 VTEP 的 IP 地址
(2)远端 VTEP MAC 和 IP 映射关系(本端主机需要根据远端 VTEP IP 获得其 MAC 地址)
vSphere 解决这个问题似乎是用一个大表(下图中间黄色部分),而 Linux 内核似乎是用两个表(下图中间蓝色和红色方框内部分):
3.2.2 Linux vxvlan fdb 表
要使用 l2population,需要在配置文件 /etc/neutron/plugins/ml2/ml2_conf.ini 中 enable 它:
[ml2]
mechanism_drivers = linuxbridge,l2pop
[vxlan]
l2_population = true
在l2pop 生效后,创建的 vxlan interface 上多了 “proxy” 功能:
root@controller:~# ip -d link show dev vxlan-1027 20: vxlan-1027: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue master brq243661e7-f7 state UNKNOWN mode DEFAULT group default link/ether d6:40:1e:47:38:59 brd ff:ff:ff:ff:ff:ff promiscuity 1 vxlan id 1027 dev eth1 port 32768 61000 proxy ageing 300
这使得 vxlan interface 可以被用作虚机的 ARP Proxy。它查询 ip neighbour table 来响应虚机的 ARP 请求。
关于l2pop的实现原理,可参考 Neutron 理解 (4): Neutron OVS OpenFlow 流表 和 L2 Population [Netruon OVS OpenFlow tables + L2 Population]。
在使用 Linux bridge + VXLAN + L2POP 的情况下,l2pop 提供两种 fdb 条目,然后 linuxbridge agent:
(1)将 VTEP 的 MAC-IP 映射关系保存到主机的 ip neighbour 表中
linux bridge agent 的实现采用的是直接调用 ip neighbour 命令行来操作 fdb 表。该命令同样是 iproute2 包提供的命令之一。
def add_fdb_ip_entry(self, mac, ip, interface): ip_lib.IPDevice(interface).neigh.add(ip, mac) def remove_fdb_ip_entry(self, mac, ip, interface): ip_lib.IPDevice(interface).neigh.delete(ip, mac)
示例:
root@compute1:~# ip n | grep eth1
10.0.0.10 dev eth1 lladdr 52:54:00:7c:c0:79 STALE
10.0.0.100 dev eth1 lladdr fa:80:13:21:6b:56 STALE
10.0.0.14 dev eth1 lladdr 52:54:00:c6:4d:42 STALE
这样的话,各节点上的 VTEP 就不需要使用 ARP 来获取目的VTEP的 MAC 地址了。
注:看起来这里只会更新 VTEP 的 IP 和 MAC 地址,这样省去的是linux 的 TCP/IP 协议栈找目的 VTEP MAC 地址的过程。那么,vxlan interface 作为 arp-proxy 的功能只是针对主机系统的。而传统意义上的 arp-proxy,按照我的理解,应该是向虚机提供 ARP Response,就像 OVS 所实现的那样。这一块还待进一步的研究。
(2)vxlan interface 的 fdb 表项
linux-linuxbridge-agent 实现了通过 “bridge” 命令来操作 fdb 表项的各个方法(代码在这里):
def add_fdb_bridge_entry(self, mac, agent_ip, interface, operation="add"):
utils.execute(['bridge', 'fdb', operation, mac, 'dev', interface, 'dst', agent_ip],
run_as_root=True, check_exit_code=False)
def remove_fdb_bridge_entry(self, mac, agent_ip, interface):
utils.execute(['bridge', 'fdb', 'del', mac, 'dev', interface, 'dst', agent_ip],
run_as_root=True, check_exit_code=False)
def add_fdb_entries(self, agent_ip, ports, interface):
for mac, ip in ports:
if mac != constants.FLOODING_ENTRY[0]:
self.add_fdb_ip_entry(mac, ip, interface)
self.add_fdb_bridge_entry(mac, agent_ip, interface,
operation="replace")
elif self.vxlan_mode == lconst.VXLAN_UCAST:
if self.fdb_bridge_entry_exists(mac, interface):
self.add_fdb_bridge_entry(mac, agent_ip, interface,
"append")
else:
self.add_fdb_bridge_entry(mac, agent_ip, interface)
def remove_fdb_entries(self, agent_ip, ports, interface):
for mac, ip in ports:
if mac != constants.FLOODING_ENTRY[0]:
self.remove_fdb_ip_entry(mac, ip, interface)
self.remove_fdb_bridge_entry(mac, agent_ip, interface)
elif self.vxlan_mode == lconst.VXLAN_UCAST:
self.remove_fdb_bridge_entry(mac, agent_ip, interface)
比如:
root@compute1:~# bridge fdb show dev vxlan-1027 66:23:9c:87:f1:6c vlan 0 permanent fa:16:3e:ba:43:4c vlan 0 fa:16:3e:aa:56:a4 vlan 0 00:00:00:00:00:00 dst 224.0.0.1 via eth1 self permanent fa:16:3e:ba:43:4c dst 10.0.0.14 self fa:16:3e:aa:56:a4 dst 10.0.0.10 self
这样的话,VTEP 就不需要通过多播来获取目的虚机的 VTEP 的 IP 地址了。
3.3 multicast group 多播组
这是单播、多播和广播的概念:
XVLAN 需要物理网络的多播功能的支持。多播的协议是 Internet Group Management Protocol (IGMP v1, v2, v3)。每个 VTEP 可以加入不同的多播组。
Neutorn 的实现中,默认情况下,一个机器上所有的 VTEP 都在一个组中,这个组可以配置,不配置的话使用默认的组 224.0.0.1。如果需要的话,比如机器很多的情况下,如果需要缩小多播组规模,可以使用多个多播组。
加入多播组:
- 主机1 上的 VTEP 发送 IGMP 加入消息 (join message)去加入多播组
- 主机4 上的 VTEP 发送 IGMP 加入消息去加入同一个多播组
多播过程:
- 虚机 VM1 发送一个广播帧
- 主机1 上的VM1所在网络对应的 VTEP interface 将该帧封装成 UDP 包,设置目的IP地址为 VTEP interface 多播组的地址。
- 物理网络将其发到该多播组内的所有主机
- 主机4 上的 VTEP,收到该 IP 包后,检查其 VXLAN ID,发现其上有同样 VXLAN ID 的虚机,则将该包解包后转发给该虚机。
- 主机2 和 3 同样收到该包,因为它们也在在多播组内,然而,它们发现没有其上虚机在指定的 VXLAN ID 内,因此将包丢弃。
以上是理论部分,具体还要进一步的实践。TBD。
3.4 Neutron 基于 Linux bridge 的防火墙 IptablesFirewallDriver
github 上的源代码在这里。支持 ipset。使用 iptables,作用在各个 linux bridge 上。
在 /etc/neutron/plugins/ml2/ml2_conf.ini 中的配置:
[securitygroup] # enable_ipset = True enable_security_group = True enable_ipset = True firewall_driver = neutron.agent.linux.iptables_firewall.IptablesFirewallDriver
从实现上看,OVSHybridIptablesFirewallDriver 类是从IptablesFirewallDriver 继承过来的,只有小幅的改动。因此,可以参考 Neutron 理解 (8): Neutron 是如何实现虚机防火墙的 [How Neutron Implements Security Group],不同的部分的细节待将来再进一步的分析。
4. Linux bridge agent 源代码分析
github 上的源代码在这里。其代码逻辑并不复杂,主要实现了如下的逻辑:
(1)main()函数。在启动 neutron-plugin-linuxbridge-agent 服务时被调用,它初始化一个 LinuxBridgeNeutronAgentRPC 实例,并调用其 start 方法启动它。
(2)LinuxBridgeNeutronAgentRPC 类是主类,它初始化SecurityGroupAgentRpc 类的实例用来处理安全组;设置并启动该Rpc (setup_rpc);启动一个循环来不断地 scan tap devices (scan_devices)来获取待处理的 tap 设备,并在设备列表有变化时进行相应的处理(包括 add 或者 remove interface)
(3)对于 Rpc,LinuxBridgeNeutronAgentRPC 会启动一个 LinuxBridgeRpcCallbacks 实例,默认情况下,会处理 PORT_UPDATE,NETWORK_DELETE 和 SG_UPDATE RPC 消息;在设置了 l2pop 的情况下,会增加处理 L2POP_UPDATE 消息;并且还会启动一个循环来不断调用 _report_state 来想 neutron server 报告其状态。
consumers = [[topics.PORT, topics.UPDATE],[topics.NETWORK, topics.DELETE],[topics.SECURITY_GROUP, topics.UPDATE]] if cfg.CONF.VXLAN.l2_population: consumers.append([topics.L2POPULATION, topics.UPDATE])
(4)RPC 处理
- network_delete:在 network 被删除后,将相应的 linux bridge 删除
- port_update:将 port 的 tap 名称放到 updated_devices 中,待进一步处理
- fdb_add:增加 fdb 表项
- fdb_remove:删除 fdb 表项
- _fdb_chg_ip:修改 ip neighbor fdb 表项
- 直接使用 sg_rpc.SecurityGroupAgentRpcCallbackMixin 的方法来处理安全组通知消息
画了一个大致的流程图:
参考
http://www.slideshare.net/JamesDenton1/2014-openstack-summit-neutron-ovs-to-linuxbridge-migration
https://blogs.vmware.com/vsphere/2013/05/vxlan-series-multicast-basics-part-2.html
http://blogs.vmware.com/vsphere/2013/05/vxlan-series-multicast-usage-in-vxlan-part-3.html
https://github.com/torvalds/linux/blob/master/drivers/net/vxlan.c
- 1楼hmings888
- 在vxlan模式下,使用linuxbridge比使用ovs,在层次上简化了,感觉这是一个改进,liberty版本看来在稳定简洁方面做了选择啊:)
- Re: SammyLiu
- @hmings888,我对社区的这个变化比较好奇,也许就是你说的理由,其实我也没看出来有使用 openvswitch 的必要。