1. 程式人生 > linux教程 > Linux網路虛擬化:network namespace的學習

Linux網路虛擬化:network namespace的學習

簡介

network namespace是linux核心提供的一個功能,是用來實現網路虛擬化的重要功能,可以建立多個相互隔離的網路空間,它們有獨自的網路棧資訊。不管是虛擬機器還是容器,執行的時候彷彿自己就在獨立的網路中。Docker的網路隔離實現便用到了該技術。
主要是實現方法是使用ip命令,由於需要修改系統的網路配置,因此需要使用root身份。
關於network namespace的命令都是使用ip netns子命令,可以輸入ip netns help來獲取命令幫助資訊。

[email protected]102-49-ubuntu:/home/ubuntu# ip netns helpUsage: ip netns list ip netns add NAME ip netns set NAME NETNSID ip [-all] netns delete [NAME] ip netns identify [PID] ip netns pids NAME ip [-all] netns exec [NAME] cmd ... ip netns monitor ip netns list-id

ip netns list可以使用ip netns ls來代替,剛開始顯然是什麼都沒有,命令什麼也不會輸出。
這時候嘗試建立一個network namespace,名字就叫net0

[email protected]:/home/ubuntu# ip netns add [email protected]:/home/ubuntu# ip netns lsnet0

這時候就建立成功了一個network namespace。這時候這個net0會被建立在/var/run/netns/中,可以進入到該目錄中驗證一下。如果想要一併去管理不是用netns建立的network namespace,比如容器或者虛擬機器建立的,那麼只要在這裡建立一個指向檔案的連結即可。
創建出來之後,那麼就相當於擁有了一個屬於自己的網路空間,會有自己獨立的網絡卡、路由表、ARP 表、iptables 等和網路相關的資源,當你想檢視該空間的資訊的時候,可以使用ip netns exec命令進入空間去執行相應的命令,相應的命令放到後面。例如,相差看這個network namespace的所有虛擬網絡卡資訊:

[email protected]:/home/ubuntu# ip netns exec net0 ip addr1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN group default qlen 1 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00

可以看到,裡邊只有一個預設的lo虛擬網絡卡,處於DOWN狀態,相當於是在net0這個network namespace裡邊執行的ip addr這個命令。
預設情況下,network namespace 是不能和主機網路,或者其他 network namespace 通訊的。但是這樣創建出來的network namespace就毫無用武之地了,所以得用ip link的子命令來進行連線,使得可以進行通訊,模擬一個網路叢集。現在我們來實現一下下面兩種典型情況。

實驗1.

Linux網路虛擬化:network namespace的學習
這個是最簡單的,直接將兩個network namespace連線起來。這時候需要一根網線來連線,這根網線就是採用的veth pair技術。這根網線是雙向的,而且當資料包來到某一端的時候,會無條件的傳輸到另一端。需要注意的是veth pair只能是成對存在的,只要建立就會成對的創建出來,一旦刪除其中一端,那麼另一端也會跟著消失。
我們先來建立一對veth pair:

[email protected]:/home/ubuntu# ip link add type veth

然後用ip link show命令來檢視所有的虛擬裝置資訊,會看到最後兩個分別是veth0和veth1,即我們剛剛創建出來的兩個veth pair,當然也可以用ip link add name1 type veth peer name name2來創建出兩個指定名字為name1和name2的veth pair。接下來開始進行環境的搭建。先建立另一個network namespace:

[email protected]:/home/ubuntu# ip netns add [email protected]:/home/ubuntu# ip netns lsnet1net0

然後將其中一端veth0掛到net0上,配置上本地ip地址並且啟動它:

[email protected]:/home/ubuntu# ip link set veth0 netns [email protected]:/home/ubuntu# ip netns exec net0 ip link set veth0 [email protected]:/home/ubuntu# ip netns exec net0 ip addr add 10.0.1.1/24 dev veth0

再這中間可以不斷用ip netns exec net0 ip addr來觀察變化,加深理解。同理,將veth1也這樣掛到net1上,配置上本地ip地址並且啟動它。
這時候虛擬網路叢集就搭建起來了,我們來ping一下驗證:

[email protected]102-49-ubuntu:/home/ubuntu# ip netns exec net0 ping -c 3 10.0.1.2PING 10.0.1.2 (10.0.1.2) 56(84) bytes of data.64 bytes from 10.0.1.2: icmp_seq=1 ttl=64 time=0.079 ms64 bytes from 10.0.1.2: icmp_seq=2 ttl=64 time=0.051 ms64 bytes from 10.0.1.2: icmp_seq=3 ttl=64 time=0.050 ms--- 10.0.1.2 ping statistics ---3 packets transmitted, 3 received, 0% packet loss, time 1998msrtt min/avg/max/mdev = 0.050/0.060/0.079/0.013 ms

很顯然,ping通了,可以再用net1去ping net0試一下,結果也是一樣。因此搭建成功。

實驗2.

Linux網路虛擬化:network namespace的學習
上一個是僅僅兩個虛擬網路的情況下,那麼如果多了起來,再去兩兩進行配對就明顯不太可行了。所以引入了網橋bridge這個技術。將網路全都與bridge連通,那麼所有的兩兩之間的網路就都可以通訊了。
首先建立兩個network namespace:

ip netns add net0ip netns add net1

然後建立網橋且開啟它:

ip link add br0 type bridgeip link set dev br0 up

然後建立第一對veth pair,像實驗1一樣,將veth pair的一端掛在net0上,並且給配上ip地址啟動它:

ip link add type vethip link set dev veth0 netns net0ip netns exec net0 ip link set dev veth0 name eth0ip netns exec net0 ip addr add 10.0.1.1/24 dev eth0ip netns exec net0 ip link set dev eth0 up

再將另一端掛在bridge上:

ip link set dev veth1 master br0ip link set dev veth1 up

這時候net0與bridge便可以通訊了。同理,將net1與bridge進行同樣的操作,使兩者可以通訊:

ip link set dev veth0 netns net1ip netns exec net1 ip link set dev veth0 name eth0ip netns exec net1 ip addr add 10.0.1.2/24 dev eth0ip netns exec net1 ip link set dev eth0 upip link set dev veth2 master br0ip link set dev veth2 up

這時候,這個環境就基本搭建完成了。但是別忘了去ping一下試試:

[email protected]102-49-ubuntu:/home/ubuntu# ip netns exec net0 ping -c 3 10.0.1.2PING 10.0.1.2 (10.0.1.2) 56(84) bytes of data.--- 10.0.1.2 ping statistics ---3 packets transmitted, 0 received, 100% packet loss, time 2007ms

咦?沒有ping通??
當我做到這一步的時候幾乎崩潰了,檢視各種環境變數都沒有問題。用tcpdump去抓包也沒有發現問題,再往下深究就牽扯到linux核心和網路協議棧的實現了,這塊基礎並不是很好。。後來去論壇求救,終於有一個大牛給出了原因:

原因是因為系統為bridge開啟了iptables功能,導致所有經過br0的資料包都要受iptables裡面規則的限制,而docker為了安全性,將iptables裡面filter表的FORWARD鏈的預設策略設定成了drop,於是所有不符合docker規則的資料包都不會被forward,導致你這種情況ping不通。
解決辦法有兩個,二選一:
1. 關閉系統bridge的iptables功能,這樣資料包轉發就不受iptables影響了:echo 0 > /proc/sys/net/bridge/bridge-nf-call-iptables
2. 為br0新增一條iptables規則,讓經過br0的包能被forward:iptables -A FORWARD -i br0 -j ACCEPT
第一種方法不確定會不會影響docker,建議用第二種方法。
大概分析過程:1.先跑一下你的命令,檢查相應的arp快取和br0裡面埠和mac地址的對應關係,發現沒有錯誤。2.注意到br0不轉發icmp報文,但轉發arp報文,說明二層通訊沒有問題,有可能是因為什麼原因將icmp報文過濾了。3.由於br0工作在第二層,所以開始根本沒有往iptables方面想,一直在網上搜索br0不轉發資料包的原因,直到發現有些文章提到有可能是iptables的原因。4.搜尋bridge跟iptables相關的配置,知道了有相應的選項控制bridge的資料包是否會呼叫iptables的規則進行過濾。5.檢視iptables的規則,發現經過docker配置後的iptables預設會丟棄掉forward的資料包。

然後用了方法2,添加了一條iptables規則。然後求著大牛給出了分析過程,網路基礎太差看不懂orz。。研究懂了再更新。。
最後再去ping一下驗證一下:

[email protected]102-49-ubuntu:~# ip netns exec net1 ping -c 3 10.0.1.1PING 10.0.1.1 (10.0.1.1) 56(84) bytes of data.64 bytes from 10.0.1.1: icmp_seq=1 ttl=64 time=0.078 ms64 bytes from 10.0.1.1: icmp_seq=2 ttl=64 time=0.081 ms64 bytes from 10.0.1.1: icmp_seq=3 ttl=64 time=0.078 ms--- 10.0.1.1 ping statistics ---3 packets transmitted, 3 received, 0% packet loss, time 1999msrtt min/avg/max/mdev = 0.078/0.079/0.081/0.001 ms

終於可以了!於是大功告成。
這時候研究過docker網路模式的可能會發現,實驗2的拓撲結構跟bridge網路模式基本是一樣的。當然要實現每個 namespace 對外網的訪問還需要額外的配置(設定預設閘道器,開啟 ip_forward,為網路新增 NAT 規則等)。