Neutron二层网络服务实现原理

目录

  1. 二层网络基本原理
  2. 大二层网络
    1. 大二层多路径技术
    2. 大二层互联技术
  3. 二层网络的实现——ML2插件
  4. 二层网络在Linux中的实现
    1. veth pair
      1. 创建veth pair
      2. 验证veth pair
    2. Linux网桥
      1. 创建Linux网桥
      2. 验证Linux网桥
  5. Openvswitch中二层网络的实现
  6. 虚拟机部署与二层网络的绑定(Port Binding)

二层网络基本原理

二层网络是TCP/IP七层网络中的数据链路层,通过交换机设备进行帧转发。交换机在收到帧之后(二层网络叫帧,三层网络叫包)先解析出帧头的MAC地址,再在转发表中查找是否有对应MAC地址的端口,有的话就从相应的端口转发出去。没有,就洪泛(flood),即将帧转发到交换机的所有端口,每个端口上的计算机都检查帧头中的MAC地址是否与本机网卡的MAC地址一致,一致的话就接收数据帧,不一致就直接丢弃。而转发表是通过自学习自动建立的。

混杂模式:默认情况下计算机只接收和本机MAC地址一致的数据帧,不一致就丢弃。如果要求计算机接收所有帧的话,就要设置网卡为混杂模式(ifconfig eth0 0.0.0.0 promisc up)。所以在虚拟网桥中,如果希望虚拟机和外部通信,必须打开桥接到虚拟网桥中物理网卡的混杂模式特性(对于flat、vlan网络适用,对于gre、vxlan等覆盖网络不适用)。

广播风暴:ARP通过广播充当了二层控制平面的角色,实现通过IP找MAC地址的功能,但是ARP广播中的洪泛经常会在一个局域网内产生大量的广播,也就是所谓的广播风暴。

虚拟局域网(VLAN):VLAN(Virtual Local Area Network)。在计算机网络中,一个二层网络可以被划分为多个不同的广播域,一个广播域对应了一个特定的用户组,默认情况下这些不同的广播域是相互隔离的。不同的广播域之间想要通信,需要通过一个或多个路由器。这样的一个广播域就称为VLAN。在基于VLAN的二层网络里,从以前的根据MAC地址接收帧改为同时根据VLAN号与MAC地址接收帧,避免了广播风暴。

Neutron目前已经实现了FLAT、VLAN、GRE、VxLAN四种网络拓扑。
结合VLAN号和MAC地址进行转发的是VLAN模式。
只根据MAC地址进行转发的是FLAT模式。

VLAN的缺点。

  • 要求穿越的所有物理交换机都配置允许带有某个VLAN号的数据帧通过,因为物理交换机通常只提供CLI命令,不提供远程接口可编程调用,所以都要手工配置,容易出错且工作量巨大,纯粹的体力劳动,影响了大规模部署。
  • VLAN号的范围是0-4094,VLAN的数量遭到限制。

为了克服VLAN的缺点,衍生出GRE、VxLan、VPN等隧道技术。这些技术的本质是重定制二层网络和三层网络的头部,通过三层网络或其他层的技术将数据传送到远端,在远端接收到这种定制的数据之后,再重新解包。这也就是覆盖网络(Overlay Network),它也是实现SDN的一个重要基础。因为使用隧道传输数据,所以无需再去配置隧道两端沿路的所有网络设备,这是SDN的好处(当然SDN还有一个好处是有控制器可以集中控制)

GRE便是其中一种支持覆盖网络的隧道技术,它克服了VLAN需要人工去硬件交换机那里配置VLAN的缺点。但是GRE也有不足之处。
第一体现在性能上,因为GRE在数据帧里添加上了自己的GRE帧头,这样可能造成整个数据帧长度大于硬件网卡的MTU值(一般默认设置为1500),于是需要将数据帧分片造成性能下降。同时,由于GRE隧道是点对点的,所以,如果有N个节点,就会有N*(N-1)条隧道,对于四层网络的端口资源也是一个浪费。这也是为什么GRE隧道使用UDP而不是TCP的原因,因为UDP用完后就可以释放,不会老占着端口又不用。

VxLAN与GRE类似,但它定义了一种在二层网络定制数据帧头的标准格式,它也提供了多播在局域网中进一步缩小广播范围。

大二层网络

大二层多路径技术

二层多路径技术可以解决二层的东西向流量问题。由于传统的二层网络没有控制平面,都是基于传统的生成树协议(STP)来构建网络,即按照传统的接入层、汇聚层、核心层的分级网络。相反的,大二层多路径技术的网络应该是网状的。在这种网状的结构里,需要将传统三层构建控制平面的思想引入到二层。Cisco的FabricPath便是这样一种技术,将三层基于IP的中间系统到中间系统的路由协议(IS-IS)技术引入到二层,基于交换机的ID来创建全网的交换机的网络拓扑结构。这样,传统三层的多路径技术也自然就是下沉到了二层。根据FabricPath创建的网络,不需要再像STP网络那样分层部署交换机,网络中交换机的地位是对等的:不需要再运行STP生成树协议:也不需要再执行MAC学习,因为它已经通过二层的中间系统的路由协议(IS-IS)交换了二层的全局拓扑信息,建立了二层的控制平面从而使得数据帧直接基于这个拓扑计算出的路径来转发,不再依赖于MAC地址。一句话,FabricPath技术是传统三层思想在二层的一个复制,是一个思维上的创新。

FabricPath是Cisco的私有标准,而TRILL(Transparent Interconnection of Lots of Links,多链接半透明互联)则是IETF组织的公开标准。SPB(Shorest Path Bridging,最短桥接路径)是IEEE组织的公开标准。

大二层互联技术

覆盖网络技术是大二层互联技术的关键,它也是SDN技术的技术本质,即自定义二层帧,以各层传输技术将帧发送到远端,在远端再把自定义帧解释出来。

覆盖网络传输虚拟化(OTV,Overlay Transport Virtualization)技术,其核心思想是通过“MAC in IP”的方式,即通过IP协议来传输OTV这个自定义的二层帧。OTV相较于VxLAN最大的特点是可以在广域网上进行地址学习,原因是OTV在大二层建立了控制平面,IS-IS动态路由协议可以在三层建立控制平面,而OTV仍然延用了传统的MAC地址的学习机制,自定义的二层帧会建立连接关系之后,通过广播或多播交换各自的MAC地址表,从而计算出一张具有全局网络拓扑状态的数据库。它有点类似MPLS通过不同的权重控制走不同的路径从而进行负载均衡。这样,OTV在发送第一个数据帧时就已经确定了路径,并对二层网关进行优化,同时具备内置的负载均衡(HSRP,Host Standby Router Protocol,热备份路由协议;Virtual Router Redundancy Protocol,虚拟路由冗余协议;GLBP,Gateway Load Balancing Protocol,网关负载均衡协议)。

对于跨广域网数据中心之间的相同子网的互联,二层VPN或其他隧道技术都可以实现。但这里有个问题就是,用于创建隧道的隧道两端的公有地址肯定是不同的,如果一个虚拟机从一个数据中心迁移到另一个数据中心想要保持IP不变,它的网关也需要跟着移动。位置/身份分离协议(LISP,Locator/Identifier Separation Protocol)正是来解决这种网络资源移动性问题的。LISP除了实现移动前后IP不变的特性外,还在控制层面提出使用eBGP协议进行二层地址的学习。

二层网络的实现——ML2插件

Neutron中的代码结构分两类,一类是插件,一类是代理。插件负责和数据库打交道并对外提供北向API。ML2是一种插件结构,其中包含两种Driver:一种叫做Type Driver,定义了五种不同的二层网络类型,即LOCAL、FLAT、VLAN、GRE、VXLAN;另一种叫做Mechenism Driver,实现了对象Open vSwitch、Linux Bridge、Bigswitch SDN等二层网络设备的支持。

二层网络在Linux中的实现

Neutron二层网络提供了对十多种二层网络设备的支持,但其中比较常用的,也是经常拿来作为参考的,是Linux网桥和Open vSwitch两种。两种方式都采用了以下技术:

  • TAP设备。比如vnet0,是虚拟化技术如KVM和Xen用来实现虚拟网卡的。当一个以太网帧发送给TAP设备时,这个以太网帧就会被虚拟机操作系统所接收。命名空间,用于隔离虚拟网络设备。

  • veth配对设备(veth pair)或OVS配对端口(patch port),是一对直接相连的虚拟网络接口。当一个以太网帧被发送给一个veth配对设备的一端时,将会被此配对设备的另一端来接收,在虚拟网络技术中,经常使用veth配对设备作为虚拟配对线缆来连接两个虚拟网桥。

  • Linux网桥,可以看做是一个简单的MAC地址学习的交换机,可以将多个(物理的或者虚拟的)网络接口设备连接到Linux网桥上。Linux网桥使用一个MAC地址缓存表来记录使用网桥上的哪个接口和链路上的主机通信。对于任何来自于连接在网桥上的网络接口的以太网帧,主机的MAC地址,以及在哪个端口上这个网帧被接收的,这个信息被记录在MAC缓存表中,并保留一段时间。当网桥需要转发一个网帧时,它会检查这个网帧的目的MAC地址是否记录在缓存表里。如果是,Linux网桥就将网帧只通过那个端口转发出去。否则,网帧会被广播到网桥上的所有网络端口,接收这个网帧的端口除外。Open vSwitch网桥,可以看做是一个虚拟交换机。网络接口设备连接到Open vSwitch网桥上的端口,并且这些端口可以像物理交换机的端口那样进行配置,比如配置VLAN。

veth pair



创建veth pair
  1. 创建命名空间

    1
    2
    ip netns add ns1
    ip netns add ns2
  2. 创建两个veth类型的tap设备tap1和tap2,并设置它们为peer。

    1
    ip link add tap1 type veth peer name tap2
  3. 设置tap1和tap2的命名空间。

    1
    2
    ip link set tap1 netns ns1
    ip link set tap2 netns ns2
  4. 启动tap1和tap2。

    1
    2
    ip netns exec ns1 ip link set dev tap1 up
    ip netns exec ns2 ip link set dev tap2 up
验证veth pair
  1. 列出命名空间

    1
    ip netns list
  2. 查看命名空间中的tap设备信息。

    1
    2
    ip netns exec ns1 ip addr
    ip netns exec ns2 ip addr


Linux网桥

创建Linux网桥
  1. 创建两个命名空间。

    1
    2
    ip netns add ns1
    ip netns add ns2
  2. 创建名为br-test的网桥。

    1
    brctl addbr br-test
  3. 禁用网桥的STP。

    1
    brctl stp br-test off
  4. 启用网桥。

    1
    ip link set dev br-test up
  5. 创建两个veth类型的tap设备tap1和br-tap1,并设置它们为peer。

    1
    ip link add tap1 type veth peer name br-tap1
  6. 将br-tap1加为网桥br-test上的接口。

    1
    brctl addif br-test br-tap1
  7. 将tap1加入到命名空间ns1。

    1
    ip link set tap1 netns ns1
  8. 启动tap1和br-tap1。

    1
    2
    ip netns exec ns1 ip link set dev tap1 up
    ip link set dev br-tap1 up
  9. 重复类似步骤五~七创建tap2和br-tap2。

    1
    2
    3
    4
    5
    ip link add tap2 type veth peer name br-tap2
    br-ctl addif br-test br-tap2
    ip link set tap2 netns ns2
    ip netns exec ns2 ip link set dev tap2 up
    ip link set dev br-tap2 up
验证Linux网桥
  1. 列出命名空间

    1
    ip netns list
  2. 查看命名空间中tap设备信息。

    1
    2
    ip netns exec ns1 ip addr
    ip netns exec ns2 ip addr



  3. 查看网桥br-test,可以看到之前创建的两个接口。

    1
    brctl show br-test


Openvswitch中二层网络的实现

Openvswitch是一个致力于提供产品质量、多层虚拟交换机的开源项目。

网络节点网络节点

计算节点计算节点

在neutron的隧道中有两个网桥,br-int和br-tun,它们之间通过ovs的patch port级联。

  • inbound,从gre_port或者vxlan_port进入br-tun的为入口流量,要转到表2&3,匹配到隧道流量打上本地标签,转到表10学习隧道远端的MAC地址,然后输出到patch_int。
  • outbound,从patch_int进入br-tun的为出口流量,转到表1,单播转表20,多播或组播转表21。在表20中的单播中,要记录学习发消息的虚拟机的MAC地址,同时去掉tag,打上隧道号,默认是转到21。

Neutron L2-agent OVS流表Neutron L2-agent OVS流表

核心思想就是,之前的做法是进出的广播均无限制,现在是进来的广播就不让了(因为已经通过MAC学习到了),出去的广播只允许本机有VLAN的流量到相应同类型(GRE或VxLAN)的端口。

之前的做法,如果在一个OVS桥如br-tun里同时用了GRE和VxLAN这些隧道,且隧道端口也一样的话,就会出现安全隔离问题。在br-tun里使用normal作为action,接受来路不明的广播,这将引起安全问题。所以从安全上将,任何时候都不应该用normal作为action,对于从隧道端口(tunnel ports)接收到的包应该动态的从OVS的20号表中学习MAC地址创建流规则,这样对于不清楚的广播再重发送到表21。

虚拟机部署与二层网络的绑定(Port Binding)

当创建一个虚拟机时,虚拟机将通过绑定端口和二层网络绑定在一起。

  1. 当在Neutron中创建一个新的网络时,会在Neutron的数据库中创建一条相应的记录。
  2. 新创建的网络可以关联到租户的虚拟路由上。
  3. 同时,网络节点上的l3-agent会周期扫描路由,如果发现有新的网络关联到路由上,就调用vif(通过l3_agent.ini中的interface_driver参数配置)为和它关联的子网创建该子网的内部网关,如果关联的是外网,也会创建外部网络相应的tap设备。
  4. 当Nova创建虚拟机时,Nova会调用Neutron的接口来获取虚拟机的IP地址,并在数据库中记录虚拟机的网络信息。
  5. 之后Nova会调用vif为虚拟机创建tap设备。
  6. 在计算节点上的neutron-l2-agent会周期性扫描,当检测到新的物理tap设备时,会从数据库中检索次tap设备所关联的端口信息、网络信息和安全组信息,然后根据这些信息在计算节点上做相关的设置,将虚拟机通过tap设备绑定到二层网络的网桥上。
  7. 在这个过程中,ml2_port_binding这张数据表记录了虚拟机和网络之间的端口绑定关系,包含端口ID、计算节点主机、虚拟接口类型、虚拟网卡类型和网络分段等信息。

Nova和Neutron进行信息交互。

  1. 在通过**nova boot**命令创建虚拟机时,通过Nova本身的调度功能选定目标计算节点。
  2. 计算节点上的nova-compute服务会把计算节点主机的信息传递给Neutron。Neutron首先会记录节点信息(binding_host)到ml2_port_bindings表中,然后循环调用各种mechanism_drivers的bind_port方法,每个mechanism_driver会调用PortContext类中的host_agent方法,根据自身的agent_type和binding_host找到对应的agent信息。
1
2
3
4
5
6
# Rocky
# neutron/plugins/ml2/driver_context.py:PortContext
def host_agents(self, agent_type):
return self._plugin.get_agents(self._plugin_context,
filters={'agent_type': [agent_type],
'host': [self._binding.host]})
  1. 继续在ml2_port_bindings表记录segment以及vif_type信息。

    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
    51
    52
    53
    54
    55
    56
    57
    58
    # Rocky
    # neutron/plugins/ml2/drivers/openvswitch/mech_driver/mech_openvswitch.py:OpenvswitchMechanismDriver
    def bind_port(self, context):
    vnic_type = context.current.get(portbindings.VNIC_TYPE,
    portbindings.VNIC_NORMAL)
    profile = context.current.get(portbindings.PROFILE)
    capabilities = []
    if profile:
    capabilities = profile.get('capabilities', [])
    if (vnic_type == portbindings.VNIC_DIRECT and
    'switchdev' not in capabilities):
    LOG.debug("Refusing to bind due to unsupported vnic_type: %s with "
    "no switchdev capability", portbindings.VNIC_DIRECT)
    return
    super(OpenvswitchMechanismDriver, self).bind_port(context)

    # neutron/plugins/ml2/drivers/mech_agent.py:AgentMechanismDriverBase
    def bind_port(self, context):
    LOG.debug("Attempting to bind port %(port)s on "
    "network %(network)s",
    {'port': context.current['id'],
    'network': context.network.current['id']})
    vnic_type = context.current.get(portbindings.VNIC_TYPE,
    portbindings.VNIC_NORMAL)
    if vnic_type not in self.supported_vnic_types:
    LOG.debug("Refusing to bind due to unsupported vnic_type: %s",
    vnic_type)
    return
    agents = context.host_agents(self.agent_type)
    if not agents:
    LOG.debug("Port %(pid)s on network %(network)s not bound, "
    "no agent of type %(at)s registered on host %(host)s",
    {'pid': context.current['id'],
    'at': self.agent_type,
    'network': context.network.current['id'],
    'host': context.host})
    for agent in agents:
    LOG.debug("Checking agent: %s", agent)
    if agent['alive']:
    for segment in context.segments_to_bind:
    if self.try_to_bind_segment_for_agent(context, segment,
    agent):
    LOG.debug("Bound using segment: %s", segment)
    return
    else:
    LOG.warning("Refusing to bind port %(pid)s to dead agent: "
    "%(agent)s",
    {'pid': context.current['id'], 'agent': agent})

    # neutron/plugins/ml2/drivers/mech_agent.py:SimpleAgentMechanismDriverBase
    def try_to_bind_segment_for_agent(self, context, segment, agent):
    if self.check_segment_for_agent(segment, agent):
    context.set_binding(segment[api.ID],
    self.get_vif_type(context, agent, segment),
    self.get_vif_details(context, agent, segment))
    return True
    else:
    return False
  2. 网络分段(segment)指的是网络里具有特定物理网络能力的部分,一个网络可以包含多个分段,所以,每个计算节点上的neutron-l2-agent所对应的网络分段也许是不同的,对应VLAN、隧道及外部物理网络的能力。每个neutron-l2-agent通过读取配置指定,并在汇报状态时上报给Neutron,这样Neutron里的mechanism_driver就可以知道哪个l2-agent能够提供什么能力了。

    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
    # Rocky
    # neutron/plugins/ml2/drivers/openvswitch/agent/ovs_neutron_agent.py:OVSNeutronAgent
    self.agent_state = {
    'binary': 'neutron-openvswitch-agent',
    'host': host,
    'topic': n_const.L2_AGENT_TOPIC,
    'configurations': {'bridge_mappings': self.bridge_mappings,
    'tunnel_types': self.tunnel_types,
    'tunneling_ip': self.local_ip,
    'l2_population': self.l2_pop,
    'arp_responder_enabled':
    self.arp_responder_enabled,
    'enable_distributed_routing':
    self.enable_distributed_routing,
    'log_agent_heartbeats':
    agent_conf.log_agent_heartbeats,
    'extensions': self.ext_manager.names(),
    'datapath_type': ovs_conf.datapath_type,
    'ovs_capabilities': self.ovs.capabilities,
    'vhostuser_socket_dir':
    ovs_conf.vhostuser_socket_dir,
    portbindings.OVS_HYBRID_PLUG: hybrid_plug},
    'resource_versions': resources.LOCAL_RESOURCE_VERSIONS,
    'agent_type': agent_conf.agent_type,
    'start_flag': True}
  3. 对于虚拟接口类型信息(vi_type),Nova需要用到它去设置相关的网桥信息。

    1