C13-OpenStack 網路服務 2:OVN 架構、Provider 與 Self-Service 網路

0. 環境與準備

0.1 本課環境

主機角色管理網 IP
jmp001學員操作機(OpenStack CLI),接在外部網段 192.168.100.0/2410.127.0.3
ctl001 / ctl002 / ctl003控制節點:OVN Central(ovn_nb_db、ovn_northd、ovn_sb_db),同時是 Gateway(承載 cr-lrp)10.127.0.4 / .14 / .15
scp001運算節點(ovn_controller、openvswitch_vswitchd),c13-vm-a 在這台10.127.0.7
scp002運算節點(ovn_controller、openvswitch_vswitchd),c13-vm-b 在這台10.127.0.18

講師建立的範例情境(對應投影片 p.10–11、p.29):

C13 範例情境的網路拓樸 Provider 網路 public1(flat、physnet1,192.168.100.0/24)上有 jmp001(.10)與實體路由器(.1)。c13-router 的 Gateway Port(.185)接 public1,實際落在 ctl003 的 cr-lrp;Interface Port(192.168.13.1)接 Self-Service 網路 c13-private(geneve,192.168.13.0/24)。c13-vm-a 在 scp001,fixed IP .252,以 Floating IP 192.168.100.156 做 1:1 NAT;c13-vm-b 在 scp002,fixed IP .82,經 router SNAT 以 .185 對外。 provider network public1(flat ・ physnet1) public1-subnet 192.168.100.0/24 OVN:localnet 埠 provnet-406a…(physnet1) jmp001 學員操作機 .10 Internet 實體路由器 .1 FIP .156 1:1 NAT(vm-a 的 FIP) nat type: dnat_and_snat .185 Gateway Port lrp-5c6c01e8… .1 Interface Port lrp-362f0f36… SNAT:沒有 FIP 的 VM 共用 .185 nat type: snat c13-router Logical Router neutron-aa7271d2… Gateway Port 實際落在 ctl003 cr-lrp-5c6c01e8…(Gateway Chassis) self-service network c13-private(geneve ・ VNI 1155) c13-private-subnet 192.168.13.0/24 OVN:Logical Switch neutron-cffbde07… .252 c13-vm-a 主機:scp001 出口 .156(FIP) Port_Binding 1021e30d… tap1021e30d-24 .82 c13-vm-b 主機:scp002 出口 .185(Router SNAT) Port_Binding f7221753… 兩台都套用 c13-sg Port Group pg_dd92c260… + ACL
C13 範例情境:c13-private 經 c13-router 接到 public1
資源名稱說明
Network / Subnetc13-private / 192.168.13.0/24Self-Service 網路(geneve)
Routerc13-router接 c13-private,對外閘道為 public1,SNAT 開啟
Security Groupc13-sgingress 只放行 ICMP 與 TCP 22
VMc13-vm-a在 scp001,綁 Floating IP(1:1 NAT)
VMc13-vm-b在 scp002,沒有 Floating IP(走 Router 的 SNAT)

0.2 連線方式

所有指令都在 jmp001 執行。需要看 OVN 或 OVS 時,用 ssh <節點 IP> "sudo docker exec <容器> <指令>" 送到該節點,這樣 jmp001 上設好的變數(例如 $NET)可以直接帶進去。

⚠️ 規則

  • 你使用的是和講師相同的管理員權限,本課只執行查詢指令
  • 預期輸出是示意,UUID、MAC、IP 若講師重建情境會不同;請以你自己 0.3 查到的值為準
  • ovn-trace 偶爾會先印出 clustered database server is not cluster leader; trying another server,這是它在找 SB DB 的 leader,屬正常訊息

0.3 準備:把要追蹤的資源 ID 存成變數

指令
# [jmp001]
export NN=01                                     # ← 換成你的學員編號(01–20)
source ~/venv/bin/activate
source ~/creds/stu${NN}-openrc.sh
NET=$(openstack network show c13-private -f value -c id)
ROUTER=$(openstack router show c13-router -f value -c id)
SG=$(openstack security group show c13-sg -f value -c id)
PORT_A=$(openstack port list --server c13-vm-a -f value -c ID)
PORT_B=$(openstack port list --server c13-vm-b -f value -c ID)
MAC_A=$(openstack port list --server c13-vm-a -f value -c "MAC Address")
MAC_B=$(openstack port list --server c13-vm-b -f value -c "MAC Address")
IP_A=$(openstack port list --server c13-vm-a -f value -c "Fixed IP Addresses" | cut -d"'" -f8)
IP_B=$(openstack port list --server c13-vm-b -f value -c "Fixed IP Addresses" | cut -d"'" -f8)
GW_MAC=$(openstack port list --router c13-router --device-owner network:router_interface -f value -c "MAC Address")
GW_PORT=$(openstack port list --router c13-router --device-owner network:router_gateway -f value -c ID)
echo "NET=$NET ROUTER=$ROUTER"
echo "A: $PORT_A $MAC_A $IP_A"
echo "B: $PORT_B $MAC_B $IP_B"
echo "GW_MAC=$GW_MAC GW_PORT=$GW_PORT"

預期輸出:

預期輸出
NET=cffbde07-14dd-4707-a48b-5149515de3a5 ROUTER=aa7271d2-b52c-408a-b397-5a72036746de
A: 1021e30d-24cf-4a41-8403-85852c7119e4 fa:16:3e:73:0f:22 192.168.13.252
B: f7221753-088b-4710-9136-623f2c565c6f fa:16:3e:e8:53:d8 192.168.13.82
GW_MAC=fa:16:3e:61:02:68 GW_PORT=5c6c01e8-0f46-418c-91da-260f1e488cd6
變數內容後面用在哪裡
NET / ROUTERNetwork、Router 的 Neutron IDOVN 用 neutron-<ID> 命名對應的 Switch、Router
PORT_A / PORT_B兩台 VM 網卡的 Neutron Port ID在 OVN、OVS 中找到同一個網卡
MAC_A / IP_A 等VM 網卡的 MAC 與 IPF 節模擬封包時當作來源
GW_MACRouter Interface(192.168.13.1)的 MACF 節模擬「送往閘道」的封包
GW_PORTRouter Gateway Port(對外那一側)的 IDC1 查它落在哪台節點

Part A:API 層:先從 Neutron 資源開始(p.8、10)

指令
# [jmp001]
openstack server list --name ^c13- --long -c Name -c Networks -c Host
openstack router show c13-router -c interfaces_info -c external_gateway_info -f yaml

預期輸出:

預期輸出
+----------+---------------------------------------------+---------------------+
| Name     | Networks                                    | Host                |
+----------+---------------------------------------------+---------------------+
| c13-vm-b | c13-private=192.168.13.82                   | scp002-6164930-iaas |
| c13-vm-a | c13-private=192.168.100.156, 192.168.13.252 | scp001-6161072-iaas |
+----------+---------------------------------------------+---------------------+
external_gateway_info:
  enable_snat: true
  external_fixed_ips:
  - ip_address: 192.168.100.185
    subnet_id: b7dabca1-dc52-47dd-b3ba-22e03e7f2851
  network_id: 351d82a1-2a0e-42d5-a428-3c7baa0be512
interfaces_info:
- ip_address: 192.168.13.1
  port_id: 362f0f36-a4e0-48c5-a4f9-e663a72c8ab3
  subnet_id: 27dce4dc-97e0-4134-b1ea-9a85650f7f01

怎麼讀這段輸出

輸出意思
Host 欄VM 實際跑在哪一台運算節點;D 節要登入的就是這台
vm-a 的 Networks 有兩個位址192.168.13.252 是 VM 自己的 fixed IP,192.168.100.156 是 Floating IP。這個欄位不區分類型(C12 學過)
external_gateway_info.enable_snat: trueRouter 對外閘道已設定,SNAT 開著
external_fixed_ips … 192.168.100.185Router 在外部網路上的位址;沒有 FIP 的 VM 出外網都用這個位址
interfaces_info … 192.168.13.1Router 接在 c13-private 這一側的位址,也就是 VM 的預設閘道

到這裡為止都是 Neutron 的視角。下面的表是本課的「尋寶清單」:同一個資源,接下來在每一層都要找到它。

Neutron 資源值後面在哪裡出現
Network c13-private$NETB:Logical Switch neutron-$NET
Router c13-router$ROUTERB:Logical Router neutron-$ROUTER
vm-a 的 Port$PORT_A,192.168.13.252,FIP 192.168.100.156B:LSP、NAT;C:Port_Binding;D:tap
vm-b 的 Port$PORT_B,192.168.13.82B:LSP;C:Port_Binding
Router Interface362f0f36-…,192.168.13.1B:type: router 的 port 與 lrp-362f0f36-…
Router Gateway Port$GW_PORT,192.168.100.185B:lrp-5c6c01e8-…、snat;C:cr-lrp-5c6c01e8-…

Part B:第一層:NB DB,Neutron 的意圖(p.5、11)

ML2/OVN driver 把 Neutron 資源寫成 Northbound DB 裡的邏輯物件。這一層只描述「網路應該長什麼樣」,還沒有任何一台節點的資訊。

B1. Logical Switch:Network 變成了什麼

指令
# [jmp001]
ssh 10.127.0.4 "sudo docker exec ovn_nb_db ovn-nbctl show neutron-$NET"

預期輸出:

預期輸出
switch 987e880a-b8c4-4e91-bf21-3d20c6c674de (neutron-cffbde07-14dd-4707-a48b-5149515de3a5) (aka c13-private)
    port 362f0f36-a4e0-48c5-a4f9-e663a72c8ab3
        type: router
        router-port: lrp-362f0f36-a4e0-48c5-a4f9-e663a72c8ab3
    port f7221753-088b-4710-9136-623f2c565c6f
        addresses: ["fa:16:3e:e8:53:d8 192.168.13.82"]
    port 1021e30d-24cf-4a41-8403-85852c7119e4
        addresses: ["fa:16:3e:73:0f:22 192.168.13.252"]
    port 7dc732a2-d036-42d0-a6f5-6e27d3188e8b
        type: localport
        addresses: ["fa:16:3e:50:24:fd 192.168.13.2"]

怎麼讀這段輸出

輸出意思
switch 987e880a-…OVN 自己替這個 Logical Switch 編的 UUID,和 Neutron 無關,排查時用不到
(neutron-cffbde07-…)Switch 的名稱,就是 neutron- 加上 Neutron Network ID($NET)。用 Neutron ID 搜尋最可靠
(aka c13-private)給人看的別名,就是 openstack 指令看到的網路名稱
port 1021e30d-…Logical Switch Port(LSP)。名稱就是 Neutron Port ID,這一個是 vm-a($PORT_A)
addresses: ["MAC IP"]這個埠允許使用的 MAC 與 IP。OVN 依此做 port security:VM 偽造其他來源位址的封包會被丟掉
type: router + router-port: lrp-362f0f36-…這個 LSP 不是 VM,而是 Router Interface 在 Switch 這一側的接點,另一端是 Router 上的 lrp-362f0f36-…(B2 會看到)
type: localport(192.168.13.2)每台節點本機各有一份的 metadata 埠,VM 開機時從這裡取得 metadata。C12 看到的 network:distributed 就是它

一句話總結:Network 變成 Logical Switch,Port 變成 Logical Switch Port,名稱都直接用 Neutron ID。

B2. Logical Router 與 NAT:Router 和 FIP 變成了什麼

指令
# [jmp001]
ssh 10.127.0.4 "sudo docker exec ovn_nb_db ovn-nbctl show neutron-$ROUTER"
ssh 10.127.0.4 "sudo docker exec ovn_nb_db ovn-nbctl lr-route-list neutron-$ROUTER"

預期輸出:

預期輸出
router 0df3c906-4c39-43ed-9423-57cede0ecb10 (neutron-aa7271d2-b52c-408a-b397-5a72036746de) (aka c13-router)
    port lrp-5c6c01e8-0f46-418c-91da-260f1e488cd6
        mac: "fa:16:3e:f1:b7:52"
        ipv6-lla: "fe80::f816:3eff:fef1:b752"
        networks: ["192.168.100.185/24"]
        gateway chassis: [ctl001-6161060-iaas ctl002-6161063-iaas ctl003-6161069-iaas]
    port lrp-362f0f36-a4e0-48c5-a4f9-e663a72c8ab3
        mac: "fa:16:3e:61:02:68"
        ipv6-lla: "fe80::f816:3eff:fe61:268"
        networks: ["192.168.13.1/24"]
    nat 0248d8a8-f9d7-4f40-b8b3-1d7078d2a0e7
        external ip: "192.168.100.185"
        logical ip: "192.168.13.0/24"
        type: "snat"
    nat f91483e9-5c7a-4e06-a9a4-cf793071c5f5
        external ip: "192.168.100.156"
        logical ip: "192.168.13.252"
        type: "dnat_and_snat"
IPv4 Routes
Route Table <main>:
                0.0.0.0/0             192.168.100.1 dst-ip

怎麼讀這段輸出

輸出意思對應的 Neutron 資源
port lrp-362f0f36-…,networks: ["192.168.13.1/24"]Logical Router Port(LRP),接內部子網Router Interface,mac 就是 $GW_MAC
port lrp-5c6c01e8-…,networks: ["192.168.100.185/24"]LRP,接外部網路Router Gateway Port($GW_PORT)
mac這個 Router 埠的 MAC。F 節會看到封包經過 Router 後,來源 MAC 被換成它—
ipv6-lla自動產生的 IPv6 link-local 位址,本課不使用—
gateway chassis: [ctl001 ctl002 ctl003]這個對外埠可以落在哪幾台節點。這是候選名單,列出的順序不代表優先順序;實際落點在 C1 確認—
nat … type: "dnat_and_snat",.156 ↔ .2521:1 NAT:進來時把目的位址 .156 換成 .252(DNAT),出去時把來源 .252 換成 .156(SNAT)vm-a 的 Floating IP
nat … type: "snat",192.168.13.0/24 → .185多對一 NAT:整個子網出去時,來源都換成 .185Router 的 SNAT(enable_snat: true)
0.0.0.0/0 192.168.100.1Router 的預設路由:不認識的目的地都交給 192.168.100.1(外部的實體路由器)由 public1-subnet 的 gateway 自動產生

B3. 表格形式的 NAT 清單

指令
# [jmp001]
ssh 10.127.0.4 "sudo docker exec ovn_nb_db ovn-nbctl lr-nat-list neutron-$ROUTER"

預期輸出:

預期輸出
TYPE             GATEWAY_PORT          MATCH                 EXTERNAL_IP        EXTERNAL_PORT    LOGICAL_IP          EXTERNAL_MAC         LOGICAL_PORT
dnat_and_snat    lrp-5c6c01e8-0f46-                          192.168.100.156                     192.168.13.252
snat                                                         192.168.100.185                     192.168.13.0/24

怎麼讀這段輸出

欄位意思
TYPEdnat_and_snat = Floating IP;snat = Router 的 SNAT
EXTERNAL_IP / LOGICAL_IP外面看到的位址 / 裡面真正的位址
GATEWAY_PORT這條 NAT 在哪個 Router 埠上做(欄寬有限,名稱被截斷)
EXTERNAL_MAC、LOGICAL_PORT 空白FIP 是集中式:NAT 在 Gateway 節點上做,所以 vm-a 的對外流量要先經 Tunnel 送到 Gateway(ctl)。若這兩欄有值,則是分散式 FIP,NAT 直接在 VM 所在節點做

p.33 的判斷法:有 snat 表示整個 Subnet 的出口已成立;有 dnat_and_snat 表示某個 Port 另外掛了 Floating IP。兩者都沒有,問題在 Router 兩側的接線。

B4. Security Group 變成 Port Group + ACL

指令
# [jmp001]
ssh 10.127.0.4 "sudo docker exec ovn_nb_db ovn-nbctl acl-list pg_${SG//-/_}"

預期輸出:

預期輸出
from-lport  1002 (inport == @pg_dd92c260_5907_4b53_b3de_235c2a3ba09a && ip4) allow-related
from-lport  1002 (inport == @pg_dd92c260_5907_4b53_b3de_235c2a3ba09a && ip6) allow-related
  to-lport  1002 (outport == @pg_dd92c260_5907_4b53_b3de_235c2a3ba09a && ip4 && ip4.src == 0.0.0.0/0 && icmp4) allow-related
  to-lport  1002 (outport == @pg_dd92c260_5907_4b53_b3de_235c2a3ba09a && ip4 && ip4.src == 0.0.0.0/0 && tcp && tcp.dst == 22) allow-related

${SG//-/_} 是 bash 的字串取代:把 SG ID 裡的 - 換成 _,因為 Port Group 的名稱是 pg_ 加上 SG ID,而名稱裡不能有 -。

怎麼讀一條 ACL:以第三條為例,可以拆成四段

預期輸出
  to-lport    1002    (outport == @pg_3d64… && ip4 && ip4.src == 0.0.0.0/0 && icmp4)    allow-related
  ────────    ────    ─────────────────────────────────────────────────────────────    ─────────────
   ① 方向     ② 優先權                          ③ 比對條件                                ④ 動作
段意思
① 方向from-lport:封包從 VM 出來(egress);to-lport:封包要進入 VM(ingress)
② 優先權數字越大越先比對。Neutron 建的 SG 規則都是 1002
③ 比對條件outport == @pg_…:目的埠屬於這個 Port Group,也就是套用 c13-sg 的 VM(@ 表示引用一整組埠)。ip4.src == 0.0.0.0/0:來源不限,對應 SG 規則的 remote IP prefix。icmp4 / tcp.dst == 22:協定與目的埠
④ 動作allow-related:放行,而且記住這條連線,回應封包自動放行(stateful)
ACL對應的 c13-sg 規則
兩條 from-lport … ip4 / ip6預設的 egress 規則:VM 往外全部放行
to-lport … icmp4ingress ICMP
to-lport … tcp.dst == 22ingress TCP 22

沒有列在這裡的流量(例如進入 VM 的 TCP 80)會被丟棄:Neutron 把所有套用 SG 的埠另外放進一個叫 neutron_pg_drop 的 Port Group,上面有兩條優先權 1001 的 drop 規則(進、出各一條)。1002 的放行規則先比對,都沒符合才輪到 1001 的丟棄。F3 會用 ovn-trace 驗證。

B5. Subnet 沒有獨立物件

指令
# [jmp001]
ssh 10.127.0.4 "sudo docker exec ovn_nb_db ovn-nbctl --columns=cidr,options find DHCP_Options cidr=192.168.13.0/24"

預期輸出(節錄):

預期輸出
cidr                : "192.168.13.0/24"
options             : {classless_static_route="{169.254.169.254/32,192.168.13.2, 0.0.0.0/0,192.168.13.1}", dns_server="{127.0.0.53}", lease_time="43200", mtu="8492", router="192.168.13.1", server_id="192.168.13.1", ...}

Subnet 在 OVN 裡沒有對應的物件(p.5),它的設定變成 OVN 內建 DHCP 服務要發給 VM 的參數:

options 的鍵意思
router="192.168.13.1"發給 VM 的預設閘道,就是 Subnet 的 gateway_ip
classless_static_route發給 VM 的靜態路由:169.254.169.254/32 經 192.168.13.2(metadata 走 localport),0.0.0.0/0 經 192.168.13.1
mtu="8492"發給 VM 的 MTU,就是 Network 的 MTU
lease_time="43200"DHCP 租期 12 小時
server_idDHCP 回應的來源位址

Part C:第二層:SB DB,誰扛了什麼(p.12、13)

ovn-northd 把 NB 的邏輯物件編譯進 Southbound DB;每台節點的 ovn-controller 再向 SB DB 報到,認領自己負責的埠。這一層開始出現「節點」。

C1. 每台 Chassis 承載的 Port

指令
# [jmp001]
ssh 10.127.0.4 "sudo docker exec ovn_sb_db ovn-sbctl show"

預期輸出:

預期輸出
Chassis scp002-6164930-iaas
    hostname: scp002-6164930-iaas
    Encap geneve
        ip: "10.127.0.18"
        options: {csum="true"}
    Port_Binding "a9edc7e7-6d03-47d0-a698-1166e7f3deb3"
    Port_Binding "085ffce1-bdc4-4ebb-b59b-0fdc798666c1"
    Port_Binding "16f96757-589b-4d89-a692-cbb69f53eb17"
    Port_Binding "f7221753-088b-4710-9136-623f2c565c6f"
    Port_Binding "a51cda76-2b69-456d-9e31-8871344dc6d3"
Chassis scp001-6161072-iaas
    hostname: scp001-6161072-iaas
    Encap geneve
        ip: "10.127.0.7"
        options: {csum="true"}
    Port_Binding "1021e30d-24cf-4a41-8403-85852c7119e4"
Chassis ctl002-6161063-iaas
    hostname: ctl002-6161063-iaas
    Encap geneve
        ip: "10.127.0.14"
        options: {csum="true"}
Chassis ctl003-6161069-iaas
    hostname: ctl003-6161069-iaas
    Encap geneve
        ip: "10.127.0.15"
        options: {csum="true"}
    Port_Binding cr-lrp-5c6c01e8-0f46-418c-91da-260f1e488cd6
Chassis ctl001-6161060-iaas
    hostname: ctl001-6161060-iaas
    Encap geneve
        ip: "10.127.0.4"
        options: {csum="true"}
    Port_Binding cr-lrp-da5107b9-d39c-4621-b3f1-a9adf97f0b59

怎麼讀這段輸出:它是「以節點為單位」的清單,每個 Chassis 區塊是一台節點,底下列出這台節點目前負責的埠。

輸出意思
Chassis scp002-…一台加入 OVN 的節點。名稱是該節點 OVS 的 system-id,本環境剛好設成和主機名稱相同
Encap geneve / ip這台節點的 Tunnel 端點:別的節點要送封包給它時,用 Geneve 封裝後送到這個 IP
options: {csum="true"}Tunnel 封包會計算 checksum,可忽略
Port_Binding "<Port ID>"這台節點的 ovn-controller 認領了這個埠,代表那張 VM 網卡就插在這台的 br-int 上
Port_Binding cr-lrp-<ID>chassis-redirect logical router port:Router 對外埠在這台節點「落地」,南北向流量都從這台出去

在輸出中找出:

找什麼在哪一台 Chassis 底下意義
vm-a 的 $PORT_A(1021e30d…)scp001VM 在哪台,Port_Binding 就在哪台
vm-b 的 $PORT_B(f7221753…)scp002vm-a 與 vm-b 不在同一台,兩者之間的封包要走 Geneve tunnel
cr-lrp-5c6c01e8-…($GW_PORT)ctl003(你看到的可能是另一台 ctl)c13-router 的對外埠實際落點
cr-lrp-da5107b9-…ctl001另一個 Router(demo-router)的對外埠
沒有任何埠ctl002沒有 VM,也不是任何 Router 目前的 Gateway。若環境中有其他 Router,它的 cr-lrp-… 也可能出現在這裡:每個 Router 各有一個,落點各自依優先權決定

為什麼 cr-lrp 落在 ctl003? B2 的 gateway chassis 有三台候選,每台有一個優先權,數字最大且存活的那台承載 cr-lrp:

指令
# [jmp001]
ssh 10.127.0.4 "sudo docker exec ovn_nb_db ovn-nbctl lrp-get-gateway-chassis lrp-$GW_PORT"

預期輸出:

預期輸出
lrp-5c6c01e8-0f46-418c-91da-260f1e488cd6_ctl003-6161069-iaas     3
lrp-5c6c01e8-0f46-418c-91da-260f1e488cd6_ctl002-6161063-iaas     2
lrp-5c6c01e8-0f46-418c-91da-260f1e488cd6_ctl001-6161060-iaas     1

ctl003 的優先權最高(3),所以 cr-lrp 在 ctl003。ctl003 故障時,cr-lrp 會自動移到 ctl002(2)。每個 Router 的優先權順序由 Neutron 分配,不一定相同。

C2. Gateway 節點與運算節點的差別

指令
# [jmp001]
ssh 10.127.0.4 "sudo docker exec ovn_sb_db ovn-sbctl get Chassis ctl001-6161060-iaas other_config:ovn-bridge-mappings other_config:ovn-cms-options"
ssh 10.127.0.4 "sudo docker exec ovn_sb_db ovn-sbctl get Chassis scp001-6161072-iaas other_config:ovn-bridge-mappings other_config:ovn-cms-options"

預期輸出(前兩行是 ctl001,後兩行是 scp001):

預期輸出
"physnet1:br-ex"
enable-chassis-as-gw
"physnet1:br-ex,physnet2:br-ex2"
""
ctl001(控制節點)scp001(運算節點)
ovn-bridge-mappingsphysnet1:br-exphysnet1:br-ex,physnet2:br-ex2
ovn-cms-optionsenable-chassis-as-gw""(空的)

Part D:第三層:本機 OVS(p.15)

前兩層看的是整個叢集的資料庫;OVS 只看本機,所以要到 vm-a 所在的 scp001 查。

D1. 這台機器上真正存在的介面

指令
# [jmp001]
ssh 10.127.0.7 "sudo docker exec openvswitch_vswitchd ovs-vsctl show"

預期輸出(節錄):

預期輸出
175b65a7-7146-4518-ba59-4e94025ba2b4
    Bridge br-int
        fail_mode: secure
        datapath_type: system
        Port ovn-scp002-0
            Interface ovn-scp002-0
                type: geneve
                options: {csum="true", key=flow, local_ip="10.127.0.7", remote_ip="10.127.0.18"}
        Port ovn-ctl001-0
            Interface ovn-ctl001-0
                type: geneve
                options: {csum="true", key=flow, local_ip="10.127.0.7", remote_ip="10.127.0.4"}
                bfd_status: {diagnostic="No Diagnostic", flap_count="1", forwarding="true", remote_diagnostic="No Diagnostic", remote_state=up, state=up}
        ...(ovn-ctl002-0、ovn-ctl003-0 與 ovn-ctl001-0 相同,只有 remote_ip 不同)
        Port br-int
            Interface br-int
                type: internal
        Port tap1021e30d-24
            Interface tap1021e30d-24
        Port tapcffbde07-10
            Interface tapcffbde07-10
    Bridge br-ex
        fail_mode: standalone
        Port br-ex
            Interface br-ex
                type: internal
        Port ens4
            Interface ens4
    Bridge br-ex2
        ...

怎麼讀這段輸出:Bridge 是一台虛擬交換器,底下每個 Port 是插在上面的一條線。

輸出意思
Bridge br-int整合橋:所有 VM 網卡與 Tunnel 都插在這裡,OVN 的規則全部裝在它身上
fail_mode: secure沒有規則時一律丟棄,不會自己學習 MAC 轉送。所以 br-int 完全依賴 ovn-controller 下發的規則
datapath_type: system使用 Linux 核心的 OVS 模組轉送封包
tap1021e30d-24vm-a 的網卡,名稱是 tap + Neutron Port ID 前 11 碼($PORT_A)
tapcffbde07-10c13-private 的 metadata localport,名稱帶 Network ID($NET)
ovn-scp002-0,type: geneve通往 scp002 的 Tunnel;remote_ip 就是 C1 看到的 scp002 Encap IP
ovn-ctl00X-0 的 bfd_status … state=up到 Gateway 節點的 Tunnel 會跑 BFD 心跳,確認對方活著;Gateway 掛掉時,cr-lrp 才能快速切到下一台。到 scp002 的 Tunnel 不需要,所以沒有 BFD
Bridge br-ex + ens4接 physnet1(外部網路)的 bridge,ens4 是實體網卡

D2. 從 Port ID 找到 tap

指令
# [jmp001]
ssh 10.127.0.7 "sudo docker exec openvswitch_vswitchd ovs-vsctl --columns=name,ofport,external_ids find Interface external_ids:iface-id=$PORT_A"

預期輸出:

預期輸出
name                : tap1021e30d-24
ofport              : 25
external_ids        : {attached-mac="fa:16:3e:73:0f:22", iface-id="1021e30d-24cf-4a41-8403-85852c7119e4", iface-status=active, ovn-installed="true", ovn-installed-ts="1793935759521", vm-uuid="a7750220-c2fb-44c6-892b-d11f214db292"}

這個指令的意思是:在本機所有介面中,找出 external_ids:iface-id 等於 vm-a Port ID 的那一個。

欄位意思
iface-id = Neutron Port IDOVS 與 OVN 之間的橋樑。Nova 建立 tap 時寫入這個值,ovn-controller 看到後就知道「這個 tap 是邏輯埠 1021e30d…」,於是去 SB DB 認領,C1 的 Port_Binding 就是這樣出現的
attached-mac / vm-uuid這張網卡的 MAC,以及它屬於哪台 VM(Nova 的 server ID)
ovn-installed="true"ovn-controller 已經把這個埠需要的轉送規則裝好;是 false 或沒有這個欄位時,VM 網路通常不通
ofport = 25這個介面在 br-int 上的 OpenFlow 埠號,ofproto/trace 的 in_port 要填這個數字(p.17)

Part E:Provider 與 Self-Service(p.20–21)

指令
# [jmp001]
openstack network show public1 -c provider:network_type -c provider:physical_network -c router:external
openstack network show c13-private -c provider:network_type -c provider:physical_network -c provider:segmentation_id
ssh 10.127.0.4 "sudo docker exec ovn_nb_db ovn-nbctl --columns=name,type,options find Logical_Switch_Port type=localnet"

預期輸出:

預期輸出
+---------------------------+----------+
| Field                     | Value    |
+---------------------------+----------+
| provider:network_type     | flat     |
| provider:physical_network | physnet1 |
| router:external           | External |
+---------------------------+----------+
+---------------------------+--------+
| Field                     | Value  |
+---------------------------+--------+
| provider:network_type     | geneve |
| provider:physical_network | None   |
| provider:segmentation_id  | 1155   |
+---------------------------+--------+
name                : provnet-406a2db9-da57-4935-b1fd-033478598467
type                : localnet
options             : {localnet_learn_fdb="false", mcast_flood="false", mcast_flood_reports="true", network_name=physnet1}

name                : provnet-8cecc002-8242-47cc-a4ee-20a11f5a6605
type                : localnet
options             : {localnet_learn_fdb="false", mcast_flood="false", mcast_flood_reports="true", network_name=physnet2}

怎麼讀這段輸出

輸出意思
public1:flat、physnet1、ExternalProvider 網路:直接對映到實體網路 physnet1,沒有 VLAN tag;可當 Router 的對外閘道
c13-private:geneve、None、1155Self-Service 網路:不對映任何實體網路,用 Geneve 封裝,segmentation_id 1155 是它在 Tunnel 裡的編號(VNI)
type: localnet一種特殊的 LSP,代表「通往實體網路的出口」
options: network_name=physnet1這個出口接到 physnet1;每台節點再依 C2 的 ovn-bridge-mappings 找到 br-ex
其他 options學習與廣播相關的細節設定,本課不需要
public1c13-private
類型Provider(flat,physnet1)Self-Service(geneve,VNI 1155)
OVN 的 localnet 埠有:provnet-406a2db9-…沒有
封包怎麼離開localnet 埠 → 本機 br-ex → 實體網路Geneve tunnel;對外要經 Router(SNAT / FIP)

在 OVN 裡,Provider 與 Self-Service 的差別就是 Logical Switch 上有沒有 localnet 埠(p.20)。全環境只有兩個 localnet:physnet1 是 public1,physnet2 是 Octavia 管理網。F 節會看到 vm-a 的封包最後就是從 provnet-406a2d… 這個埠離開。


Part F:第四層:ovn-trace,邏輯上應該怎麼走(p.16)

F0. 先看懂 ovn-trace 的輸入與輸出

ovn-trace 讀 SB DB 裡編譯好的 Logical Flow,模擬一個封包在邏輯網路裡會被怎麼處理。它不送真的封包,也不碰任何節點,所以可以放心執行。

輸入:描述一個封包

預期輸出
ovn-trace --minimal --ct new  neutron-$NET  'inport=="…" && eth.src==… && ip4.src==… && eth.dst==… && ip4.dst==… && ip.ttl==64 && icmp4.type==8'
          ───────── ────────  ────────────  ─────────────────────────────────────────────────────────────────────────────────────────────
           ① 輸出量  ② 連線狀態  ③ 從哪個網路進入                              ④ 封包長什麼樣子(用 && 串起來)
參數意思
① --minimal只列出封包「被改了什麼、從哪裡出去」,省略中間每一條規則。換成 --summary 可以看到經過的每個 Switch / Router,但輸出長很多
② --ct new假設這是一條新連線。SG 只在新連線時檢查,不加的話 ovn-trace 預設當成已建立的連線,看不出 SG 會不會擋
③ neutron-$NET封包從哪個邏輯網路開始走,就是 B1 的 Logical Switch 名稱
④ inport=="$PORT_A"從 vm-a 的網卡送出
④ eth.src / eth.dst來源 / 目的 MAC。VM 要送到外網時,目的 MAC 是閘道的 MAC($GW_MAC),不是 8.8.8.8 的
④ ip4.src / ip4.dst來源 / 目的 IP
④ ip.ttl==64封包的 TTL;經過 Router 會減 1
④ icmp4.type==8ICMP echo request,也就是 ping

輸出:讀懂四種寫法就夠了

輸出長這樣意思
第一行 # icmp,…,dl_src=…,nw_src=…ovn-trace 把你的條件翻成 OpenFlow 的寫法再印一次,確認輸入無誤。dl_ = MAC、nw_ = IP、reg14 是 OVN 內部替輸入埠編的號碼,可以忽略
ct_next(…) { … }封包送進 conntrack(連線追蹤)查詢狀態,大括號裡是查完之後繼續做的事。巢狀越深,代表處理越後面
欄位 = 值;、ip.ttl--;封包被修改了,例如換來源 MAC、TTL 減 1
ct_snat(ip4.src=X)做 SNAT:來源 IP 被改成 X
output("名稱")封包從這個埠送出去。名稱只印出前 6 碼:VM 埠是 Port ID 前 6 碼,provnet-406a2d 是 public1 的 localnet 埠
沒有任何 output封包被丟棄

F1. vm-a(有 FIP)連到 8.8.8.8

指令
# [jmp001]
ssh 10.127.0.4 "sudo docker exec ovn_northd ovn-trace --minimal --ct new neutron-$NET 'inport==\"$PORT_A\" && eth.src==$MAC_A && ip4.src==$IP_A && eth.dst==$GW_MAC && ip4.dst==8.8.8.8 && ip.ttl==64 && icmp4.type==8'"

預期輸出(左側加了行號方便對照,實際輸出沒有行號):

預期輸出
 1  # icmp,reg14=0x3,vlan_tci=0x0000,dl_src=fa:16:3e:73:0f:22,dl_dst=fa:16:3e:61:02:68,nw_src=192.168.13.252,nw_dst=8.8.8.8,nw_tos=0,nw_ecn=0,nw_ttl=64,nw_frag=no,icmp_type=8,icmp_code=0
 2  ct_next(ct_state=new|trk) {
 3      ip.ttl--;
 4      eth.src = fa:16:3e:f1:b7:52;
 5      eth.dst = 00:00:00:00:00:00;
 6      arp {
 7          eth.dst = ff:ff:ff:ff:ff:ff;
 8          arp.spa = 0xc0a864b9;
 9          arp.tpa = 0xc0a86401;
10          arp.op = 1;
11          output("e474ff");
12          output("provnet-406a2d");
13      };
14      ct_dnat /* assuming no un-dnat entry, so no change */ {
15          ct_next(ct_state=est|trk /* default (use --ct to customize) */) {
16              ct_snat(ip4.src=192.168.100.156) {
17                  output("provnet-406a2d");
18              };
19          };
20      };
21  };

下圖把這段輸出畫成封包走過的路,右側是每一步對應的輸出行:

ovn-trace F1 / F2:封包從 VM 到外部網路的邏輯路徑 封包從 vm-a 或 vm-b 出發,先在 Logical Switch c13-private 通過 SG 的 egress 檢查,進入 Logical Router c13-router 後 TTL 減 1、依預設路由改由對外埠送出並換來源 MAC,因為不知道 192.168.100.1 的 MAC 而先送 ARP,接著在落於 ctl003 的 Gateway(cr-lrp)做 NAT:vm-a 的來源改成 Floating IP 192.168.100.156,vm-b 改成 Router 對外位址 192.168.100.185,最後從 public1 的 localnet 埠 provnet-406a2d 離開到實體網路。右側列出每一步在 ovn-trace 輸出中對應的行。 封包走過的邏輯路徑 ovn-trace 輸出中對應的行(F1) 1 vm-a 送出 ping 來源 192.168.13.252 → 目的 8.8.8.8;目的 MAC 是閘道 $GW_MAC 行 1 # icmp,…,nw_src=192.168.13.252,nw_dst=8.8.8.8,… 2 Logical Switch c13-private SG 檢查「離開 VM」的方向(from-lport) egress 全部放行 → 繼續往 Router 送 行 2 ct_next(ct_state=new|trk) { 新連線,SG 放行;被擋的話大括號裡會是空的 3 Logical Router c13-router TTL 64 → 63;查路由 0.0.0.0/0 → 192.168.100.1 改由對外埠送出,來源 MAC 換成對外埠的 MAC 行 3–4 ip.ttl--; eth.src = fa:16:3e:f1:b7:52; 4 還不知道下一跳的 MAC:先送 ARP 「誰是 192.168.100.1?請回覆 192.168.100.185」 實際網路中封包會等 ARP 回應後才送出 行 5–13 arp { arp.spa = 0xc0a864b9; → 192.168.100.185 arp.tpa = 0xc0a86401; → 192.168.100.1 } 5 Gateway(cr-lrp,落在 ctl003)做 NAT 改寫來源 IP;改成什麼,取決於這台 VM 有沒有 FIP F1:vm-a(有 FIP) .252 → .156 dnat_and_snat F2:vm-b(沒有 FIP) .82 → .185 snat(Router 對外位址) 行 14–16 ct_dnat /* assuming no un-dnat entry … */ { ct_next(ct_state=est|trk …) { ct_snat(ip4.src=192.168.100.156) { F2 沒有 ct_dnat 那一行,且最後是: ct_snat(ip4.src=192.168.100.185) { 6 Logical Switch public1 → localnet 埠 provnet-406a2d → ctl003 的 br-ex → 實體網路 E 節看到的 Provider 網路出口 行 17 output("provnet-406a2d"); 有 output = 封包成功送出
ovn-trace F1 / F2:封包從 VM 到外部網路的邏輯路徑

逐段說明

行發生在哪裡白話說明
1—你送進去的封包:從 vm-a(.252)送往閘道 MAC、目的 8.8.8.8 的 ping
2c13-private(Switch)封包進入 conntrack,被當成新連線(new)。SG 的 egress 規則(B4 的 from-lport … allow-related)放行,所以繼續往下;若被擋,大括號裡會是空的
3c13-router(Router)經過一個 Router,TTL 從 64 減為 63
4c13-router查路由表(B2 的 0.0.0.0/0 → 192.168.100.1),決定從對外埠送出,來源 MAC 換成對外埠的 MAC fa:16:3e:f1:b7:52(B2 的 lrp-5c6c01e8-…)
5–13c13-router → public1Router 還不知道下一跳 192.168.100.1 的 MAC,所以目的 MAC 先是全 0,並先送一個 ARP 詢問。arp.spa 是詢問者的 IP、arp.tpa 是要查的 IP,以 16 進位表示(見下方換算)。ARP 從 public1 的兩個埠送出:e474ff 是 public1 的 metadata localport,provnet-406a2d 是通往實體網路的 localnet 埠
14c13-router 的 Gateway(cr-lrp,在 ctl003)檢查這個封包是不是某條 DNAT 連線的回應;不是,所以不變
15同上第二次進入 conntrack。--ct 只指定了第一次的狀態,這次 ovn-trace 用預設的 est(已建立),註解 default (use --ct to customize) 就是在說這件事
16同上做 SNAT:來源 IP 從 192.168.13.252 改成 192.168.100.156,就是 vm-a 的 Floating IP,對應 B2 的 dnat_and_snat
17public1(Switch)從 localnet 埠 provnet-406a2d 送出,也就是 E 節的 provnet-406a2db9-…,接著經 ctl003 的 br-ex 進入實體網路

16 進位 IP 換算:IP 的四段各是 0–255,正好是兩位 16 進位數。

16 進位拆成四段換成 10 進位是誰
0xc0a864b9c0 a8 64 b9192.168.100.185Router 的對外位址(詢問者)
0xc0a86401c0 a8 64 01192.168.100.1實體路由器(要查 MAC 的對象)

F2. vm-b(沒有 FIP)連到 8.8.8.8

指令和 F1 相同,只是來源換成 vm-b:

指令
# [jmp001]
ssh 10.127.0.4 "sudo docker exec ovn_northd ovn-trace --minimal --ct new neutron-$NET 'inport==\"$PORT_B\" && eth.src==$MAC_B && ip4.src==$IP_B && eth.dst==$GW_MAC && ip4.dst==8.8.8.8 && ip.ttl==64 && icmp4.type==8'"

預期輸出:

預期輸出
# icmp,reg14=0x4,vlan_tci=0x0000,dl_src=fa:16:3e:e8:53:d8,dl_dst=fa:16:3e:61:02:68,nw_src=192.168.13.82,nw_dst=8.8.8.8,nw_tos=0,nw_ecn=0,nw_ttl=64,nw_frag=no,icmp_type=8,icmp_code=0
ct_next(ct_state=new|trk) {
    ip.ttl--;
    eth.src = fa:16:3e:f1:b7:52;
    eth.dst = 00:00:00:00:00:00;
    arp {
        eth.dst = ff:ff:ff:ff:ff:ff;
        arp.spa = 0xc0a864b9;
        arp.tpa = 0xc0a86401;
        arp.op = 1;
        output("e474ff");
        output("provnet-406a2d");
    };
    ct_next(ct_state=est|trk /* default (use --ct to customize) */) {
        ct_snat(ip4.src=192.168.100.185) {
            output("provnet-406a2d");
        };
    };
};

和 F1 逐項比較:

F1:vm-a(有 FIP)F2:vm-b(沒有 FIP)
第一行的 nw_src192.168.13.252192.168.13.82
TTL、換 MAC、ARP(行 3–13)相同相同:同一個 Router、同一個出口
ct_dnat 那一行有沒有:vm-b 沒有 FIP,不需要檢查 DNAT
ct_snat 改成192.168.100.156(自己的 FIP)192.168.100.185(Router 的對外位址)
對應 B2 的哪條 NATdnat_and_snatsnat

同一個 Router、同一個 Subnet,有沒有 FIP 決定了出外網時的來源位址(C12 p.28)。外面的主機看到 .156 的封包,知道是 vm-a;看到 .185 的封包,分不出是子網裡哪一台。

F3. Security Group:vm-a → vm-b 的 ICMP 與 TCP 80

這次封包不出外網,而是從 vm-a 直接送到同一個子網的 vm-b,所以目的 MAC 直接填 vm-b 的 MAC。兩個指令只差最後的協定:

指令
# [jmp001]
ssh 10.127.0.4 "sudo docker exec ovn_northd ovn-trace --minimal --ct new neutron-$NET 'inport==\"$PORT_A\" && eth.src==$MAC_A && ip4.src==$IP_A && eth.dst==$MAC_B && ip4.dst==$IP_B && ip.ttl==64 && icmp4.type==8'"
ssh 10.127.0.4 "sudo docker exec ovn_northd ovn-trace --minimal --ct new neutron-$NET 'inport==\"$PORT_A\" && eth.src==$MAC_A && ip4.src==$IP_A && eth.dst==$MAC_B && ip4.dst==$IP_B && ip.ttl==64 && tcp.dst==80'"

預期輸出:

預期輸出
# icmp,reg14=0x3,vlan_tci=0x0000,dl_src=fa:16:3e:73:0f:22,dl_dst=fa:16:3e:e8:53:d8,nw_src=192.168.13.252,nw_dst=192.168.13.82,nw_tos=0,nw_ecn=0,nw_ttl=64,nw_frag=no,icmp_type=8,icmp_code=0
ct_next(ct_state=new|trk) {
    ct_next(ct_state=est|trk /* default (use --ct to customize) */) {
        output("f72217");
    };
};
# tcp,reg14=0x3,vlan_tci=0x0000,dl_src=fa:16:3e:73:0f:22,dl_dst=fa:16:3e:e8:53:d8,nw_src=192.168.13.252,nw_dst=192.168.13.82,nw_tos=0,nw_ecn=0,nw_ttl=64,nw_frag=no,tp_src=0,tp_dst=80,tcp_flags=0
ct_next(ct_state=new|trk) {
    ct_next(ct_state=est|trk /* default (use --ct to customize) */);
};
ovn-trace F3:同一子網內,SG 決定封包能不能進入 vm-b vm-a 送往 vm-b 的封包只經過 Logical Switch c13-private,不經過 Router。第一個檢查點是離開 vm-a 的 from-lport,egress 全部放行;第二個檢查點是進入 vm-b 的 to-lport,c13-sg 只放行 ICMP 與 TCP 22。所以 ICMP 送達 vm-b,ovn-trace 輸出 output("f72217");TCP 80 在第二個檢查點被丟棄,輸出沒有 output。實際封包會經 scp001 到 scp002 的 Geneve tunnel,但 ovn-trace 不顯示。 Logical Switch c13-private(同一子網,不經過 Router) c13-vm-a 192.168.13.252 在 scp001 c13-vm-b 192.168.13.82 在 scp002 ① 離開 vm-a(from-lport) egress 全部放行 ② 進入 vm-b(to-lport) c13-sg 只放行 ICMP、TCP 22 ICMP(ping) 符合 icmp4 → 放行 TCP 80 在 ② 沒有規則符合 → 被 neutron_pg_drop 丟棄 ovn-trace 輸出(ICMP) ct_next(ct_state=new|trk) { ← ① ct_next(ct_state=est|trk …) { ← ② output("f72217"); ← 送到 vm-b ovn-trace 輸出(TCP 80) ct_next(ct_state=new|trk) { ← ① ct_next(ct_state=est|trk …); ← ② (沒有 output = 被丟棄) 實際封包:scp001 的 br-int → Geneve tunnel(ovn-scp002-0)→ scp002 的 br-int;ovn-trace 只模擬邏輯層,不顯示這段
ovn-trace F3:同一子網內,SG 決定封包能不能進入 vm-b

逐段說明

ICMP(ping)TCP 80
第一個 ct_next離開 vm-a:egress 規則放行離開 vm-a:egress 規則放行
第二個 ct_next要進入 vm-b:符合 to-lport … icmp4 allow-related要進入 vm-b:沒有任何放行規則符合
結果output("f72217"):送到 vm-b 的埠($PORT_B 前 6 碼)大括號後直接是 ;,沒有 output:被丟棄

延伸:ofproto/trace(p.17):ovn-trace 走得通、實際卻不通時,到 VM 所在節點用 ovs-appctl ofproto/trace br-int in_port=<D2 的 ofport>,… 追蹤本機 br-int 上真正的 OpenFlow 規則。輸出很長,本課不實作。


小結:同一組資源在四層中的樣子

Neutron 資源B. NB(ovn-nbctl)C. SB(ovn-sbctl)D. OVS(ovs-vsctl)F. ovn-trace
Network c13-privateswitch … (neutron-$NET)——第一個參數 neutron-$NET
vm-a Portport 1021e30d-…Port_Binding 在 scp001tap1021e30d-24,iface-id = Port IDinport
Router c13-routerrouter … (neutron-$ROUTER)——eth.dst = $GW_MAC 進入 Router,ip.ttl--
Router Gateway Portlrp-5c6c01e8-…、gateway chassiscr-lrp-5c6c01e8-… 在 ctl003ctl 上的 br-exeth.src = fa:16:3e:f1:b7:52
vm-a 的 FIPnat dnat_and_snat——ct_snat(…156)
Router SNATnat snat——ct_snat(…185)(vm-b)
c13-sgPort Group + ACL——ICMP output、TCP 80 無 output