虛擬化與容器

Linux 命名空間(Linux namespaces)

想像作業系統給每一群程式各自一份系統資源的私有副本——自己的行程識別碼清單、自己的一組網路卡、自己對檔案系統的視野——即使它們全都共用一個核心。Linux 命名空間做的正是這件事。一個命名空間把「某一種」全域資源切分開,使裡頭的行程只看得見自己那一片,甚至無從察覺其餘的存在。命名空間是「容器如何被造出來」的隔離那一半(控制群組是限制那一半)。

命名空間有好幾種,每一種包住一種資源。PID 命名空間給行程自己的一套編號,於是裡頭的第一個行程以為自己是 PID 1,且看不見外面的行程。網路(net)命名空間給一份私有的網路堆疊——自己的介面、路由表與連接埠——所以兩個容器可以都綁定連接埠 80 而不衝突。掛載(mnt)命名空間給一組私有的檔案系統掛載,這是容器擁有自己根檔案系統的基礎。UTS 命名空間隔離主機名稱;IPC 命名空間隔離共享記憶體、訊息佇列這類行程間通訊物件;使用者(user)命名空間重新映射使用者與群組識別碼,於是一個行程可以在容器「內」是 root(uid 0)、在容器「外」卻是個沒有特權、無害的使用者;而 cgroup 命名空間則隱藏主機的控制群組階層。一個行程可以分別處於每一種的不同命名空間,新的命名空間用 clone() 或 unshare() 系統呼叫建立、用 setns() 加入。

這為何重要:命名空間是讓容器「在沒有獨立客體核心」的情況下成為可能的核心機制——一切仍跑在那唯一的主機核心上,只是視野被仔細地切分了。誠實而尖銳的告誡是:命名空間隔離的是「你能看見與命名的東西」,而不是核心本身:每個容器仍呼叫進同一個共享核心,所以一個從容器內部可觸及的核心臭蟲,能從「所有」容器裡逃脫出來。這正是為何容器的隔離一般被認為弱於虛擬機那種「獨立核心」的隔離——是「種類」上的差別,不只是「程度」上的。尤其使用者命名空間威力強大,但歷史上一直是特權提升臭蟲的來源,正是因為它讓沒有特權的使用者也能建立新的隔離世界。

執行 unshare --pid --fork --mount-proc bash,在新的 shell 裡跑 ps 幾乎什麼都看不到——你的 shell 在它自己的 PID 命名空間裡是 PID 1,看不見主機那數百個行程,即使底下是同一個正在執行的核心。

一個新的 PID 命名空間讓你的 shell 成為 PID 1、對主機的行程視而不見——同一個核心,切分的視野。

命名空間隔離的是資源的「視野」,不是核心:每個容器共用那唯一的主機核心,所以一個從內部可觸及的核心漏洞能逃脫出來。這正是容器隔離弱於虛擬機獨立核心的根本原因。

又称
namespacescontainer isolation primitive名稱空間