Linux Namespaces機制提供一種資源隔離方案。 PID,IPC,Network等系統資源不再是全局性的,而是屬於特定的Namespace。每個Namespace裡面的資源對其他Namespace都是透明的。 要創建新的Namespace,只需要在調用clone時指定相應的flag。 Li ...
Linux Namespaces機制提供一種資源隔離方案。
PID,IPC,Network等系統資源不再是全局性的,而是屬於特定的Namespace。每個Namespace裡面的資源對其他Namespace都是透明的。要創建新的Namespace,只需要在調用clone時指定相應的flag。 Linux Namespaces機製為實現基於容器的虛擬化技術提供了很好的基礎,LXC(Linux containers)就是利用這一特性實現了資源的隔離。不同Container內的進程屬於不同的Namespace,彼此透明,互不幹擾。下麵我們就從clone系統調用的flag出發,來介紹各個Namespace。
命名空間提供了虛擬化的一種輕量級形式,使得我們可以從不同的方面來查看運行系統的全局屬性。該機制類似於Solaris中的zone或 FreeBSD中的jail。對該概念做一般概述之後,我將討論命名空間框架所提供的基礎設施。
命名空間概念
傳統上,在Linux以及其他衍生的UNIX變體中,許多資源是全局管理的。
例如,系統中的所有進程按照慣例是通過PID標識的,這意味著內核必須管理一個全局的PID列表。而且,所有調用者通過uname系統調用返回的系統相關信息(包括系統名稱和有關內核的一些信息)都是相同的。用戶ID的管理方式類似,即各個用戶是通過一個全局唯一的UID號標識。
全局ID使得內核可以有選擇地允許或拒絕某些特權。雖然UID為0的root用戶基本上允許做任何事,但其他用戶ID則會受到限制。例如UID為n 的用戶,不允許殺死屬於用戶m的進程(m≠ n)。但這不能防止用戶看到彼此,即用戶n可以看到另一個用戶m也在電腦上活動。只要用戶只能操縱他們自己的進程,這就沒什麼問題,因為沒有理由不允許用戶看到其他用戶的進程。
但有些情況下,這種效果可能是不想要的。如果提供Web主機的供應商打算向用戶提供Linux電腦的全部訪問許可權,包括root許可權在內。傳統上,這需要為每個用戶準備一臺電腦,代價太高。使用KVM或VMWare提供的虛擬化環境是一種解決問題的方法,但資源分配做得不是非常好。電腦的各個用戶都需要一個獨立的內核,以及一份完全安裝好的配套的用戶層應用。
命名空間提供了一種不同的解決方案,所需資源較少。在虛擬化的系統中,一臺物理電腦可以運行多個內核,可能是並行的多個不同的操作系統。而命名空間則只使用一個內核在一臺物理電腦上運作,前述的所有全局資源都通過命名空間抽象起來。這使得可以將一組進程放置到容器中,各個容器彼此隔離。隔離可以使容器的成員與其他容器毫無關係。但也可以通過允許容器進行一定的共用,來降低容器之間的分隔。例如,容器可以設置為使用自身的PID集合,但仍然與其他容器共用部分文件系統。
本質上,命名空間建立了系統的不同視圖。此前的每一項全局資源都必須包裝到容器數據結構中,只有資源和包含資源的命名空間構成的二元組仍然是全局唯一的。雖然在給定容器內部資源是自足的,但無法提供在容器外部具有唯一性的ID。
考慮系統上有3個不同命名空間的情況。命名空間可以組織為層次,我會在這裡討論這種情況。一個命名空間是父命名空間,衍生了兩個子命名空間。假定容器用於虛擬主機配置中,其中的每個容器必須看起來像是單獨的一臺Linux電腦。因此其中每一個都有自身的init進程,PID為0,其他進程的PID 以遞增次序分配。兩個子命名空間都有PID為0的init進程,以及PID分別為2和3的兩個進程。由於相同的PID在系統中出現多次,PID號不是全局唯一的。
雖然子容器不瞭解系統中的其他容器,但父容器知道子命名空間的存在,也可以看到其中執行的所有進程。圖中子容器的進程映射到父容器中,PID為4到 9。儘管系統上有9個進程,但卻需要15個PID來表示,因為一個進程可以關聯到多個PID。至於哪個PID是”正確”的,則依賴於具體的上下文。
如果命名空間包含的是比較簡單的量,也可以是非層次的,例如下文討論的UTS命名空間。在這種情況下,父子命名空間之間沒有聯繫。
請註意,Linux系統對簡單形式的命名空間的支持已經有很長一段時間了,主要是chroot系統調用。該方法可以將進程限制到文件系統的某一部分,因而是一種簡單的命名空間機制。但真正的命名空間能夠控制的功能遠遠超過文件系統視圖。
Linux內核命名空間描述
在Linux內核中提供了多個namespace,其中包括fs (mount), uts, network, sysvipc, 等。一個進程可以屬於多個namesapce,既然namespace和進程相關,那麼在task_struct結構體中就會包含和namespace相關聯的變數。在task_struct 結構中有一個指向namespace結構體的指針nsproxy。
struct task_struct
{
……..
/* namespaces */
struct nsproxy *nsproxy;
…….
}
再看一下nsproxy是如何定義的,在include/linux/nsproxy.h文件中,這裡一共定義了5個各自的命名空間結構體,在該結構體中定義了5個指向各個類型namespace的指針,由於多個進程可以使用同一個namespace,所以nsproxy可以共用使用,count欄位是該結構的引用計數。
/* 'count' is the number of tasks holding a reference.
* The count for each namespace, then, will be the number
* of nsproxies pointing to it, not the number of tasks.
* The nsproxy is shared by tasks which share all namespaces.
* As soon as a single namespace is cloned or unshared, the
* nsproxy is copied
*/
struct nsproxy
{
atomic_t count;
struct uts_namespace *uts_ns;
struct ipc_namespace *ipc_ns;
struct mnt_namespace *mnt_ns;
struct pid_namespace *pid_ns_for_children;
struct net *net_ns;
};
- UTS命名空間包含了運行內核的名稱、版本、底層體繫結構類型等信息。UTS是UNIX Timesharing System的簡稱。
- 保存在struct ipc_namespace中的所有與進程間通信(IPC)有關的信息。
- 已經裝載的文件系統的視圖,在struct mnt_namespace中給出。
- 有關進程ID的信息,由struct pid_namespace提供。
- struct net_ns包含所有網路相關的命名空間參數。
系統中有一個預設的nsproxy
,init_nsproxy,該結構在task初始化是也會被初始,定義在include/linux/init_task.h
#define INIT_TASK(tsk) \
{
……..
.nsproxy = &init_nsproxy,
……..
}
其中init_nsproxy的定義為:
struct nsproxy init_nsproxy = {
.count = ATOMIC_INIT(1),
.uts_ns = &init_uts_ns,
#if defined(CONFIG_POSIX_MQUEUE) || defined(CONFIG_SYSVIPC)
.ipc_ns = &init_ipc_ns,
#endif
.mnt_ns = NULL,
.pid_ns_for_children = &init_pid_ns,
#ifdef CONFIG_NET
.net_ns = &init_net,
#endif
};
對於.mnt_ns沒有進行初始化,其餘的namespace都進行了系統預設初始化;
命名空間的創建
新的命名空間可以用下麵兩種方法創建。
- 在用fork或clone系統調用創建新進程時,有特定的選項可以控制是與父進程共用命名空間,還是建立新的命名空間。
- unshare系統調用將進程的某些部分從父進程分離,其中也包括命名空間。更多信息請參見手冊頁unshare(2)。
在進程已經使用上述的兩種機制之一從父進程命名空間分離後,從該進程的角度來看,改變全局屬性不會傳播到父進程命名空間,而父進程的修改也不會傳播到子進 程,至少對於簡單的量是這樣。而對於文件系統來說,情況就比較複雜,其中的共用機制非常強大,帶來了大量的可能性。
命名空間的實現需要兩個部分:每個子系統的命名空間結構,將此前所有的全局組件包裝到命名空間中;將給定進程關聯到所屬各個命名空間的機制。
在用fork或clone系統調用創建新進程時,有特定的選項可以控制是與父進程共用命名空間,還是建立新的命名空間。這些選項如下:
- CLONE_NEWPID 進程命名空間。空間內的PID 是獨立分配的,意思就是命名空間內的虛擬 PID 可能會與命名空間外的 PID 相衝突,於是命名空間內的 PID 映射到命名空間外時會使用另外一個 PID。比如說,命名空間內第一個 PID 為1,而在命名空間外就是該 PID 已被 init 進程所使用。
- CLONE_NEWIPC 進程間通信(IPC)的命名空間,可以將 SystemV 的 IPC 和 POSIX 的消息隊列獨立出來。
- CLONE_NEWNET 網路命名空間,用於隔離網路資源(/proc/net、IP 地址、網卡、路由等)。後臺進程可以運行在不同命名空間內的相同埠上,用戶還可以虛擬出一塊網卡。
- CLONE_NEWNS 掛載命名空間,進程運行時可以將掛載點與系統分離,使用這個功能時,我們可以達到 chroot 的功能,而在安全性方面比 chroot 更高。
- CLONE_NEWUTS UTS 命名空間,主要目的是獨立出主機名和網路信息服務(NIS)。
- CLONE_NEWUSER 用戶命名空間,同進程 ID 一樣,用戶 ID 和組 ID 在命名空間內外是不一樣的,並且在不同命名空間內可以存在相同的 ID。
PID Namespace
當調用clone時,設定了CLONE_NEWPID
,就會創建一個新的PID Namespace,clone出來的新進程將成為Namespace里的第一個進程。一個PID Namespace為進程提供了一個獨立的PID環境,PID Namespace內的PID將從1開始,在Namespace內調用fork,vfork或clone都將產生一個在該Namespace內獨立的PID。新創建的Namespace里的第一個進程在該Namespace內的PID將為1,就像一個獨立的系統里的init進程一樣。該Namespace內的孤兒進程都將以該進程為父進程,當該進程被結束時,該Namespace內所有的進程都會被結束。PID Namespace是層次性,新創建的Namespace將會是創建該Namespace的進程屬於的Namespace的子Namespace。子Namespace中的進程對於父Namespace是可見的,一個進程將擁有不止一個PID,而是在所在的Namespace以及所有直系祖先Namespace中都將有一個PID。系統啟動時,內核將創建一個預設的PID Namespace,該Namespace是所有以後創建的Namespace的祖先,因此系統所有的進程在該Namespace都是可見的。
IPC Namespace
當調用clone時,設定了CLONE_NEWIPC
,就會創建一個新的IPC Namespace,clone出來的進程將成為Namespace里的第一個進程。一個IPC Namespace有一組System V IPC objects 標識符構成,這標識符有IPC相關的系統調用創建。在一個IPC Namespace裡面創建的IPC object對該Namespace內的所有進程可見,但是對其他Namespace不可見,這樣就使得不同Namespace之間的進程不能直接通信,就像是在不同的系統里一樣。當一個IPC Namespace被銷毀,該Namespace內的所有IPC object會被內核自動銷毀。
PID Namespace和IPC Namespace可以組合起來一起使用,只需在調用clone時,同時指定CLONE_NEWPID
和CLONE_NEWIPC
,這樣新創建的Namespace既是一個獨立的PID空間又是一個獨立的IPC空間。不同Namespace的進程彼此不可見,也不能互相通信,這樣就實現了進程間的隔離。
mount Namespace
當調用clone時,設定了CLONE_NEWNS
,就會創建一個新的mount Namespace。每個進程都存在於一個mount Namespace裡面,mount Namespace為進程提供了一個文件層次視圖。如果不設定這個flag,子進程和父進程將共用一個mount Namespace,其後子進程調用mount或umount將會影響到所有該Namespace內的進程。如果子進程在一個獨立的mount Namespace裡面,就可以調用mount或umount建立一份新的文件層次視圖。該flag配合pivot_root系統調用,可以為進程創建一個獨立的目錄空間。
Network Namespace
當調用clone時,設定了CLONE_NEWNET
,就會創建一個新的Network Namespace。一個Network Namespace為進程提供了一個完全獨立的網路協議棧的視圖。包括網路設備介面,IPv4和IPv6協議棧,IP路由表,防火牆規則,sockets等等。一個Network Namespace提供了一份獨立的網路環境,就跟一個獨立的系統一樣。一個物理設備只能存在於一個Network Namespace中,可以從一個Namespace移動另一個Namespace中。虛擬網路設備(virtual network device)提供了一種類似管道的抽象,可以在不同的Namespace之間建立隧道。利用虛擬化網路設備,可以建立到其他Namespace中的物理設備的橋接。當一個Network Namespace被銷毀時,物理設備會被自動移回init Network Namespace,即系統最開始的Namespace。
UTS Namespace
當調用clone時,設定了CLONE_NEWUTS
,就會創建一個新的UTS Namespace。一個UTS Namespace就是一組被uname返回的標識符。新的UTS Namespace中的標識符通過複製調用進程所屬的Namespace的標識符來初始化。Clone出來的進程可以通過相關係統調用改變這些標識符,比如調用sethostname來改變該Namespace的hostname。這一改變對該Namespace內的所有進程可見。CLONE_NEWUTS和CLONE_NEWNET一起使用,可以虛擬出一個有獨立主機名和網路空間的環境,就跟網路上一臺獨立的主機一樣。
以上所有clone flag都可以一起使用,為進程提供了一個獨立的運行環境。LXC正是通過clone時設定這些flag,為進程創建一個有獨立PID,IPC,FS,Network,UTS空間的container。一個container就是一個虛擬的運行環境,對container里的進程是透明的,它會以為自己是直接在一個系統上運行的。一個container就像傳統虛擬化技術裡面的一臺安裝了OS的虛擬機,但是開銷更小,部署更為便捷。
Linux Namespaces機制本身就是為了實現 container based virtualizaiton開發的。它提供了一套輕量級、高效率的系統資源隔離方案,遠比傳統的虛擬化技術開銷小,不過它也不是完美的,它為內核的開髮帶來了更多的複雜性,它在隔離性和容錯性上跟傳統的虛擬化技術比也還有差距。
user_namespace
CLONE_NEWUSER指定子進程擁有新的用戶空間