Neutron 懂得(14):Neutron ML2 + Linux bridge + VxLAN 组网

Neutron 理解(14):Neutron ML2 + Linux bridge + VxLAN 组网

学习 Neutron 系列文章:

(1)Neutron 所实现的虚拟化网络

(2)Neutron OpenvSwitch + VLAN 虚拟网络

(3)Neutron OpenvSwitch + GRE/VxLAN 虚拟网络

(4)Neutron OVS OpenFlow 流表 和 L2 Population

(5)Neutron DHCP Agent

(6)Neutron L3 Agent  

(7)Neutron LBaas

(8)Neutron Security Group

(9)Neutron FWaas 和 Nova Security Group

(10)Neutron VPNaas

(11)Neutron DVR

(12)Neutron VRRP

(13)High Availability (HA)

(14)Linux bridge + VXLAN

 

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,请参见下图:

Neutron 懂得(14):Neutron ML2 + Linux bridge + VxLAN 组网

本段以 Rackspace 为例,说明他们使用 Linux bridge 的理由:

(1)Open vSwitch 目前还存在不少稳定性问题,比如:
  • Kernetl panics 1.10
  • ovs-switched segfaults 1.11
  • 广播风暴
  • Data corruption 2.01
(2)为什么可以使用 Linux bridge?
  • 稳定性和可靠性要求:Linux bridge 有十几年的使用历史,非常成熟。
  • 易于问题诊断 (troubleshooting)
  • 社区也支持
  • 还是可以使用 Overlay 网络 VxLAN 9需要 Linux 内核 3.9 版本或者以上)
(3)使用 Linux bridge 的局限性
  • Neutron DVR 还不支持 Linux bridge
  • 不支持 GRE
  • 一些 OVS 提供但是 Neutorn 不支持的功能

(4)长期来看,随着稳定性的进一步提高,Open vSwitch 会在生产环境中成为主流。

1.2 Linux bridge 和 Open vSwitch 的功能对比

Neutron 懂得(14):Neutron ML2 + Linux bridge + VxLAN 组网

可以看出:

(1)OVS 将各种功能都原生地实现在其中,这在起始阶段不可避免地带来潜在的稳定性和可调试性问题

(2)Linux bridge 依赖各种其他模块来实现各种功能,而这些模块的发布时间往往都已经很长,稳定性较高

(3)两者在核心功能上没有什么差距,只是在集中管控和性能优化这一块OVS有一些新的功能或者优化。但是,从测试结果看,两者的性能没有明显差异:

Neutron 懂得(14):Neutron ML2 + Linux bridge + VxLAN 组网 Neutron 懂得(14):Neutron ML2 + Linux bridge + VxLAN 组网

总之,目前,除了 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 配置步骤

  1. 创建三个网络,每个网络都启动DHCP,每个网络创建一个子网
  2. 创建一个 router,连接其中两个网络的若干子网
  3. 创建多个虚机

2.2.2 租户网络的构成

Neutron 懂得(14):Neutron ML2 + Linux bridge + VxLAN 组网 

计算节点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 互访

Neutron 懂得(14):Neutron ML2 + Linux bridge + VxLAN 组网

 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 为例,它使用如下的配置项:

Neutron 懂得(14):Neutron ML2 + Linux bridge + VxLAN 组网

(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 内核似乎是用两个表(下图中间蓝色和红色方框内部分):

Neutron 懂得(14):Neutron ML2 + Linux bridge + VxLAN 组网

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 多播组

这是单播、多播和广播的概念:

Neutron 懂得(14):Neutron ML2 + Linux bridge + VxLAN 组网

XVLAN 需要物理网络的多播功能的支持。多播的协议是 Internet Group Management Protocol (IGMP v1, v2, v3)。每个 VTEP 可以加入不同的多播组。

Neutorn 的实现中,默认情况下,一个机器上所有的 VTEP 都在一个组中,这个组可以配置,不配置的话使用默认的组 224.0.0.1。如果需要的话,比如机器很多的情况下,如果需要缩小多播组规模,可以使用多个多播组。

加入多播组:

  • 主机1 上的 VTEP 发送 IGMP 加入消息 (join message)去加入多播组
  • 主机4 上的 VTEP 发送 IGMP 加入消息去加入同一个多播组

多播过程:

  1. 虚机 VM1 发送一个广播帧
  2. 主机1 上的VM1所在网络对应的 VTEP interface 将该帧封装成 UDP 包,设置目的IP地址为 VTEP interface 多播组的地址。
  3. 物理网络将其发到该多播组内的所有主机
  4. 主机4 上的 VTEP,收到该 IP 包后,检查其 VXLAN ID,发现其上有同样 VXLAN ID 的虚机,则将该包解包后转发给该虚机。
  5. 主机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 的方法来处理安全组通知消息

画了一个大致的流程图:

Neutron 懂得(14):Neutron ML2 + Linux bridge + VxLAN 组网

 

参考

http://www.slideshare.net/JamesDenton1/2014-openstack-summit-neutron-ovs-to-linuxbridge-migration

http://blogs.vmware.com/vsphere/2013/05/vxlan-series-how-vtep-learns-and-creates-forwarding-table-part-5.html

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 的必要。