Linux namespace 是 Linux 提供的一種內核級別環境隔離的方法。
Linux namespace 將全局系統資源封裝在一個抽象中,從而使 namespace 內的進程認為自己具有獨立的資源實例。
分類系統調用參數相關內核版本UTS namespaceCLONE_NEWUTSLinux 2.6.19IPC namespaceCLONE_NEWIPCLinux 2.6.19PID namespaceCLONE_NEWPIDLinux 2.6.24Mount namespaceCLONE_NEWNSLinux 2.4.19Network namespaceCLONE_NEWNETLinux 2.6.29User namespaceCLONE_NEWUSERLinux 3.8proc每個進程都有一個 /proc/pid/ns 目錄,其下面的文件依次表示每個 namespace。
查看當前進程所屬的 namespace 信息
ll /proc/$$/nstotal 0lrwxrwxrwx 1 root root 0 Nov 3 11:50 cgroup -> cgroup:[4026531835]lrwxrwxrwx 1 root root 0 Nov 3 11:50 ipc -> ipc:[4026531839]lrwxrwxrwx 1 root root 0 Nov 3 11:50 mnt -> mnt:[4026531840]lrwxrwxrwx 1 root root 0 Nov 3 11:50 net -> net:[4026531992]lrwxrwxrwx 1 root root 0 Nov 3 11:50 pid -> pid:[4026531836]lrwxrwxrwx 1 root root 0 Nov 3 11:50 pid_for_children -> pid:[4026531836]lrwxrwxrwx 1 root root 0 Nov 3 11:50 user -> user:[4026531837]lrwxrwxrwx 1 root root 0 Nov 3 11:50 uts -> uts:[4026531838]從 3.8 版本的內核開始,該目錄下的每個文件都是一個特殊的符號連結,連結指向 $namespace:[$namespace-inode-number]。前半部份為 namespace 的名稱,後半部份的數字表示這個 namespace 的 inode number(句柄號),inode number 用來對進程所關聯的 namespace 執行某些操作。
這些符號連結的用途之一是用來確認兩個不同的進程是否處於同一 namespace 中。如果兩個進程指向的 namespace inode number 相同,就說明他們在同一個 namespace 下,否則就在不同的 namespace 下。這些符號連結指向的文件比較特殊,不能直接訪問,事實上指向的文件存放在被稱為 nsfs 的文件系統中,該文件系統用戶不可見。可以使用系統調用 stat() 在返回的結構體的 st_ino 欄位中獲取 inode number,shell 命令也可以查看。
stat -L /proc/$$/ns/net File: 『/proc/1244/ns/net』 Size: 0 Blocks: 0 IO Block: 4096 regular empty fileDevice: 4h/4d Inode: 4026531992 Links: 1Access: (0444/-r--r--r--) Uid: ( 0/ root) Gid: ( 0/ root)Access: 2020-11-03 12:22:39.789973532 +0800Modify: 2020-11-03 12:22:39.789973532 +0800Change: 2020-11-03 12:22:39.789973532 +0800 Birth: -如果打開了其中一個文件,那麼只要與該文件相關聯的文件描述符處於打開狀態。即使該 namespace 中的所有進程都終止了,該 namespace 依然不會被刪除。
APILinux 提供的 namespace 操作 API 有 clone()、setns()、unshare()。
clone()創建一個新的進程。
int clone(int (*child_func)(void *), void *child_stack, int flags, void *arg);child_func 傳入子進程運行的程序主函數。有別於系統調用 fork(),雖然都相當於把當前進程複製了一份。但 clone() 可以更細粒度地控制與子進程共享的資源,包括虛擬內存、打開的文件描述符和信號量等等。一旦指定了標誌位 CLONE_NEW*,相對應類型的 namespace 就會被創建,新創建的進程也會成為該 namespace 中的一員。
setns()將進程加入到一個已經存在的 namespace 中。
將調用的進程與特定類型 namespace 的一個實例分離,並將該進程與該類型 namespace 的另一個實例重新關聯。
int setns(int fd, int nstype);fd 表示要加入的 namespace 的文件描述符,可以通過打開其中一個符號連結來獲取,也可以通過打開 bind mount 到其中一個連結的文件來獲取。nstype 讓調用者可以去檢查 fd 指向的 namespace 類型,值可以設置為前文提到的常量 CLONE_NEW*,填 0 表示不檢查。如果調用者已經明確知道自己要加入了 namespace 類型,或者不關心 namespace 類型,就可以使用該參數來自動校驗。結合 setns() 和 execve() 可以實現一個簡單但非常有用的功能:將某個進程加入某個特定的 namespace,然後在該 namespace 中執行命令。util-linux 包裡提供了 nsenter 命令,其提供了一種方式將新創建的進程運行在指定的 namespace 裡面。通過命令行(-t 參數)指定要進入的 namespace 的符號連結,然後利用 setns() 將當前的進程放到指定的 namespace 裡面,再調用 clone() 運行指定的執行文件。
nsenter --target $pid --mount --uts --ipc --net --pidunshare()讓進程脫離當前 namespace,加入一個新的 namespace。
unshare() 與 clone() 類似,但它運行在原先的進程上,不需要創建一個新進程。先通過指定的 flags 參數 CLONE_NEW* 創建一個新的 namespace,然後將調用者加入該 namespace。
Linux 自帶 unshare 命令。
echo $$1244
cat /proc/1244/mounts | grep mqmqueue /dev/mqueue mqueue rw,relatime 0 0
readlink /proc/1244/ns/mntmnt:[4026531840]
unshare -m /bin/bash
readlink /proc/$$/ns/mntmnt:[4026532213]對比兩個 readlink 命令的輸出,可以知道兩個 shell 處於不同的 mount namespace 中。
umount /dev/mqueue
cat /proc/$$/mounts | grep mq
cat /proc/1244/mounts | grep mqmqueue /dev/mqueue mqueue rw,relatime 0 0改變新的 namespace 中的某個掛載點。可以看出,新的 namespace 中的掛載點 /dev/mqueue 已經消失了,但在原來的 namespace 中依然存在。
namespace 之外在 Linux 內核中,有很多資源和對象是不能被 namespace 化的,最典型的例子就是:時間。