-------------------------------------------------------------------------------------------------------------------------------------

網絡虛擬化是虛擬化技術中最復雜的部分,學習難度最大。 但因為網絡是虛擬化中非常重要的資源,所以再硬的骨頭也必須要把它啃下來。
為了讓大家對虛擬化網絡的復雜程度有一個直觀的認識,請看下圖

這是 OpenStack 官網上給出的計算節點(可以理解為 KVM 的宿主機)虛擬網絡的邏輯圖,上面的網絡設備很多,層次也很復雜。
我第一次看到這張圖,也着實被嚇了一跳。
不過大家也不要怕,萬丈高樓從地起,虛擬網絡再復雜,也是由一些基礎的組件構成的。只要我們將這些基礎組件的概念和它們之間的邏輯關系搞清楚了,就能深刻理解虛擬網絡的架構,那么雲環境下的虛擬化網絡也就不在話下了。
下面我們來學習網絡虛擬化中最重要的兩個東西:Linux Bridge 和 VLAN
Linux Bridge 基本概念
假設宿主機有 1 塊與外網連接的物理網卡 eth0,上面跑了 1 個虛機 VM1,現在有個問題是: 如何讓 VM1 能夠訪問外網?
至少有兩種方案
-
將物理網卡eth0直接分配給VM1,但隨之帶來的問題很多: 宿主機就沒有網卡,無法訪問了; 新的虛機,比如 VM2 也沒有網卡。 下面看推薦的方案
-
給 VM1 分配一個虛擬網卡 vnet0,通過 Linux Bridge br0 將 eth0 和 vnet0 連接起來,如下圖所示

Linux Bridge 是 Linux 上用來做 TCP/IP 二層協議交換的設備,其功能大家可以簡單的理解為是一個二層交換機或者 Hub。多個網絡設備可以連接到同一個 Linux Bridge,當某個設備收到數據包時,Linux Bridge 會將數據轉發給其他設備。
在上面這個例子中,當有數據到達 eth0 時,br0 會將數據轉發給 vnet0,這樣 VM1 就能接收到來自外網的數據; 反過來,VM1 發送數據給 vnet0,br0 也會將數據轉發到 eth0,從而實現了 VM1 與外網的通信。
現在我們增加一個虛機 VM2,如下圖所示

VM2 的虛擬網卡 vnet1 也連接到了 br0 上。 現在 VM1 和 VM2 之間可以通信,同時 VM1 和 VM2 也都可以與外網通信。
有了上面的基礎知識,下一節將演示如何在實驗環境中實現這套虛擬網絡

本節將演示如何在實驗環境中實現下圖所示的虛擬網絡

配置 Linux Bridge br0
編輯 /etc/network/interfaces,配置 br0。
下面用 vimdiff 展示了對 /etc/network/interfaces 的修改

有兩點需要注意: 1. 之前宿主機的 IP 是通過 dhcp 配置在 eth0 上的;創建 Linux Bridge 之后,IP 就必須放到 br0 上了 2. 在 br0 的配置信息中請注意最后一行 “bridge_ports eth0”,其作用就是將 eth0 掛到 br0 上
重啟宿主機,查看 IP 配置,可以看到 IP 已經放到 br0 上了
# ifconfig br0 Link encap:Ethernet HWaddr 00:0c:29:8d:ec:be inet addr:192.168.111.107 Bcast:192.168.111.255 Mask:255.255.255.0 inet6 addr: fe80::20c:29ff:fe8d:ecbe/64 Scope:Link UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 RX packets:22573 errors:0 dropped:0 overruns:0 frame:0 TX packets:2757 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:0 RX bytes:4927580 (4.9 MB) TX bytes:368895 (368.8 KB)
eth0 Link encap:Ethernet HWaddr 00:0c:29:8d:ec:be inet6 addr: fe80::20c:29ff:fe8d:ecbe/64 Scope:Link UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 RX packets:24388 errors:0 dropped:0 overruns:0 frame:0 TX packets:2816 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:1000 RX bytes:5590438 (5.5 MB) TX bytes:411558 (411.5 KB)
lo Link encap:Local Loopback inet addr:127.0.0.1 Mask:255.0.0.0 inet6 addr: ::1/128 Scope:Host UP LOOPBACK RUNNING MTU:65536 Metric:1 RX packets:146 errors:0 dropped:0 overruns:0 frame:0 TX packets:146 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:0 RX bytes:10521 (10.5 KB) TX bytes:10521 (10.5 KB)
virbr0 Link encap:Ethernet HWaddr 72:db:fb:f2:19:91 inet addr:192.168.122.1 Bcast:192.168.122.255 Mask:255.255.255.0 UP BROADCAST MULTICAST MTU:1500 Metric:1 RX packets:0 errors:0 dropped:0 overruns:0 frame:0 TX packets:0 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:0 RX bytes:0 (0.0 B) TX bytes:0 (0.0 B)
用 brctl show 查看當前 Linux Bridge 的配置。 eth0 已經掛到 br0 上了
# brctl show bridge name bridge id STP enabled interfaces br0 8000.000c298decbe no eth0 virbr0 8000.000000000000 yes
除了 br0,大家應該注意到還有一個 virbr0 的 Bridge,而且 virbr0 上已經配置了 IP 地址 192.168.122.1。 virbr0 的作用我們會在后面介紹。
在宿主機中 CloudMan 已經提前創建好了虛機 VM1 和 VM2,現在都處於關機狀態
# virsh list --all Id Name State ---------------------------------------------------- - VM1 shut off - VM2 shut off
配置 VM1
下面我們在 virt-manager 中查看一下 VM1 的網卡配置(為了使大家能夠熟練使用命令行工具 virsh 和圖形工具 virt-manager,CloudMan 在演示的時候會同時用到它們,兩個工具都很重要)

可以看到虛擬網卡的 source device 我們選擇的是 br0
下面我們啟動 VM1,看會發生什么
# virsh start VM1 Domain VM1 started
# brctl show
bridge name bridge id STP enabled interfaces br0 8000.000c298decbe no eth0 vnet0 virbr0 8000.000000000000 yes
brctl show 告訴我們 br0 下面添加了一個 vnet0 設備,通過 virsh 確認這就是VM1的虛擬網卡。
# virsh domiflist VM1 Interface Type Source Model MAC ------------------------------------------------------- vnet0 bridge br0 rtl8139 52:54:00:75:dd:1a
VM1 的 IP 是 DHCP 獲得的(設置靜態 IP 當然也可以),通過 virt-manager 控制台登錄 VM1,查看 IP。
# ifconfig eth0 Link encap:Ethernet HWaddr 52:54:00:75:dd:1a inet addr:192.168.111.106 Bcast:192.168.111.255 Mask:255.255.255.0 inet6 addr: fe80::5054:ff:fe75:dd1a/64 Scope:Link UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 RX packets:400 errors:0 dropped:0 overruns:0 frame:0 TX packets:101 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:1000 RX bytes:41950 (41.9 KB) TX bytes:12583 (12.5 KB)
VM1 通過 DHCP 拿到的 IP 是 192.168.111.106,與宿主機(IP為192.168.111.107)是同一個網段。Ping 一下外網
root@VM1:~# ping www.baidu.com PING www.a.shifen.com (180.97.33.108) 56(84) bytes of data. 64 bytes from 180.97.33.108: icmp_seq=1 ttl=53 time=34.9 ms 64 bytes from 180.97.33.108: icmp_seq=2 ttl=53 time=36.2 ms 64 bytes from 180.97.33.108: icmp_seq=3 ttl=53 time=38.8 ms
沒問題,可以訪問。
另外,在 VM1 中虛擬網卡是 eth0,並不是 vnet0。 vent0 是該虛擬網卡在宿主機中對應的設備名稱,其類型是 TAP 設備,這里需要注意一下。
配置 VM2
跟 VM1 一樣,VM2 的虛擬網卡也掛在 br0上,啟動 VM1,查看網卡信息
# virsh start VM2 Domain VM2 started
# brctl show
bridge name bridge id STP enabled interfaces br0 8000.000c298decbe no eth0 vnet0 vnet1 virbr0 8000.000000000000 yes
br0 下面多了 vnet1,通過 virsh 確認這就是 VM2 的虛擬網卡。
# virsh domiflist VM2 Interface Type Source Model MAC ------------------------------------------------------- vnet0 bridge br0 rtl8139 52:54:00:cf:33:a1
VM2 通過 DHCP 拿到的 IP 是 192.168.111.108,登錄 VM2,驗證網絡的連通性
Ping VM1
root@VM2:~# ping VM1 PING VM1 (192.168.111.106) 56(84) bytes of data. 64 bytes from 192.168.111.106: icmp_seq=1 ttl=64 time=4.54 ms 64 bytes from 192.168.111.106: icmp_seq=2 ttl=64 time=1.63 ms 64 bytes from 192.168.111.106: icmp_seq=3 ttl=64 time=2.16 ms
Ping 宿主機
root@VM2:~# ping 192.168.111.107 PING 192.168.111.107 (192.168.111.107) 56(84) bytes of data. 64 bytes from 192.168.111.107: icmp_seq=1 ttl=64 time=1.02 ms 64 bytes from 192.168.111.107: icmp_seq=2 ttl=64 time=0.052 ms 64 bytes from 192.168.111.107: icmp_seq=3 ttl=64 time=0.064 ms
Ping 外網
root@VM2:~# ping www.baidu.com PING www.a.shifen.com (180.97.33.107) 56(84) bytes of data. 64 bytes from 180.97.33.107: icmp_seq=1 ttl=53 time=53.9 ms 64 bytes from 180.97.33.107: icmp_seq=2 ttl=53 time=45.0 ms 64 bytes from 180.97.33.107: icmp_seq=3 ttl=53 time=44.2 ms
可見,通過 br0 這個 Linux Bridge,我們實現了 VM1、VM2、宿主機和外網這四者之間的數據通信。
下節我們討論 virbr0

virbr0 是 KVM 默認創建的一個 Bridge,其作用是為連接其上的虛機網卡提供 NAT 訪問外網的功能。
virbr0 默認分配了一個IP 192.168.122.1,並為連接其上的其他虛擬網卡提供 DHCP 服務。
下面我們演示如何使用 virbr0。
在 virt-manager 打開 VM1 的配置界面,網卡 Source device 選擇 “default”,將 VM1 的網卡掛在 virbr0 上。
啟動 VM1,brctl show 可以查看到 vnet0 已經掛在了 virbr0 上。
# brctl show bridge name bridge id STP enabled interfaces br0 8000.000c298decbe no eth0 virbr0 8000.fe540075dd1a yes vnet0
用 virsh 命令確認 vnet 就是 VM1 的虛擬網卡。
# virsh domiflist VM1 Interface Type Source Model MAC ------------------------------------------------------- vnet0 network default rtl8139 52:54:00:75:dd:1a
virbr0 使用 dnsmasq 提供 DHCP 服務,可以在宿主機中查看該進程信息
# ps -elf|grep dnsmasq
5 S libvirt+ 2422 1 0 80 0 - 7054 poll_s 11:26 ? 00:00:00 /usr/sbin/dnsmasq --conf-file=/var/lib/libvirt/dnsmasq/default.conf
在 /var/lib/libvirt/dnsmasq/ 目錄下有一個 default.leases 文件,當 VM1 成功獲得 DHCP 的 IP 后,可以在該文件中查看到相應的信息
# cat /var/lib/libvirt/dnsmasq/default.leases 1441525677 52:54:00:75:dd:1a 192.168.122.6 ubuntu *
上面顯示 192.168.122.6 已經分配給 MAC 地址為 52:54:00:75:dd:1a 的網卡,這正是 vnet0 的 MAC。之后就可以使用該 IP 訪問 VM1 了。
# ssh 192.168.122.6 root@192.168.122.6's password: Welcome to Ubuntu 14.04.2 LTS (GNU/Linux 3.16.0-30-generic x86_64) Last login: Sun Sep 6 01:30:23 2015 root@VM1:~# ifconfig eth0 Link encap:Ethernet HWaddr 52:54:00:75:dd:1a inet addr:192.168.122.6 Bcast:192.168.122.255 Mask:255.255.255.0 inet6 addr: fe80::5054:ff:fe75:dd1a/64 Scope:Link UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 RX packets:61 errors:0 dropped:0 overruns:0 frame:0 TX packets:66 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:1000 RX bytes:7453 (7.4 KB) TX bytes:8649 (8.6 KB)
Ping一下外網。
root@VM1:~# ping www.baidu.com PING www.a.shifen.com (180.97.33.107) 56(84) bytes of data. 64 bytes from 180.97.33.107: icmp_seq=1 ttl=52 time=36.9 ms 64 bytes from 180.97.33.107: icmp_seq=2 ttl=52 time=119 ms 64 bytes from 180.97.33.107: icmp_seq=3 ttl=52 time=88.5 ms 64 bytes from 180.97.33.107: icmp_seq=4 ttl=52 time=38.0 ms 64 bytes from 180.97.33.107: icmp_seq=5 ttl=52 time=122 ms
沒有問題,可以訪問外網,說明 NAT 起作用了。
需要說明的是,使用 NAT 的虛機 VM1 可以訪問外網,但外網無法直接訪問 VM1。 因為 VM1 發出的網絡包源地址並不是 192.168.122.6,而是被 NAT 替換為宿主機的 IP 地址了。
這個與使用 br0 不一樣,在 br0 的情況下,VM1 通過自己的 IP 直接與外網通信,不會經過 NAT 地址轉換。
下節我們討論 vlan 在 linux bridge 中的實現

LAN 表示 Local Area Network,本地局域網,通常使用 Hub 和 Switch 來連接 LAN 中的計算機。
一般來說,兩台計算機連入同一個 Hub 或者 Switch 時,它們就在同一個 LAN 中。
一個 LAN 表示一個廣播域。 其含義是:LAN 中的所有成員都會收到任意一個成員發出的廣播包。
VLAN 表示 Virtual LAN。一個帶有 VLAN 功能的switch 能夠將自己的端口划分出多個 LAN。
計算機發出的廣播包可以被同一個 LAN 中其他計算機收到,但位於其他 LAN 的計算機則無法收到。 簡單地說,VLAN 將一個交換機分成了多個交換機,限制了廣播的范圍,在二層將計算機隔離到不同的 VLAN 中。
比方說,有兩組機器,Group A 和 B。
我們想配置成 Group A 中的機器可以相互訪問,Group B 中的機器也可以相互訪問,但是 A 和 B 中的機器無法互相訪問。
一種方法是使用兩個交換機,A 和 B 分別接到一個交換機。 另一種方法是使用一個帶 VLAN 功能的交換機,將 A 和 B 的機器分別放到不同的 VLAN 中。
請注意,VLAN 的隔離是二層上的隔離,A 和 B 無法相互訪問指的是二層廣播包(比如 arp)無法跨越 VLAN 的邊界。
但在三層上(比如IP)是可以通過路由器讓 A 和 B 互通的。概念上一定要分清。
現在的交換機幾乎都是支持 VLAN 的。 通常交換機的端口有兩種配置模式: Access 和 Trunk。看下圖

Access 口
這些端口被打上了 VLAN 的標簽,表明該端口屬於哪個 VLAN。 不同 VLAN 用 VLAN ID 來區分,VLAN ID 的 范圍是 1-4096。 Access 口都是直接與計算機網卡相連的,這樣從該網卡出來的數據包流入 Access 口后就被打上了所在 VLAN 的標簽。 Access 口只能屬於一個 VLAN。
Trunk 口
假設有兩個交換機 A 和 B。 A 上有 VLAN1(紅)、VLAN2(黃)、VLAN3(藍);B 上也有 VLAN1、2、3 那如何讓 AB 上相同 VLAN 之間能夠通信呢?
辦法是將 A 和 B 連起來,而且連接 A 和 B 的端口要允許 VLAN1、2、3 三個 VLAN 的數據都能夠通過。
這樣的端口就是Trunk口了。 VLAN1、2、3 的數據包在通過 Trunk 口到達對方交換機的過程中始終帶着自己的 VLAN 標簽。
了解了 VLAN 的概念之后,我們來看 KVM 虛擬化環境下是如何實現 VLAN 的。還是先看圖,

eth0 是宿主機上的物理網卡,有一個命名為 eth0.10 的子設備與之相連。 eth0.10 就是 VLAN 設備了,其 VLAN ID 就是 VLAN 10。 eth0.10 掛在命名為 brvlan10 的 Linux Bridge 上,虛機 VM1 的虛擬網卡 vent0 也掛在 brvlan10 上。
這樣的配置其效果就是: 宿主機用軟件實現了一個交換機(當然是虛擬的),上面定義了一個 VLAN10。 eth0.10,brvlan10 和 vnet0 都分別接到 VLAN10 的 Access口上。而 eth0 就是一個 Trunk 口。
VM1 通過 vnet0 發出來的數據包會被打上 VLAN10 的標簽。
eth0.10 的作用是:定義了 VLAN10 brvlan10 的作用是:Bridge 上的其他網絡設備自動加入到 VLAN10 中
我們再增加一個 VLAN20,見下圖

這樣虛擬交換機就有兩個 VLAN 了,VM1 和 VM2 分別屬於 VLAN10 和 VLAN20。 對於新創建的虛機,只需要將其虛擬網卡放入相應的 Bridge,就能控制其所屬的 VLAN。
VLAN 設備總是以母子關系出現,母子設備之間是一對多的關系。 一個母設備(eth0)可以有多個子設備(eth0.10,eth0.20 ……),而一個子設備只有一個母設備

本節我們來看如何在實驗環境中實施和配置如下 VLAN 網絡
配置 VLAN
編輯 /etc/network/interfaces,配置 eth0.10、brvlan10、eth0.20 和 brvlan20。
下面用 vmdiff 展示了對 /etc/network/interfaces 的修改
重啟宿主機,ifconfig 各個網絡接口
用 brctl show 查看當前 Linux Bridge 的配置。 eth0.10 和 eth0.20 分別掛在 brvlan10 和 brvlan20上 了
在宿主機中已經提前創建好了虛機 VM1 和 VM2,現在都處於關機狀態
配置 VM1
在 virt-manager 中將 VM1 的虛擬網卡掛到 brvlan10 上。
啟動 VM1
查看 Bridge,發現 brvlan10 已經連接了一個 vnet0 設備
通過 virsh 確認這就是 VM1 的虛擬網卡。
配置VM2
類似的,將 VM2 的網卡掛在 brvlan20 上
啟動 VM2
查看 Bridge,發現 brvlan20 已經連接了一個 vnet1 設備
通過 virsh 確認這就是 VM2 的虛擬網卡。
驗證 VLAN 的隔離性
為了驗證 VLAN10 和 VLAN20 之間的隔離,我們為 VM1 和 VM2 配置同一網段的 IP。
配置 VM1 的 IP

配置 VM2 的 IP
Ping 測試結果: VM1 與 VM2 是不通的
原因如下: 1. VM2 向 VM1 發 Ping 包之前,需要知道 VM1 的 IP 192.168.100.10 所對應的 MAC 地址。VM2 會在網絡上廣播 ARP 包,其作用就是問 “誰知道 192.168.100.10 的 MAC 地址是多少?” 2. ARP 是二層協議,VLAN 的隔離作用使得 ARP 只能在 VLAN20 范圍內廣播,只有 brvlan20 和 eth0.20 能收到,VLAN10 里的設備是收不到的。VM1 無法應答 VM2 發出的ARP包。 3. VM2 拿不到 VM1 vnet0 的 MAC 地址,也就 Ping 不到 VM1。
Linux Bridge + VLAN = 虛擬交換機
現在對 KVM 的網絡虛擬化做個總結。
-
物理交換機存在多個 VLAN,每個 VLAN 擁有多個端口。 同一 VLAN 端口之間可以交換轉發,不同 VLAN 端口之間隔離。 所以交換機其包含兩層功能:交換與隔離。
-
Linux 的 VLAN 設備實現的是隔離功能,但沒有交換功能。 一個 VLAN 母設備(比如 eth0)不能擁有兩個相同 ID 的 VLAN 子設備,因此也就不可能出現數據交換情況。
-
Linux Bridge 專門實現交換功能。 將同一 VLAN 的子設備都掛載到一個 Bridge 上,設備之間就可以交換數據了。
總結起來,Linux Bridge 加 VLAN 在功能層面完整模擬現實世界里的二層交換機。
eth0 相當於虛擬交換機上的 trunk 口,允許 vlan10 和 vlan20 的數據通過 eth0.10,vent0 和 brvlan10 都可以看着 vlan10 的 access 口 eth0.20,vent1 和 brvlan20 都可以看着 vlan20 的 access 口
