目錄
文章目錄
Neutron 的資源模型
Neutron 資源模型官方文檔:https://developer.openstack.org/api-ref/network/v2/index.html
Network
Network 是 Network Connectivity as s Service 的 “根” 操作對象,是 Neutron 對二層網絡的抽象,包含了用戶對「大二層」網絡的一切想象,支持 Local、 Flat、 VLAN、 VXLAN、 GRE、 Geneve 等多種網絡類型並不斷擴充。如果將 Network 映射到現實世界的話,它就相當於一個巨大的交換機:從介質的角度,它擁有許多端口;從網絡的角度,它划分廣播域;從功能的角度,它提供了 “隔離” 和 “轉發”。
在《Networking 基本術語/概念》一文中,我們已經記錄了關於大二層網絡的介紹,這里不再贅述。
Neutron 網絡概念的定義和類型花樣之多,令人眼花繚亂。我們姑且從幾個不同的方面對 “網絡” 做一個分類歸納,區分理解。
從網絡實現模型分層的角度看:
- 本地網絡(br-int)
- 租戶網絡(br-ethX/br-tun)
- 外部網絡(br-ex)
從網絡實現技術的角度看:
- 非隧道網絡(Local、Flat、VLAN)
- 隧道網絡(VxLAN、GRE、Geneve)
從網絡從屬關系的角度看:
- 運營商(物理)網絡
- 租戶網絡
從 Neutron 所追求的「多租戶隔離多平面網絡」實現來看,我們可以感受到每一個 Network 都應該具有以下 3 個核心要素,我將其稱之為 “核心三要素”:
- Network Type
- VID Range
- Physical Network Mapping
接下來的內容,主要就是圍繞這核心三要素展開的。
運營商網絡和租戶網絡
本章節我們要使用的就是第三種視角。所謂 “從屬” 即是 “屬於誰”:由 Neutron 創建的,屬於 Neutron 管理范疇的網絡,我們稱之為租戶網絡(Tenant Network);屬於運營商(e.g. OpenStack 平台的運營者)原有的,Neutron 無法管理、只能映射(記錄)的網絡,稱之為運營商網絡( Provider Network)。兩種網絡都采用了 Network 資源模型,從模型的角度來看,兩者的區別在於是否會持久化下列三個字段屬性值(運營商網絡具有,租戶網絡反之):
- provider:network_ type (string) — The type of physical network that this network is mapped to.
- provider:physical_network (string) — The physical network where this network is implemented.
- provider:segmentation_id (int) — The ID of the isolated segment on the physical network.
首先需要提出的問題是:為什么要區分租戶網絡和運營商網絡?這是因為單存的 Neutron(虛擬網絡)本身只是一座孤島,如果希望與外界取得聯系,就需要依賴於公共設施(e.g. 移動通信網絡、橋梁)的支持。雖然 Neutron 對這些公共設施沒有控制權,但卻可以使用(e.g. 購買手機號碼)它們,所以 Neutron 要創建一個 Network 資源對象來存儲這些(運營商網絡)信息。而存儲的地方就是上述的 3 個字段了。
舉例說明運營商網絡的應用場景:用戶希望 OpenStack 中的 VMs 與產品部(VLAN 10=100)的若干台個人電腦處於同一個二層網絡,那么用戶可以為這些 VMs 創建一個 VLAN ID=100 的運營商網絡 p-network1,並且填入下列字段的值。
- provider:network_ type ==> VLAN
- provider:physical_network ==> “產品部網絡”
- provider:segmentation_id ==> 100
NOTE:以上只是舉例,實際這三個字段的值並非如此。
需要注意的是,上述例子中無論是 VLAN 類型的網絡,還是 VLAN ID=100 都並非是 Neutron 創建的,而是運營商本來就已經存在的網絡,Neutron 只是創建了一個 Network 資源類型並記錄下這些運營商網絡的信息,繼而進一步使用這個運營商網絡的特性而已。
簡而言之,運營商網絡的作用就是為了讓 Neutron 內部的虛擬網絡可以與物理網絡連接起來。運營商網絡是運營商的某個物理網絡在 Neutron 上的延伸,Neutron 租戶無法管理這個物理網絡的生命周期。
創建運營商網絡
創建運營商網絡有兩個要點:一是只能有管理員創建,二是需要手動填寫 “核心三要素”。
上圖為通過 admin 創建一個 VLAN 類型的運營商網絡,填寫的三個表單就對應了 “核心三要素” 並存儲到數據庫中 networks 表的 provider:network_ type、provider:physical_network、provider:segmentation_id 字段中。“核心三要素” 中的 Network Type 和 VID Range 很好理解,兩者描述了運營商網絡的特征。但 Physical Network Mapping 的意義你或許會感到迷惑,其實它描述的是 Neutron Network 應該如何接入運營商實際的物理網絡。直白的說,就是 Neutron Network 要通過哪一張 “網卡” 來接入到實際的運營商網絡中。
但為什么上圖 “物理網絡” 表單項填寫的 “網卡” 名稱是 “public” 呢?其實 “public” 是 Neutron 實現的一種 Label 機制,本質是一個 Key-Value Mapping,將便於人類理解的 Label Name 與實際的 Physical Network 隱射起來,這就是所謂的 Physical Network Mapping。幾乎所有的物理網絡是使用 Label 的方式來標的。而這個 Physical Network Mapping 就定義在 Neutron OvS(本文以 OvS 為例)的配置文件中。e.g.
# /etc/neutron/plugins/ml2/openvswitch_agent.ini
bridge_mappings = physnet1:br-ethx1, physnet2:br-ethx2
那為什么 Lable “public” 對應的 Physical Network 是 “br-ethx1” 而不是一張具體的物理網卡名稱(e.g. eth0)呢?這個問題其實我們在 Neutron 的網絡實現模型已經提到過,因為 OvS Bridge 會將物理服務器上的物理網卡掛載到 OvS br-ethX(IP 地址也在此),所以 Physical Network 填入的是 br-ethX 而不是 eth0。
上圖為創建一個 VxLAN 類型的運營商網絡。奇怪的是為什么沒有了 “物理網絡” 這個表單項呢?這還是要說到 Physical Network Mapping(provider:physical_network)描述的是 Neutron Network 應該如何接入到實際的運營商網絡,由於 Flat、VLAN 等非隧道網絡類型本質是一個二層網絡,所以需要指定 “網卡” 才能讓二層的數據幀流入運營商網絡。但像 VxLAN、GRE 之類的隧道網絡類型的實現原理是基於三層 IP 協議的,所以隧道網絡能否與運營商網絡互通的關鍵是隧道外層封裝 IP 地址是否填寫正確,而非一張 “網卡”。
再次強調一下創建運營商網絡的要點:
- 只能由管理員創建
- 需要手動填寫 “核心三要素”,並持久化到數據庫中
- 非隧道網絡類型:需要傳入 Physical Network Mapping(provider:physical_network)字段值,描述 Neutron Network 接入運營商網絡的 “網卡(br-ethX)”。
- 隧道網絡類型:只需要保證節點上已經定義好了與運營商網絡對應的 Tunnel 端口 IP 地址並且三層網絡通信無障礙即可。
上圖為創建一個 Flat 類型的運營商網絡。Flat(扁平)類型網絡是沒有 VID 的,不想 VLAN 類型在網絡包從 Bridge 發出前還是打 VLAN tag 並進行內外 VID 轉換。Flat 的網絡包就是一個 Untag 網絡包,直接接入物理網絡,只需要告訴它用哪一張 “網卡(物理網絡)” 即可。
創建租戶網絡
上圖為創建一個租戶網絡,可見並沒有填入 “核心三要素” 的表單項。這是因為 Neutron 認為:對於租戶創建自己的網絡而言,租戶希望得到的只是一個二層網絡,至於這個二層網絡是由 VLAN 提供的或由 VxLAN 提供的並無所謂,VID 是多少也並無所謂,Physical Network 是怎么 Mapping 的就更無所謂了。
一言以蔽之,租戶無需關心這個 Network(二層網絡)的底層實現細節。Neutron 只有做到了這一點,才能稱之為 “服務”!這不禁讓我想起柯達公司的一句經典廣告語 —— 你只管快門,其余我來!
但話又說回來,租戶自然是不必操心這些細節,但雲平台管理員可不行。雲平台管理員是可以通過 Neutron 的配置文件來聲明定義 “核心三要素” 的。e.g.
[ml2]
...
tenant_network_types = vlan,vxlan
mechanism_drivers = openvswitch,linuxbridge
[securitygroup]
firewall_driver = openvswitch
[ovs]
datapath_type = system
bridge_mappings = public:br-ex,
tunnel_bridge = br-tun
local_ip = 172.18.22.200
[ml2_type_flat]
flat_networks = public,
[ml2_type_vlan]
network_vlan_ranges = public:3001:4000,
[ml2_type_geneve]
vni_ranges = 1:1000
[ml2_type_gre]
tunnel_id_ranges = 1:1000
[ml2_type_vxlan]
vni_ranges = 1:1000
所以,就租戶網絡而言,其 “核心三要素” 是不需要持久化到數據庫中的,也就不具有 provider:network_ type、provider:physical_network、provider:segmentation_id 這三個字段值了。再一個就是從配置中可以看出,只有非隧道類型是需要填寫 “物理網絡” Lable 的。
創建外部網絡
創建外部網絡(能夠訪問公網的網絡)屬於運營商網絡的另一種應用場景。
實際上所謂的外部網絡和運營商網絡的底層實現並無區別,不同之處在於外部網絡具有分配 Floating IP 的功能,即外部網絡在網絡節點的 qrouter-XXX namespace 中配置的 iptables 的 NAT(SNAT/DNAT)功能,使得內部虛擬機得以訪問到外部公網。
Network 小結
Network 是 Neutron 的二層網絡資源模型,對外提供二層網絡的服務接口(創建、刪除二層網絡)。租戶可以通過這個接口來創建專屬於自己的租戶網絡,管理員可以通過這個接口來創建(對接)Neutron 所無法管理的運營商的物理網絡與外部網絡。
從資源的角度來看,租戶網絡和運營商網絡的主要區別在於「可控性」:租戶網絡是 Neutron 完全可控的,基於 Neutron 網絡實現模型支撐的多租戶隔離多平面網絡需求,租戶網絡的 “核心三要素” 由雲管人員通過 Neutron 的 ML2 配置文件定義;而運營商網絡卻是 Neutron 所無法管理的運營商的物理網絡在 Neutron 上的一種延伸,其 “核心三要素” 只是對運營商物理網絡信息的一個記錄,並無管理性質。
運營商網絡的兩種典型應用場景:一是打通 Neutron Network 與運營商內部的物理網絡,使得兩者之間的虛擬機可以互相通信;再一個是打通 Neutron Network 與 Internet,使得 Neutron Network 中的虛擬機可以訪問公網。第二種應用場景也就是 Neutron 的 Floating IP 功能,是通過網絡節點上 qrouter-XXX 中的 iptables NAT 實現的。
Subnet
Subnet 下屬於 Network,是 Neutron 對三層子網的抽象,它作為一個具體網段(CIDR)的 IP 地址池,同時為使用接入到該子網的 VMs 提供 IP 核心網絡服務,還負責保證三層網絡之間的路由互通。在通常情況下,Subnet 符合人們對 “子網” 的常規理解。
- IP VERSION
- IPADDR
- NETMASK
- GATEWAY
- ROUTE
- DNS
- DHCP
- IPAM
NOTE:在某些特定的應用場景中(e.g. Multi-Segments),Subnet 被賦予了更加深厚的含義,這里我們暫且不談。
IP 核心網絡服務
IP 核心網絡服務(IP CoreNetwork Services),又稱 DDI(DNS、DHCP、IPAM)服務。
- DNS — Domain Name System,域名系統
- DHCP — Dynamic Host Configuration Protocol,動態主機設置協議
- IPAM — IP Address Manager,IP 地址管理
DNS 和 DHCP 相信大家不會陌生,這里介紹一下 IPAM 服務。IPAM 用於發現、監視、審核和管理企業網絡上使用的 IP 地址空間,IPAM 還可以對運行的 DHCP 和 DNS 服務器進行管理和監視。簡而言之,IPAM 就是一種 IP 地址的管理手段,目的是讓 IP 地址的分配、使用更加便利。
Subnet 資源模型中與 DDI 服務相關的字段:
enable_dhcp:布爾類型,表示是否為 Subnet 啟用 DHCP 服務,若啟用,再會在 qdhcp-XXX namesapce 中啟動 dnsmasq 服務進程。
allocation_pools:是一個數組,表示 DHCP 服務可分配的 IP 地址池列表,每個地址池的格式為 [start IP, end IP]
。若沒有配置則以 Subnet 的 cidr 作為地址池。
dns_nameservers:是一個數組,用於指定一批 DNS Server 的地址。這里僅記錄地址,實際的 DNS 服務並不由 Neutron 提供。
subnetpool_id:是 tables subnetpools 的外鍵,指向 SubnetPools 資源模型。
NOTE:SubnetPools 與 allocation_pools 是兩個 “同類不同源” 的功能。兩者均為 IP 地址的資源池,而前者是對后者的一種優化,在 Kilo 版本引入,為了更好的管理子網網絡資源池(e.g. 提供訪問接口)。
小結一下,IP 核心網絡服務(DNS、DHCP、IPAM)是 Subnet 資源模型提供的服務,而服務的對象是使用該 Subnet 的 VMs。可見,Subnet 不僅僅是一個抽象資源接口,其具有一定的管理功能。這一點非常重要,因為我們在啟動虛擬機時,時常選擇的是一個 Network 而非 Subnet。e.g.
$ openstack server create -h
...
[--nic <net-id=net-uuid,v4-fixed-ip=ip-addr,v6-fixed-ip=ip-addr,port-id=port-uuid,auto,none>]
這會讓我們產生一種錯覺,虛擬機網絡來自於 Network,從表面看並沒有問題,也是 Neutron 有意為之(對用戶隱藏復雜細節的表現)。但對於開發者而言,如果帶着這種認知去閱讀源碼就難免會產生疑惑,IP 核心網絡服務的代碼邏輯處理對象實際是 Subnet 而非 Network,這一點完全契合 Subnet 的資源模型設計。所以再次強調,Network 是 NaaS 的根操作對象,表示一個二層網絡;而 Subnet 下屬於 Network,為 Network 中的 VMs 提供 IP 核心服務。
SubnetPools 資源模型
你是否考慮過,用戶真的需要關心一個 Subnet 的網段是 192.X、還是 172.X、還是 10.X 嗎?用戶要關心的僅僅是這個 Subnet 可以提供多少個 IP 地址而已!出於這樣的需求,Network 引入了 SubnetPools 資源模型,通過 subnetpool_id 字段與 Subnet 關聯。SubnetPools 和 Sunbet 的關系就相當於:前者定義了一個大的(不重復的)網段,后者從中分配了一個小的網段。e.g.
$ openstack subnet create -h
...
[--subnet-pool <subnet-pool> | --use-prefix-delegation USE_PREFIX_DELEGATION | --use-default-subnet-pool]
NOTE:在較新的版本(Rocky)中,配合 OpenStack Placement Project,SubnetPools 還可以被一個第三方的網絡軟件提供的外部 IP 資源池代替。
# 記錄了一個 SubnetPool 的子網網段划分規則
MariaDB [neutron]> select * from subnetpools\G;
*************************** 1. row ***************************
project_id: 031cec3e2df143259d302aa1993fd410
id: 5bbd5cab-c6a0-4851-b2d7-1137742e6adf
name: shared-default-subnetpool-v4
ip_version: 4
default_prefixlen: 26
min_prefixlen: 8
max_prefixlen: 32
shared: 1
default_quota: NULL
hash: 27e49575-31f7-4012-b57b-55c990cb6d82
address_scope_id: NULL
is_default: 1
standard_attr_id: 1
# 記錄了一個 SubnetPool 的子網網段信息
MariaDB [neutron]> select * from subnetpoolprefixes;
+----------------+--------------------------------------+
| cidr | subnetpool_id |
+----------------+--------------------------------------+
| 192.168.1.0/24 | 5bbd5cab-c6a0-4851-b2d7-1137742e6adf |
+----------------+--------------------------------------+
Multi-Segments
在上文中我們反復提到,Network 是一個二層網絡的抽象,Subnet 是一個三層子網的抽象。這是大多數 SDN 軟件設計的常規定義,創建一個 Network 就相當於獲得了一個廣播域,創建一個 Subnet 就相當於獲得了一個三層 IP 地址池。對用戶的理解非常友好。
不過在實際的生產需求中 Neutron 面臨一個問題:如果一個 Network 內的 VMs/Hosts 數量太多,就會出現諸如二層廣播風暴、網絡內交換機 ARP 表項容量不足等情況。簡單來說,這就是 Network 作為雲計算平台中的大二層網絡卻無法支撐起 “無限” 多個 VMs 的問題。為了滿足這一需求 Neutron 引入了 Multi-Segments 設計。
Multi-Segments 解決這一問題的辦法就是將 Network 的定義進一步升級為 “多個二層網絡的容器”,同時也將 Subnet 的定義進一步升級為 “一個二層網絡以及基於此的三層子網”,Subnet 之間通過三層路由互通。
Multi-Segments 顧名思義是一個 Network 具有多個 Segments,而一個 Segment 其實是一個運營商網絡,這一點可以從 Segment 資源模型看出(每條 Segment 記錄都包含一個運營商網絡的 “核心三要素”)。
顯然,天下沒有免費的午餐,Multi-Segments 的設計實際上具有非常大的局限性 —— 需要規划整個物理、虛擬網絡拓撲。它是一種通過 “物理手段” 來完成的 “Network 升級”。比如:Routed Network 應用場景。
所謂 Routed Network,就是一個 Network 內的 Subnet(對應運營商的物理網絡)之間使用物理路由器連通,Subnet 的 gateway_ip 指向物理路由器直連網口的 IP 地址。在現實機房中,一個機櫃接入物理路由器的網口基本固定,網口的 IP 地址也不會輕易改動,這意味着 Subnet 的三層子網需要與該物理路由器網口的 IP 處於同一個網段,否則無法通信。也就是說,使用 Routed Network 要求管理員提前做好物理、虛擬網絡、每個機櫃的網關 IP、DHCP Server 的全盤規划。
Multi-Segments 的應用場景除了 Routed Network 還有「VTEP 位於 TOR 交換機上」這一場景,暫不作介紹。可以感受到 Multi-Segments 的應用大多需要結合實際的物理網絡拓撲對機房網絡進行全盤規划,雖然都並非是一種通用的方案,但 Multi-Segments 的存在也的確讓 Neutron 落地物理機房多了一些更加靈活的配置選項。不過,很可惜的是,至今為止筆者依然沒有碰見過在生產環境中落地的案例,有時候難免讓人懷疑 Multi-Segments 存在的必要性,這是一個大家都對其 “諱莫如深” 的話題。在本章節中提出也只是為了作一個了解而已。
Routed provider networks 官方示例文檔:https://docs.openstack.org/newton/networking-guide/config-routed-networks.html
NOTE:有趣的是,經過上述文檔的實驗發現 Multi-Segments 無法實現預期的 Segments(Subnets)之間的二層隔離,這是因為 VMs 雖然在不同 Segments(Subnets)上,但 VMs 連接到同一個(計算節點的)OvS br-int 上的 qvo-XXX 卻具有相同的 Tag ID,自然就無法進行隔離了。由於 Multi-Segments 的場景實在少見,筆者也就不再深究了,只是做出提醒,了解就好。
創建 Subnet
創建 Subnet 可以手動輸入你預期的網段,也可以應用 SubnetPools 資源模型,直接選擇一個 IP 地址資源池。顯然后者要來的更加簡便一些,只需要通過 “網絡掩碼” 表單來描述你想要的 IP 地址數量即可(e.g. NETMASK=24 即 2**8 == 256 個 IP 地址)。
創建 Subnet 時你還可以 “手動輸入網絡地址”,該表單對應 Subnet 資源模型的 allocation_pools 屬性,所以 “手動輸入網絡地址” 是無法和 SubnetPools 共存的,只有通過手動指定網段的方式才可以使用。“主機路由” 對應 host_routes 屬性,格式正如上文所說:[目標網絡, 下一跳 IP 地址]
。“DNS 服務器” 對應 dns_nameservers 屬性,是一個數值類型,填入 DNS Server 的 IP 地址。
NOTE:同一個 Network 不可以同時具有從資源池中分配(SubnetPools)和手動輸入網絡地址(allocation_pools)兩種類型的 Subnet — Subnets hosted on the same network must be allocated from the same subnet pool.
Network 與 Subnet 的一對多關系
Network 和 Subnet 是一對多的關系,這跟應用了 Multi-Segments 與否無關。Network 下屬的 Subnets 可以有不同的 IP 網段(CIDR)且不能重疊,但不同 Networks 的 Subnets 之間可以具有相同的 CIDR。這得益於 Neutron L3 Router 應用了 qrouter-XXX network namespace,解決了 Networks 之間 CIDR 沖突的問題。對於不同 Networks 的 Subnets 之間具有相同 CIDR 時,會有以下兩種情況:
- 若兩個 Subnets 通過同一個 Router 路由,根據 Router 的配置,只有指定的一個 Subnet 可被路由。
- 若兩個 Subnets 通過不同的 Router 路由,因為 Router 的路由表隔離,所以兩個 Subnets 都可以被路由。
我們知道常規的 Network 就是二層廣播域,那么你是否想過,為什么 Network 與 Subnet 的聚合關系被設計成一對多?為什么 Neutron 允許在同一個二層廣播域之上可以配置多個不同的子網?其實這並非出於什么特殊的意圖,只是一對多的關系使得 Neutron 的網絡應用更加靈活。通常情況下,用戶大概率只會在一個 Network 下創建一個 Subnet,讓它們盡可能維護一個 “表面上” 的一比一關系。但總有特殊的情況,例如:一個 Subnet 的 IP 地址用完了;例如:我要啟用 Multi-Segments。無論從操作層面還是從架構設計層面上看,Network 與 Subnet 的一對多關系都讓 Neutron 變得更加靈活。
那么選定一個具有多個 Subnets 的 Network 來啟動一個虛擬機時 Neutron 會怎么處理呢(Nova 不支持選定 Subnet 來啟動虛擬機)?Neutron 會按照 Subnets 的順序選定第一個可用的 Subnet 來給虛擬機使用,知道該 Subnet 的 IP 被分配完畢為止。由此可以感受到,如果不是有特殊需求,還是盡量讓 Network 與 Subnet 維持 “表面上” 的一比一關系吧。
NOTE:雖然 Network 下屬有多個 Subnet,但為整個 Network 服務的 DHCP 仍然只有一個。e.g.
Port
Network 是二層網絡的抽象,Subnet 是三層子網的抽象,Port 就是網卡(NIC)的抽象,是連接 Nova 虛擬機與 Neutron Subnet 的橋梁,也是 Subnet 接入 L3 Router 的橋梁。
直白的說,Port 就是一個虛擬網卡(vNIC),Port 的關鍵屬性就是 IP 地址和 MAC 地址。虛擬機需要綁定 Port,路由器也要綁定 Port。
Port 與 Subnet 一樣下屬於 Network,Port 與 Subnet 的關系是水平的,為多對多。Port 的 IP 地址來源於 Subnet,也就是說一個 Port 可以具有多個 IP 地址。不過通常情況下,Port 有且只有一個 MAC 地址,對應 Port 資源模型的 mac_address 屬性。
Port 與 Network、Subnet 的關系如下圖:
Neutron 安全組(Security Group)
Neutron 提供了 Security Group 和 FWaaS 兩種安全機制,前者的作用域是單個虛擬機或 Port,后者的作用域是一個具體的網絡。而 Security Group 又分為 Nova Security Group 和 Neutron Security Group,這里我們主要討論的是 Neutron(Port)Security Group。
當用戶將一個 Port 掛載到虛擬機時,底層邏輯會在該虛擬機所處的計算節點上創建一個 Tap 設備並將 Port 的特征信息隱射到這個 Tap 設備,從而實現了為虛擬機添上一張虛擬網卡(vNIC)。我們在 Neutron 網絡實現模型的章節中提到過,虛擬機的 vNIC(Tap 設備)並非是直連到 OvS br-int(綜合網橋)的,之間還存在一個安全網橋(Linux Bridge qbr-XXX)層。e.g.
如上圖,Linux Bridge qbr-XXX 就是 Neutron Security Group 的底層支撐。當用戶為指定 Port 設定一系列 Security Group Rules 時,Neutron 實際上是通過 CLI 方式調用操作系統的 iptables 指令為 qbr-xxx 上的 Tap 設備配置了相應的 iptables rules。這就是所謂的 “安全層”。
那么問題來了:為什么不能直接在 OvS br-int 上為虛擬機 Port(Tap 設備)設定安全組規則?非得莫名的增加一個安全層?這是因為 OvS Bridge 本身不支持對連接到其自身的 Tap 設備使用 iptables。
直到,OvS 2.5 提出了 “基於流表” 的 Security Group 特性,它應用 Linux 內核的 conntrack(CT)模塊實現了對網絡連接狀態識別和處理。OpenStack 則從 M 版開始應用 OvS 這一新特性,支持 openvswitch securitygroup driver,支持使用 OvS 來實現 “有狀態防火牆” 功能。可以通過修改 Neutron 的 ML2 配置文件來選擇 Neutron Security Group 的驅動類型。
# /etc/neutron/plugins/ml2/ml2_conf.ini
[securitygroup]
firewall_driver = openvswitch
當然了,如果選擇了 openvswitch securitygroup driver,那么 Neutron 的網絡實現模型就不再需要 qbr-XXX 安全層了。相應了少了一層轉發之后,網絡性能也會有所提高,尤其在安全組規則數量巨大的時候。
關於 OvS Security Group 的實現細節,這里暫不作過多討論,感興趣的小伙伴可以瀏覽《OVS實現安全組,你需要知道這些!》
可用地址對(Allowed address pairs)
Neutron Security Group 不僅僅只會一味的按照安全組規則來對虛擬機進行保護,Security Group 本身的存在(啟用)就已經設定了一些安全策略,例如:為了防止虛擬機被 ARP Spoofing(ARP 欺詐)、 DHCP Spoofing 的 Port IP/MAC 地址綁定安全策略。由此,默認情況下,一個 Port 的 IP 地址和 MAC 地址是一一對應且綁定,也就是說對這個 Port 的 IP 地址進行添加、修改、刪除都會被 Neutron Security Group 判定為非法行為。這也是之所以在 Neutron 網絡環境中無法直接使用 Keepalived 來做虛擬機高可用的原因,這是一個無處安放的 VIP 的問題。
NOTE:ARP 反欺詐有很多方案,將 IP/MAC 地址一一綁定是最直接簡單的辦法。
但就像上文提到的 Keepalived 例子,一個 Port 具有多個 IP 地址的情況總是存在需求的,Port 資源模型的 fixed_ips 屬性就被設計為一個數組類型(Port 可具有多個 IP 地址,1 個 MAC 地址)。針對這樣的場景一般有兩個辦法:關閉 Port Security(port_security_enabled,不建議使用)或使用 Allowed address pairs(allowed_address_pairs)機制。
每個 Port 都具有自己的 allowed_address_pairs 數組,它記錄了若干對合法的 IP/MAC 地址映射,以此來支持允許多個 IP 與 Port 連通,從而滿足一個 Port 具有多個 IP 地址的需求。
更多可用地址對的詳情請瀏覽官方介紹:https://docs.openstack.org/developer/dragonflow/specs/allowed_address_pairs.html
創建一個 Port
NOTE:Port 是依托於 Network 的,所以首先需要創建一個 Network。
上圖創建了一個簡單的 Port:
- Port 的 Fixed IP 地址會從 Subnet1 的 IP 地址池中隨機分配,對應 fixed_ips 屬性。
- 開啟了 Port Security,現在 Port 的 IP/MAC 地址一一綁定,對應 port_security_enabled 屬性。
- VNIC 類型為正常(normal),對應 binding:vnic_type 屬性。
- 沒有指定 “設備 ID(device_id)” 和 “設備所屬者(device_owner)”,所以現在設備的 binding_vif_type 狀態為 unbound。binding:vif_type 除了用來標識 Port 的綁定狀態(unbound,bound_failed)之外,還用於標識這個 Port 的 Mechanism 類型(e.g. ovs、bridge、macvtap)。
- 在創建 Port 的同時可以指定這個 Port 的安全組規則
[root@localhost ~]# openstack port show Port1
+-------------------------+-----------------------------------------------------------------------------+
| Field | Value |
+-------------------------+-----------------------------------------------------------------------------+
| admin_state_up | UP |
| allowed_address_pairs | |
| binding_host_id | |
| binding_profile | |
| binding_vif_details | |
| binding_vif_type | unbound |
| binding_vnic_type | normal |
| created_at | 2019-03-08T03:17:23Z |
| data_plane_status | None |
| description | |
| device_id | |
| device_owner | |
| dns_assignment | None |
| dns_domain | None |
| dns_name | None |
| extra_dhcp_opts | |
| fixed_ips | ip_address='192.168.1.27', subnet_id='96f33568-70cd-47a2-a0b5-a32a853caa11' |
| id | 07995f4e-b6b2-493f-9ce5-b1d945a13807 |
| location | None |
| mac_address | fa:16:3e:0f:ff:74 |
| name | Port1 |
| network_id | e28bd712-352f-439d-88ea-35a994a4a765 |
| port_security_enabled | True |
| project_id | 031cec3e2df143259d302aa1993fd410 |
| propagate_uplink_status | None |
| qos_policy_id | None |
| resource_request | None |
| revision_number | 1 |
| security_group_ids | 12519ba0-d1f1-46e3-a0b2-48e6639ee8ad |
| status | DOWN |
| tags | |
| trunk_details | None |
| updated_at | 2019-03-08T03:17:23Z |
+-------------------------+-----------------------------------------------------------------------------+
這里強調一下 “設備 ID(device_id)” 和 “設備所屬者(device_owner)” 兩個字段,它們共同標識了 Port 的綁定實體。比如:Port 綁定到了一台虛擬機,那么 device_id=<instance_uuid>
,device_owner=compute:nova
。常見的 device_owner 還有:
- network:dhcp — Neutron DHCP Agent 使用的端口,為 Network 提供 DHCP 服務
- network:router_interface — Neutron L3 Router Agent 使用的端口,將 Subnet 接入路由器
- network:router_gateway — Neutron L3 Router Agent 使用的接口,外部網絡接入路由器的網管接口
還需要強調一下的是 Port 的類型,Neutron 支持多種類型的 Port,不同類型的 Port 底層可能由不同的 Agent 實現(e.g. ovs-agent、sriov-agent)。
只需要提供用戶預期的 IP/MAC 地址,即可為 Port 添加可用地址對。在可用地址對清單中的 IP/MAC 都可以通過自動或手動的方式應用到這個 Port 所對應的 vNIC(Tap 設備)上。
還需要注意一點,fixe_ips 屬性是一個數值類型,就是說加入一個 Network 下屬具有多個 Subnet,那么這個 Port 就可以從多個 Subnets 中獲取多個 IP 地址。e.g.
[root@localhost ~]# openstack port create --network Net2 --fixed-ip subnet=Subnet2-1 --fixed-ip subnet=Subnet2-2 --enable-port-security Port2-1
+-------------------------+--------------------------------------------------------------------------------+
| Field | Value |
+-------------------------+--------------------------------------------------------------------------------+
| admin_state_up | UP |
| allowed_address_pairs | |
| binding_host_id | |
| binding_profile | |
| binding_vif_details | |
| binding_vif_type | unbound |
| binding_vnic_type | normal |
| created_at | 2019-03-08T04:21:56Z |
| data_plane_status | None |
| description | |
| device_id | |
| device_owner | |
| dns_assignment | None |
| dns_domain | None |
| dns_name | None |
| extra_dhcp_opts | |
| fixed_ips | ip_address='172.16.100.38', subnet_id='0f15d289-26f2-4c83-9538-fae158bf3153' |
| | ip_address='192.168.100.125', subnet_id='7a2fa4b5-c8ca-48e9-94fb-c74f5e59510f' |
| id | 51383a86-56b5-4907-8bd2-80801351fc1b |
| location | None |
| mac_address | fa:16:3e:df:03:8c |
| name | Port2-1 |
| network_id | 4472e95b-f2e1-4ff5-8bce-429e1997f3cf |
| port_security_enabled | True |
| project_id | 031cec3e2df143259d302aa1993fd410 |
| propagate_uplink_status | None |
| qos_policy_id | None |
| resource_request | None |
| revision_number | 1 |
| security_group_ids | 12519ba0-d1f1-46e3-a0b2-48e6639ee8ad |
| status | DOWN |
| tags | |
| trunk_details | None |
| updated_at | 2019-03-08T04:21:57Z |
+-------------------------+--------------------------------------------------------------------------------+
掛載一個 Port
使用 Port 來啟動一個虛擬機:
openstack server create --flavor cirros256 --image cirros-0.3.4-x86_64-disk --port Port1 VM1
需要注意的是,即便 Port 具有多個可用地址對,但虛擬機網卡原生的 IP/MAC 依舊是 Port 的原生 fixed_ips/mac_address。可用地址對的含義是可以被 Port 使用的 IP/MAC,而非一定會被使用的 IP/MAC。
Port 掛載到虛擬機之后,其自身的信息也會被更新:
[root@localhost ~]# openstack port show Port1
+-------------------------+-------------------------------------------------------------------------------------------+
| Field | Value |
+-------------------------+-------------------------------------------------------------------------------------------+
| admin_state_up | UP |
| allowed_address_pairs | ip_address='192.168.1.28', mac_address='fa:16:3e:0f:ff:28' |
| | ip_address='192.168.1.29', mac_address='fa:16:3e:0f:ff:29' |
| binding_host_id | localhost.localdomain |
| binding_profile | |
| binding_vif_details | bridge_name='br-int', datapath_type='system', ovs_hybrid_plug='False', port_filter='True' |
| binding_vif_type | ovs |
| binding_vnic_type | normal |
| created_at | 2019-03-08T03:17:23Z |
| data_plane_status | None |
| description | |
| device_id | 6da255cb-402a-4273-844f-5aad549d65e7 |
| device_owner | compute:nova |
| dns_assignment | None |
| dns_domain | None |
| dns_name | None |
| extra_dhcp_opts | |
| fixed_ips | ip_address='192.168.1.27', subnet_id='96f33568-70cd-47a2-a0b5-a32a853caa11' |
| id | 07995f4e-b6b2-493f-9ce5-b1d945a13807 |
| location | None |
| mac_address | fa:16:3e:0f:ff:74 |
| name | Port1 |
| network_id | e28bd712-352f-439d-88ea-35a994a4a765 |
| port_security_enabled | True |
| project_id | 031cec3e2df143259d302aa1993fd410 |
| propagate_uplink_status | None |
| qos_policy_id | None |
| resource_request | None |
| revision_number | 6 |
| security_group_ids | 12519ba0-d1f1-46e3-a0b2-48e6639ee8ad |
| status | ACTIVE |
| tags | |
| trunk_details | None |
| updated_at | 2019-03-08T03:51:05Z |
+-------------------------+-------------------------------------------------------------------------------------------+
- binding_vif_type — 從 unbound 變成 ovs,表示底層使用的是 OvS Mechanism Driver。
- binding_host_id — Port 對應的 Tap 設備所在的主機,也就是 VM 所在的計算節點。
- binding_vif_details — 記錄了一些 Port 掛載后的信息,例如 Tap 設備加入的 OvS Bridge 名稱,OvS Bridge 的類型。
- device_id & device_owner — 指定綁定 Port 的實體對象(虛擬機)和類型(compute:nova)。
NOTE:從上述信息可以看出,Port 對應的 Tap 設備實際上實在 “綁定” 之后再創建的,Tap 設備的命名規則為 tap[port-uuid]
。e.g.
[root@localhost ~]# ovs-vsctl show
ccb13b70-cffb-4a64-97b1-734bf9040abc
Manager "ptcp:6640:127.0.0.1"
is_connected: true
...
Bridge br-int
Controller "tcp:127.0.0.1:6633"
is_connected: true
fail_mode: secure
...
Port "tap07995f4e-b6"
tag: 5
Interface "tap07995f4e-b6"
...
ovs_version: "2.9.0"
使用具有多個 fixed_ips 的 Port 來啟動一個虛擬機:
[root@localhost ~]# openstack server create --flavor cirros256 --image cirros-0.3.4-x86_64-disk --port Port2-1 VM2
奇怪為什么 Port 和虛擬機明明有兩個 IP 地址,但 GuestOS 起來后卻只看見了一個?其實虛擬機啟動獲取 IP 地址時是隨機挑選一個的,不會把 Port 的所有 IP 都配置上去,當然你也可以手動的將兩個 IP 地址配置上去。總的來說這種做法和應用可用地址對的效果差不多,如果你只是單純的希望虛擬機具有多個 IP,還是建議你使用可用地址對。但如果你希望虛擬機可以實現跨網段中斷,那么就可以使用具有多個 Subnet IP 的 Port。
Router
Router 是 Neutron 實現的路由器抽象。
需要注意的是,Router 只是 Neutron 對 Linux 服務器路由功能的抽象,而非 Neutron 實現了一個完整的 vRouter 軟件。這之間有着巨大的區別。Neutron Router 對 Linux 路由功能進行了封裝,並由 L3 Agent 服務進程負責處理。通常的,L3 Agent 只運行在網絡節點中,所以所有的跨網段訪問流量、公網訪問流量都會流經網絡節點。由此,網絡節點成為了 Neutron 三層網絡的性能瓶頸,為了解決這個問題,Neutron 提出了 DVR(分布式路由器)機制,讓每個計算節點都具有三層網絡功能。
簡而言之,對於上層應用而言,只需要關注 Router 的 端口、網關、路由表 即可。
NOTE:關於 Linux 路由功能在《Linux 的路由功能》一文中已經介紹過,這里不再贅述。
外部網關
Neutron 語義環境中的 “外部網關” 有兩重含義:
- 實際的外部網關(下文稱為物理外部網關),在 Neutron 的管理范疇之外。如下圖的 Router_2 上的 Port2。
- Neutron 的外部網關(下文成為 Neutron 外部網關,以作區別),在 Neutron 的管理范疇之內。如下圖 Router_1 上的 Port1。
我們思考一個問題:為什么 Neutron 內部網絡需要通過 Router_1 再與 Router_2 進行連接?而不將內部網絡直連 Router_2 然后訪問公網呢?首先明確一點,下圖中 “Neutron 管理的內部網絡” 指的是租戶網絡而非運營商網絡。運營商網絡當然可以映射到一個直連物理外部網關的物理網絡,這是因為雲管人員手動的為運營商網絡提供了物理網絡的 “核心三要素”。同理,如果租戶網絡希望直連到物理外部網關,那么就需要雲管人員提供連接到物理外部網關的 “核心要素(e.g. 外部網關的 IP 地址)”。但是,讓雲管人員為每個租戶網絡都手動的 “填入”(實際上並非填入,而是獲取) “核心要素” 顯然是不明智的,再者物理路由器很可能沒有足夠的接口數量供租戶網絡使用。
出於這樣的前提,Neutron 在租戶網絡和外部網關之間引入了一個內部路由層。每個租戶都可以創建專屬的內部路由層,包含若干個 L3 Router 實例對象。租戶可以隨意的將租戶網絡接入內部路由層,然后再由內部路由層統一對接物理外部網關。這樣物理路由器的端口就能得到更高效的利用。
NOTE:創建多個 L3 Router 實例對象並非是說需要多個 Linux 服務器,Neutron 通過 qrouter-XXX network namespace 在網絡節點上隔離出了多個 “虛擬路由器”。每創建一個 Router,運行在網絡節點的 L3 Agent 就會新建一個 qrouter-XXX。由於 qrouter-XXX 具有自己獨立的路由表,所以同一租戶下不同的 Network 之間允許具有相同 IP 網段(CIDR)的 Subnet,只要這些 Subnet 只要不連接到同一個 Router 上就不會出現 IP 地址重疊的問題。
如上圖,Router_1 對接 Router_2 的方式很簡單,只需在 Router_1 添加一條路由表項即可。e.g.
destination next_hop out_interface
104.20.110.0/24 182.24.4.1 Port1(182.21.4.6)
這條路由表項的關鍵信息有三:物理外部網關 IP(182.24.4.1)、Neutron 外部網關 IP(182.21.4.6)以及隱藏信息 —— 運營商網絡(外部網絡)182.24.4.0/24。
NOTE:此處的運營商網絡(外部網絡)182.24.4.0/24 不是上圖中的外部網絡(公網),而是勾選了 “外部網絡” 的運營商網絡。
可見,Neutron 內部網絡要想連接公網,關鍵在於如何獲取上述 3 個關鍵信息。辦法很簡單,只需要創建一個運營商網絡(外部網絡)即可,例如:182.24.4.0/24。物理外部網關 IP(182.24.4.1)就是運營商網絡(外部網絡)對應的 Subnet 的 gateway_ip 屬性,而 Neutron 外部網關 IP(182.21.4.6)就是該 Subnet 分配的一個子網 IP 地址。存放這些信息的 Router 資源模型屬性就是 external_gateway_info:
{
"external_gateway_info": {
"enable_snat": true,
"external_fixed_ips": [{
"ip_address": "182.24.4.6",
"subnet_id": "b930d7f6-ceb7-40a0-8b81-a425dd994ccf"
}],
"network_id": "ae34051f-aa6c-4c75-abf5-50dc9ac99ef3"
}
}
- network_id — 運營商網絡(外部網絡)
- subnet_id — 運營商網絡(外部網絡)的 Subnet(其 gateway_ip 就是物理外部網關 IP 地址)
- ip_address — Neutron 外部網關 IP 地址
- enable_snat — Neutron 外部網關端口是否開啟 SNAT 功能
如果開啟了 SNAT,Neutron 就會在 qroute-XXX namespace 中添加這樣的一條 iptables rule,所有從 qg-426c2bcd-f4(如上圖 Port 1)出去的數據包,無論你來自哪里(源 IP 地址)或者要去哪里(目的 IP 地址),數據包的源 IP 地址都會被轉換為 172.18.22.210。
[root@localhost ~]# ip netns exec qrouter-a1adb970-dba9-49f0-ba4b-4294f0d07f6f iptables -nvL -t nat
...
Chain neutron-l3-agent-snat (1 references)
pkts bytes target prot opt in out source destination
2 142 neutron-l3-agent-float-snat all -- * * 0.0.0.0/0 0.0.0.0/0
2 142 SNAT all -- * qg-426c2bcd-f4 0.0.0.0/0 0.0.0.0/0 to:172.18.22.210
0 0 SNAT all -- * * 0.0.0.0/0 0.0.0.0/0 mark match ! 0x2/0xffff ctstate DNAT to:172.18.22.210
NOTE:添加這條 SNAT 的原因是,Linux 作為路由器時僅僅開啟了內核的路由轉發功能是不夠的,還需要添加這么一條 SNAT。那為什么不需要添加 DNAT 呢?因為作為路由器而言,所有的數據包都只是 SNAT(送出去)而已。
新建 Router 和外部網關端口
從上文可一直,當我們希望 Neutron 內部網絡連通公網時,就創建一個運營商網絡(外部網絡),然后使用 L3 Router 將內部網絡與運營商網絡(外部網絡)直連起來即可。e.g.
- 新建 Router。如果此時用戶選擇了 “外部網絡”,那么 Neutron 會自動的創建 Router1 與 public 直連的外部網關端口。需要注意的是,只有這一種情況下 Neutron 會自動創建路由端口,否則都需要用戶手動創建。
- 內部網絡 Net1 和運營商網絡(外部網絡)public 通過 Router 直連
- public 有一個端口連接到 Router,類型為
network:router_gateway
,IP 為 172.18.22.210(Neutron 外部網關 IP)。
- public 的 Subnet public-subnet 具有物理外部網關 IP 地址 172.18.22.1。
[root@localhost ~]# openstack subnet show public-subnet
+-------------------+--------------------------------------+
| Field | Value |
+-------------------+--------------------------------------+
| allocation_pools | 172.18.22.201-172.18.22.210 |
| cidr | 172.18.22.0/24 |
| created_at | 2019-02-13T03:41:09Z |
| description | |
| dns_nameservers | |
| enable_dhcp | False |
| gateway_ip | 172.18.22.1 |
| host_routes | |
| id | 99749410-a6b0-418b-9ebc-fd060e1a746e |
| ip_version | 4 |
| ipv6_address_mode | None |
| ipv6_ra_mode | None |
| location | None |
| name | public-subnet |
| network_id | 282e146e-6948-436f-992c-f2d50588e357 |
| project_id | 031cec3e2df143259d302aa1993fd410 |
| revision_number | 0 |
| segment_id | None |
| service_types | |
| subnetpool_id | None |
| tags | |
| updated_at | 2019-02-13T03:41:09Z |
+-------------------+--------------------------------------+
-
Router 具有兩個端口,一個連接 Net1,IP 地址是 Net1 的網關 IP 192.168.1.1;另一個連接 public,IP 是 Neutron 外部網關 IP 172.18.22.210。
-
Router1 的路由表和網絡設備:
[root@localhost ~]# ip netns exec qrouter-a1adb970-dba9-49f0-ba4b-4294f0d07f6f ifconfig
lo: flags=73<UP,LOOPBACK,RUNNING> mtu 65536
inet 127.0.0.1 netmask 255.0.0.0
inet6 ::1 prefixlen 128 scopeid 0x10<host>
loop txqueuelen 1000 (Local Loopback)
RX packets 0 bytes 0 (0.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 0 bytes 0 (0.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
qg-426c2bcd-f4: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 172.18.22.210 netmask 255.255.255.0 broadcast 172.18.22.255
inet6 fe80::f816:3eff:fe0b:c722 prefixlen 64 scopeid 0x20<link>
ether fa:16:3e:0b:c7:22 txqueuelen 1000 (Ethernet)
RX packets 765265 bytes 36064217 (34.3 MiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 90 bytes 5316 (5.1 KiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
qr-cf018461-bc: 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:fea5:b2bb prefixlen 64 scopeid 0x20<link>
ether fa:16:3e:a5:b2:bb txqueuelen 1000 (Ethernet)
RX packets 216 bytes 18796 (18.3 KiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 139 bytes 13870 (13.5 KiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
[root@localhost ~]# ip netns exec qrouter-a1adb970-dba9-49f0-ba4b-4294f0d07f6f 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-426c2bcd-f4
172.18.22.0 0.0.0.0 255.255.255.0 U 0 0 0 qg-426c2bcd-f4
192.168.1.0 0.0.0.0 255.255.255.0 U 0 0 0 qr-cf018461-bc
其中 qr-XXX 和 qg-YYY 在 qroute-XXX namespace 中用於連接 br-int 和 br-ex,從第二、三條路由表項是由鏈路層協議發現並創建的,分別是 Net1 和 public 子網的直連路由,由設備 qr-XXX 和 qg-YYY 轉發。而第一條路由表項,則是由 Neutron 添加的默認靜態路由,Neutron 從 public 的 gateway_ip 獲取到物理外部網關 IP 地址,所有非直連路由的子網訪問流量,都會使用默認靜態路由進行轉發。
Router 的路由表
我們知道路由器的路由表項分配靜態路由和動態路由,Neutron 中所有的路由表項均為靜態路由(不遵循動態路由協議),又細分為:
- 靜態路由
- 靜態默認路由
- 直連路由
其中只有 “靜態路由” 一種類型會被 Neutron 記錄在 Router 資源模型的 routes(數值類型)屬性中。
MariaDB [neutron]> desc routerroutes;
+-------------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------------+-------------+------+-----+---------+-------+
| destination | varchar(64) | NO | PRI | NULL | |
| nexthop | varchar(64) | NO | PRI | NULL | | # 目的網段( CIDR)
| router_id | varchar(36) | NO | PRI | NULL | | # 下 一 跳 IP(下一個路由器的端口 IP)
+-------------+-------------+------+-----+---------+-------+
后面兩種路由表項類型為什么不被持久化呢?因為靜態默認路由的信息來自外部網絡的 gateway_ip 屬性,而直連路由則由鏈路層協議自動發現、創建。所以不需要額外的持久化。
那么靜態路由又是什么路由,為什么要持久化呢?
+----------------+-------------+--------------------------------------+
| destination | nexthop | router_id |
+----------------+-------------+--------------------------------------+
| 192.168.1.2/32 | 172.18.22.1 | a1adb970-dba9-49f0-ba4b-4294f0d07f6f |
+----------------+-------------+--------------------------------------+
如上,Router 的靜態路由其實就是常規的主機路由或網絡路由,使用戶自定義的路由表項。但需要注意的是,“下一跳” 表單不能隨意亂填,而是限制在 Router 夠得着的子網 IP 地址。否則就會 the nexthop is not connected with router。
NOTE:在講述 Subnet 資源模型的章節中你或許會對 Subnet 的 gateway_ip 和 host_routes 屬性感到疑惑,通本章的舉例,或許你能夠更好的理解這些字段所具有的功能和含義。
Floating IP
從功能的角度可以將 Neutron 的 IP 分為:
- Fixed IP:由租戶網絡 Subnet 分配,用於虛擬機的內部通訊,創建虛擬機時自動分配。
- Floating IP:有外部網絡分配,用於虛擬機訪問公網,需要手動分配。
前文中我們提到過,一般的運營商網絡和勾選了 “外部網絡” 的運營商網絡的主要區別在於后者可以分配 Floating IP,實現的原理就是內部網絡與外部網絡之間的 Router(qroute-XXX)的 NAT(SNAT&DNAT)功能。
上圖可見,外部網絡 public 成為了一個 Floating IP 地址池。創建一個 Floating IP 會相應的創建一條數據庫記錄:
MariaDB [neutron]> select * from floatingips\G;
*************************** 1. row ***************************
project_id: 031cec3e2df143259d302aa1993fd410
id: 67272b76-493a-4a60-b63a-22e5aefebfc0
floating_ip_address: 172.18.22.204
floating_network_id: 282e146e-6948-436f-992c-f2d50588e357
floating_port_id: 04aceef8-a3b2-46e4-a815-3104a2031ed7
fixed_port_id: NULL
fixed_ip_address: NULL
router_id: NULL
last_known_router_id: NULL
status: DOWN
standard_attr_id: 82
1 row in set (0.01 sec)
為虛擬機綁定這個 Floating IP 之后,數據庫記錄會被更改:
MariaDB [neutron]> select * from floatingips\G;
*************************** 1. row ***************************
project_id: 031cec3e2df143259d302aa1993fd410
id: 67272b76-493a-4a60-b63a-22e5aefebfc0
floating_ip_address: 172.18.22.204
floating_network_id: 282e146e-6948-436f-992c-f2d50588e357
floating_port_id: 04aceef8-a3b2-46e4-a815-3104a2031ed7
fixed_port_id: 07995f4e-b6b2-493f-9ce5-b1d945a13807
fixed_ip_address: 192.168.1.27
router_id: a1adb970-dba9-49f0-ba4b-4294f0d07f6f
last_known_router_id: NULL
status: ACTIVE
standard_attr_id: 82
1 row in set (0.00 sec)
qroute-XXX 中的 iptables rules 也會相同的添加 SNAT、DNAT 規則:
[root@localhost ~]# ip netns exec qrouter-a1adb970-dba9-49f0-ba4b-4294f0d07f6f iptables -nvL -t nat
...
# 到目的 IP 地址 172.18.22.204 的數據包 DNAT 至 192.168.1.27
Chain neutron-l3-agent-PREROUTING (1 references)
pkts bytes target prot opt in out source destination
...
0 0 DNAT all -- * * 0.0.0.0/0 172.18.22.204 to:192.168.1.27
...
# 從源 IP 地址 192.168.1.27 來的數據包 SNAT 至 172.18.22.204
Chain neutron-l3-agent-float-snat (1 references)
pkts bytes target prot opt in out source destination
0 0 SNAT all -- * * 192.168.1.27 0.0.0.0/0 to:172.18.22.204
...
這一切都發生在 qroute-XXX network namespace 中,實現了不同 Router 之間的網絡隔離。