此篇文章轉自 http://pipul.org/2016/02/create-the-container-virtual-network-by-veth-model/
關於Network Namespace的原理不再詳解,請直接移步:Namespaces in operation, part 7: Network namespaces
但是需要注意的,這個文章里network namespace操作所使用的是最新內核&操作系統提供的非常便利的ip netns工具,不過這些工具在低版本的操作系統上都是不提供的。如果真的需要使用network namespace,最好通過netlink編程的方式來實現,直接基於操作系統調用來完成所有設備的虛擬化工作
我們知道在clone進程的時候使用CLONE_NEWNET參數可以創建一個新的獨立的network namespace,但是光有這個還是遠遠不夠的,所有網絡設備都沒有初始化、沒啟動,這個時候的容器就是一個完全的離線的容器,不在任何網絡里,也訪問不了任何網絡。
為了讓容器獨立能夠與外網接通,我們需要創建並初始化一些設備,讓容器內的網絡和外網互通,veth是一種比較簡單的方案
veth網絡的原理圖如下,當創建容器時,為每個容器創建一個veth-pair設備,veth0作為容器內的eth0網卡,veth1橋接到br0,通過br0來解決同宿主機跨容器間的網絡訪問問題。由於br0網橋使用的是虛擬ip,虛擬ip是不被外網識別,不被路由的,因此我們需要一些iptables規則將br0發出的數據包偽裝成本機發送出的數據包,從而打通容器內到外網的網絡
在基於veth的容器虛擬網絡模型里:
- 容器是一個虛擬的網絡節點,擁有獨立的網絡設施:lo設備、eth0網卡、ip地址、iptables防火牆規則等等
- 容器能夠訪問網絡
- 除非通過端口映射的方式來暴露容器特定的端口外,外網一律無法訪問容器。這個是當前這個方案的局限性
1. veth-pair設備
veth設備全稱為Virtual Enternet device,veth主要的目的是為了跨Network Namespace之間提供一種類似於Linux進程間通信的技術,所以veth總是成對出現的,例如veth0@veth1等等,其中veth0在一個Network Namespace內,而另一個veth1在另外一個Network Namespace,其中往veth設備上任意一端上RX到的數據,都會在另一端上以TX的方式發送出去,veth工作在L2數據鏈路層,veth-pair設備在轉發數據包過程中並不串改數據包內容
顯然,僅有veth-pair設備,容器是無法訪問網絡的。因為容器發出的數據包,實質上直接進入了veth1設備的協議棧里。如果容器需要訪問網絡,需要使用bridge等技術,將veth1接收到的數據包通過某種方式轉發出去
2 Linux bridge網橋
網橋是用於連接兩個不同網段的常見手段,不同網段通過網橋連接后就如同在一個網段一樣,它的工作原理很簡單,就是在L2數據鏈路層進行數據包轉發
網橋和路由很相似,都可以用來分發網絡數據包,當時他們本質上還是有很大不同的:路由工作在L3網絡層,使用路由協議,但網橋是工作在L2數據鏈路層的,它不使用路由協議,而是通過學習和緩存在鏈路上傳輸的數據包中的源地址以及物理層的輸入端口:
- 收到新數據包時,記錄源MAC地址和輸入端口
- 根據數據包中的目的MAC地址查找本地緩存,如果能找到對應的MAC地址記錄
- 若發現記錄不在本地網段,直接丟棄數據包
- 若發現記錄存在對應的端口,則將數據包直接從該端口轉發出去
- 如果本地緩存中找不到任何記錄,則在本網段內進行廣播
基本原理圖如下:
因此,通過Linux網橋來實現打通容器網絡是一個非常有效的方法,通過bridge,我們能做到:
- 連接同宿主機內所有容器的虛擬網絡
- 打通容器內網與外網,通過bridge將數據轉發到真實的物理網卡eth0
如上圖所示,我們有兩個容器A和B,A容器在獨立的Network ns虛擬內,B容器在獨立的Network ns虛擬網絡內,默認情況下,如果沒有bridge設備只有veth的話,容器只能夠與宿主機器進行通信,容器之間無法通信,容器與外網也無法通信。
但是通過bridge,我們就能同時解決這兩個問題,ns1中的veth設備連接ns1與Host,而ns2中的veth設備用來連接ns2與Host,將ns1中的veth-pair設備對端橋接到bridge0上,同時將ns2中的veth-pair設備對端橋接到bridge0上,由bridge0負責兩個ns之間以及ns與host之間的通信
3. 網絡設備的初始化
3.1 虛擬網卡IP
創建一個容器時,根據bridge所在的網段,為虛擬網卡分配一個獨立的IP地址即可
3.2 虛擬網絡內的路由策略
默認情況下,Linux內核在給網絡接口設定IP地址時,會創建一個特定路由策略,該路由策略會明確確定從本機發往同網段的數據包通過當前網絡接口發送出去。虛擬設備指定IP地址后的容器內的第一條路由規則如下:
# route -n Kernel IP routing table Destination Gateway Genmask Flags Metric Ref Use Iface 172.27.0.0 0.0.0.0 255.255.0.0 U 0 0 0 eth0
但是這條規則只能路由bridge0網段內的數據包,也就是說,只能完成bridge0下同網段內容器之間的數據通信,對於其他目的地址的數據包是不能夠被路由的,因為內核根本不知道通過哪個網卡發送出去。所以我們還需要增加一條默認路由規則,該默認路由聲明:當路由表中找不到任何路由規則可以路由當前數據包時,數據包將依據默認的路由策略轉發出去
# route add -net 0.0.0.0 gw 172.27.0.1 # route -n Kernel IP routing table Destination Gateway Genmask Flags Metric Ref Use Iface 172.27.0.0 0.0.0.0 255.255.0.0 U 0 0 0 eth0 0.0.0.0 172.27.0.1 0.0.0.0 UG 0 0 0 eth0
3.3 iptables防火牆規則
veth0設備和bridge0網橋是打通容器虛擬網絡與外網必要的基礎設施,但是這些網絡設備地址都是虛擬,這些虛擬地址發出的數據包在真實的網絡上是不可尋址和不可被路由的:
換句話說,你的數據包能夠發送出去,因為數據包中的目的地址是真實存在的,可被路由和尋址的,然后當目的主機發送響應數據包時,由於響應數據包中的目的地址指向了一個虛擬網絡,真實網絡上的網絡設備會以Destination Host Unreachable為由直接丟棄
因此,我們需要將容器內虛擬網絡發出的數據包偽裝成本機的網絡數據包發送出去,當接收到響應的數據包時,再將數據包轉換回來。這就是NAT的數據偽裝技術,NAT的基本工作原理是,當私有網主機和公共網主機通信的IP包經過NAT網關時,將IP包中的源IP或目的IP在私有IP和NAT的公共IP之間進行轉換