這篇文章主要為大家展示了“Docker基礎知識之Linux namespace的示例分析”,內容簡而易懂,條理清晰,希望能夠幫助大家解決疑惑,下面讓小編帶領大家一起研究并學習一下“Docker基礎知識之Linux namespace的示例分析”這篇文章吧。
Docker 是“新瓶裝舊酒”的產物,依賴于 Linux 內核技術 chroot 、namespace 和 cgroup。
Docker 和虛擬機技術一樣,從操作系統級上實現了資源的隔離,它本質上是宿主機上的進程(容器進程),所以資源隔離主要就是指進程資源的隔離。實現資源隔離的核心技術就是 Linux namespace。這技術和很多語言的命名空間的設計思想是一致的(如 C++ 的 namespace)。
隔離意味著可以抽象出多個輕量級的內核(容器進程),這些進程可以充分利用宿主機的資源,宿主機有的資源容器進程都可以享有,但彼此之間是隔離的,同樣,不同容器進程之間使用資源也是隔離的,這樣,彼此之間進行相同的操作,都不會互相干擾,安全性得到保障。
為了支持這些特性,Linux namespace 實現了 6 項資源隔離,基本上涵蓋了一個小型操作系統的運行要素,包括主機名、用戶權限、文件系統、網絡、進程號、進程間通信。
這 6 項資源隔離分別對應 6 種系統調用,通過傳入上表中的參數,調用 clone()
函數來完成。
int clone(int (*child_func)(void *), void *child_stack, int flags, void *arg);
clone()
函數相信大家都不陌生了,它是 fork()
函數更通用的實現方式,通過調用 clone()
,并傳入需要隔離資源對應的參數,就可以建立一個容器了(隔離什么我們自己控制)。
一個容器進程也可以再 clone()
出一個容器進程,這是容器的嵌套。
如果想要查看當前進程下有哪些 namespace 隔離,可以查看文件 /proc/[pid]/ns (注:該方法僅限于 3.8 版本以后的內核)。
可以看到,每一項 namespace 都附帶一個編號,這是唯一標識 namespace 的,如果兩個進程指向的 namespace 編號相同,則表示它們同在該 namespace 下。同時也注意到,多了一個 cgroup,這個 namespace 是 4.6 版本的內核才支持的。Docker 目前對它的支持普及度還不高。所以我們暫時先不考慮它。
下面通過簡單的代碼來實現 6 種 namespace 的隔離效果,讓大家有個直觀的印象。
UTS namespace
UTS namespace 提供了主機名和域名的隔離,這樣每個容器就擁有獨立的主機名和域名了,在網絡上就可以被視為一個獨立的節點,在容器中對 hostname 的命名不會對宿主機造成任何影響。
首先,先看總體的代碼骨架:
#define _GNU_SOURCE #include <sys/types.h> #include <sys/wait.h> #include <stdio.h> #include <sched.h> #include <signal.h> #include <unistd.h> #define STACK_SIZE (1024 * 1024) static char container_stack[STACK_SIZE]; char* const container_args[] = { "/bin/bash", NULL }; // 容器進程運行的程序主函數 int container_main(void *args) { printf("在容器進程中!\n"); execv(container_args[0], container_args); // 執行/bin/bash return 1; } int main(int args, char *argv[]) { printf("程序開始\n"); // clone 容器進程 int container_pid = clone(container_main, container_stack + STACK_SIZE, SIGCHLD, NULL); // 等待容器進程結束 waitpid(container_pid, NULL, 0); return 0; }
該程序骨架調用 clone()
函數實現了子進程的創建工作,并定義子進程的執行函數,clone()
第二個參數指定了子進程運行的??臻g大小,第三個參數即為創建不同 namespace 隔離的關鍵。
對于 UTS namespace,傳入 CLONE_NEWUTS,如下:
int container_pid = clone(container_main, container_stack + STACK_SIZE, SIGCHLD | CLONE_NEWUTS, NULL);
為了能夠看出容器內和容器外主機名的變化,我們子進程執行函數中加入:
sethostname("container", 9);
最終運行可以看到效果如下:
IPC namespace
IPC namespace 實現了進程間通信的隔離,包括常見的幾種進程間通信機制,如信號量,消息隊列和共享內存。我們知道,要完成 IPC,需要申請一個全局唯一的標識符,即 IPC 標識符,所以 IPC 資源隔離主要完成的就是隔離 IPC 標識符。
同樣,代碼修改僅需要加入參數 CLONE_NEWIPC 即可,如下:
int container_pid = clone(container_main, container_stack + STACK_SIZE, SIGCHLD | CLONE_NEWUTS | CLONE_NEWIPC, NULL);
為了看出變化,首先在宿主機上建立一個消息隊列:
然后運行程序,進入容器查看 IPC,沒有找到原先建立的 IPC 標識,達到了 IPC 隔離。
PID namespace
PID namespace 完成的是進程號的隔離,同樣在 clone()
中加入 CLONE_NEWPID 參數,如:
int container_pid = clone(container_main, container_stack + STACK_SIZE, SIGCHLD | CLONE_NEWUTS | CLONE_NEWIPC | CLONE_NEWPID, NULL);
效果如下,echo $$
輸出 shell 的 PID 號,發生了變化。
但是對于 ps/top 之類命令卻沒有改變:
具體的原因和接下來的內容(包括 mount namespace,network namespace 和 user namespace),大家可以關注我的公眾號閱讀,那里的閱讀體驗會更好一些。
以上是“Docker基礎知識之Linux namespace的示例分析”這篇文章的所有內容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內容對大家有所幫助,如果還想學習更多知識,歡迎關注億速云行業資訊頻道!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。