BGP 浮动 IP 跨 L2 分段网络

基本原理是 L2 连接将绑定到单个机架。机架交换机之外的所有内容都将使用 BGP 路由。为了执行 BGP 宣告,利用 neutron-dynamic-routing。

为此,在每个机架上,服务器都配置了不同的管理网络,每个机架使用一个 VLAN ID(下图中的浅绿色和橙色网络)。请注意,每个机架使用唯一的 VLAN ID 并非强制性的,也可以在所有机架上使用相同的 VLAN ID。这里的重点只是隔离 L2 分段(通常,每个机架交换机之间的路由将通过 BGP 完成,没有 L2 连接)。

../_images/bgp-floating-ip-over-l2-segmented-network.png

在 OpenStack 侧,必须设置一个提供商网络,该网络为每个机架使用不同的子网范围和 VLAN ID。这包括

  • 一个地址范围

  • 与命名物理网络关联的该网络的一些网络分段

  • 使用该地址范围的子网池

  • 每个分段一个提供商网络子网(每个子网+分段对匹配一个机架物理网络名称)

一个分段附加到特定的 VLAN 和物理网络名称。在上面的图中,提供商网络由 2 个子网表示:深绿色和红色。深绿色子网位于一个网络分段上,红色子网位于另一个网络分段上。这两个子网的子网服务类型都是“network:floatingip_agent_gateway”,因此它们不能直接由虚拟机使用。

在所有这些之上,添加一个没有分段的浮动 IP 子网,该子网跨越所有机架。此子网必须具有以下服务类型

  • network:routed

  • network:floatingip

  • network:router_gateway

由于 network:routed 子网未绑定到分段,因此它可以在所有机架上使用。由于 provider 网络使用服务类型 network:floatingip 和 network:router_gateway,因此该子网只能用于浮动 IP 和路由器网关,这意味着使用分段的子网将用作浮动 IP 网关(即:到达这些浮动 IP / 路由器外部网关的下一跳)。

配置 Neutron API 侧

在控制器侧(即 API 和 RPC 服务器),只需安装 Neutron Dynamic Routing Python 库(例如,在 Debian 的情况下,将是 neutron-dynamic-routing-common 和 python3-neutron-dynamic-routing 包)。除此之外,必须将“segments”和“bgp”添加到 service_plugins 列表中的插件中。例如在 neutron.conf 中

[DEFAULT]
service_plugins=router,metering,qos,trunk,segments,bgp

BGP 代理

必须安装 neutron-bgp-agent。最好在每个机架上的任何机器上安装两次(这并不重要)。然后,这些 BGP 代理中的每一个都将与一个交换机建立会话,并通告所有 BGP 配置。

设置与交换机的 BGP 对等

必须创建一个代表网络设备的对等体。然后,需要创建一个匹配的 BGP speaker。然后,BGP speaker 必须与一个 dynamic-routing-agent 关联(在我们的示例中,dynamic-routing agent 在 compute 1 和 4 上运行)。最后,将对等体添加到 BGP speaker,以便 speaker 向网络设备启动 BGP 会话。

$ # Create a BGP peer to represent the switch 1,
$ # which runs FRR on 10.1.0.253 with AS 64601
$ openstack bgp peer create \
      --peer-ip 10.1.0.253 \
      --remote-as 64601 \
      rack1-switch-1

$ # Create a BGP speaker on compute-1
$ BGP_SPEAKER_ID_COMPUTE_1=$(openstack bgp speaker create \
      --local-as 64999 --ip-version 4 mycloud-compute-1.example.com \
      --format value -c id)

$ # Get the agent ID of the dragent running on compute 1
$ BGP_AGENT_ID_COMPUTE_1=$(openstack network agent list \
      --host mycloud-compute-1.example.com --agent-type bgp \
      --format value -c ID)

$ # Add the BGP speaker to the dragent of compute 1
$ openstack bgp dragent add speaker \
      ${BGP_AGENT_ID_COMPUTE_1} ${BGP_SPEAKER_ID_COMPUTE_1}

$ # Add the BGP peer to the speaker of compute 1
$ openstack bgp speaker add peer \
      compute-1.example.com rack1-switch-1

$ # Tell the speaker not to advertize tenant networks
$ openstack bgp speaker set \
      --no-advertise-tenant-networks mycloud-compute-1.example.com

如果部署使用 bonding(以及交换机之间的 LACP),可以对同一机架上的第二个机器重复此操作,如上图所示。它也可以在每个机架上完成。一种部署方法是在每个机架中选择两台计算机(例如,一个计算节点和一个网络节点),并在它们上安装 neutron-dynamic-routing-agent,以便它们可以“与”机架的两个交换机通信。所有这些都取决于交换机侧的配置。您可能只需要与整个部署中的两个 ToR 机架通信。您必须知道的是,您可以部署尽可能多的 dynamic-routing agent,并且一个 agent 可以与单个设备通信。

设置物理网络名称

在设置提供商网络之前,必须在每个主机中根据机架名称设置物理网络名称。在计算或网络节点上,这在 /etc/neutron/plugins/ml2/openvswitch_agent.ini 中使用 bridge_mappings 指令完成

[ovs]
bridge_mappings = physnet-rack1:br-ex

以这种方式创建的所有物理网络也必须添加到 neutron-server 的配置中(即:neutron-api 和 neutron-rpc-server 都使用它)。例如,使用 3 个机架,/etc/neutron/plugins/ml2/ml2_conf.ini 的外观如下

[ml2_type_flat]
flat_networks = physnet-rack1,physnet-rack2,physnet-rack3

[ml2_type_vlan]
network_vlan_ranges = physnet-rack1,physnet-rack2,physnet-rack3

完成此操作后,可以使用 physnet-rack1 作为“物理网络”创建提供商网络。

设置提供商网络

提供商网络范围内的所有内容都将通过 BGP 进行通告。以下是如何创建网络范围

$ # Create the address scope
$ openstack address scope create --share --ip-version 4 provider-addr-scope

然后,可以使用上面设置的物理网络名称创建网络

$ # Create the provider network that spawns over all racks
$ openstack network create --external --share \
      --provider-physical-network physnet-rack1 \
      --provider-network-type vlan \
      --provider-segment 11 \
      provider-network

这会自动创建一个网络 AND 一个分段。虽然默认情况下,此分段没有名称,这不太方便。但是可以通过以下方式更改此名称

$ # Get the network ID:
$ PROVIDER_NETWORK_ID=$(openstack network show provider-network \
      --format value -c id)

$ # Get the segment ID:
$ FIRST_SEGMENT_ID=$(openstack network segment list \
      --format csv -c ID -c Network | \
      q -H -d, "SELECT ID FROM - WHERE Network='${PROVIDER_NETWORK_ID}'")

$ # Set the 1st segment name, matching the rack name
$ openstack network segment set --name segment-rack1 ${FIRST_SEGMENT_ID}

设置第二个分段

将附加到我们提供商网络的第二个分段是这样创建的

$ # Create the 2nd segment, matching the 2nd rack name
$ openstack network segment create \
      --physical-network physnet-rack2 \
      --network-type vlan \
      --segment 13 \
      --network provider-network \
      segment-rack2

设置用于 BGP 下一跳路由的提供商子网

这些子网将在不同的机架中使用,具体取决于机器中使用的物理网络。为了使用地址范围,必须使用子网池。以下是如何创建包含稍后在创建子网时使用的两个范围的子网池

$ # Create the provider subnet pool which includes all ranges for all racks
$ openstack subnet pool create \
      --pool-prefix 10.1.0.0/24 \
      --pool-prefix 10.2.0.0/24 \
      --address-scope provider-addr-scope \
      --share \
      provider-subnet-pool

然后,这样创建两个子网。在此示例中,我们将网关保留在 .1 中,DHCP 服务器保留在 .2 中,以及 .253 +.254,因为这些地址将由交换机用于 BGP 通告

$ # Create the subnet for the physnet-rack-1, using the segment-rack-1, and
$ # the subnet_service_type network:floatingip_agent_gateway
$ openstack subnet create \
      --service-type 'network:floatingip_agent_gateway' \
      --subnet-pool provider-subnet-pool \
      --subnet-range 10.1.0.0/24 \
      --allocation-pool start=10.1.0.3,end=10.1.0.252 \
      --gateway 10.1.0.1 \
      --network provider-network \
      --network-segment segment-rack1 \
      provider-subnet-rack1

$ # The same, for the 2nd rack
$ openstack subnet create \
      --service-type 'network:floatingip_agent_gateway' \
      --subnet-pool provider-subnet-pool \
      --subnet-range 10.2.0.0/24 \
      --allocation-pool start=10.2.0.3,end=10.2.0.252 \
      --gateway 10.2.0.1 \
      --network provider-network \
      --network-segment segment-rack2 \
      provider-subnet-rack2

请注意服务类型。network:floatingip_agent_gateway 确保这些子网仅用作网关(即:下一个 BGP 跳)。可以对每个新机架重复上述操作。

添加用于 VM 浮动 IP 和路由器网关的子网

每次需要为浮动 IP 和路由器网关创建新子网时,都需要这样做。首先,将范围添加到子网池中,然后创建子网本身

$ # Add a new prefix in the subnet pool for the floating IPs:
$ openstack subnet pool set \
      --pool-prefix 203.0.113.0/24 \
      provider-subnet-pool

$ # Create the floating IP subnet
$ openstack subnet create vm-fip \
      --service-type 'network:routed' \
      --service-type 'network:floatingip' \
      --service-type 'network:router_gateway' \
      --subnet-pool provider-subnet-pool \
      --subnet-range 203.0.113.0/24 \
      --network provider-network

服务类型 network:routed 确保我们使用通过提供商网络通告 IP 的 BGP。network:floatingip 和 network:router_gateway 将这些 IP 的使用限制为浮动 IP 和路由器网关。

设置 BGP 通告

需要将提供商网络添加到每个 BGP speaker。这意味着每次设置新机架时,都必须将提供商网络添加到该机架的 2 个 BGP speaker。

$ # Add the provider network to the BGP speakers.
$ openstack bgp speaker add network \
      mycloud-compute-1.example.com provider-network
$ openstack bgp speaker add network \
      mycloud-compute-4.example.com provider-network

在此示例中,我们选择了也在运行 neutron-dynamic-routing-agent 守护程序的两个计算节点。

每个项目操作

这可以由每个客户完成。子网池不是必需的,但很好用。通常,客户网络不会通过 BGP 通告(但如果需要可以这样做)。

$ # Create the tenant private network
$ openstack network create tenant-network

$ # Self-service network pool:
$ openstack subnet pool create \
      --pool-prefix 192.168.130.0/23 \
      --share \
      tenant-subnet-pool

$ # Self-service subnet:
$ openstack subnet create \
      --network tenant-network \
      --subnet-pool tenant-subnet-pool \
      --prefix-length 24 \
      tenant-subnet-1

$ # Create the router
$ openstack router create tenant-router

$ # Add the tenant subnet to the tenant router
$ openstack router add subnet \
      tenant-router tenant-subnet-1

$ # Set the router's default gateway. This will use one public IP.
$ openstack router set \
      --external-gateway provider-network tenant-router

$ # Create a first VM on the tenant subnet
$ openstack server create --image debian-10.5.0-openstack-amd64.qcow2 \
      --flavor cpu2-ram6-disk20 \
      --nic net-id=tenant-network \
      --key-name yubikey-zigo \
      test-server-1

$ # Eventually, add a floating IP
$ openstack floating ip create provider-network
+---------------------+--------------------------------------+
| Field               | Value                                |
+---------------------+--------------------------------------+
| created_at          | 2020-12-15T11:48:36Z                 |
| description         |                                      |
| dns_domain          | None                                 |
| dns_name            | None                                 |
| fixed_ip_address    | None                                 |
| floating_ip_address | 203.0.113.17                         |
| floating_network_id | 859f5302-7b22-4c50-92f8-1f71d6f3f3f4 |
| id                  | 01de252b-4b78-4198-bc28-1328393bf084 |
| name                | 203.0.113.17                         |
| port_details        | None                                 |
| port_id             | None                                 |
| project_id          | d71a5d98aef04386b57736a4ea4f3644     |
| qos_policy_id       | None                                 |
| revision_number     | 0                                    |
| router_id           | None                                 |
| status              | DOWN                                 |
| subnet_id           | None                                 |
| tags                | []                                   |
| updated_at          | 2020-12-15T11:48:36Z                 |
+---------------------+--------------------------------------+
$ openstack server add floating ip test-server-1 203.0.113.17

Cumulus 交换机配置

由于 Neutron 的工作方式,对于与 IP 地址关联的每个新端口,都会发出 GARP,以告知交换机新的 MAC / IP 关联。不幸的是,这会使交换机感到困惑,它们可能会认为应该使用本地 ARP 表来路由数据包,而不是将其交给下一跳进行路由。明确的解决方案是修补 Neutron,使其停止为具有 network:routed 服务类型的任何子网上的任何端口发送 GARP。这种修补程序很难编写,但幸运的是,有一个有效的修复程序(至少对于 Cumulus 交换机而言)。以下是如何操作。

在 /etc/network/switchd.conf 中,我们更改以下内容

# configure a route instead of a neighbor with the same ip/mask
#route.route_preferred_over_neigh = FALSE
route.route_preferred_over_neigh = TRUE

然后只需重新启动 switchd

systemctl restart switchd

这将重新启动交换机的 ASIC,因此在没有交换机冗余的情况下执行此操作可能很危险(因此在执行此操作时请小心)。如果每个机架都有 2 个交换机,则完全安全的程序如下

# save clagd priority
OLDPRIO=$(clagctl status | sed -r -n  's/.*Our.*Role: ([0-9]+) 0.*/\1/p')
# make sure that this switch is not the primary clag switch. otherwise the
# secondary switch will also shutdown all interfaces when loosing contact
# with the primary switch.
clagctl priority 16535

# tell neighbors to not route through this router
vtysh
vtysh# router bgp 64999
vtysh# bgp graceful-shutdown
vtysh# exit
systemctl restart switchd
clagctl priority $OLDPRIO

验证

如果一切顺利,浮动 IP 将通过提供商网络通过 BGP 进行通告。这是一个示例,其中在 2 个机架上部署了 4 个 VM。Neutron 在这里选择分段网络上的 IP 作为 Nexthop。

$ # Check the advertized routes:
$ openstack bgp speaker list advertised routes \
      mycloud-compute-4.example.com
+-----------------+-----------+
| Destination     | Nexthop   |
+-----------------+-----------+
| 203.0.113.17/32 | 10.1.0.48 |
| 203.0.113.20/32 | 10.1.0.65 |
| 203.0.113.40/32 | 10.2.0.23 |
| 203.0.113.55/32 | 10.2.0.35 |
+-----------------+-----------+