您好,登錄后才能下訂單哦!
本篇內(nèi)容主要講解“Namespace機(jī)制是什么”,感興趣的朋友不妨來(lái)看看。本文介紹的方法操作簡(jiǎn)單快捷,實(shí)用性強(qiáng)。下面就讓小編來(lái)帶大家學(xué)習(xí)“Namespace機(jī)制是什么”吧!
Linux Namespace 是 Linux 提供的一種內(nèi)核級(jí)別環(huán)境隔離的方法。這種隔離機(jī)制和 chroot 很類(lèi)似,chroot 是把某個(gè)目錄修改為根目錄,從而無(wú)法訪(fǎng)問(wèn)外部的內(nèi)容。Linux Namesapce 在此基礎(chǔ)之上,提供了對(duì) UTS、IPC、Mount、PID、Network、User 等的隔離機(jī)制,如下所示。
分類(lèi) | 系統(tǒng)調(diào)用參數(shù) | 相關(guān)內(nèi)核版本 |
---|---|---|
Mount Namespaces | CLONE_NEWNS | Linux 2.4.19 |
UTS Namespaces | CLONE_NEWUTS | Linux 2.6.19 |
IPC Namespaces | CLONE_NEWIPC | Linux 2.6.19 |
PID Namespaces | CLONE_NEWPID | Linux 2.6.19 |
Network Namespaces | CLONE_NEWNET | 始于Linux 2.6.24 完成于 Linux 2.6.29 |
User Namespaces | CLONE_NEWUSER | 始于 Linux 2.6.23 完成于 Linux 3.8) |
★Linux Namespace 官方文檔:Namespaces in operation
”
namespace 有三個(gè)系統(tǒng)調(diào)用可以使用:
clone()
--- 實(shí)現(xiàn)線(xiàn)程的系統(tǒng)調(diào)用,用來(lái)創(chuàng)建一個(gè)新的進(jìn)程,并可以通過(guò)設(shè)計(jì)上述參數(shù)達(dá)到隔離。unshare()
--- 使某個(gè)進(jìn)程脫離某個(gè) namespacesetns(int fd, int nstype)
--- 把某進(jìn)程加入到某個(gè) namespace下面使用這幾個(gè)系統(tǒng)調(diào)用來(lái)演示 Namespace 的效果,更加詳細(xì)地可以看 DOCKER基礎(chǔ)技術(shù):LINUX NAMESPACE(上)、 DOCKER基礎(chǔ)技術(shù):LINUX NAMESPACE(下)。
UTS Namespace 主要是用來(lái)隔離主機(jī)名的,也就是每個(gè)容器都有自己的主機(jī)名。我們使用如下的代碼來(lái)進(jìn)行演示。注意:假如在容器內(nèi)部沒(méi)有設(shè)置主機(jī)名的話(huà)會(huì)使用主機(jī)的主機(jī)名的;假如在容器內(nèi)部設(shè)置了主機(jī)名但是沒(méi)有使用 CLONE_NEWUTS 的話(huà)那么改變的其實(shí)是主機(jī)的主機(jī)名。
#define _GNU_SOURCE
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/mount.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* arg) {
printf("Container [%5d] - inside the container!\n", getpid());
sethostname("container_dawn", 15);
execv(container_args[0], container_args);
printf("Something's wrong!\n");
return 1;
}
int main() {
printf("Parent [%5d] - start a container!\n", getpid());
int container_id = clone(container_main, container_stack + STACK_SIZE,
CLONE_NEWUTS | SIGCHLD, NULL);
waitpid(container_id, NULL, 0);
printf("Parent - container stopped!\n");
return 0;
}
每個(gè)容器都有自己的進(jìn)程環(huán)境中,也就是相當(dāng)于容器內(nèi)進(jìn)程的 PID 從 1 開(kāi)始命名,此時(shí)主機(jī)上的 PID 其實(shí)也還是從 1 開(kāi)始命名的,就相當(dāng)于有兩個(gè)進(jìn)程環(huán)境:一個(gè)主機(jī)上的從 1 開(kāi)始,另一個(gè)容器里的從 1 開(kāi)始。
為啥 PID 從 1 開(kāi)始就相當(dāng)于進(jìn)程環(huán)境的隔離了呢?因此在傳統(tǒng)的 UNIX 系統(tǒng)中,PID 為 1 的進(jìn)程是 init,地位特殊。它作為所有進(jìn)程的父進(jìn)程,有很多特權(quán)。另外,其還會(huì)檢查所有進(jìn)程的狀態(tài),我們知道如果某個(gè)進(jìn)程脫離了父進(jìn)程(父進(jìn)程沒(méi)有 wait 它),那么 init 就會(huì)負(fù)責(zé)回收資源并結(jié)束這個(gè)子進(jìn)程。所以要想做到進(jìn)程的隔離,首先需要?jiǎng)?chuàng)建出 PID 為 1 的進(jìn)程。
但是,【kubernetes 里面的話(huà)】
int container_main(void* arg) {
printf("Container [%5d] - inside the container!\n", getpid());
sethostname("container_dawn", 15);
execv(container_args[0], container_args);
printf("Something's wrong!\n");
return 1;
}
int main() {
printf("Parent [%5d] - start a container!\n", getpid());
int container_id = clone(container_main, container_stack + STACK_SIZE,
CLONE_NEWUTS | CLONE_NEWPID | SIGCHLD, NULL);
waitpid(container_id, NULL, 0);
printf("Parent - container stopped!\n");
return 0;
}
如果此時(shí)你在子進(jìn)程的 shell 中輸入 ps、top 等命令,我們還是可以看到所有進(jìn)程。這是因?yàn)?,ps、top 這些命令是去讀 /proc 文件系統(tǒng),由于此時(shí)文件系統(tǒng)并沒(méi)有隔離,所以父進(jìn)程和子進(jìn)程通過(guò)命令看到的情況都是一樣的。
常見(jiàn)的 IPC 有共享內(nèi)存、信號(hào)量、消息隊(duì)列等。當(dāng)使用 IPC Namespace 把 IPC 隔離起來(lái)之后,只有同一個(gè) Namespace 下的進(jìn)程才能相互通信,因?yàn)橹鳈C(jī)的 IPC 和其他 Namespace 中的 IPC 都是看不到了的。而這個(gè)的隔離主要是因?yàn)閯?chuàng)建出來(lái)的 IPC 都會(huì)有一個(gè)唯一的 ID,那么主要對(duì)這個(gè) ID 進(jìn)行隔離就好了。
想要啟動(dòng) IPC 隔離,只需要在調(diào)用 clone 的時(shí)候加上 CLONE_NEWIPC 參數(shù)就可以了。
int container_main(void* arg) {
printf("Container [%5d] - inside the container!\n", getpid());
sethostname("container_dawn", 15);
execv(container_args[0], container_args);
printf("Something's wrong!\n");
return 1;
}
int main() {
printf("Parent [%5d] - start a container!\n", getpid());
int container_id = clone(container_main, container_stack + STACK_SIZE,
CLONE_NEWUTS | CLONE_NEWPID | CLONE_NEWIPC | SIGCHLD, NULL);
waitpid(container_id, NULL, 0);
printf("Parent - container stopped!\n");
return 0;
}
Mount Namespace 可以讓容器有自己的 root 文件系統(tǒng)。需要注意的是,在通過(guò) CLONE_NEWNS 創(chuàng)建 mount namespace 之后,父進(jìn)程會(huì)把自己的文件結(jié)構(gòu)復(fù)制給子進(jìn)程中。所以當(dāng)子進(jìn)程中不重新 mount 的話(huà),子進(jìn)程和父進(jìn)程的文件系統(tǒng)視圖是一樣的,假如想要改變?nèi)萜鬟M(jìn)程的視圖,一定需要重新 mount(這個(gè)是 mount namespace 和其他 namespace 不同的地方)。
另外,子進(jìn)程中新的 namespace 中的所有 mount 操作都只影響自身的文件系統(tǒng)(注意這邊是 mount 操作,而創(chuàng)建文件等操作都是會(huì)有所影響的),而不對(duì)外界產(chǎn)生任何影響,這樣可以做到比較嚴(yán)格地隔離(當(dāng)然這邊是除 share mount 之外的)。
下面我們重新掛載子進(jìn)程的 /proc
目錄,從而可以使用 ps 來(lái)查看容器內(nèi)部的情況。
int container_main(void* arg) {
printf("Container [%5d] - inside the container!\n", getpid());
sethostname("container_dawn", 15);
if (mount("proc", "/proc", "proc", 0, NULL) !=0 ) {
perror("proc");
}
execv(container_args[0], container_args);
printf("Something's wrong!\n");
return 1;
}
int main() {
printf("Parent [%5d] - start a container!\n", getpid());
int container_id = clone(container_main, container_stack + STACK_SIZE,
CLONE_NEWUTS | CLONE_NEWPID | CLONE_NEWNS | SIGCHLD, NULL);
waitpid(container_id, NULL, 0);
printf("Parent - container stopped!\n");
return 0;
}
★這里會(huì)有個(gè)問(wèn)題就是在退出子進(jìn)程之后,當(dāng)再次使用
ps -elf
的時(shí)候會(huì)報(bào)錯(cuò),如下所示這是因?yàn)?/proc 是 share mount,對(duì)它的操作會(huì)影響所有的 mount namespace,可以看這里:http://unix.stackexchange.com/questions/281844/why-does-child-with-mount-namespace-affect-parent-mounts
”
上面僅僅重新 mount 了 /proc
這個(gè)目錄,其他的目錄還是跟父進(jìn)程一樣視圖的。一般來(lái)說(shuō),容器創(chuàng)建之后,容器進(jìn)程需要看到的是一個(gè)獨(dú)立的隔離環(huán)境,而不是繼承宿主機(jī)的文件系統(tǒng)。接下來(lái)演示一個(gè)山寨鏡像,來(lái)模仿 Docker 的 Mount Namespace。也就是給子進(jìn)程實(shí)現(xiàn)一個(gè)較為完整的獨(dú)立的 root 文件系統(tǒng),讓這個(gè)進(jìn)程只能訪(fǎng)問(wèn)自己構(gòu)成的文件系統(tǒng)中的內(nèi)容(想想我們平常使用 Docker 容器的樣子)。
首先我們使用 docker export
將 busybox 鏡像導(dǎo)出成一個(gè) rootfs 目錄,這個(gè) rootfs 目錄的情況如圖所示,已經(jīng)包含了 /proc
、/sys
等特殊的目錄。
之后我們?cè)诖a中將一些特殊目錄重新掛載,并使用 chroot()
系統(tǒng)調(diào)用將進(jìn)程的根目錄改成上文的 rootfs
目錄。
char* const container_args[] = {
"/bin/sh",
NULL
};
int container_main(void* arg) {
printf("Container [%5d] - inside the container!\n", getpid());
sethostname("container_dawn", 15);
if (mount("proc", "rootfs/proc", "proc", 0, NULL) != 0) {
perror("proc");
}
if (mount("sysfs", "rootfs/sys", "sysfs", 0, NULL)!=0) {
perror("sys");
}
if (mount("none", "rootfs/tmp", "tmpfs", 0, NULL)!=0) {
perror("tmp");
}
if (mount("udev", "rootfs/dev", "devtmpfs", 0, NULL)!=0) {
perror("dev");
}
if (mount("devpts", "rootfs/dev/pts", "devpts", 0, NULL)!=0) {
perror("dev/pts");
}
if (mount("shm", "rootfs/dev/shm", "tmpfs", 0, NULL)!=0) {
perror("dev/shm");
}
if (mount("tmpfs", "rootfs/run", "tmpfs", 0, NULL)!=0) {
perror("run");
}
if ( chdir("./rootfs") || chroot("./") != 0 ){
perror("chdir/chroot");
}
// 改變根目錄之后,那么 /bin/bash 是從改變之后的根目錄中搜索了
execv(container_args[0], container_args);
perror("exec");
printf("Something's wrong!\n");
return 1;
}
int main() {
printf("Parent [%5d] - start a container!\n", getpid());
int container_id = clone(container_main, container_stack + STACK_SIZE,
CLONE_NEWUTS | CLONE_NEWPID | CLONE_NEWNS | SIGCHLD, NULL);
waitpid(container_id, NULL, 0);
printf("Parent - container stopped!\n");
return 0;
}
最后,查看實(shí)現(xiàn)效果如下圖所示。
實(shí)際上,Mount Namespace 是基于 chroot 的不斷改良才被發(fā)明出來(lái)的,chroot 可以算是 Linux 中第一個(gè) Namespace。那么上面被掛載在容器根目錄上、用來(lái)為容器鏡像提供隔離后執(zhí)行環(huán)境的文件系統(tǒng),就是所謂的容器鏡像,也被叫做 rootfs(根文件系統(tǒng))。需要明確的是,rootfs 只是一個(gè)操作系統(tǒng)所包含的文件、配置和目錄,并不包括操作系統(tǒng)內(nèi)核。
容器內(nèi)部看到的 UID 和 GID 和外部是不同的了,比如容器內(nèi)部針對(duì) dawn 這個(gè)用戶(hù)顯示的是 0,但是實(shí)際上這個(gè)用戶(hù)在主機(jī)上應(yīng)該是 1000。要實(shí)現(xiàn)這樣的效果,需要把容器內(nèi)部的 UID 和主機(jī)的 UID 進(jìn)行映射,需要修改的文件是 /proc/<pid>/uid_map
和 /proc/<pid>/gid_map
,這兩個(gè)文件的格式是
ID-INSIDE-NS ID-OUTSIDE-NS LENGTH
比如,下面就是將真實(shí)的 uid=1000 的映射為容器內(nèi)的 uid =0:
$ cat /proc/8353/uid_map
0 1000 1
再比如,下面則表示把 namesapce 內(nèi)部從 0 開(kāi)始的 uid 映射到外部從 0 開(kāi)始的 uid,其最大范圍是無(wú)符號(hào) 32 位整型(下面這條命令是在主機(jī)環(huán)境中輸入的)。
$ cat /proc/$$/uid_map
0 0 4294967295
默認(rèn)情況,設(shè)置了 CLONE_NEWUSER 參數(shù)但是沒(méi)有修改上述兩個(gè)文件的話(huà),容器中默認(rèn)情況下顯示為 65534,這是因?yàn)槿萜髡也坏秸嬲?UID,所以就設(shè)置了最大的 UID。如下面的代碼所示:
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/mount.h>
#include <sys/capability.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* arg) {
printf("Container [%5d] - inside the container!\n", getpid());
printf("Container: eUID = %ld; eGID = %ld, UID=%ld, GID=%ld\n",
(long) geteuid(), (long) getegid(), (long) getuid(), (long) getgid());
printf("Container [%5d] - setup hostname!\n", getpid());
//set hostname
sethostname("container",10);
execv(container_args[0], container_args);
printf("Something's wrong!\n");
return 1;
}
int main() {
const int gid=getgid(), uid=getuid();
printf("Parent: eUID = %ld; eGID = %ld, UID=%ld, GID=%ld\n",
(long) geteuid(), (long) getegid(), (long) getuid(), (long) getgid());
printf("Parent [%5d] - start a container!\n", getpid());
int container_pid = clone(container_main, container_stack+STACK_SIZE,
CLONE_NEWUTS | CLONE_NEWPID | CLONE_NEWUSER | SIGCHLD, NULL);
printf("Parent [%5d] - Container [%5d]!\n", getpid(), container_pid);
printf("Parent [%5d] - user/group mapping done!\n", getpid());
waitpid(container_pid, NULL, 0);
printf("Parent - container stopped!\n");
return 0;
}
當(dāng)我以 dawn 這個(gè)用戶(hù)執(zhí)行的該程序的時(shí)候,那么會(huì)顯示如下圖所示的效果。使用 root 用戶(hù)的時(shí)候是同樣的:
接下去,我們要開(kāi)始來(lái)實(shí)現(xiàn)映射的效果了,也就是讓 dawn 這個(gè)用戶(hù)在容器中顯示為 0。代碼是幾乎完全拿耗子叔的博客上的,鏈接可見(jiàn)文末:
int pipefd[2];
void set_map(char* file, int inside_id, int outside_id, int len) {
FILE* mapfd = fopen(file, "w");
if (NULL == mapfd) {
perror("open file error");
return;
}
fprintf(mapfd, "%d %d %d", inside_id, outside_id, len);
fclose(mapfd);
}
void set_uid_map(pid_t pid, int inside_id, int outside_id, int len) {
char file[256];
sprintf(file, "/proc/%d/uid_map", pid);
set_map(file, inside_id, outside_id, len);
}
int container_main(void* arg) {
printf("Container [%5d] - inside the container!\n", getpid());
printf("Container: eUID = %ld; eGID = %ld, UID=%ld, GID=%ld\n",
(long) geteuid(), (long) getegid(), (long) getuid(), (long) getgid());
/* 等待父進(jìn)程通知后再往下執(zhí)行(進(jìn)程間的同步) */
char ch;
close(pipefd[1]);
read(pipefd[0], &ch, 1);
printf("Container [%5d] - setup hostname!\n", getpid());
//set hostname
sethostname("container",10);
//remount "/proc" to make sure the "top" and "ps" show container's information
mount("proc", "/proc", "proc", 0, NULL);
execv(container_args[0], container_args);
printf("Something's wrong!\n");
return 1;
}
int main() {
const int gid=getgid(), uid=getuid();
printf("Parent: eUID = %ld; eGID = %ld, UID=%ld, GID=%ld\n",
(long) geteuid(), (long) getegid(), (long) getuid(), (long) getgid());
pipe(pipefd);
printf("Parent [%5d] - start a container!\n", getpid());
int container_pid = clone(container_main, container_stack+STACK_SIZE,
CLONE_NEWUTS | CLONE_NEWPID | CLONE_NEWNS | CLONE_NEWUSER | SIGCHLD, NULL);
printf("Parent [%5d] - Container [%5d]!\n", getpid(), container_pid);
//To map the uid/gid,
// we need edit the /proc/PID/uid_map (or /proc/PID/gid_map) in parent
set_uid_map(container_pid, 0, uid, 1);
printf("Parent [%5d] - user/group mapping done!\n", getpid());
/* 通知子進(jìn)程 */
close(pipefd[1]);
waitpid(container_pid, NULL, 0);
printf("Parent - container stopped!\n");
return 0;
}
實(shí)現(xiàn)的最終效果如圖所示,可以看到在容器內(nèi)部將 dawn 這個(gè)用戶(hù) UID 顯示為了 0(root),但其實(shí)這個(gè)容器中的 /bin/bash 進(jìn)程還是以一個(gè)普通用戶(hù),也就是 dawn 來(lái)運(yùn)行的,只是顯示出來(lái)的 UID 是 0,所以當(dāng)查看 /root 目錄的時(shí)候還是沒(méi)有權(quán)限。
User Namespace 是以普通用戶(hù)運(yùn)行的,但是別的 Namespace 需要 root 權(quán)限,那么當(dāng)使用多個(gè) Namespace 該怎么辦呢?我們可以先用一般用戶(hù)創(chuàng)建 User Namespace,然后把這個(gè)一般用戶(hù)映射成 root,那么在容器內(nèi)用 root 來(lái)創(chuàng)建其他的 Namespace。
隔離容器中的網(wǎng)絡(luò),每個(gè)容器都有自己的虛擬網(wǎng)絡(luò)接口和 IP 地址。在 Linux 中,可以使用 ip 命令創(chuàng)建 Network Namespace(Docker 的源碼中,它沒(méi)有使用 ip 命令,而是自己實(shí)現(xiàn)了 ip 命令內(nèi)的一些功能)。
下面就使用 ip 命令來(lái)講解一下 Network Namespace 的構(gòu)建,以 bridge 網(wǎng)絡(luò)為例。bridge 網(wǎng)絡(luò)的拓?fù)鋱D一般如下圖所示,其中 br0 是 Linux 網(wǎng)橋。
在使用 Docker 的時(shí)候,如果啟動(dòng)一個(gè) Docker 容器,并使用 ip link show 查看當(dāng)前宿主機(jī)上的網(wǎng)絡(luò)情況,那么你會(huì)看到有一個(gè) docker0 還有一個(gè) veth****
的虛擬網(wǎng)卡,這個(gè) veth 的虛擬網(wǎng)卡就是上圖中 veth,而 docker0 就相當(dāng)于上圖中的 br0。
那么,我們可以使用下面這些命令即可創(chuàng)建跟 docker 類(lèi)似的效果(參考自耗子叔的博客,鏈接見(jiàn)文末參考,結(jié)合上圖加了一些文字)。
## 1. 首先,我們先增加一個(gè)網(wǎng)橋 lxcbr0,模仿 docker0
brctl addbr lxcbr0
brctl stp lxcbr0 off
ifconfig lxcbr0 192.168.10.1/24 up #為網(wǎng)橋設(shè)置IP地址
## 2. 接下來(lái),我們要?jiǎng)?chuàng)建一個(gè) network namespace ,命名為 ns1
# 增加一個(gè) namesapce 命令為 ns1 (使用 ip netns add 命令)
ip netns add ns1
# 激活 namespace 中的 loopback,即127.0.0.1(使用 ip netns exec ns1 相當(dāng)于進(jìn)入了 ns1 這個(gè) namespace,那么 ip link set dev lo up 相當(dāng)于在 ns1 中執(zhí)行的)
ip netns exec ns1 ip link set dev lo up
## 3. 然后,我們需要增加一對(duì)虛擬網(wǎng)卡
# 增加一對(duì)虛擬網(wǎng)卡,注意其中的 veth 類(lèi)型。這里有兩個(gè)虛擬網(wǎng)卡:veth-ns1 和 lxcbr0.1,veth-ns1 網(wǎng)卡是要被安到容器中的,而 lxcbr0.1 則是要被安到網(wǎng)橋 lxcbr0 中的,也就是上圖中的 veth。
ip link add veth-ns1 type veth peer name lxcbr0.1
# 把 veth-ns1 按到 namespace ns1 中,這樣容器中就會(huì)有一個(gè)新的網(wǎng)卡了
ip link set veth-ns1 netns ns1
# 把容器里的 veth-ns1 改名為 eth0 (容器外會(huì)沖突,容器內(nèi)就不會(huì)了)
ip netns exec ns1 ip link set dev veth-ns1 name eth0
# 為容器中的網(wǎng)卡分配一個(gè) IP 地址,并激活它
ip netns exec ns1 ifconfig eth0 192.168.10.11/24 up
# 上面我們把 veth-ns1 這個(gè)網(wǎng)卡按到了容器中,然后我們要把 lxcbr0.1 添加上網(wǎng)橋上
brctl addif lxcbr0 lxcbr0.1
# 為容器增加一個(gè)路由規(guī)則,讓容器可以訪(fǎng)問(wèn)外面的網(wǎng)絡(luò)
ip netns exec ns1 ip route add default via 192.168.10.1
## 4. 為這個(gè) namespace 設(shè)置 resolv.conf,這樣,容器內(nèi)就可以訪(fǎng)問(wèn)域名了
echo "nameserver 8.8.8.8" > conf/resolv.conf
上面基本上就相當(dāng)于 docker 網(wǎng)絡(luò)的原理,只不過(guò):
同理,我們還可以使用如下的方式為正在運(yùn)行的 docker 容器增加一個(gè)新的網(wǎng)卡
ip link add peerA type veth peer name peerB
brctl addif docker0 peerA
ip link set peerA up
ip link set peerB netns ${container-pid}
ip netns exec ${container-pid} ip link set dev peerB name eth2
ip netns exec ${container-pid} ip link set eth2 up
ip netns exec ${container-pid} ip addr add ${ROUTEABLE_IP} dev eth2
Cgroup 的操作接口是文件系統(tǒng),位于 /sys/fs/cgroup
中。假如想查看 namespace 的情況同樣可以查看文件系統(tǒng),namespace 主要查看 /proc/<pid>/ns
目錄。
我們以上面的 [PID Namespace 程序](#PID Namespace) 為例,當(dāng)這個(gè)程序運(yùn)行起來(lái)之后,我們可以看到其 PID 為 11702。
之后,我們保持這個(gè)子進(jìn)程運(yùn)行,然后打開(kāi)另一個(gè) shell,查看這個(gè)程序創(chuàng)建的子進(jìn)程的 PID,也就是容器中運(yùn)行的進(jìn)程在主機(jī)中的 PID。
最后,我們分別查看 /proc/11702/ns
和 /proc/11703/ns
這兩個(gè)目錄的情況,也就是查看這兩個(gè)進(jìn)程的 namespace 情況??梢钥吹狡渲?cgroup、ipc、mnt、net、user 都是同一個(gè) ID,而 pid、uts 是不同的 ID。如果兩個(gè)進(jìn)程的 namespace 編號(hào)相同,那么表示這兩個(gè)進(jìn)程位于同一個(gè) namespace 中,否則位于不同 namespace 中。
如果可以查看 ns 的情況之外,這些文件一旦被打開(kāi),只要 fd 被占用著,即使 namespace 中所有進(jìn)程都已經(jīng)結(jié)束了,那么創(chuàng)建的 namespace 也會(huì)一直存在。比如可以使用 mount --bind /proc/11703/ns/uts ~/uts
,讓 11703 這個(gè)進(jìn)程的 UTS Namespace 一直存在。
Namespace 技術(shù)實(shí)際上修改了應(yīng)用進(jìn)程看待整個(gè)計(jì)算機(jī)“視圖”,即它的”視圖“已經(jīng)被操作系統(tǒng)做了限制,只能”看到“某些指定的內(nèi)容,這僅僅對(duì)應(yīng)用進(jìn)程產(chǎn)生了影響。但是對(duì)宿主機(jī)來(lái)說(shuō),這些被隔離了的進(jìn)程,其實(shí)還是進(jìn)程,跟宿主機(jī)上其他進(jìn)程并無(wú)太大區(qū)別,都由宿主機(jī)統(tǒng)一管理。只不過(guò)這些被隔離的進(jìn)程擁有額外設(shè)置過(guò)的 Namespace 參數(shù)。那么 Docker 項(xiàng)目在這里扮演的,更多是旁路式的輔助和管理工作。如下左圖所示
因此,相比虛擬機(jī)的方式,容器會(huì)更受歡迎。這是假如使用虛擬機(jī)的方式作為應(yīng)用沙盒,那么必須要由 Hypervisor 來(lái)負(fù)責(zé)創(chuàng)建虛擬機(jī),這個(gè)虛擬機(jī)是真實(shí)存在的,并且里面必須要運(yùn)行一個(gè)完整的 Guest OS 才能執(zhí)行用戶(hù)的應(yīng)用進(jìn)程。這樣就導(dǎo)致了采用虛擬機(jī)的方式之后,不可避免地帶來(lái)額外的資源消耗和占用。根據(jù)實(shí)驗(yàn),一個(gè)運(yùn)行著 CentOS 的 KVM 虛擬機(jī)啟動(dòng)后,在不做優(yōu)化的情況下,虛擬機(jī)就需要占用 100-200 MB 內(nèi)存。此外,用戶(hù)應(yīng)用運(yùn)行在虛擬機(jī)中,它對(duì)宿主機(jī)操作系統(tǒng)的調(diào)用就不可避免地要經(jīng)過(guò)虛擬機(jī)軟件的攔截和處理,這本身就是一層消耗,尤其對(duì)資源、網(wǎng)絡(luò)和磁盤(pán) IO 的損耗非常大。
而假如使用容器的方式,容器化之后應(yīng)用本質(zhì)還是宿主機(jī)上的一個(gè)進(jìn)程,這也就意味著因?yàn)樘摂M機(jī)化帶來(lái)的性能損耗是不存在的;而另一方面使用 Namespace 作為隔離手段的容器并不需要單獨(dú)的 Guest OS,這就使得容器額外的資源占用幾乎可以忽略不計(jì)。
總得來(lái)說(shuō),“敏捷”和“高性能”是容器相對(duì)于虛擬機(jī)最大的優(yōu)勢(shì),也就是容器能在 PaaS 這種更加細(xì)粒度的資源管理平臺(tái)上大行其道的重要原因。
但是!基于 Linux Namespace 的隔離機(jī)制相比于虛擬化技術(shù)也有很多不足之處,其中最主要的問(wèn)題就是隔離不徹底。
首先,容器只是運(yùn)行在宿主機(jī)上的一種特殊進(jìn)程,那么容器之間使用的還是同一個(gè)宿主機(jī)上的操作系統(tǒng)。盡管可以在容器里面通過(guò) mount namesapce 單獨(dú)掛載其他不同版本的操作系統(tǒng)文件,比如 centos、ubuntu,但是這并不能改變共享宿主機(jī)內(nèi)核的事實(shí)。這就意味著你要在 windows 上運(yùn)行 Linux 容器,或者在低版本的 Linux 宿主機(jī)上運(yùn)行高版本的 Linux 容器都是行不通的。
而擁有虛擬機(jī)技術(shù)和獨(dú)立 Guest OS 的虛擬機(jī)就要方便多了。
其次,在 Linux 內(nèi)核中,有很多資源和對(duì)象都是不能被 namespace 化的,比如時(shí)間。假如你的容器中的程序使用 settimeofday(2)
系統(tǒng)調(diào)用修改了時(shí)間,整個(gè)宿主機(jī)的時(shí)間都會(huì)被隨之修改。
相比虛擬機(jī)里面可以隨意折騰的自由度,在容器里部署應(yīng)用的時(shí)候,“什么能做,什么不能做” 是用戶(hù)必須考慮的一個(gè)問(wèn)題。之外,容器給應(yīng)用暴露出來(lái)的攻擊面是相當(dāng)大的,應(yīng)用“越獄”的難度也比虛擬機(jī)低很多。雖然,實(shí)踐中可以使用 Seccomp 等技術(shù)對(duì)容器內(nèi)部發(fā)起的所有系統(tǒng)調(diào)用進(jìn)行過(guò)濾和甄別來(lái)進(jìn)行安全加固,但這種方式因?yàn)槎嗔艘粚訉?duì)系統(tǒng)調(diào)用的過(guò)濾,也會(huì)對(duì)容器的性能產(chǎn)生影響。因此,在生產(chǎn)環(huán)境中沒(méi)有人敢把運(yùn)行在物理機(jī)上的 Linux 容器直接暴露到公網(wǎng)上。
另外,容器是一個(gè)“單進(jìn)程”模型。容器的本質(zhì)是一個(gè)進(jìn)程,用戶(hù)的應(yīng)用進(jìn)程實(shí)際上就是容器里 PID=1 的進(jìn)程,而這個(gè)進(jìn)程也是后續(xù)創(chuàng)建的所有進(jìn)程的父進(jìn)程。這也就意味著,在一個(gè)容器中,你沒(méi)辦法同時(shí)運(yùn)行兩個(gè)不同的應(yīng)用,除非能事先找到一個(gè)公共的 PID=1 的程序來(lái)充當(dāng)兩者的父進(jìn)程,比如使用 systemd 或者 supervisord。容器的設(shè)計(jì)更多是希望容器和應(yīng)用同生命周期的,而不是容器還在運(yùn)行,而里面的應(yīng)用早已經(jīng)掛了。
到此,相信大家對(duì)“Namespace機(jī)制是什么”有了更深的了解,不妨來(lái)實(shí)際操作一番吧!這里是億速云網(wǎng)站,更多相關(guān)內(nèi)容可以進(jìn)入相關(guān)頻道進(jìn)行查詢(xún),關(guān)注我們,繼續(xù)學(xué)習(xí)!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。