代码改变世界

我非要捅穿这 Neutron(四)Open vSwitch in Neutron

2019-04-02 12:09  云物互联  阅读(2722)  评论(0编辑  收藏  举报

目录

前文列表

我非要捅穿这 Neutron(一)网络实现模型篇
我非要捅穿这 Neutron(二)上层资源模型篇
我非要捅穿这 Neutron(三)架构分析与代码实现篇(基于 OpenStack Rocky)
手动部署 OpenStack Rocky 双节点

OvS In Neutron 网络拓扑

在这里插入图片描述

OvS In Neutron 网络实现模型

在这里插入图片描述

双节点混合平面网络

在这里插入图片描述

双节点网络拓扑

在这里插入图片描述

NOTE:本文中的网络设计只是为了方便描述,仅作参考,实际的网络设计会更加灵活。

Controller

  • ens160:172.18.22.231(OpenStack Management Network)
  • ens161:br-ex
  • ens192:10.0.0.1(OpenStack Internal Tunnel Network)
  • ens224:br-provider(Flat)
  • ens256:br-provider-1(VLAN)
ovs-vsctl add-br br-ex
ovs-vsctl add-port br-ex ens161

ovs-vsctl add-br br-provider
ovs-vsctl add-port br-provider ens224

ovs-vsctl add-br br-provider-1
ovs-vsctl add-port br-provider-1 ens256

Compute

  • ens160:172.18.22.232(OpenStack Management Network)
  • ens192:10.0.0.2(OpenStack Internal Tunnel Network)
  • ens224:br-provider(Flat)
  • ens256:br-provider-1(VLAN)
ovs-vsctl add-br br-provider
ovs-vsctl add-port br-provider ens224

ovs-vsctl add-br br-provider-1
ovs-vsctl add-port br-provider-1 ens256

OvS Bridges 与初始流表项

OvS br-int

OVS Integration Bridge 综合网桥,属于本地网络层网桥,在每个计算节点上作为本地虚拟机的直连虚拟交换机。

[root@controller ~]# ovs-ofctl dump-flows br-int
 cookie=0xea50ae8e9cc7754f, duration=69221.341s, table=0, n_packets=292387, n_bytes=19859352, priority=2,in_port="int-br-provider" actions=drop
 cookie=0xea50ae8e9cc7754f, duration=69222.176s, table=0, n_packets=0, n_bytes=0, priority=0 actions=resubmit(,60)
 cookie=0xea50ae8e9cc7754f, duration=69222.178s, table=23, n_packets=0, n_bytes=0, priority=0 actions=drop
 cookie=0xea50ae8e9cc7754f, duration=69222.172s, table=24, n_packets=0, n_bytes=0, priority=0 actions=drop
 cookie=0xea50ae8e9cc7754f, duration=69222.174s, table=60, n_packets=0, n_bytes=0, priority=3 actions=NORMAL

OvS br-tun

OVS Tunnel Bridge 隧道网桥,属于隧道类型(VxLAN、GRE)租户网络层网桥,进行数据包的内外 VID 转换以及桥接各节点上的同类型网桥。

[root@controller ~]# ovs-ofctl dump-flows br-tun
 cookie=0xbababcd06622b167, duration=69203.910s, table=0, n_packets=0, n_bytes=0, priority=1,in_port="patch-int" actions=resubmit(,2)
 cookie=0xbababcd06622b167, duration=69203.909s, table=0, n_packets=0, n_bytes=0, priority=0 actions=drop
 cookie=0xbababcd06622b167, duration=69203.907s, table=2, n_packets=0, n_bytes=0, priority=0,dl_dst=00:00:00:00:00:00/01:00:00:00:00:00 actions=resubmit(,20)
 cookie=0xbababcd06622b167, duration=69203.905s, table=2, n_packets=0, n_bytes=0, priority=0,dl_dst=01:00:00:00:00:00/01:00:00:00:00:00 actions=resubmit(,22)
 cookie=0xbababcd06622b167, duration=69203.903s, table=3, n_packets=0, n_bytes=0, priority=0 actions=drop
 cookie=0xbababcd06622b167, duration=69203.901s, table=4, n_packets=0, n_bytes=0, priority=0 actions=drop
 cookie=0xbababcd06622b167, duration=69203.900s, table=6, n_packets=0, n_bytes=0, priority=0 actions=drop
 cookie=0xbababcd06622b167, duration=69203.897s, table=10, n_packets=0, n_bytes=0, priority=1 actions=learn(table=20,hard_timeout=300,priority=1,cookie=0xbababcd06622b167,NXM_OF_VLAN_TCI[0..11],NXM_OF_ETH_DST[]=NXM_OF_ETH_SRC[],load:0->NXM_OF_VLAN_TCI[],load:NXM_NX_TUN_ID[]->NXM_NX_TUN_ID[],output:OXM_OF_IN_PORT[]),output:"patch-int"
 cookie=0xbababcd06622b167, duration=69203.894s, table=20, n_packets=0, n_bytes=0, priority=0 actions=resubmit(,22)
 cookie=0xbababcd06622b167, duration=69203.892s, table=22, n_packets=0, n_bytes=0, priority=0 actions=drop

OvS br-provider

OVS Provider Bridge 运营商网桥,又称物理网桥。因为需要将运营商网络的物理网卡挂载到 Bridge 上,所以称为 Provider Bridge。属于非隧道类型(Flat、VLAN)的租户层网桥,进行数据包的内外 VID 转换以及桥接各节点上的同类型网桥。为了方便后文的流表项分析,本文中 br-provider 专用于 Flat Network。

[root@controller ~]# ovs-ofctl dump-flows br-provider
 cookie=0x9bf5dfe2802f0eee, duration=69241.924s, table=0, n_packets=0, n_bytes=0, priority=2,in_port="phy-br-provider" actions=drop
 cookie=0x9bf5dfe2802f0eee, duration=69241.957s, table=0, n_packets=3936526, n_bytes=2971679460, priority=0 actions=NORMAL

NOTE:当 br-provider 被 OvS Agent “接管” 之后会插入第一条流表项,含义是默认(priority=2)将所有从 br-int 发送过来的数据包 Drop 掉,直到有优先级 Priority 更高的流表项写入为止。

OvS br-provider-1

OVS Provider Bridge 运营商网桥,同上。为了方便后文的流表项分析,本文中 br-provider-1 专用于 VLAN Network。

[root@controller ~]# ovs-ofctl dump-flows br-provider-1
 cookie=0x5a3fea9b762885b6, duration=938.826s, table=0, n_packets=4097, n_bytes=279042, priority=2,in_port="phy-br-pr53399e" actions=drop
 cookie=0x5a3fea9b762885b6, duration=938.841s, table=0, n_packets=71286, n_bytes=25799617, priority=0 actions=NORMAL

OvS br-ex

OVS Provider Bridge 运营商网桥,同上。本文中 br-ex 作为外部网络网桥,将 L3 Router 转发过来的数据包通过物理网络发送到外部网络(第二次网关)。

[root@controller ~]# ovs-ofctl dump-flows br-ex
 cookie=0x2ab9d6fd329b3642, duration=22.621s, table=0, n_packets=0, n_bytes=0, priority=2,in_port="phy-br-ex" actions=drop
 cookie=0x2ab9d6fd329b3642, duration=22.644s, table=0, n_packets=868, n_bytes=311055, priority=0 actions=NORMAL

NOTE:无论是 br-provider、br-provider-1 还是 br-ex 都只是命名区别而已,它们均属于 OVS Provider Bridge 一类,根本作用是 连接 Provider Physical Network(运营商物理网络)。区别命名也只是为了更好的将它们应用到网管人员设计好的网络中而已。抛开性能问题,实际上租户网络(Flat、VLAN)和运营商网络(外部网络)均可以共用同一个 br-provider。与由 OvS Agent 创建并维护的 br-int、br-tun 不同,br-provider 顾名思义是对接 “运营商物理网络” 的网桥,不属于 Neutron 的管理范畴,所以需要自己手动创建并挂载一张物理网卡。

OvS in Neutron 配置

  • Controller
[root@controller ~]# cat /etc/neutron/neutron.conf
[DEFAULT]
core_plugin = ml2
service_plugins = router
allow_overlapping_ips = true
transport_url = rabbit://openstack:fanguiju@controller
auth_strategy = keystone
notify_nova_on_port_status_changes = true
notify_nova_on_port_data_changes = true
rpc_state_report_workers = 0
api_workers = 4

[database]
connection = mysql+pymysql://neutron:fanguiju@controller/neutron

[keystone_authtoken]
www_authenticate_uri = http://controller:5000
auth_url = http://controller:5000
memcached_servers = controller:11211
auth_type = password
project_domain_name = default
user_domain_name = default
project_name = service
username = neutron
password = fanguiju

[nova]
auth_url = http://controller:5000
auth_type = password
project_domain_name = default
user_domain_name = default
region_name = RegionOne
project_name = service
username = nova
password = fanguiju

[oslo_concurrency]
lock_path = /var/lib/neutron/tmp

[agent]
root_helper_daemon = sudo /usr/bin/neutron-rootwrap-daemon /etc/neutron/rootwrap.conf
root_helper = sudo /usr/bin/neutron-rootwrap /etc/neutron/rootwrap.conf

[root@controller ~]# cat /etc/neutron/plugins/ml2/ml2_conf.ini
[ml2]
type_drivers = local,flat,vlan,vxlan
tenant_network_types = vxlan
extension_drivers = port_security
mechanism_drivers = openvswitch,l2population

[ml2_type_flat]
flat_networks = provider,external

[ml2_type_vlan]
network_vlan_ranges = provider1:1:1000

[ml2_type_vxlan]
vni_ranges = 1:1000

[root@controller ~]# cat /etc/neutron/plugins/ml2/openvswitch_agent.ini
[ovs]
datapath_type = system
bridge_mappings = provider:br-provider,provider1:br-provider-1,external:br-ex
tunnel_bridge = br-tun
local_ip = 10.0.0.1

[agent]
tunnel_types = vxlan
l2_population = True

[securitygroup]
firewall_driver = openvswitch
  • Compute
[root@compute ~]# cat /etc/neutron/neutron.conf
[DEFAULT]
transport_url = rabbit://openstack:fanguiju@controller
auth_strategy = keystone

[keystone_authtoken]
www_authenticate_uri = http://controller:5000
auth_url = http://controller:5000
memcached_servers = controller:11211
auth_type = password
project_domain_name = default
user_domain_name = default
project_name = service
username = neutron
password = fanguiju

[oslo_concurrency]
lock_path = /var/lib/neutron/tmp

[root@compute ~]# cat /etc/neutron/plugins/ml2/openvswitch_agent.ini
[ovs]
datapath_type = system
bridge_mappings = provider:br-provider,provider1:br-provider-1
tunnel_bridge = br-tun
local_ip = 10.0.0.2

[agent]
tunnel_types = vxlan
l2_population = True

[securitygroup]
firewall_driver = openvswitch

Flat Network(扁平网络)

Flat Network 的特点是无 VLAN Tagging,所以每个 Flat Network 独占一张物理网卡,在生产环境中很少见。

NOTE:一个 Provider Physical Network(运营商物理网络)只能创建一个 Flat Network,否则会触发异常。

关键配置

  • Controller
# /etc/neutron/plugins/ml2/ml2_conf.ini
[ml2]
type_drivers = flat
mechanism_drivers = openvswitch

[ml2_type_flat]
flat_networks = provider,

# /etc/neutron/plugins/ml2/openvswitch_agent.ini
[ovs]
bridge_mappings = provider:br-provider
  • Compute
# /etc/neutron/plugins/ml2/openvswitch_agent.ini
[ovs]
bridge_mappings = provider:br-provider

provider:br-provider 中的 provider 只是一个 Label (String),用于在跨节点、跨网络类型场景中标识同一个 Provider Physical Network(运营商物理网络),是一种 friendly 的用户操作方式。而 br-provider 则是手动创建的 OVS Provider Bridge,挂载了一张物理网卡,作为 Flat Network 的租户层网桥。

创建 Flat 类型租户网络(管理员权限)

  • Create Flat Network
openstack network create --enable --project admin --provider-network-type flat --provider-physical-network provider flat-net-1
  • Create Subnet
openstack subnet create --network flat-net-1 --dhcp --ip-version 4 --subnet-range 192.168.1.0/24 --gateway 192.168.1.1 flat-subnet-1

网络变更

在这里插入图片描述

  • 因为 Subnet enabled DHCP 所以在 Flat Network 创建了一个 Port network:dhcp。
    在这里插入图片描述
  • Port network:dhcp 本质是一个 br-int 的 Port tap{dhcp_port_uuid},Local VLAN tag:1。OvS Agent 在 br-int 上通过 Local VLAN tag 实现了不同 Networks 之间的二层广播域隔离。
[root@controller ~]# ovs-vsctl show
...
    Bridge br-int
    ...
        Port "tapec3789c1-0e"
            tag: 1
            Interface "tapec3789c1-0e"
                type: internal
  • Port tap{dhcp_port_uuid} 的 Interface 设备创建在 Network Namespace qdhcp-{network_uuid},通过 Linux Network Namespace 进行隔离,防止多网络环境的 IP:DomainName 解析冲突。
[root@controller ~]# ip netns
qdhcp-6967cf07-3846-44a3-bcb8-258eaabf69fc (id: 0)

[root@controller ~]# ip netns exec qdhcp-6967cf07-3846-44a3-bcb8-258eaabf69fc ifconfig
...
tapec3789c1-0e: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 192.168.1.2  netmask 255.255.255.0  broadcast 192.168.1.255
        inet6 fe80::f816:3eff:fe83:5c9f  prefixlen 64  scopeid 0x20<link>
        ether fa:16:3e:83:5c:9f  txqueuelen 1000  (Ethernet)
        RX packets 409  bytes 21138 (20.6 KiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 5  bytes 446 (446.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

NOTE:以上步骤在每个 Enabled DHCP 的 Subnet 中都会发生,后文中不再赘述。

NOTE:因为 Controller 同时兼顾网络节点,所以 DHCP 和 Router 相关的网络设备会在 Controller 上维护,否则会在专属的网络节点上维护。

跨界点启动虚拟机

openstack server create --image cirros --flavor mini --network flat-net-1 --availability-zone nova:controller VM1
openstack server create --image cirros --flavor mini --network flat-net-1 --availability-zone nova:compute VM2

网络变更

在这里插入图片描述

  • 在 Flat Network 创建了两个 Port compute:nova,表示是虚拟机 vNIC 对应的 Port。
    在这里插入图片描述

Controller

  • Port compute:nova 本质还是 br-int 的 Port tap{vnic_port_uuid},Local VLAN tag:1,Port 对应的 Interface 设备直接创建在 HostOS 上。
[root@controller ~]# ovs-vsctl show
...
    Bridge br-int
        ...
        Port "tapff2655ef-40"
            tag: 1
            Interface "tapff2655ef-40"
        Port "tapec3789c1-0e"
            tag: 1
            Interface "tapec3789c1-0e"
                type: internal

NOTE:上述三个 Port 的 Local VLAN tag 相同,表示三者同属一个 Network。

NOTE:因为我们启用了基于 Open vSwitch 的安全组,所以虚拟机 vNIC 对应的 Tap 设备直连到 br-int 上,再不需要 Linux Bridge qbr-XXX 的中继。

Compute

  • 在 Compute 的 br-int 上同样会创建虚拟机 vNIC 对应的 Port tap{vnic_port_uuid},Local VLAN tag:1,Port 对应的 Interface 设备直接创建在 HostOS 上。
[root@compute ~]# ovs-vsctl show
...
    Bridge br-int
        ...
        Port "tap9b085083-01"
            tag: 1
            Interface "tap9b085083-01"

NOTE:同一个 Network 的 Port 在同一个节点(br-int)上的 Local VLAN tag 肯定是相同的,但处于不同节点上的 Local VLAN tag 未必是相同的。e.g. 节点1:tap{vm1_vnic_port_uuid} Local VLAN tag:1;节点2:tap{vm2_vnic_port_uuid} Local VLAN tag:2。这并不会影响同一网络的跨节点连通性,因为内外 VID 转换的工作是由 OvS 流表控制的,只要确保同一个网络的租户网络层 VID 唯一即可。

NOTE:以上步骤在每个虚拟机创建的过程中都会发生,后文不再赘述。

抓包分析

VM1 ping VM2,在 Compute 上抓包。监听的 Interface 是 ens224,它是 Network “flat-net-1” 所使用的 Provider Physical Network Label “provider” 在 Compute 节点上对应的物理网卡。只有相同 Provider Physical Network Label 对应的物理网卡才会接收到跨界点通信的流量。

[root@compute ~]# tcpdump -i ens224 -nntve -p icmp and src host 192.168.1.3
tcpdump: listening on ens224, link-type EN10MB (Ethernet), capture size 262144 bytes
fa:16:3e:8c:bc:d4 > fa:16:3e:48:c3:89, ethertype IPv4 (0x0800), length 98: (tos 0x0, ttl 64, id 44899, offset 0, flags [DF], proto ICMP (1), length 84)
    192.168.1.3 > 192.168.1.22: ICMP echo request, id 23809, seq 0, length 64

流表分析

[root@controller ~]# ovs-ofctl dump-flows br-provider
...
 cookie=0x1467957b313183c6, duration=859.522s, table=0, n_packets=118, n_bytes=11918, priority=4,in_port="phy-br-provider",dl_vlan=1 actions=strip_vlan,NORMAL

br-provider 追加了一条流表,含义是从 br-int 过来的 VLAN tag 为 1 的数据包,剥除 VLAN tag,其余数据包正常转发。通过这条流表将本地网络层原本具有 Local VLAN tag 的数据包在发出到租户网络层时重新变成 Untag 数据包,符合 Flat Network 特征。

VLAN Network

VLAN Network 是具有 802.1q Tagging(基于 802.1.q VLAN 协议) 的网络。每个 VLAN 都是一个基于二层实现的广播域,同一 VLAN 中的 虚拟机之间可以通信,不同 VLAN 间的虚拟机之间需要通过 L3 Router 通信。VLAN 网络是应用最广泛的网络类型,适合中小型网络拓扑。

关键配置

  • Controller
# /etc/neutron/plugins/ml2/ml2_conf.ini
[ml2]
type_drivers = vlan
mechanism_drivers = openvswitch

[ml2_type_vlan]
network_vlan_ranges = provider1:1:1000

# /etc/neutron/plugins/ml2/openvswitch_agent.ini
[ovs]
bridge_mappings = provider1:br-provider-1
  • Compute
# /etc/neutron/plugins/ml2/openvswitch_agent.ini
[ovs]
bridge_mappings = provider1:br-provider-1

NOTE:VLAN Network 对应的物理网络(e.g. br-provider-1)应该接入的是一个 Trunk 口,配置的 VLAN Ranges 应该和实际的 Trunk Ranges 一致。因为 OvS Agent 只管从 network_vlan_ranges 读取数据并落库,并不具有 VLAN ID 可行性判断逻辑。所以,如果不考虑实际情况而胡乱填写 network_vlan_ranges 的话,就很可能出现创建网络、虚拟机成功,但虚拟机却无法跨网络通信的问题。

创建 VLAN 类型租户网络(管理员权限)

  • Create VLAN Network
openstack network create --enable --project admin --provider-network-type vlan --provider-physical-network provider1 --provider-segment 100 vlan-net-100
  • Create Subnet
openstack subnet create --network vlan-net-100 --dhcp --ip-version 4 --subnet-range 192.168.1.0/24 --gateway 192.168.1.1 vlan-subnet-1

网络变更

在这里插入图片描述

DHCP Network Namespace 的意义

至此,flat-net-1、vlan-net-1 都具有相同网段 192.168.1.0/24 的 Subnet,下面看看两个 Networks 是怎么通过 Network Namespace 来实现隔离的。
在这里插入图片描述
当 Enabled DHCP Subnet 时,DHCP Agent 会创建一个 Network Namespace ,并在之内启动一个 dnsmasq 服务进程。启动指令如下:

dnsmasq --no-hosts --no-resolv \
    --except-interface=lo \
    --pid-file=/var/lib/neutron/dhcp/6967cf07-3846-44a3-bcb8-258eaabf69fc/pid \
    --dhcp-hostsfile=/var/lib/neutron/dhcp/6967cf07-3846-44a3-bcb8-258eaabf69fc/host \
    --addn-hosts=/var/lib/neutron/dhcp/6967cf07-3846-44a3-bcb8-258eaabf69fc/addn_hosts \
    --dhcp-optsfile=/var/lib/neutron/dhcp/6967cf07-3846-44a3-bcb8-258eaabf69fc/opts \
    --dhcp-leasefile=/var/lib/neutron/dhcp/6967cf07-3846-44a3-bcb8-258eaabf69fc/leases \
    --dhcp-match=set:ipxe,175 \
    --bind-interfaces \
    --interface=tapec3789c1-0e \
    --dhcp-range=set:tag0,192.168.1.0,static,255.255.255.0,86400s \
    --dhcp-option-force=option:mtu,1500 \
    --dhcp-lease-max=256 \
    --conf-file= \
    --domain=openstacklocal

--dhcp-hostsfile 保存了该 Namespace 中的 IP:DomainName 映射。e.g.

[root@controller ~]# cat /var/lib/neutron/dhcp/6967cf07-3846-44a3-bcb8-258eaabf69fc/host
fa:16:3e:83:5c:9f,host-192-168-1-2.openstacklocal,192.168.1.2

[root@controller ~]# cat /var/lib/neutron/dhcp/f31b2060-ccc0-457a-948c-d805a7680faf/host
fa:16:3e:5a:9b:b2,host-192-168-1-2.openstacklocal,192.168.1.2

可见,即便两个 Subnets 的 IP 地址 192.168.1.2 重叠了也不会造成冲突,因为它们分别运行在不同的 Namespace 中。除了 DHCP 之外,dnsmasq 还会为 Subnet 提供 DNS 解析、路由表项等功能,它们同样需要进行网络隔离。

在这里插入图片描述

简而言之,qdhcp-XXX 隔离的意义就是为了同时支撑多个 Subnets 的 IP 核心网络服务(DNS、DHCP、IPAM)

跨界点创建虚拟机

openstack server create --image cirros --flavor mini --network vlan-net-100 --availability-zone nova:controller VM3
openstack server create --image cirros --flavor mini --network vlan-net-100 --availability-zone nova:compute VM4

网络变更

在这里插入图片描述

虚拟机无法获取到 IP 地址的问题

问题:Compute 上的 VM4 无法获取到 IP 地址。

TS:预计 VM4 无法访问到同一个 Subnet 中的 DHCP 服务。手动为 VM4 配上 IP 地址并从 VM4 ping DHCP,抓包发现 VM4 发出的 ARP 广播无法抵达 Controller,判断为物理网络的问题。

[root@compute ~]# tcpdump -i ens256 -nntve src host 192.168.1.6
tcpdump: listening on ens256, link-type EN10MB (Ethernet), capture size 262144 bytes
fa:16:3e:e8:47:46 > ff:ff:ff:ff:ff:ff, ethertype 802.1Q (0x8100), length 46: vlan 100, p 0, ethertype ARP, Ethernet (len 6), IPv4 (len 4), Request who-has 192.168.1.2 tell 192.168.1.6, length 28

解决:VMware 原生的 VM Network 不具有 VLAN Trunking 特性,所以再创建一个 trunk PortGroup 来替换 VM Network 作为 OpenStack Internal VLAN Network 的物理网络接口。
在这里插入图片描述
在这里插入图片描述

抓包分析

VM3 ping VM4,在 Compute 上抓包。再次强调,只有相同 Provider Physical Network Label 对应的物理网卡才会接收到跨界点通信的流量。因为 VLAN Network 的 Label 是 provider1,所以从配置文件可知在 Compute 上监听的 Interface 是 ens256。并且可以看到数据包均具有 VLAN tag:100。

[root@compute ~]# tcpdump -i ens256 -nntve -p icmp and src host 192.168.1.12
tcpdump: listening on ens256, link-type EN10MB (Ethernet), capture size 262144 bytes
fa:16:3e:5c:7f:fd > fa:16:3e:ba:60:dd, ethertype 802.1Q (0x8100), length 102: vlan 100, p 0, ethertype IPv4, (tos 0x0, ttl 64, id 25722, offset 0, flags [DF], proto ICMP (1), length 84)
    192.168.1.12 > 192.168.1.11: ICMP echo request, id 24321, seq 30, length 64

流表项分析

[root@controller ~]# ovs-ofctl dump-flows br-provider-1
...
 cookie=0xe68b683827a8e499, duration=1287.762s, table=0, n_packets=378, n_bytes=36670, priority=4,in_port="phy-br-pr53399e",dl_vlan=2 actions=mod_vlan_vid:100,NORMAL

br-provider-1 添加了一条流表项,含义是将从 br-int 发送过来的具有 Local VLAN tag:2 的数据包的 VLAN tag 修改为 100。

在出报文的时候,完成了一次 VLAN Network 的内->外 VID 转换

本地网络层 租户网络层
VLAN 2 VLAN 100
[root@controller ~]# ovs-ofctl dump-flows br-int
...
 cookie=0x4442a4feb6c04e73, duration=47473.976s, table=0, n_packets=49144, n_bytes=4911584, priority=3,in_port="int-br-pr53399e",dl_vlan=100 actions=mod_vlan_vid:2,resubmit(,60)

br-int 添加了一条流表项,含义是将从 br-provider-1 发送过来的 VLAN tag:100 的数据包的 VLAN tag 修改为 Local VLAN tag:2。

在入报文的时候,完成了一次 VLAN Network 的外->内 VID 转换

租户网络层 本地网络层
VLAN 100 VLAN 2

VxLAN Network

VxLAN Network 是基于隧道(Tunnel)技术的 Overlay 覆盖网络。所谓 Overlay 就是覆盖在三层网络之上的自定义网络,从用户的角度来看,它屏蔽了底层网络的复杂性。VxLAN 网络通过唯一的 Segmentation ID(VNI)来进行划分,将二层的数据帧封装在三层数据包进行传输(L2 in L3),从而克服 VLAN 和物理网络基础设施的限制。

VxLAN 的传输协议是 IP+UDP,它定义了一个 MAC-in-UDP 的封装格式。在原始的 Layer 2 数据帧前加上 VxLAN Header,然后再放到 UDP 数据报和 IP 数据包中。通过 MAC-in-UDP 封装,VxLAN 能够在 Layer 3 网络上建立起了一条 Layer 2 的隧道。VxLAN 引入了 8-byte VXLAN Header,其中 VNI(VXLAN Network Identifier)占 24-bit。VxLAN Header 和原始的 L2 Frame 被封装到 UDP 数据报中。这 24-bit 的 VNI 用于标示不同的二层网段,能够支持 16777216 个 LAN。
在这里插入图片描述
VxLAN 使用 VTEP(VxLAN tunnel endpoint,隧道端点)设备来处理 VxLAN 的封装和解封装。每个 VTEP 有一个 IP interface 并配置上一个 IP 地址。VTEP 使用该 IP 作为目的 IP 或源 IP 来封装 Layer 2 frame,并通过该 IP interface 传输和接收封装后的 VxLAN 数据包。
在这里插入图片描述

关键配置

  • Controller
# /etc/neutron/plugins/ml2/ml2_conf.ini

[ml2]
type_drivers = vxlan
mechanism_drivers = openvswitch

[ml2_type_vxlan]
vni_ranges = 1:1000

# /etc/neutron/plugins/ml2/openvswitch_agent.ini
[ovs]
datapath_type = system
# VTEP
tunnel_bridge = br-tun
local_ip = 10.0.0.1

[agent]
tunnel_types = vxlan
  • Compute
# /etc/neutron/plugins/ml2/openvswitch_agent.ini

[ovs]
datapath_type = system
# VTEP
tunnel_bridge = br-tun
local_ip = 10.0.0.2

[agent]
tunnel_types = vxlan

NOTE:上文介绍过 VxLAN 是 L2 in L3 的网络,这意味着 VxLAN Network 正常运作的关键在于隧道端点(VTEP)及其 IP 地址的连通性,而不需要进行额外的 “br-provider” 配置。

创建 VxLAN 类型租户网络(管理员权限)

  • Create VxLAN Network
openstack network create --enable --project admin --provider-network-type vxlan --provider-segment 1000 vxlan-net-1000
  • Create Subnet
openstack subnet create --network vxlan-net-1000 --dhcp --ip-version 4 --subnet-range 172.16.1.0/24 --gateway 172.16.1.1 vxlan-subnet-1

网络变更

在这里插入图片描述

跨界点创建虚拟机

openstack server create --image cirros --flavor mini --network vxlan-net-1000 --availability-zone nova:controller VM5
openstack server create --image cirros --flavor mini --network vxlan-net-1000 --availability-zone nova:compute VM6

网络变更

在这里插入图片描述

在 VxLAN Network 上启动虚拟机时,除了会在 OvS br-int 上创建 vNIC 相应的 Port。同时还会分别在 Controller、Compute 上的 OvS br-tun 上创建 VTEP Port vxlan-0a000002 和 vxlan-0a000001,建立 VxLAN Tunnel。就相当于执行了 OvS 指令:

# Host A
ovs-vsctl add-br br-vxlan
# Host B
ovs-vsctl add-br br-vxlan

# Host A 上添加连接到 Host B 的 Tunnel Port
ovs-vsctl add-port br-vxlan tun0 -- set Interface tun0 type=vxlan options:remote_ip=<host_b_ip>
# Host B 上添加连接到 Host A 的 Tunnel Port
ovs-vsctl add-port br-vxlan tun0 -- set Interface tun0 type=vxlan options:remote_ip=<host_a_ip>

抓包分析

VM5 ping VM6,在 Compute 上抓包。如果直接抓 ICMP 协议是抓不到数据包的,因为从 VM5 发出的 ICMP request 包被加上 VxLAN header 最终封装成 IP+UDP 网络包进行传输。

[root@compute ~]# tcpdump -i ens192 -nnvte -p icmp
tcpdump: listening on ens192, link-type EN10MB (Ethernet), capture size 262144 bytes

所以,我们无论是监听 IP 数据包还是 UDP 数据报都是可以抓到包的。

[root@compute ~]# tcpdump -i ens192 -nnvte -p ip
tcpdump: listening on ens192, link-type EN10MB (Ethernet), capture size 262144 bytes
00:50:56:bf:18:9f > 00:50:56:bf:68:83, ethertype IPv4 (0x0800), length 148: (tos 0x0, ttl 64, id 40765, offset 0, flags [DF], proto UDP (17), length 134)
    10.0.0.1.52193 > 10.0.0.2.4789: VXLAN, flags [I] (0x08), vni 1000
fa:16:3e:4a:36:b5 > fa:16:3e:20:a2:69, ethertype IPv4 (0x0800), length 98: (tos 0x0, ttl 64, id 41744, offset 0, flags [DF], proto ICMP (1), length 84)
    172.16.1.20 > 172.16.1.9: ICMP echo request, id 23553, seq 16, length 64
00:50:56:bf:68:83 > 00:50:56:bf:18:9f, ethertype IPv4 (0x0800), length 148: (tos 0x0, ttl 64, id 33286, offset 0, flags [DF], proto UDP (17), length 134)
    10.0.0.2.37669 > 10.0.0.1.4789: VXLAN, flags [I] (0x08), vni 1000
fa:16:3e:20:a2:69 > fa:16:3e:4a:36:b5, ethertype IPv4 (0x0800), length 98: (tos 0x0, ttl 64, id 60034, offset 0, flags [none], proto ICMP (1), length 84)
    172.16.1.9 > 172.16.1.20: ICMP echo reply, id 23553, seq 16, length 64

[root@compute ~]# tcpdump -i ens192 -nnvte -p udp
tcpdump: listening on ens192, link-type EN10MB (Ethernet), capture size 262144 bytes
00:50:56:bf:18:9f > 00:50:56:bf:68:83, ethertype IPv4 (0x0800), length 148: (tos 0x0, ttl 64, id 48222, offset 0, flags [DF], proto UDP (17), length 134)
    10.0.0.1.52193 > 10.0.0.2.4789: VXLAN, flags [I] (0x08), vni 1000
fa:16:3e:4a:36:b5 > fa:16:3e:20:a2:69, ethertype IPv4 (0x0800), length 98: (tos 0x0, ttl 64, id 44342, offset 0, flags [DF], proto ICMP (1), length 84)
    172.16.1.20 > 172.16.1.9: ICMP echo request, id 23553, seq 35, length 64
00:50:56:bf:68:83 > 00:50:56:bf:18:9f, ethertype IPv4 (0x0800), length 148: (tos 0x0, ttl 64, id 40124, offset 0, flags [DF], proto UDP (17), length 134)
    10.0.0.2.37669 > 10.0.0.1.4789: VXLAN, flags [I] (0x08), vni 1000
fa:16:3e:20:a2:69 > fa:16:3e:4a:36:b5, ethertype IPv4 (0x0800), length 98: (tos 0x0, ttl 64, id 62271, offset 0, flags [none], proto ICMP (1), length 84)
    172.16.1.9 > 172.16.1.20: ICMP echo reply, id 23553, seq 35, length 64

流表分析

NOTE:下述以 Controller 的 OvS br-tun 流表项为了,Compute 类同。
在这里插入图片描述
table 0:第一张流表的编号,所有网络包都要通过 table 0。table 0 在这里主要用作数据包来源的划分(内/外)。

 cookie=0xfaee3d9234e8f8fa, duration=431758.646s, table=0, n_packets=2779206, n_bytes=224498704, priority=1,in_port="patch-int" actions=resubmit(,2)

 cookie=0xfaee3d9234e8f8fa, duration=2108.689s, table=0, n_packets=1364, n_bytes=129334, priority=1,in_port="vxlan-0a000002" actions=resubmit(,4)

 cookie=0xfaee3d9234e8f8fa, duration=431758.644s, table=0, n_packets=0, n_bytes=0, priority=0 actions=drop
  1. 从 Port patch-int(内部)发送过来的数据包 resubmit 到 table 2 处理。
  2. 从 Port vxlan-0a000002(外部)发送过来的数据包 resubmit 到 table 4 处理。
  3. 默认丢弃数据包。

处理外部送入的数据

table 4:作为对外部数据包的处理,主要有两点。一个是进行 “外=>内 VID 转换”,另一个是进行 MAC 地址表学习(转交到 table 10 完成)。

 cookie=0xfaee3d9234e8f8fa, duration=369.313s, table=4, n_packets=193, n_bytes=18272, priority=1,tun_id=0x3e8 actions=mod_vlan_vid:4,resubmit(,10)

 cookie=0xfaee3d9234e8f8fa, duration=496998.621s, table=4, n_packets=0, n_bytes=0, priority=0 actions=drop
  1. 进行 VxLAN Network 的 “外=>内 VID 转换”,然后 resubmit 到 table 10 处理。
租户网络层 本地网络层
VNI 1000 VLAN 4
  1. 默认丢弃数据包。

table 10:进行 MAC 地址表学习,学习外部虚拟机对本地虚拟机访问数据包的 MAC 地址,本质是生成数据包返程(本地=>远程)的转发规则。

 cookie=0xfaee3d9234e8f8fa, duration=496998.615s, table=10, n_packets=44971, n_bytes=4269468, priority=1 actions=learn(table=20,hard_timeout=300,priority=1,cookie=0xfaee3d9234e8f8fa,NXM_OF_VLAN_TCI[0..11],NXM_OF_ETH_DST[]=NXM_OF_ETH_SRC[],load:0->NXM_OF_VLAN_TCI[],load:NXM_NX_TUN_ID[]->NXM_NX_TUN_ID[],output:OXM_OF_IN_PORT[]),output:"patch-int"
  • learn(table=20,hard_timeout=300,priority=1,cookie=0xfaee3d9234e8f8fa,NXM_OF_VLAN_TCI[0..11],NXM_OF_ETH_DST[]=NXM_OF_ETH_SRC[],load:0->NXM_OF_VLAN_TCI[],load:NXM_NX_TUN_ID[]->NXM_NX_TUN_ID[],output:OXM_OF_IN_PORT[])往 table 20 中添加对返程包的正常转发规则
    • learn():MAC 学习函数。
    • table=20:指定学习表编号,即往 table 20 添加返程包流表项。
    • NXM_OF_VLAN_TCI[0..11],相当于 NXM_OF_VLAN_TCI[0..11]=NXM_OF_VLAN_TCI[0..11]:(匹配条件)流表项匹配的网络包 VLAN ID 和当前处理的网络包 VLAN ID 一样(e.g. VLAN tag:4)。
    • NXM_OF_ETH_DST[]=NXM_OF_ETH_SRC[]:(匹配条件)流表项匹配的网络包目的 MAC 地址和当前处理的网络包源 MAC 地址一样(e.g. 从 Compute:VM 6 发送过来的访问数据包的源 MAC 地址为 fa:16:3e:20:a2:69)。
    • load (load:value−>dst[start…end])::(动作)写入区域。因为是返程包,所以下述含义是进行 “内=>外 VID 转换”,网络包发送出去的时候,VLAN ID 设为 0 并设置 Tunnel ID。
      • load:0->NXM_OF_VLAN_TCI[]:将 0 写入流表项匹配网络包的 VLAN ID。
      • load:NXM_NX_TUN_ID[]->NXM_NX_TUN_ID[]:将当前的 TUN ID 写入到流表项匹配网络包的 TUN ID。
    • output (output:src[start…end]):(动作)输入端口
      • output:OXM_OF_IN_PORT[]:将流表项匹配网络包从当前处理的网络包的 in_port(输入端口)输出。(从同一个端口返程)
  • output:"patch-int"“外=>内 VID 转换” 之后的数据包转发到 br-int

处理内部发出的数据

table 2:从内部 OvS br-int 发送过来的网络包主要是单播包和广播(组播)包两种类型。table 2 对这两种类型的网络包进行分发。

单播(Unicast):在发送者和每个接收者之间实现点对点网络连接。如果一台发送者同时给多个的接收者传输相同的数据,也必须相应的复制多份相同的数据包。如果有大量主机希望获得数据包的同一份拷贝时,将导致发送者负担沉重、延迟长、网络拥塞;

广播(Broadcasting):是指在 IP 子网内广播数据包,所有在子网内部的主机都将收到这些数据包。广播意味着网络向子网每一个主机都投递一份数据包,不论这些主机是否乐于接收该数据包。所以广播的使用范围非常小,只在本地子网内有效,通过路由器、VLAN 和网络设备控制广播传输区域。

组播(Multicast):在发送者和每个接收者之间实现点对多点网络连接。如果一台发送者同时给多个接收者传输相同的数据,也只需复制一份相同的数据包。它提高了数据传送效率,减少了骨干网络出现拥塞的可能性。组播解决了单播和广播方式效率低的问题。当网络中的某些用户需求特定信息时,组播源(即组播信息发送者)仅发送一次信息,组播路由器借助组播路由协议为组播数据包建立树型路由,被传递的信息在尽可能远的分叉路口才开始复制和分发。

 cookie=0xfaee3d9234e8f8fa, duration=431758.641s, table=2, n_packets=929179, n_bytes=104019186, priority=0,dl_dst=00:00:00:00:00:00/01:00:00:00:00:00 actions=resubmit(,20)
 
 cookie=0xfaee3d9234e8f8fa, duration=431758.637s, table=2, n_packets=1850027, n_bytes=120479518, priority=0,dl_dst=01:00:00:00:00:00/01:00:00:00:00:00 actions=resubmit(,22)
  1. br-int 发送过来的单播包 00:00:00:00:00:00/01:00:00:00:00:00,resubmit 到 table 20 处理。
  2. br-int 发送过来的多播或广播包 01:00:00:00:00:00/01:00:00:00:00:00,resubmit 到 table 22 处理。

table 20:处理 br-int 发送过来的单播包,这类数据包通常有明确的 “点对点连接” 信息,例如:MAC 地址。

 cookie=0xfaee3d9234e8f8fa, duration=226.964s, table=20, n_packets=149, n_bytes=15172, priority=2,dl_vlan=4,dl_dst=fa:16:3e:20:a2:69 actions=strip_vlan,load:0x3e8->NXM_NX_TUN_ID[],output:"vxlan-0a000002"
 
 cookie=0xfaee3d9234e8f8fa, duration=216.630s, table=20, n_packets=0, n_bytes=0, hard_timeout=300, priority=1,vlan_tci=0x0004/0x0fff,dl_dst=fa:16:3e:20:a2:69 actions=load:0->NXM_OF_VLAN_TCI[],load:0x3e8->NXM_NX_TUN_ID[],output:"vxlan-0a000002"

 cookie=0xfaee3d9234e8f8fa, duration=496998.612s, table=20, n_packets=1067130, n_bytes=117276295, priority=0 actions=resubmit(,22)
  1. 将 Local VLAN tag:3,目的 MAC 地址为 fa:16:3e:b2:38:82 的网络包去掉 VLAN tag,设定 VxLAN VNI 为 0x3e8 并输出到 Port vxlan-0a000002。

  2. 第二条流表项,与第一条类似。区别在于第二条流表项是自 table 10 学习而来的。主要是针对 MAC 地址的学习,所以一把被称为 MAC 学习表(MAC Learning table)。

  3. 对于没有学习到规则的数据包,resubmit 到 table 22 处理。

table 22:处理 br-int 发送过来的广播或多播包。

 cookie=0xfaee3d9234e8f8fa, duration=226.968s, table=22, n_packets=68, n_bytes=6436, priority=1,dl_vlan=4 actions=strip_vlan,load:0x3e8->NXM_NX_TUN_ID[],output:"vxlan-0a000002"

 cookie=0xfaee3d9234e8f8fa, duration=496998.611s, table=22, n_packets=3195237, n_bytes=255860585, priority=0 actions=drop
  1. 将 Local VLAN tag:4 的数据包去除 VLAN tag,添加上 Tunnel ID 并由 Port vxlan-0a000002 输出。
  2. 默认丢弃数据包。

基于 OvS 的 L3 Router

Neutron L3 Router 是一个 Service Plugins(扩展插件),由 L3 Agent 提供服务。默认情况下(非 DVR)L3 Agent 运行在网络节点,所以跨网络的三层转发流量都会途经网络节点。

NOTE:本文环境中 Controller 重叠了控制节点、网络节点、计算节点。

[root@controller ~]# openstack network agent list
+--------------------------------------+--------------------+------------+-------------------+-------+-------+---------------------------+
| ID                                   | Agent Type         | Host       | Availability Zone | Alive | State | Binary                    |
+--------------------------------------+--------------------+------------+-------------------+-------+-------+---------------------------+
| 41925586-9119-4709-bc23-4668433bd413 | Metadata agent     | controller | None              | :-)   | UP    | neutron-metadata-agent    |
| 43281ac1-7699-4a81-a5b6-d4818f8cf8f9 | Open vSwitch agent | controller | None              | :-)   | UP    | neutron-openvswitch-agent |
| b815e569-c85d-4a37-84ea-7bdc5fe5653c | DHCP agent         | controller | nova              | :-)   | UP    | neutron-dhcp-agent        |
| d1ef7214-d26c-42c8-ba0b-2a1580a44446 | L3 agent           | controller | nova              | :-)   | UP    | neutron-l3-agent          |
| f55311fc-635c-4985-ae6b-162f3fa8f886 | Open vSwitch agent | compute    | None              | :-)   | UP    | neutron-openvswitch-agent |
+--------------------------------------+--------------------+------------+-------------------+-------+-------+---------------------------+

关键配置

# /etc/neutron/neutron.conf
[DEFAULT]
...
service_plugins = router

# /etc/neutron/l3_agent.ini
[DEFAULT]
interface_driver = openvswitch
external_network_bridge = br-ex
  • interface_driver:指定 Mechanism Driver 类型。
  • external_network_bridge:指定连接外部网络(公网)的网桥,默认是 OvS br-ex。

创建集中式 Router

openstack router create --enable --centralized --project admin router1

添加相同网段的 Subnets 到 Router

每创建一个 Router 实例都会在网络节点创建一个 qrouter-{router_uuid} Network Namespace,用于隔离路由表项。e.g.

[root@controller ~]# ip netns exec qrouter-703b2e70-af1b-4945-b0ff-f5efb1e5164d route -nne
Kernel IP routing table
Destination     Gateway         Genmask         Flags   MSS Window  irtt Iface

但需要注意的是,不同 Router 可以加入相同的网段的 Subnet,因为 Router 之间是隔离的。但相同网段的 Subnet 不应该加入到同一个 Router,因为这样没有跨网段转发的意义,而且还会造成 IP 地址重叠的问题。下面我们首先尝试将都具有 192.168.1.0/24 网段的 Subnets vlan-subnet-1 和 flat-subnet-1 加入到 router1。

[root@controller ~]# openstack subnet list
+--------------------------------------+----------------+--------------------------------------+----------------+
| ID                                   | Name           | Network                              | Subnet         |
+--------------------------------------+----------------+--------------------------------------+----------------+
| 85c68fdd-85f7-4f19-9538-ff82b5c8c5f0 | vxlan-subnet-1 | be8ca1f5-f243-4640-b7e1-4107fe16dd70 | 172.16.1.0/24  |
| a6a0e4a3-60e9-4bcf-9c7b-210807637571 | flat-subnet-1  | 6967cf07-3846-44a3-bcb8-258eaabf69fc | 192.168.1.0/24 |
| d3cf6b12-f78b-42c0-9e0b-29ec8915007e | vlan-subnet-1  | f31b2060-ccc0-457a-948c-d805a7680faf | 192.168.1.0/24 |
+--------------------------------------+----------------+--------------------------------------+----------------+

[root@controller ~]# openstack router add subnet router1 vlan-subnet-1
[root@controller ~]# openstack router add subnet router1 flat-subnet-1

虽然整个过程不会触发异常中断,但实际上只有 vlan-subnet-1 的网关接口(Port)192.168.1.1 成功加入了 Router。而 flat-subnet-1 则属于 “无效添加”。
在这里插入图片描述

[root@controller ~]# openstack router show router1
+-------------------------+-----------------------------------------------------------------------------------------------------------------------------------------+
| Field                   | Value                                                                                                                                   |
+-------------------------+-----------------------------------------------------------------------------------------------------------------------------------------+
| admin_state_up          | UP                                                                                                                                      |
| availability_zone_hints |                                                                                                                                         |
| availability_zones      | nova                                                                                                                                    |
| created_at              | 2019-04-08T06:24:09Z                                                                                                                    |
| description             |                                                                                                                                         |
| distributed             | False                                                                                                                                   |
| external_gateway_info   | None                                                                                                                                    |
| flavor_id               | None                                                                                                                                    |
| ha                      | False                                                                                                                                   |
| id                      | 703b2e70-af1b-4945-b0ff-f5efb1e5164d                                                                                                    |
| interfaces_info         | [{"subnet_id": "d3cf6b12-f78b-42c0-9e0b-29ec8915007e", "ip_address": "192.168.1.1", "port_id": "2e95d893-af62-40dd-9560-3fbe39e9b217"}] |
| name                    | router1                                                                                                                                 |
| project_id              | a2b55e37121042a1862275a9bc9b0223                                                                                                        |
| revision_number         | 3                                                                                                                                       |
| routes                  |                                                                                                                                         |
| status                  | ACTIVE                                                                                                                                  |
| tags                    |                                                                                                                                         |
| updated_at              | 2019-04-08T06:45:07Z                                                                                                                    |
+-------------------------+-----------------------------------------------------------------------------------------------------------------------------------------+

添加不同网段的 Subnets 到 Router

[root@controller ~]# openstack subnet list
+--------------------------------------+----------------+--------------------------------------+----------------+
| ID                                   | Name           | Network                              | Subnet         |
+--------------------------------------+----------------+--------------------------------------+----------------+
| 85c68fdd-85f7-4f19-9538-ff82b5c8c5f0 | vxlan-subnet-1 | be8ca1f5-f243-4640-b7e1-4107fe16dd70 | 172.16.1.0/24  |
| a6a0e4a3-60e9-4bcf-9c7b-210807637571 | flat-subnet-1  | 6967cf07-3846-44a3-bcb8-258eaabf69fc | 192.168.1.0/24 |
| d3cf6b12-f78b-42c0-9e0b-29ec8915007e | vlan-subnet-1  | f31b2060-ccc0-457a-948c-d805a7680faf | 192.168.1.0/24 |
+--------------------------------------+----------------+--------------------------------------+----------------+

[root@controller ~]# openstack router add subnet router1 vlan-subnet-1
[root@controller ~]# openstack router add subnet router1 vxlan-subnet-1

网络变更

在这里插入图片描述

  • 分别在两个 Networks 创建了 Gateway Port network:router_interface。
    在这里插入图片描述
    在这里插入图片描述
  • 两个 Networks 的 Gateway Port 都接入了 Router router1,类型为 “内部接口”。
    在这里插入图片描述
  • Controller 的 br-int 上添加了 qr-{vlan_subnet_gw_port_uuid} 和 qr-{vxlan_subnet_gw_port_uuid} 两个设备,它们分别具有各自 Network 的 Local VLAN tag。
[root@controller ~]# ovs-vsctl show
...
    Bridge br-int
    ...
        Port "qr-2e95d893-af"
            tag: 2
            Interface "qr-2e95d893-af"
                type: internal
...
        Port "qr-5a408fb2-e3"
            tag: 4
            Interface "qr-5a408fb2-e3"
                type: internal
  • qr-{vlan_subnet_gw_port_uuid} 和 qr-{vxlan_subnet_gw_port_uuid} 被隔离在 Network Namespace qrouter-{router_uuid} 中,并配置上了各自的 Network Gateway IP 地址。即通过 qr-xxx 设备,将两个 Networks 接入同一个 “路由空间” 中
[root@controller ~]# ip netns exec qrouter-703b2e70-af1b-4945-b0ff-f5efb1e5164d ifconfig
...
qr-2e95d893-af: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 192.168.1.1  netmask 255.255.255.0  broadcast 192.168.1.255
        inet6 fe80::f816:3eff:fe4c:c8a8  prefixlen 64  scopeid 0x20<link>
        ether fa:16:3e:4c:c8:a8  txqueuelen 1000  (Ethernet)
        RX packets 2250  bytes 181720 (177.4 KiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 19  bytes 1474 (1.4 KiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

qr-5a408fb2-e3: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1450
        inet 172.16.1.1  netmask 255.255.255.0  broadcast 172.16.1.255
        inet6 fe80::f816:3eff:fef3:aec2  prefixlen 64  scopeid 0x20<link>
        ether fa:16:3e:f3:ae:c2  txqueuelen 1000  (Ethernet)
        RX packets 1640  bytes 133168 (130.0 KiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 19  bytes 1474 (1.4 KiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0
  • 因为两个 Networks 都是直接接入 Router 的,所以在 Namespace 自动添加了两台直连路由(U)表项:
[root@controller ~]# ip netns exec qrouter-703b2e70-af1b-4945-b0ff-f5efb1e5164d route -nne
Kernel IP routing table
Destination     Gateway         Genmask         Flags   MSS Window  irtt Iface
172.16.1.0      0.0.0.0         255.255.255.0   U         0 0          0 qr-5a408fb2-e3
192.168.1.0     0.0.0.0         255.255.255.0   U         0 0          0 qr-2e95d893-af

跨网段访问原理

Network 的跨网段访问流量会经由 qr-XXX Interface 涉设备进入 Network Namespace qrouter-XXX。然后内核 TCP/IP 协议栈根据 qrouter-XXX 中的路由规则对访问流量进行路由、改写数据帧的源 MAC 地址和目的 MAC 地址之后,流量再经过路由目的 qr-YYY Interface 进入另一个 Network。从而实现不同网段之间的路由转发。
在这里插入图片描述

基于 OvS 的 External Network

关键配置

# /etc/neutron/plugins/ml2/ml2_conf.ini

[ml2]
type_drivers = flat
mechanism_drivers = openvswitch

[ml2_type_flat]
flat_networks = external

[ovs]
datapath_type = system
bridge_mappings = external:br-ex

创建外部(运营商)网络

在前文提到过 External Network 是一种特殊的(具有 Floating IP 功能)运营商网络,Neutron 通过记录运营商的 “网络核心三要素” 来使用它。受限于本文的实践环境,运营商网络的物理网卡 ens161 连接在 vCenter VM Network(Untag Network),所以我们将采用 Flat 网络类型来创建 External Network。

  • Create External Network
openstack network create --enable --project admin --external --provider-network-type flat --provider-physical-network external ext_net
  • Create Subnet:External Network 的网段记录了运营商网络的网段,External Network 的 Gateway IP 地址记录了运营商网络的 Gateway IP 地址(第二层网关 IP 地址)。
openstack subnet create --network ext_net --ip-version 4 --no-dhcp --subnet-range 172.18.22.0/24 --allocation-pool start=172.18.22.241,end=172.18.22.250 --gateway 172.18.22.1 ext_subnet-1

NOTE:因为运营商网络 IP 地址资源有限,所以建议限定 External Subnet 的 IP 地址池。而且外部网络一般不直接为虚拟机分配 IP 地址,所以也不需要 Enable Subnet DHCP。

创建第一层网关(Router)

因为要连接第二层网关(运营商物理路由器),所以我们需要创建外部网络。而第一层网关(Router)是通过 Neutron 创建的,是接入外部网络的中转站。
在这里插入图片描述
或者执行 CLI 创建

openstack router create --enable --centralized --project admin router-ext
openstack router set --enable-snat --external-gateway ext_net router-ext

网络变更

  • 在外部网络 ext_net 上添加了 Pot network:router_gateway(区别于 network:router_interface)。
    在这里插入图片描述

  • 将 Pot network:router_gateway 接入第一层网关 router-ext,是第一层网关接入外部网络的端口。
    在这里插入图片描述

  • 在 Controller 的 br-int 上添加了 Port qg-{router_gateway_port_uuid} 设备。

[root@controller ~]# ovs-vsctl show
...
    Bridge br-int
    ...
        Port "qg-424af937-c4"
            tag: 8
            Interface "qg-424af937-c4"
                type: internal
...
  • Port qg-{router_gateway_port_uuid} 的 Interface 设备创建在 Router 对应的 Network Namespace 中,设置上了第一层网关 IP 地址 172.18.22.245。
[root@controller ~]# ip netns exec qrouter-ccce705c-5b0f-4bf7-a82d-94f143a88d3d ifconfig
...
qg-424af937-c4: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 172.18.22.245  netmask 255.255.255.0  broadcast 172.18.22.255
        inet6 fd00:310:22:0:f816:3eff:fea0:276a  prefixlen 64  scopeid 0x0<global>
        inet6 fe80::f816:3eff:fea0:276a  prefixlen 64  scopeid 0x20<link>
        ether fa:16:3e:a0:27:6a  txqueuelen 1000  (Ethernet)
        RX packets 2438  bytes 124540 (121.6 KiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 29  bytes 1786 (1.7 KiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0
  • 第一层网关的 Network Namespace 中,因为 qg-XXX 直接连接着外部网络,所以存在一条直连路由(U)表项。又因为第一层网关的最终目的是将流表转发到第二层网关,所以默认的下一跳(默认网关路由表项 UG)是第二层网关 IP 地址。
[root@controller ~]# ip netns exec qrouter-ccce705c-5b0f-4bf7-a82d-94f143a88d3d route -nne
Kernel IP routing table
Destination     Gateway         Genmask         Flags   MSS Window  irtt Iface
0.0.0.0         172.18.22.1     0.0.0.0         UG        0 0          0 qg-424af937-c4
172.18.22.0     0.0.0.0         255.255.255.0   U         0 0          0 qg-424af937-c4
  • 从 router_ext 的 Namespace 可以 ping 通第二次网关的 IP 地址。
[root@controller ~]# ip netns exec qrouter-ccce705c-5b0f-4bf7-a82d-94f143a88d3d ping 172.18.22.1
PING 172.18.22.1 (172.18.22.1) 56(84) bytes of data.
64 bytes from 172.18.22.1: icmp_seq=1 ttl=255 time=21.4 ms
  • 从外部网络也可以 ping 通过第一层网关的外部接口的 IP 地址。
╭─mickeyfan@localhost  ~
╰─$ ping 172.18.22.245
PING 172.18.22.245 (172.18.22.245): 56 data bytes
64 bytes from 172.18.22.245: icmp_seq=0 ttl=62 time=10.032 ms

NOTE:旧版本的 qg-XXX 是挂载在 br-ex 上的,新版本则挂载在 br-int。虽然位置改变了,但功能不变。

访问外部网络

任意租户网络接入到第一层网关(router-ext)之后,该租户网络的虚拟机就可以访问外网了。如果同时还希望外部网络可以访问租户虚拟机,那么就为虚拟机分配 Floating IP 地址即可。

  • 将租户网络 flat-subnet-1 接入第一层网关
openstack router add subnet router-ext flat-subnet-1
  • 创建 Floating IP
openstack floating ip create --project admin ext_net
  • 将 Floating IP 绑定到虚拟机 VM1
openstack server add floating ip VM1 172.18.22.242

网络变更

在这里插入图片描述

  • 租户网络添加了 Port network:router_interface。
    在这里插入图片描述
  • 在 Controller 的 br-int 上添加了相应的 Port qr-{router_interface_port_uuid},与租户网络具有相同的 Local VLAN tag:1。
[root@controller ~]# ovs-vsctl show
...
    Bridge br-int
    ...
        Port "qr-6b394d55-47"
            tag: 1
            Interface "qr-6b394d55-47"
                type: internal
  • qr-{router_interface_port_uuid} 的 Interface 设备创建在第一层网关的 Network Namespace 中,租户网络通过 qr-XXX 设备接入特定的 “路由空间”。
[root@controller ~]# ip netns exec qrouter-ccce705c-5b0f-4bf7-a82d-94f143a88d3d ifconfig
...
qg-424af937-c4: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 172.18.22.245  netmask 255.255.255.0  broadcast 172.18.22.255
        inet6 fd00:310:22:0:f816:3eff:fea0:276a  prefixlen 64  scopeid 0x0<global>
        inet6 fe80::f816:3eff:fea0:276a  prefixlen 64  scopeid 0x20<link>
        ether fa:16:3e:a0:27:6a  txqueuelen 1000  (Ethernet)
        RX packets 13279  bytes 680632 (664.6 KiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 75  bytes 5842 (5.7 KiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

qr-6b394d55-47: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 192.168.1.1  netmask 255.255.255.0  broadcast 192.168.1.255
        inet6 fe80::f816:3eff:feeb:f148  prefixlen 64  scopeid 0x20<link>
        ether fa:16:3e:eb:f1:48  txqueuelen 1000  (Ethernet)
        RX packets 4751  bytes 246730 (240.9 KiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 198  bytes 17998 (17.5 KiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0
  • 第一层网关的路由表项:
[root@controller ~]# ip netns exec qrouter-ccce705c-5b0f-4bf7-a82d-94f143a88d3d route -nne
Kernel IP routing table
Destination     Gateway         Genmask         Flags   MSS Window  irtt Iface
0.0.0.0         172.18.22.1     0.0.0.0         UG        0 0          0 qg-424af937-c4
172.18.22.0     0.0.0.0         255.255.255.0   U         0 0          0 qg-424af937-c4
192.168.1.0     0.0.0.0         255.255.255.0   U         0 0          0 qr-6b394d55-47
  1. 默认网关路由(UG),跳到第二层网关。
  2. 外部网络直连路由(U),转发至外部网络。
  3. 租户网络直连路由(U),转发至内部租户网络。
  • 命名空间中的 Floating IP NAT iptables 规则:
[root@controller ~]# ip netns exec qrouter-ccce705c-5b0f-4bf7-a82d-94f143a88d3d iptables -nvL -t nat

Chain neutron-l3-agent-OUTPUT (1 references)
 pkts bytes target     prot opt in     out     source               destination
    2   168 DNAT       all  --  *      *       0.0.0.0/0            172.18.22.242        to:192.168.1.3

Chain neutron-l3-agent-PREROUTING (1 references)
 pkts bytes target     prot opt in     out     source               destination
   16   960 REDIRECT   tcp  --  qr-+   *       0.0.0.0/0            169.254.169.254      tcp dpt:80 redir ports 9697
    2   168 DNAT       all  --  *      *       0.0.0.0/0            172.18.22.242        to:192.168.1.3

Chain neutron-l3-agent-float-snat (1 references)
 pkts bytes target     prot opt in     out     source               destination
    0     0 SNAT       all  --  *      *       192.168.1.3          0.0.0.0/0            to:172.18.22.242
  • 虚拟机可以访问外部网络:
    在这里插入图片描述
  • 外部网络可以通过 Floating IP 访问虚拟机:
╭─mickeyfan@localhost  ~
╰─$ ping 172.18.22.242
PING 172.18.22.242 (172.18.22.242): 56 data bytes
64 bytes from 172.18.22.242: icmp_seq=0 ttl=61 time=218.465 ms

在这里插入图片描述

问题: 外部网络无法 ping 通过 Floating IP,虚拟机也无法 ping 通外网。

TS:在第一次网关的 Network Namespace 中可以 ping 通第二层外部网关 IP 地址 172.18.22.1,也可以 ping 通过租户网络的网关 IP 地址 192.168.1.1。唯独 ping 不通虚拟机的 Fixed IP 地址 192.168.1.3,所以判断为虚拟机安全组规则导致的。

原因:需要为虚拟机配置安全组规则,允许 ICMP 访问。

外部网络访问原理

租户网络通过 qr-XXX 将外部网络访问流量输入第一层网关的路由空间,然后内核 TCP/IP 协议栈根据 qrouter-XXX 中的路由表项进行跨网络(租户网络 => 外部网络)路由。如果是外部网络访问,就将数据包交给 qg-XXX,根据为 qg-XXX 设置的 iptables NAT 规则对数据包进行网络地址转换(Floating IP 的实现原理)。再经由 br-int、br-ex 之间的 Patch Peer 将数据包发送给 OvS br-ex,最终由挂载到 br-ex 的物理网卡送出外部网络。