天天看點

《Kubernetes網絡權威指南》讀書筆記 | 網絡虛拟化基石:namespace

作者:熱愛程式設計的通信人

書籍來源:《Kubernetes網絡權威指南:基礎、原理與實踐》

一邊學習一邊整理讀書筆記,并與大家分享,侵權即删,謝謝支援!

附上彙總貼:《Kubernetes網絡權威指南》讀書筆記 | 彙總_COCOgsta的部落格-CSDN部落格

顧名思義,Linux的namespace(名字空間)的作用就是“隔離核心資源”。Linux的namespace給裡面的程序造成了兩個錯覺:

(1)它是系統裡唯一的程序。

(2)它獨享系統的所有資源。

預設情況下,Linux程序處在和主控端相同的namespace,即初始的根namespace裡,預設享有全局系統資源。

盡管Linux的namespace隔離技術很早便存在于核心中,而且它就是為Linux的容器技術而設計的,但它一直鮮為人知。直到Docker引領的容器技術革命爆發,它才進入普羅大衆的視線。

每個網絡namespace裡都有自己的網絡裝置(如IP位址、路由表、端口範圍、/proc/net目錄等)。從網絡的角度看,network namespace使得容器非常有用,使得在一個主機上同時運作多個監聽80端口的Web伺服器變為可能,如圖1-1所示。

《Kubernetes網絡權威指南》讀書筆記 | 網絡虛拟化基石:namespace

圖1-1 network namespace示意圖

1.1.1 初識networknamespace

network namespace可以通過系統調用來建立,可以調用Linux的clone()(其實是UNIX系統調用fork()的延伸)API建立一個通用的namespace,然後傳入CLONE_NEWNET參數表面建立一個network namespace。

建立一個名為netns1的networknamespace可以使用以下指令:

《Kubernetes網絡權威指南》讀書筆記 | 網絡虛拟化基石:namespace

一個network namespace被建立出來後,可以使用ip netns exec指令進入,做一些網絡查詢/配置的工作。

《Kubernetes網絡權威指南》讀書筆記 | 網絡虛拟化基石:namespace

預設隻有一塊本地回環裝置lo。

想檢視系統中有哪些networknamespace,可以使用以下指令:

《Kubernetes網絡權威指南》讀書筆記 | 網絡虛拟化基石:namespace

想删除networknamespace,可以通過以下指令實作:

《Kubernetes網絡權威指南》讀書筆記 | 網絡虛拟化基石:namespace

注意,上面這條指令實際上并沒有删除netns1這個network namespace,它隻是移除了這個network namespace對應的挂載點。隻要裡面還有程序運作着,network namespace便會一直存在。

1.1.2 配置networknamespace

network namespace自帶的lo裝置狀态是DOWN的,是以,當嘗試通路本地回環位址時,網絡也是不通的。下面的小測試就說明了這一點。

《Kubernetes網絡權威指南》讀書筆記 | 網絡虛拟化基石:namespace

如果想通路本地回環位址,首先需要進入netns1這個network namespace,把裝置狀态設定成UP。

《Kubernetes網絡權威指南》讀書筆記 | 網絡虛拟化基石:namespace

然後,嘗試ping 127.0.0.1,發現能夠ping通。

《Kubernetes網絡權威指南》讀書筆記 | 網絡虛拟化基石:namespace

如果我們想與外界(比如主機上的網卡)進行通信,就需要在namespace裡再建立一對虛拟的以太網卡,即所謂的veth pair。

下面的指令将建立一對虛拟以太網卡,然後把veth pair的一端放到netns1 network namespace。

《Kubernetes網絡權威指南》讀書筆記 | 網絡虛拟化基石:namespace

在預設情況下,它們都在主機的根network namespce中,将其中一塊虛拟網卡veth1通過ip link set指令移動到netns1 network namespace。兩塊網卡剛建立出來還都是DOWN狀态,需要手動把狀态設定成UP。

《Kubernetes網絡權威指南》讀書筆記 | 網絡虛拟化基石:namespace

上面兩條指令首先進入netns1這個network namespace,為veth1綁定IP位址10.1.1.1/24,并把網卡的狀态設定成UP,而仍在主機根network namespace中的網卡veth0被我們綁定了IP位址10.1.1.2/24。這樣一來,我們就可以ping通veth pair的任意一頭了。

《Kubernetes網絡權威指南》讀書筆記 | 網絡虛拟化基石:namespace

同理,我們可以進入netns1 network namespace去ping主機上的虛拟網卡,如下所示:

《Kubernetes網絡權威指南》讀書筆記 | 網絡虛拟化基石:namespace

另外,不同network namespace之間的路由表和防火牆規則等也是隔離的。

《Kubernetes網絡權威指南》讀書筆記 | 網絡虛拟化基石:namespace

如上所示,我們進入netns1 network namespace,分别輸入route和iptables-L指令,期望查詢路由表和iptables規則,卻發現空空如也。這意味着從netns1 network namespace發包到網際網路也是徒勞的,因為網絡還不通!

《Kubernetes網絡權威指南》讀書筆記 | 網絡虛拟化基石:namespace

該怎麼了解上面這條看似有點複雜的指令呢?分解成兩部分:

(1)ip netns exec netns1進入netns1 network namespace。

(2)ip link set veth1 netns 1把netns1 network namespace下的veth1網卡挪到PID為1的程序(即init程序)所在的network namespace。

上面這條指令其實就是把veth1從netns1 network namespace移動到系統根network namespace。

對namespace的root使用者而言,他們都可以把其namespace裡的虛拟網絡裝置移動到其他network namespace,甚至包括主機根network namespace!這就帶來了潛在的安全風險。

1.1.3 networknamespaceAPI的使用

本節我們将通過幾個C語言程式的例子介紹network namespace API的使用方法。clone()、unshare()和setns()系統調用會使用CLONE_NEW*常量來差別要操作的namespace類型。

  1. 建立namespace的黑科技:clone系統調用

使用者可以使用clone()系統調用建立一個namespace。

《Kubernetes網絡權威指南》讀書筆記 | 網絡虛拟化基石:namespace

我們可以通過其flags參數(标志位)控制特定的功能。clone()總共有二十多種CLONE_*标志位用來控制clone(克隆)程序時的行為。

隻要在clone()設定了其中一個标志位CLONE_NEW,系統就會建立一個新的對應類型的namespace及一個新的程序,并且會把這個程序放到這個新建立的namespace中。通過|(位或)操作,我們可以實作clone()同時指定多個CLONE_NEW标志位。

  1. 維持namespace存在: /proc/PID/ns目錄的奧秘

每個Linux程序都擁有一個屬于自己的/proc/PID/ns,這個目錄下的每個檔案都代表一個類型的namespace。

從Linux核心3.8版本開始,每個檔案都是一個特殊的符号連結檔案,這些檔案提供了操作程序關聯namespace的一種方式。先來看一眼這些符号連結檔案都長什麼樣。

《Kubernetes網絡權威指南》讀書筆記 | 網絡虛拟化基石:namespace

如果兩個程序在同一個namespace中,那麼這兩個程序/proc/PID/ns目錄下對應符号連結檔案的inode數字(即上文例子中[]内的數字,例如4026531839)會是一樣的。

除此之外,/proc/PID/ns目錄下的檔案還有一個作用,保持namespace存在。是以,Linux核心提供的黑科技允許:隻要打開檔案描述符,不需要程序存在也能保持namespace存在!怎麼操作?請看下面的指令:

《Kubernetes網絡權威指南》讀書筆記 | 網絡虛拟化基石:namespace

如上所示,把/proc/PID/ns目錄下的檔案挂載起來就能起到打開檔案描述符的作用,而且這個network namespace會一直存在,直到/proc/self/ns/net被解除安裝。

  1. 往namespace裡添加程序:setns系統調用

setns()就是用來做這個工作的,其主要功能就是把一個程序加入一個已經存在的namespace中。setns()的定義如下所示:

《Kubernetes網絡權威指南》讀書筆記 | 網絡虛拟化基石:namespace

一個簡單的C語言樣例如下所示:

《Kubernetes網絡權威指南》讀書筆記 | 網絡虛拟化基石:namespace

讓我們編譯上面的代碼段,假設編譯後的二進制檔案是enterns,那麼執行以下指令:

《Kubernetes網絡權威指南》讀書筆記 | 網絡虛拟化基石:namespace

以上指令執行完後,我們就進入一個新的network namespace,并且可以在裡面執行shell指令。這種方式廣泛應用于Docker和Kubernetes中。

  1. 幫助程序逃離namespace:unshare系統調用

unshare(),用于幫助程序“逃離”namespace。unshare()系統調用的函數聲明如下:

《Kubernetes網絡權威指南》讀書筆記 | 網絡虛拟化基石:namespace

它的作用就是在目前shell所在的namespace外執行一條指令。unshare指令的用法如下所示:

《Kubernetes網絡權威指南》讀書筆記 | 網絡虛拟化基石:namespace

Linux會為需要執行的指令(在上面的例子中,即program)啟動一個新程序,然後在另外一個namespace中執行操作,這樣就可以起到執行結果和原(父)程序隔離的效果。

  1. 一個完整的例子

一個使用Linux network namespace相關系統調用的程式樣例如下。

《Kubernetes網絡權威指南》讀書筆記 | 網絡虛拟化基石:namespace
《Kubernetes網絡權威指南》讀書筆記 | 網絡虛拟化基石:namespace
《Kubernetes網絡權威指南》讀書筆記 | 網絡虛拟化基石:namespace

以上程式的大意是,在一個新的network namespace中初始化一個網絡裝置veth1,綁定IP位址169.254.1.1。當我們使用如下指令編譯并運作以上程式時,會得到如下輸出:

《Kubernetes網絡權威指南》讀書筆記 | 網絡虛拟化基石:namespace
《Kubernetes網絡權威指南》讀書筆記 | 網絡虛拟化基石:namespace

意思是父、子程序分别在兩個不同的namespace中,其中父程序的PID是1831,子程序的PID是1(建立namespace中的第一個程序)。

進入這個新的namespace中,使用nc -l 1234指令監聽0.0.0.0:1234。然後打開另一個終端,輸入以下指令:

《Kubernetes網絡權威指南》讀書筆記 | 網絡虛拟化基石:namespace

意思是向建立network namespace的169.254.1.2:1234發送一個網絡封包Hi。切回原先的終端,會發現順利地收到了一個Hi字元串。

繼續閱讀