Linux 基礎知識 - 一文學會網絡虛擬化
原文地址:https://icyfenix.cn/immutable-infrastructure/network/linux-vnet.html
Linux 目前提供的八種名稱空間裏,網絡名稱空間無疑是隔離內容最多的一種,它爲名稱空間內的所有進程提供了全套的網絡設施,包括獨立的設備界面、路由表、ARP 表,IP 地址表、iptables/ebtables 規則、協議棧,等等。虛擬化容器是以 Linux 名稱空間的隔離性爲基礎來實現的,那解決隔離的容器之間、容器與宿主機之間、乃至跨物理網絡的不同容器間通信問題的責任,很自然也落在了 Linux 網絡虛擬化技術的肩上。本節裏,我們暫時放下容器編排、雲原生、微服務等這些上層概念,走入 Linux 網絡的底層世界,去學習一些與設備、協議、通信相關的基礎網絡知識。
本節的閱讀對象設定爲以實現業務功能爲主、平常並不直接接觸網絡設備的普通開發人員,對於平臺基礎設施的開發者或者運維人員,可能會顯得有點過於囉嗦或過於基礎了,如果你已經提前掌握了這些知識,完全可以快速閱讀,或者直接跳過部分內容。
網絡通信模型
如果拋開虛擬化,只談網絡的話,那首先應該瞭解的知識筆者認爲是 Linux 系統的網絡通信模型,即信息是如何從程序中發出,通過網絡傳輸,再被另一個程序接收到的。整體上看,Linux 系統的通信過程無論按理論上的 OSI 七層模型,還是以實際上的 TCP/IP 四層模型來進行,都明顯地呈現出 “逐層調用,逐層封裝” 的特點,這種逐層處理的方式與棧結構,譬如程序執行時的方法棧很類似,因此它通常被稱爲“Linux 網絡協議棧”,簡稱 “網絡棧”,有時也稱 “協議棧”。圖 12-1 體現了 Linux 網絡通信過程與 OSI 或者 TCP/IP 模型的對應關係,也展示了網絡棧中的數據流動的路徑。
圖中傳輸模型的左側,筆者特別標出了網絡棧在用戶與內核空間的部分,可見幾乎整個網絡棧(應用層以下)都位於系統內核空間之中,之所以採用這種設計,主要是從數據安全隔離的角度出發來考慮的。由內核去處理網絡報文的收發,無疑會有更高的執行開銷,譬如數據在內核態和用戶態之間來回拷貝的額外成本,因此會損失一些性能,但是能夠保證應用程序無法竊聽到或者去僞造另一個應用程序的通信內容。針對特別關注收發性能的應用場景,也有直接在用戶空間中實現全套協議棧的旁路方案,譬如開源的 Netmap 以及 Intel 的 DPDK,都能做到零拷貝收發網絡數據包。
圖中傳輸模型的箭頭展示的是數據流動的方向,它體現了信息從程序中發出以後,到被另一個程序接收到之前,將經歷如下幾個階段:
-
Socket:應用層的程序是通過 Socket 編程接口來和內核空間的網絡協議棧通信的。Linux Socket 是從 BSD Socket 發展而來的,現在 Socket 已經不侷限於某個操作系統的專屬功能,成爲各大主流操作系統共同支持的通用網絡編程接口,是網絡應用程序實際上的交互基礎。應用程序通過讀寫收和發緩衝區(Receive/Send Buffer)來與 Socket 進行交互,在 UNIX 和 Linux 系統中,出於 “一切皆是文件” 的設計哲學,對 Socket 操作被實現爲對文件系統(socketfs)的讀寫訪問操作,通過文件描述符(File Descriptor)來進行。
-
TCP/UDP:傳輸層協議族裏最重要的協議無疑是傳輸控制協議(Transmission Control Protocol,TCP)和用戶數據報協議(User Datagram Protocol,UDP)兩種,它們也是在 Linux 內核中被直接支持的協議。此外還有流控制傳輸協議(Stream Control Transmission Protocol,SCTP)、數據報擁塞控制協議(Datagram Congestion Control Protocol,DCCP)等等。不同的協議處理流程大致是一樣的,只是封裝的報文以及頭、尾部信息會有所不同,這裏以 TCP 協議爲例,內核發現 Socket 的發送緩衝區中有新的數據被拷貝進來後,會把數據封裝爲 TCP Segment 報文,常見網絡協議的報文基本上都是由報文頭(Header)和報文體(Body,也叫荷載 “Payload”)兩部分組成。系統內核將緩衝區中用戶要發送出去的數據作爲報文體,然後把傳輸層中的必要控制信息,譬如代表哪個程序發、由哪個程序收的源、目標端口號,用於保證可靠通信(重發與控制順序)的序列號、用於校驗信息是否在傳輸中出現損失的校驗和(Check Sum)等信息封裝入報文頭中。
-
IP:網絡層協議最主要就是網際協議(Internet Protocol,IP),其他還有因特網組管理協議(Internet Group Management Protocol,IGMP)、大量的路由協議(EGP、NHRP、OSPF、IGRP、……)等等。以 IP 協議爲例,它會將來自上一層(本例中的 TCP 報文)的數據包作爲報文體,再次加入自己的報文頭,譬如指明數據應該發到哪裏的路由地址、數據包的長度、協議的版本號,等等,封裝成 IP 數據包後發往下一層。關於 TCP 和 IP 協議報文的內容,筆者曾在 “負載均衡” 中詳細講解過,有需要的讀者可以參考。
-
Device:網絡設備(Device)是網絡訪問層中面向系統一側的接口,這裏所說的設備與物理硬件設備並不是同一個概念,Device 只是一種向操作系統端開放的接口,其背後既可能代表着真實的物理硬件,也可能是某段具有特定功能的程序代碼,譬如即使不存在物理網卡,也依然可以存在迴環設備(Loopback Device)。許多網絡抓包工具,如 tcpdump、Wirshark 便是在此處工作的,前面介紹微服務流量控制時曾提到過的網絡流量整形,通常也是在這裏完成的。Device 主要的作用是抽象出統一的界面,讓程序代碼去選擇或影響收發包出入口,譬如決定數據應該從哪塊網卡設備發送出去;還有就是準備好網卡驅動工作所需的數據,譬如來自上一層的 IP 數據包、下一跳(Next Hop)的 MAC 地址(這個地址是通過 ARP Request 得到的)等等。
-
Driver:網卡驅動程序(Driver)是網絡訪問層中面向硬件一側的接口,網卡驅動程序會通過 DMA 將主存中的待發送的數據包複製到驅動內部的緩衝區之中。數據被複制的同時,也會將上層提供的 IP 數據包、下一跳 MAC 地址這些信息,加上網卡的 MAC 地址、VLAN Tag 等信息一併封裝成爲以太幀(Ethernet Frame),並自動計算校驗和。對於需要確認重發的信息,如果沒有收到接收者的確認(ACK)響應,那重發的處理也是在這裏自動完成的。
上面這些階段是信息從程序中對外發出時經過協議棧的過程,接收過程則是從相反方向進行的逆操作。程序發送數據做的是層層封包,加入協議頭,傳給下一層;接受數據則是層層解包,提取協議體,傳給上一層,你可以類比來理解數據包接收過程,筆者就不再專門列舉一遍數據接收步驟了。
干預網絡通信
網絡協議棧的處理是一套相對固定和封閉的流程,整套處理過程中,除了在網絡設備這層能看到一點點程序以設備的形式介入處理的空間外,其他過程似乎就沒有什麼可供程序插手的餘地了。然而事實並非如此,從 Linux Kernel 2.4 版開始,內核開放了一套通用的、可供代碼干預數據在協議棧中流轉的過濾器框架。這套名爲 Netfilter 的框架是 Linux 防火牆和網絡的主要維護者 Rusty Russell 提出並主導設計的,它圍繞網絡層(IP 協議)的周圍,埋下了五個鉤子(Hooks),每當有數據包流到網絡層,經過這些鉤子時,就會自動觸發由內核模塊註冊在這裏的回調函數,程序代碼就能夠通過回調來干預 Linux 的網絡通信。筆者先將這五個鉤子的名字與含義列出:
-
PREROUTING:來自設備的數據包進入協議棧後立即觸發此鉤子。PREROUTING 鉤子在進入 IP 路由之前觸發,這意味着只要接收到的數據包,無論是否真的發往本機,都會觸發此鉤子。一般用於目標網絡地址轉換(Destination NAT,DNAT)。
-
INPUT:報文經過 IP 路由後,如果確定是發往本機的,將會觸發此鉤子,一般用於加工發往本地進程的數據包。
-
FORWARD:報文經過 IP 路由後,如果確定不是發往本機的,將會觸發此鉤子,一般用於處理轉發到其他機器的數據包。
-
OUTPUT:從本機程序發出的數據包,在經過 IP 路由前,將會觸發此鉤子,一般用於加工本地進程的輸出數據包。
-
POSTROUTING:從本機網卡出去的數據包,無論是本機的程序所發出的,還是由本機轉發給其他機器的,都會觸發此鉤子,一般用於源網絡地址轉換(Source NAT,SNAT)。
Netfilter 允許在同一個鉤子處註冊多個回調函數,因此向鉤子註冊回調函數時必須提供明確的優先級,以便觸發時能按照優先級從高到低進行激活。由於回調函數會存在多個,看起來就像掛在同一個鉤子上的一串鏈條,因此鉤子觸發的回調函數集合就被稱爲 “回調鏈”(Chained Callbacks),這個名字導致了後續基於 Netfilter 設計的 Xtables 系工具,如稍後介紹的 iptables 均有使用到 “鏈”(Chain)的概念。雖然現在看來 Netfilter 只是一些簡單的事件回調機制而已,然而這樣一套簡單的設計,卻成爲了整座 Linux 網絡大廈的核心基石,Linux 系統提供的許多網絡能力,如數據包過濾、封包處理(設置標誌位、修改 TTL 等)、地址僞裝、網絡地址轉換、透明代理、訪問控制、基於協議類型的連接跟蹤,帶寬限速,等等,都是在 Netfilter 基礎之上實現的。
以 Netfilter 爲基礎的應用有很多,其中使用最廣泛的毫無疑問要數 Xtables 系列工具,譬如 iptables、ebtables、arptables、ip6tables 等等。這裏面至少 iptables 應該是用過 Linux 系統的開發人員都或多或少會使用過,它常被稱爲是 Linux 系統 “自帶的防火牆”,然而 iptables 實際能做的事情已遠遠超出防火牆的範疇,嚴謹地講,比較貼切的定位應是能夠代替 Netfilter 多數常規功能的 IP 包過濾工具。iptables 的設計意圖是因爲 Netfilter 的鉤子回調雖然很強大,但畢竟要通過程序編碼才能夠使用,並不適合系統管理員用來日常運維,而它的價值便是以配置去實現原本用 Netfilter 編碼才能做到的事情。iptables 先把用戶常用的管理意圖總結成具體的行爲預先準備好,然後在滿足條件時自動激活行爲。以下列出了部分 iptables 預置的行爲:
-
DROP:直接將數據包丟棄。
-
REJECT:給客戶端返回 Connection Refused 或 Destination Unreachable 報文。
-
QUEUE:將數據包放入用戶空間的隊列,供用戶空間的程序處理。
-
RETURN:跳出當前鏈,該鏈裏後續的規則不再執行。
-
ACCEPT:同意數據包通過,繼續執行後續的規則。
-
JUMP:跳轉到其他用戶自定義的鏈繼續執行。
-
REDIRECT:在本機做端口映射。
-
MASQUERADE:地址僞裝,自動用修改源或目標的 IP 地址來做 NAT
-
LOG:在 / var/log/messages 文件中記錄日誌信息。
-
……
這些行爲本來能夠被掛載到 Netfilter 鉤子的回調鏈上,但 iptables 又進行了一層額外抽象,不是把行爲與鏈直接掛鉤,而是根據這些底層操作的目的,先總結爲更高層次的規則。舉個例子,假設你掛載規則目的是爲了實現網絡地址轉換(NAT),那就應該對符合某種特徵的流量(譬如來源於某個網段、從某張網卡發送出去)、在某個鉤子上(譬如做 SNAT 通常在 POSTROUTING,做 DNAT 通常在 PREROUTING)進行 MASQUERADE 行爲,這樣具有相同目的的規則,就應該放到一起才便於管理,由此便形成 “規則表” 的概念。iptables 內置了五張不可擴展的規則表(其中 security 表並不常用,很多資料只計算了前四張表),如下所列:
-
raw 表:用於去除數據包上的連接追蹤機制(Connection Tracking)。
-
mangle 表:用於修改數據包的報文頭信息,如服務類型(Type Of Service,ToS)、生存週期(Time to Live,TTL)以及爲數據包設置 Mark 標記,典型的應用是鏈路的服務質量管理(Quality Of Service,QoS)。
-
nat 表:用於修改數據包的源或者目的地址等信息,典型的應用是網絡地址轉換(Network Address Translation)。
-
filter 表:用於對數據包進行過濾,控制到達某條鏈上的數據包是繼續放行、直接丟棄或拒絕(ACCEPT、DROP、REJECT),典型的應用是防火牆。
-
security 表:用於在數據包上應用 SELinux,這張表並不常用。
以上五張規則表是具有優先級的:raw→mangle→nat→filter→security,也即是上面列舉它們的順序。在 iptables 中新增規則時,需要按照規則的意圖指定要存入到哪張表中,如果沒有指定,默認將會存入 filter 表。此外,每張表能夠使用到的鏈也有所不同,具體表與鏈的對應關係如表 12-1 所示。
表 12-1 表與鏈的對應關係
從名字上就能看出預置的五條鏈直接源自於 Netfilter 的鉤子,它們與五張規則表的對應關係是固定的,用戶不能增加自定義的表,或者修改已有表與鏈的關係,但可以增加自定義的鏈,新增的自定義鏈與 Netfilter 的鉤子沒有天然的對應關係,換而言之就是不會被自動觸發,只有顯式使用 JUMP 行爲,從默認的五條鏈中跳轉過去才能被執行。
iptables 不僅僅是 Linux 系統自帶的一個網絡工具,它在容器間通信中扮演相當重要的角色,譬如 Kubernetes 用來管理 Service 的 Endpoints 的核心組件 kube-proxy,就依賴 iptables 來完成 ClusterIP 到 Pod 的通信(也可以採用 IPVS,IPVS 同樣是基於 Netfilter 的),這種通信的本質就是一種 NAT 訪問。對於 Linux 用戶,以上都是相當基礎的網絡常識,但如果你平常較少在 Linux 系統下工作,就可能需要一些用 iptables 充當防火牆過濾數據、充當作路由器轉發數據、充當作網關做 NAT 轉換的實際例子來幫助理解,由於這些操作在網上很容易就能找到,筆者便不專門去舉例說明了。
行文至此,本章用了兩個小節的篇幅去介紹 Linux 下網絡通信的協議棧模型,以及程序如何幹涉在協議棧中流動的信息,它們與虛擬化並沒有什麼直接關係,是整個 Linux 網絡通信的必要基礎。從下一節開始,我們就要開始專注於與網絡虛擬化密切相關的內容了。
虛擬化網絡設備
虛擬化網絡並不需要完全遵照物理網絡的樣子來設計,不過,由於已有大量現成的代碼原本就是面向於物理存在的網絡設備來編碼實現的,也有出於方便理解和知識繼承的方面的考慮,虛擬化網絡與物理網絡中的設備還是有相當高的相似性。所以,筆者準備從網絡中那些與網卡、交換機、路由器等對應的虛擬設施,以及如何使用這些虛擬設施來組成網絡入手,來介紹容器間網絡的通信基礎設施。
網卡:tun/tap、veth
目前主流的虛擬網卡方案有 tun/tap 和 veth 兩種,在時間上 tun/tap 出現得更早,它是一組通用的虛擬驅動程序包,裏面包含了兩個設備,分別是用於網絡數據包處理的虛擬網卡驅動,以及用於內核空間與用戶空間交互的字符設備(Character Devices,這裏具體指/dev/net/tun
)驅動。大概在 2000 年左右,Solaris 系統爲了實現隧道協議(Tunneling Protocol)開發了這套驅動,從 Linux Kernel 2.1 版開始移植到 Linux 內核中,當時是源碼中的可選模塊,2.4 版之後發佈的內核都會默認編譯 tun/tap 的驅動。
tun 和 tap 是兩個相對獨立的虛擬網絡設備,其中 tap 模擬了以太網設備,操作二層數據包(以太幀),tun 則模擬了網絡層設備,操作三層數據包(IP 報文)。使用 tun/tap 設備的目的是實現把來自協議棧的數據包先交由某個打開了/dev/net/tun
字符設備的用戶進程處理後,再把數據包重新發回到鏈路中。你可以通俗地將它理解爲這塊虛擬化網卡驅動一端連接着網絡協議棧,另一端連接着用戶態程序,而普通的網卡驅動則是一端連接網絡協議棧,另一端連接着物理網卡。只要協議棧中的數據包能被用戶態程序截獲並加工處理,程序員就有足夠的舞臺空間去玩出各種花樣,譬如數據壓縮、流量加密、透明代理等功能都能夠以此爲基礎來實現,以最典型的 VPN 應用程序爲例,程序發送給 tun 設備的數據包,會經過如圖 12-3 所示的順序流進 VPN 程序:
應用程序通過 tun 設備對外發送數據包後,tun 設備如果發現另一端的字符設備已被 VPN 程序打開(這就是一端連接着網絡協議棧,另一端連接着用戶態程序),便會把數據包通過字符設備發送給 VPN 程序,VPN 收到數據包,會修改後再重新封裝成新報文,譬如數據包原本是發送給 A 地址的,VPN 把整個包進行加密,然後作爲報文體,封裝到另一個發送給 B 地址的新數據包當中。這種將一個數據包套進另一個數據包中的處理方式被形象地形容爲 “隧道”(Tunneling),隧道技術是在物理網絡中構築邏輯網絡的經典做法。而其中提到的加密,也有標準的協議可遵循,譬如 IPSec 協議。
使用 tun/tap 設備傳輸數據需要經過兩次協議棧,不可避免地會有一定的性能損耗,如果條件允許,容器對容器的直接通信並不會把 tun/tap 作爲首選方案,一般是基於稍後介紹的 veth 來實現的。但是 tun/tap 沒有 veth 那樣要求設備成對出現、數據要原樣傳輸的限制,數據包到用戶態程序後,程序員就有完全掌控的權力,要進行哪些修改,要發送到什麼地方,都可以編寫代碼去實現,因此 tun/tap 方案比起 veth 方案有更廣泛的適用範圍。
veth 是另一種主流的虛擬網卡方案,在 Linux Kernel 2.6 版本,Linux 開始支持網絡名空間隔離的同時,也提供了專門的虛擬以太網(Virtual Ethernet,習慣簡寫做 veth)讓兩個隔離的網絡名稱空間之間可以互相通信。直接把 veth 比喻成是虛擬網卡其實並不十分準確,如果要和物理設備類比,它應該相當於由交叉網線連接的一對物理網卡。
額外知識:直連線序、交叉線序
交叉網線是指一頭是 T568A 標準,另外一頭是 T568B 標準的網線。直連網線則是兩頭採用同一種標準的網線。
網卡對網卡這樣的同類設備需要使用交叉線序的網線來連接,網卡到交換機、路由器就採用直連線序的網線,不過現在的網卡大多帶有線序翻轉功能,直連線也可以網卡對網卡地連通了。
veth 實際上不是一個設備,而是一對設備,因而也常被稱作 veth pair。要使用 veth,必須在兩個獨立的網絡名稱空間中進行纔有意義,因爲 veth pair 是一端連着協議棧,另一端彼此相連的,在 veth 設備的其中一端輸入數據,這些數據就會從設備的另外一端原樣不變地流出,它工作時數據流動如圖 12-4 所示:
由於兩個容器之間採用 veth 通信不需要反覆多次經過網絡協議棧,這讓 veth 比起 tap/tun 具有更好的性能,也讓 veth pair 的實現變的十分簡單,內核中只用了幾十行代碼實現了一個數據複製函數就完成了 veth 的主體功能。veth 以模擬網卡直連的方式很好地解決了兩個容器之間的通信問題,然而對多個容器間通信,如果仍然單純只用 veth pair 的話,事情就會變得非常麻煩,讓每個容器都爲與它通信的其他容器建立一對專用的 veth pair 並不實際,這時就迫切需要有一臺虛擬化的交換機來解決多容器之間的通信問題了。
交換機:Linux Bridge
既然有了虛擬網卡,很自然也會聯想到讓網卡接入到交換機裏,實現多個容器間的相互連接。Linux Bridge 便是 Linux 系統下的虛擬化交換機,雖然它以 “網橋”(Bridge)而不是 “交換機”(Switch)爲名,然而使用過程中,你會發現 Linux Bridge 的目的看起來像交換機,功能使用起來像交換機、程序實現起來也像交換機,實際就是一臺虛擬交換機。
Linux Bridge 是在 Linux Kernel 2.2 版本開始提供的二層轉發工具,由brctl
命令創建和管理。Linux Bridge 創建以後,便能夠接入任何位於二層的網絡設備,無論是真實的物理設備(譬如 eth0)抑或是虛擬的設備(譬如 veth 或者 tap)都能與 Linux Bridge 配合工作。當有二層數據包(以太幀)從網卡進入 Linux Bridge,它將根據數據包的類型和目標 MAC 地址,按如下規則轉發處理:
-
如果數據包是廣播幀,轉發給所有接入網橋的設備。
-
如果數據包是單播幀:
-
且 MAC 地址在地址轉發表中不存在,則洪泛(Flooding)給所有接入網橋的設備,並將響應設備的接口與 MAC 地址學習(MAC Learning)到自己的 MAC 地址轉發表中。
-
且 MAC 地址在地址轉發表中已存在,則直接轉發到地址表中指定的設備。
-
如果數據包是此前轉發過的,又重新發回到此 Bridge,說明冗餘鏈路產生了環路。由於以太幀不像 IP 報文那樣有 TTL 來約束,因此一旦出現環路,如果沒有額外措施來處理的話就會永不停歇地轉發下去。對於這種數據包就需要交換機實現生成樹協議(Spanning Tree Protocol,STP)來交換拓撲信息,生成唯一拓撲鏈路以切斷環路。
上面提到的這些名詞,譬如二層轉發、泛洪、STP、MAC 學習、地址轉發表,等等,都是物理交換機中極爲成熟的概念,它們在 Linux Bridge 中都有對應的實現,所以說 Linux Bridge 不僅用起來像交換機,實現起來也像交換機。不過,它與普通的物理交換機也還是有一點差別的,普通交換機只會單純地做二層轉發,Linux Bridge 卻還支持把發給它自身的數據包接入到主機的三層的協議棧中。
對於通過brctl
命令顯式接入網橋的設備,Linux Bridge 與物理交換機的轉發行爲是完全一致的,也不允許給接入的設備設置 IP 地址,因爲網橋是根據 MAC 地址做二層轉發的,就算設置了三層的 IP 地址也毫無意義。然而 Linux Bridge 與普通交換機的區別是除了顯式接入的設備外,它自己也無可分割地連接着一臺有着完整網絡協議棧的 Linux 主機,因爲 Linux Bridge 本身肯定是在某臺 Linux 主機上創建的,可以看作 Linux Bridge 有一個與自己名字相同的隱藏端口,隱式地連接了創建它的那臺 Linux 主機。因此,Linux Bridge 允許給自己設置 IP 地址,比普通交換機多出一種特殊的轉發情況:
- 如果數據包的目的 MAC 地址爲網橋本身,並且網橋有設置了 IP 地址的話,那該數據包即被認爲是收到發往創建網橋那臺主機的數據包,此數據包將不會轉發到任何設備,而是直接交給上層(三層)協議棧去處理。
此時,網橋就取代了 eth0 設備來對接協議棧,進行三層協議的處理。設置這條特殊轉發規則的好處是:只要通過簡單的 NAT 轉換,就可以實現一個最原始的單 IP 容器網絡。這種組網是最基本的容器間通信形式,筆者舉個具體例子來幫助你理解。假設現有如下設備,它們的連接情況如圖所示,具體配置爲:
-
網橋 br0:分配 IP 地址 192.168.31.1;
-
容器:三個網絡名稱空間(容器),分別編號爲 1、2、3,均使用 veth pair 接入網橋,且有如下配置:
-
在容器一端的網卡名爲 veth0,在網橋一端網卡名爲 veth1、veth2、veth3;
-
三個容器中的 veth0 網卡分配 IP 地址:192.168.1.10、192.168.1.11、192.168.1.12;
-
三個容器中的 veth0 網卡設置網關爲網橋,即 192.168.31.1;
-
網橋中的 veth1、veth2、veth3 無 IP 地址;
-
物理網卡 eth0:分配的 IP 地址 14.123.254.86;
-
外部網絡:外部網絡中有一臺服務器,地址爲 122.246.6.183
如果名稱空間 1 中的應用程序想訪問外網地址爲 122.246.6.183 的服務器,由於容器沒有自己的公網 IP 地址,程序發出的數據包必須經過如下步驟處理後,才能最終到達外網服務器:
- 應用程序調用 Socket API 發送數據,此時生成的原始數據包爲:
-
源 MAC:veth0 的 MAC
-
目標 MAC:網關的 MAC(即網橋的 MAC)
-
源 IP:veth0 的 IP,即 192.168.1.10
-
目標 IP:外網的 IP,即 122.246.6.183
-
從 veth0 發送的數據,會在 veth1 中原樣出來,網橋將會從 veth1 中接收到一個目標 MAC 爲自己的數據包,並且網橋有配置 IP 地址,由此觸發了 Linux Bridge 的特殊轉發規則。這個數據包便不會轉發給任何設備,而是轉交給主機的協議棧處理。注意,從這步以後就是三層路由了,已不在網橋的工作範圍之內,是由 Linux 主機依靠 Netfilter 進行 IP 轉發(IP Forward)去實現的。
-
數據包經過主機協議棧,Netfilter 的鉤子被激活,預置好的 iptables NAT 規則會修改數據包的源 IP 地址,將其改爲物理網卡 eth0 的 IP 地址,並在映射表中記錄設備端口及兩個 IP 地址之間的對應關係,經過 SNAT 之後的數據包,最終會從 eth0 出去,此時報文頭中的地址爲:
-
源 MAC:eth0 的 MAC
-
目標 MAC:下一跳(Hop)的 MAC
-
源 IP:eth0 的 IP,即 14.123.254.86
-
目標 IP:外網的 IP,即 122.246.6.183
- 可見,經過主機協議棧後,數據包的源和目標 IP 地址均爲公網的 IP,這個數據包在外部網絡中可以根據 IP 正確路由到目標服務器手上。當目標服務器處理完畢,對該請求發出響應後,返回數據包的目標地址也是公網 IP。當返回的數據包經過鏈路所有跳點,由 eth0 達到網橋時,報文頭中的地址爲:
-
源 MAC:eth0 的 MAC
-
目標 MAC:網橋的 MAC
-
源 IP:外網的 IP,即 122.246.6.183
-
目標 IP:eth0 的 IP,即 14.123.254.86
- 可見,這同樣是一個以網橋 MAC 地址爲目標的數據包,同樣會觸發特殊轉發規則,交由協議棧處理。此時 Linux 將根據映射表中的轉換關係做 DNAT 轉換,把目標 IP 地址從 eth0 替換回 veth0 的 IP,最終 veth0 收到的響應數據包爲:
-
源 MAC:網橋的 MAC
-
目標 MAC:veth0 的 MAC
-
源 IP:外網的 IP,即 122.246.6.183
-
目標 IP:veth0 的 IP,即 192.168.1.10
在以上處理過程中,Linux 主機獨立承擔了三層路由的職責,一定程度上扮演了路由器的角色。由於有 Netfilter 的存在,對網絡層的路由轉發,就無須像 Linux Bridge 一樣專門提供brctl
這樣的命令去創建一個虛擬設備,通過 Netfilter 很容易就能在 Linux 內核完成根據 IP 地址進行路由的功能,你也可以理解爲 Linux Bridge 是一個人工創建的虛擬交換機,而 Linux 內核則是一個天然的虛擬路由器。
限於篇幅,筆者僅舉例介紹 Linux Bridge 這一種虛擬交換機的方案,此外還有 OVS(Open vSwitch)等同樣常見,而且更強大、更復雜的方案這裏就不再涉及了。
網絡:VXLAN
有了虛擬化網絡設備後,下一步就是要使用這些設備組成網絡,容器分佈在不同的物理主機上,每一臺物理主機都有物理網絡相互聯通,然而這種網絡的物理拓撲結構是相對固定的,很難跟上雲原生時代的分佈式系統的邏輯拓撲結構變動頻率,譬如服務的擴縮、斷路、限流,等等,都可能要求網絡跟隨做出相應的變化。正因如此,軟件定義網絡(Software Defined Network,SDN)的需求在雲計算和分佈式時代變得前所未有地迫切,SDN 的核心思路是在物理的網絡之上再構造一層虛擬化的網絡,將控制平面和數據平面分離開來,實現流量的靈活控制,爲核心網絡及應用的創新提供良好的平臺。SDN 裏位於下層的物理網絡被稱爲 Underlay,它着重解決網絡的連通性與可管理性,位於上層的邏輯網絡被稱爲 Overlay,它着重爲應用提供與軟件需求相符的傳輸服務和網絡拓撲。
軟件定義網絡已經發展了十餘年時間,遠比雲原生、微服務這些概念出現得更早。網絡設備商基於硬件設備開發出了 EVI(Ethernet Virtualization Interconnect)、TRILL(Transparent Interconnection of Lots of Links)、SPB(Shortest Path Bridging)等大二層網絡技術;軟件廠商也提出了 VXLAN(Virtual eXtensible LAN)、NVGRE(Network Virtualization Using Generic Routing Encapsulation)、STT(A Stateless Transport Tunneling Protocol for Network Virtualization)等一系列基於虛擬交換機實現的 Overlay 網絡。由於跨主機的容器間通信,用的大多是 Overlay 網絡,本節裏,筆者會以 VXLAN 爲例去介紹 Overlay 網絡的原理。
VXLAN 你有可能沒聽說過,但 VLAN 相信只要從事計算機專業的人都有所瞭解。VLAN 的全稱是 “虛擬局域網”(Virtual Local Area Network),從名稱來看它也算是網絡虛擬化技術的早期成果之一了。由於二層網絡本身的工作特性決定了它非常依賴於廣播,無論是廣播幀(如 ARP 請求、DHCP、RIP 都會產生廣播幀),還是泛洪路由,其執行成本都隨着接入二層網絡設備數量的增長而等比例增加,當設備太多,廣播又頻繁的時候,很容易就會形成廣播風暴(Broadcast Radiation)。因此,VLAN 的首要職責就是劃分廣播域,將連接在同一個物理網絡上的設備區分開來,劃分的具體方法是在以太幀的報文頭中加入 VLAN Tag,讓所有廣播只針對具有相同 VLAN Tag 的設備生效。這樣既縮小了廣播域,也附帶提高了安全性和可管理性,因爲兩個 VLAN 之間不能直接通信。如果確有通信的需要,就必須通過三層設備來進行,譬如使用單臂路由(Router on a Stick)或者三層交換機。
然而 VLAN 有兩個明顯的缺陷,第一個缺陷在於 VLAN Tag 的設計,定義 VLAN 的 802.1Q 規範是在 1998 年提出的,當時的網絡工程師完全不可能預料到未來雲計算會如此地普及,因而只給 VLAN Tag 預留了 32 Bits 的存儲空間,其中還要分出 16 Bits 存儲標籤協議識別符(Tag Protocol Identifier)、3 Bits 存儲優先權代碼點(Priority Code Point)、1 Bit 存儲標準格式指示(Canonical Format Indicator),剩下的 12 Bits 才能用來存儲 VLAN ID(Virtualization Network Identifier,VNI),換而言之,VLAN ID 最多隻能有 212=4096 種取值。當雲計算數據中心出現後,即使不考慮虛擬化的需求,單是需要分配 IP 的物理設備都有可能數以萬計甚至數以十萬計,這樣 4096 個 VLAN 肯定是不夠用的。後來 IEEE 的工程師們又提出 802.1AQ 規範力圖補救這個缺陷,大致思路是給以太幀連續打上兩個 VLAN Tag,每個 Tag 裏仍然只有 12 Bits 的 VLAN ID,但兩個加起來就可以存儲 224=16,777,216 個不同的 VLAN ID 了,由於兩個 VLAN Tag 並排放在報文頭上,802.1AQ 規範還有了個 QinQ(802.1Q in 802.1Q)的暱稱別名。
QinQ 是 2011 年推出的規範,但是直到現在都並沒有特別普及,除了需要設備支持外,它還解決不了 VLAN 的第二個缺陷:跨數據中心傳遞。VLAN 本身是爲二層網絡所設計的,但是在兩個獨立數據中心之間,信息只能夠通過三層網絡傳遞,由於雲計算的發展普及,大型分佈式系統已不侷限於單個數據中心,完全有跨數據中心運作的可能性,此時如何讓 VLAN Tag 在兩個數據中心間傳遞又成了不得不考慮的麻煩事。
爲了統一解決以上兩個問題,IETF 定義了 VXLAN 規範,這是三層虛擬化網絡(Network Virtualization over Layer 3,NVO3)的標準技術規範之一,是一種典型的 Overlay 網絡。VXLAN 採用 L2 over L4 (MAC in UDP)的報文封裝模式,把原本在二層傳輸的以太幀放到四層 UDP 協議的報文體內,同時加入了自己定義的 VXLAN Header。在 VXLAN Header 裏直接就有 24 Bits 的 VLAN ID,同樣可以存儲 1677 萬個不同的取值,VXLAN 讓二層網絡得以在三層範圍內進行擴展,不再受數據中心間傳輸的限制。VXLAN 的整個報文結構如圖 12-6 所示:
VXLAN 對網絡基礎設施的要求很低,不需要專門的硬件提供的特別支持,只要三層可達的網絡就能部署 VXLAN。VXLAN 網絡的每個邊緣入口上佈置有一個 VTEP(VXLAN Tunnel Endpoints)設備,它既可以是物理設備,也可以是虛擬化設備,負責 VXLAN 協議報文的封包和解包。互聯網號碼分配局(Internet Assigned Numbers Authority,IANA)專門分配了 4789 作爲 VTEP 設備的 UDP 端口(以前 Linux VXLAN 用的默認端口是 8472,目前這兩個端口在許多場景中仍有並存的情況)。
從 Linux Kernel 3.7 版本起,Linux 系統就開始支持 VXLAN。到了 3.12 版本,Linux 對 VXLAN 的支持已達到完全完備的程度,能夠處理單播和組播,能夠運行於 IPv4 和 IPv6 之上,一臺 Linux 主機經過簡單配置之後,便可以把 Linux Bridge 作爲 VTEP 設備使用。
VXLAN 帶來了很高的靈活性、擴展性和可管理性,同一套物理網絡中可以任意創建多個 VXLAN 網絡,每個 VXLAN 中接入的設備都彷彿是在一個完全獨立的二層局域網中一樣,不會受到外部廣播的干擾,也很難遭受外部的攻擊,這使得 VXLAN 能夠良好地匹配分佈式系統的彈性需求。不過,VXLAN 也帶來了額外的複雜度和性能開銷,具體表現在:
-
傳輸效率的下降,如果你仔細數過前面 VXLAN 報文結構中 UDP、IP、以太幀報文頭的字節數,會發現經過 VXLAN 封裝後的報文,新增加的報文頭部分就整整佔了 50 Bytes(VXLAN 報文頭佔 8 Bytes,UDP 報文頭佔 8 Bytes,IP 報文頭佔 20 Bytes,以太幀的 MAC 頭佔 14 Bytes),而原本只需要 14 Bytes 而已,而且現在這 14 Bytes 的消耗也還在,被封到了最裏面的以太幀中。以太網的 MTU 是 1500 Bytes,如果是傳輸大量數據,額外損耗 50 Bytes 並不算很高的成本,但如果傳輸的數據本來就只有幾個 Bytes 的話,那傳輸消耗在報文頭上的成本就很高昂了。
-
傳輸性能的下降,每個 VXLAN 報文的封包和解包操作都屬於額外的處理過程,尤其是用軟件來實現的 VTEP,額外的運算資源消耗有時候會成爲不可忽略的性能影響因素。
副本網卡:MACVLAN
理解了 VLAN 和 VXLAN 的原理後,我們就有足夠的前置知識去了解 MACVLAN 這最後一種網絡設備虛擬化的方式了。
前文中提到過,兩個 VLAN 之間是完全二層隔離的,不存在重合的廣播域,因此要通信就只能通過三層設備,最簡單的三層通信就是靠單臂路由了。筆者以圖 12-7 所示的網絡拓撲結構來舉個具體例子,介紹單臂路由是如何工作的:
假設位於 VLAN-A 中的主機 A1 希望將數據包發送給 VLAN-B 中的主機 B2,由於 A、B 兩個 VLAN 之間二層鏈路不通,因此引入了單臂路由,單臂路由不屬於任何 VLAN,它與交換機之間的鏈路允許任何 VLAN ID 的數據包通過,這種接口被稱爲 TRUNK。這樣,A1 要和 B2 通信,A1 就將數據包先發送給路由(只需把路由設置爲網關即可做到),然後路由根據數據包上的 IP 地址得知 B2 的位置,去掉 VLAN-A 的 VLAN Tag,改用 VLAN-B 的 VLAN Tag 重新封裝數據包後發回給交換機,交換機收到後就可以順利轉發給 B2 了。這個過程並沒什麼複雜的地方,但你是否注意到一個問題,路由器應該設置怎樣的 IP 地址呢?由於 A1、B2 各自處於獨立的網段上,它們又各自要將同一個路由作爲網關使用,這就要求路由器必須同時具備 192.168.1.0/24 和 192.168.2.0/24 的 IP 地址。如果真的就只有 VLAN-A、VLAN-B 兩個 VLAN,那把路由器上的兩個接口分別設置不同的 IP 地址,然後用兩條網線分別連接到交換機上也勉強算是一個解決辦法,但 VLAN 最多支持 4096 個 VLAN,如果要接四千多條網線就太離譜了。爲了解決這個問題,802.1Q 規範中專門定義了子接口(Sub-Interface)的概念,其作用是允許在同一張物理網卡上,針對不同的 VLAN 綁定不同的 IP 地址。
MACVLAN 借用了 VLAN 子接口的思路,並且在這個基礎上更進一步,不僅允許對同一個網卡設置多個 IP 地址,還允許對同一張網卡上設置多個 MAC 地址,這也是 MACVLAN 名字的由來。原本 MAC 地址是網卡接口的 “身份證”,應該是嚴格的一對一關係,而 MACVLAN 打破這層關係,方法是在物理設備之上、網絡棧之下生成多個虛擬的 Device,每個 Device 都有一個 MAC 地址,新增 Device 的操作本質上相當於在系統內核中註冊了一個收發特定數據包的回調函數,每個回調函數都能對一個 MAC 地址的數據包進行響應,當物理設備收到數據包時,會先根據 MAC 地址進行一次判斷,確定交給哪個 Device 來處理,如圖 12-8 所示。以交換機一側的視角來看,這個端口後面彷彿是另一臺已經連接了多個設備的交換機一樣。
用 MACVLAN 技術虛擬出來的副本網卡,在功能上和真實的網卡是完全對等的,此時真正的物理網卡實際上確實承擔着類似交換機的職責,收到數據包後,根據目標 MAC 地址判斷這個包應轉發給哪塊副本網卡處理,由同一塊物理網卡虛擬出來的副本網卡,天然處於同一個 VLAN 之中,可以直接二層通信,不需要將流量轉發到外部網絡。
與 Linux Bridge 相比,這種以網卡模擬交換機的方法在目標上並沒有本質的不同,但 MACVLAN 在內部實現上要比 Linux Bridge 輕量得多。從數據流來看,副本網卡的通信只比物理網卡多了一次判斷而已,能獲得很高的網絡通信性能;從操作步驟來看,由於 MAC 地址是靜態的,所以 MACVLAN 不需要像 Linux Bridge 那樣考慮 MAC 地址學習、STP 協議等複雜的算法,這進一步突出了 MACVLAN 的性能優勢。
除了模擬交換機的 Bridge 模式外,MACVLAN 還支持虛擬以太網端口聚合模式(Virtual Ethernet Port Aggregator,VEPA)、Private 模式、Passthru 模式、Source 模式等另外幾種工作模式。
四種工作模式
VEPA (Virtual Ethernet Port Aggregator)
VEPA 爲默認的工作模式,該模式下,所有 macvlan 發出的流量都會經過父接口,不管目的地是否與該 macvlan 共用一個父接口。
Bridge mode
該 bridge 模式類似於傳統的網橋模式,擁有相同父接口的 macvlan 可以直接進行通信,不需要將數據發給父接口轉發。該模式下不需要交換機支持 hairpin 模式,性能比 VEPA 模式好。另外相對於傳統的網橋模式,該模式不需要學習 mac 地址,不需要 STP,使得其性能比傳統的網橋性能好得多。但是如果父接口 down 掉,則所有子接口也會 down,同時無法通信。
Private mode
該模式是 VEPA 模式的增強版,在這種模式下,同一主接口下的子接口之間彼此隔離,不能通信。即使從外部的物理交換機導流,也會被無情地丟掉。
Passthru mode
這種模式,只允許單個子接口連接主接口,且必須設置成混雜模式,一般用於子接口橋接和創建 VLAN 子接口的場景。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/Sn1ql5Pa5rBc7_C7QPqZbg