網絡虛擬化基礎一:linux名稱空間Namespaces


一 介紹

    如果把linux操作系統比作一個房子,那命名空間指的就是這個房子中的一個個房間,住在每個房間里的人都自以為獨享了整個房子的資源,但其實大家僅僅只是在共享的基礎之上互相隔離,共享指的是共享全局的資源,而隔離指的是局部上彼此保持隔離,因而命名空間的本質就是指:一種在空間上隔離的概念,當下盛行的許多容器虛擬化技術(典型代表如LXC、Docker)就是基於linux命名空間的概念而來的。

    一方面:如果我們要深入研究docker技術,linux namespace是必須掌握的基礎知識。

    另一方面:Neutron也使用Linux命名空間(Network Namespace),這是理解openstack網絡機制的根本。

 

    Linux Namespace是Linux提供的一種內核級別環境隔離的方法,關於隔離的概念其實大家早已接觸過:比如在光盤修復模式下,可以用chroot切換到其他的文件系統,chroot提供了一種簡單的隔離模式:chroot內部的文件系統無法訪問外部的內容。Linux Namespace在此基礎上又提供了很多其他隔離機制。

    當前,Linux 支持6種不同類型的命名空間。它們的出現,使用戶創建的進程能夠與系統分離得更加徹底,從而不需要使用更多的底層虛擬化技術。詳細請點擊

二 Linux Namespaces深入分析

主要是三個系統調用

  • clone() – 實現線程的系統調用,用來創建一個新的進程,並可以通過設計上述參數達到隔離。
  • unshare() – 使某進程脫離某個namespace
  • setns() – 把某進程加入到某個namespace

首先,我們來看一下一個最簡單的clone()系統調用的示例,(后面,我們的程序都會基於這個程序做修改):

文件名:clone.c

#define _GNU_SOURCE 
#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <sched.h>
#include <signal.h>
#include <unistd.h>

/* 定義一個給 clone 用的棧,棧大小1M */
#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 - inside the container!\n");
    /* 直接執行一個shell,以便我們觀察這個進程空間里的資源是否被隔離了 */
    execv(container_args[0], container_args);
    printf("Something's wrong!\n");
    return 1;
}

int main()
{
    printf("Parent - start a container!\n");
    /* 調用clone函數,其中傳出一個函數,還有一個棧空間的(為什么傳尾指針,因為棧是反着的) */
    int container_pid = clone(container_main, container_stack+STACK_SIZE, SIGCHLD, NULL);
    /* 等待子進程結束 */
    waitpid(container_pid, NULL, 0);
    printf("Parent - container stopped!\n");
    return 0;
}

 測試開辟一個新的名稱空間:

[root@www ~]# gcc -o clone clone.c #編譯clone.c
[root@www ~]# ./clone #執行編譯的結果
Parent - start a container!
Container - inside the container!
[root@www ~]#         #進入了一隔離的空間
[root@www ~]# exit    #退出該空間
exit
Parent - container stopped!
[root@www ~]#         #又回到最初的空間

從上面的程序,我們可以看到,這和pthread基本上是一樣的玩法。但是,對於上面的程序,父子進程的進程空間是沒有什么差別的,父進程能訪問到的子進程也能。

下面, 讓我們來看幾個例子看看,Linux的Namespace是什么樣的。

因為下述測試涉及到用戶權限問題,因此我們新建用戶egon(本人的英文名,哈哈),並且賦予該用戶sudo權限

執行visudo然后新增如下內容: 
egon    ALL=(ALL)     NOPASSWD:ALL

2.1 UTS命名空間(系統調用CLONE_NEWUTS)

主要目的是獨立出主機名和網絡信息服務(NIS)。

文件名:uts.c

#define _GNU_SOURCE 
#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <sched.h>
#include <signal.h>
#include <unistd.h>

/* 定義一個給 clone 用的棧,棧大小1M */
#define STACK_SIZE (1024 * 1024) 
static char container_stack[STACK_SIZE];

char* const container_args[] = {
    "/bin/bash",
    NULL
};


/* 與uts有關的代碼:此處只演示主機名的隔離 */
int container_main(void* arg) 
{ 
    printf("Container - inside the container!\n"); 
    sethostname("container",10); /* 設置hostname */ 
    execv(container_args[0], container_args); 
    printf("Something's wrong!\n"); 
    return 1; 
} 
 
int main() 
{ 
    printf("Parent - start a container!\n"); 
    int container_pid = clone(container_main, container_stack+STACK_SIZE,  
            CLONE_NEWUTS | SIGCHLD, NULL); /*啟用CLONE_NEWUTS Namespace隔離 */ 
    waitpid(container_pid, NULL, 0); 
    printf("Parent - container stopped!\n"); 
    return 0; 
} 

 測試開辟一個新的UTS名稱空間/容器container,驗證主機名的隔離性:

[egon@www ~]$ gcc -o uts uts.c #編譯utc.c得到可執行文件uts
[egon@www ~]$ sudo ./uts #需要root權限才能開辟新的container
Parent - start a container!
Container - inside the container!
[root@container egon]#      #進入一個隔離的空間,即一個container
[root@container egon]# hostname #查看該空間下的主機名
container
[root@container egon]# exit #退出該container
exit
Parent - container stopped!
[egon@www ~]$ hostname  #查看最初的空間下的主機名
www.egon.org #發現確實與剛剛我們開辟的container是不同的主機名,驗證了隔離性
[egon@www ~]$ 

2.2 IPC命名空間(系統調用CLONE_NEWIPC)

IPC全稱 Inter-Process Communication,是Unix/Linux下進程間通信的一種方式,IPC有共享內存、信號量、消息隊列等方法。所以,為了隔離,我們也需要把IPC給隔離開來,這樣,只有在同一個Namespace下的進程才能相互通信。如果你熟悉IPC的原理的話,你會知道,IPC需要有一個全局的ID,即然是全局的,那么就意味着我們的Namespace需要對這個ID隔離,不能讓別的Namespace的進程看到。

文件名:ipc.c

要啟動IPC隔離,我們只需要在調用clone時加上CLONE_NEWIPC參數就可以了(見下述代碼標紅的地方

#define _GNU_SOURCE 
#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <sched.h>
#include <signal.h>
#include <unistd.h>

/* 定義一個給 clone 用的棧,棧大小1M */
#define STACK_SIZE (1024 * 1024) 
static char container_stack[STACK_SIZE];

char* const container_args[] = {
    "/bin/bash",
    NULL
};


/* 與uts有關的代碼:此處只演示主機名的隔離 */
int container_main(void* arg) 
{ 
    printf("Container - inside the container!\n"); 
    sethostname("container",10); /* 設置hostname */ 
    execv(container_args[0], container_args); 
    printf("Something's wrong!\n"); 
    return 1; 
} 
 
int main() 
{ 
    printf("Parent - start a container!\n"); 
    int container_pid = clone(container_main, container_stack+STACK_SIZE,  
            CLONE_NEWUTS | CLONE_NEWIPC | SIGCHLD, NULL); /*新增CLONE_NEWIPC就可以了 */ 
    waitpid(container_pid, NULL, 0); 
    printf("Parent - container stopped!\n"); 
    return 0; 
} 

預備階段(在全局新建IPC隊列):

首先,我們先創建一個IPC的Queue(如下所示,全局的Queue ID是0)

ipcmk創建隊列

ipcrm刪除隊列

ipcs查看隊列

[egon@www ~]$ ipcs -q #查看隊列

------ Message Queues --------
key        msqid      owner      perms      used-bytes   messages    
[egon@www ~]$ ipcmk -Q #在全局創建一個ipc的隊列,隊列id為0
Message queue id: 0
[egon@www ~]$ ipcs -q #查看剛剛新建的全局的隊列的信息

------ Message Queues --------
key        msqid      owner      perms      used-bytes   messages    
0x0c076dce 0          egon       644        0            0      

我們暫且不運行編譯的CLONE_NEWIPC的程序ipc,讓我們先運行之前編譯的uts,發現在子進程中還是能看到這個全局的IPC Queue。

[egon@www ~]$ ipcs -q #查看全局的隊列

------ Message Queues --------
key        msqid      owner      perms      used-bytes   messages    
0x0c076dce 0          egon       644        0            0           

[egon@www ~]$ sudo ./uts #進入新的uts容器
Parent - start a container!
Container - inside the container!
[root@container egon]# ipcs -q #在uts容器下發現仍然能看到全局的IPC隊列,證明此時沒有實現IPC隔離

------ Message Queues --------
key        msqid      owner      perms      used-bytes   messages    
0x0c076dce 0          egon       644        0            0           

[root@container egon]# exit #退出uts容器
exit
Parent - container stopped!
[egon@www ~]$ 

測試開辟一個新的IPC名稱空間/容器container,驗證IPC的隔離性:

[egon@www ~]$ gcc -o ipc ipc.c #編譯
[egon@www ~]$ ipcs -q #在全局查看ipc隊列,肯定可以看到

------ Message Queues --------
key        msqid      owner      perms      used-bytes   messages    
0x0c076dce 0          egon       644        0            0           

[egon@www ~]$ sudo ./ipc #進入ipc容器
Parent - start a container!
Container - inside the container!
[root@container egon]# ipcs -q #在容器內查看ipc隊列,發現查看不到全局的ipc隊列,自己這里的ipc隊列為空,驗證了ipc的隔離性
#同理如果在該容器內用ipcmk -Q創建的隊列,在全局也無法看到,讀者可以自行測試 ------ Message Queues -------- key msqid owner perms used-bytes messages [root@container egon]# exit exit Parent - container stopped! [egon@www ~]$

2.3 PID命名空間(系統調用CLONE_NEWPID)

空間內的PID 是獨立分配的,意思就是命名空間內的虛擬 PID 可能會與命名空間外的 PID 相沖突,於是命名空間內的 PID 映射到命名空間外時會使用另外一個 PID。比如說,命名空間內第一個 PID 為1,而在命名空間外就是該 PID 已被 init 進程所使用。

文件名:pid.c

基於ipc.c修改而來,見標紅部分,其中只需新增CLONE_NEWPID就完全可實現PID的隔離,而此處我們即加了CLONE_NEWUTS又加了CLONE_NEWIPC,隨后才添加了CLONE_NEWPID,代表的意思是:在UTS和IPC隔離的基礎之上再進行PID的隔離,此時的容器已經越來越接近於在linux操作系統上新建一個隔離的操作系統了。

#define _GNU_SOURCE 
#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <sched.h>
#include <signal.h>
#include <unistd.h>

/* 定義一個給 clone 用的棧,棧大小1M */
#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()); /* 此處的getpid()是為了獲取容器的初始進程(init)的pid */
    sethostname("container",10); /* 設置hostname */ 
    execv(container_args[0], container_args); 
    printf("Something's wrong!\n"); 
    return 1; 
} 
 
int main() 
{ 
    printf("Parent [%5d] - start a container!\n",getpid()); /* 此處的getpid()則是為了獲取父進程的pid */ 
    int container_pid = clone(container_main, container_stack+STACK_SIZE,  
            CLONE_NEWUTS | CLONE_NEWIPC | CLONE_NEWPID | SIGCHLD, NULL); /*新增CLONE_NEWPID即可,此處代表在UTS和IPC隔離的基礎之上再進行PID的隔離,其實我們完全可以只加CLONE_NEWPID自己:這樣的話就只代表隔離PID了 */ 
    waitpid(container_pid, NULL, 0); 
    printf("Parent - container stopped!\n"); 
    return 0; 
}

 測試開辟一個新的PID名稱空間/容器container,驗證PID的隔離性:

[egon@www ~]$ gcc -o pid pid.c #編譯
[egon@www ~]$ sudo ./pid #進入一個新的容器
Parent [ 4520] - start a container!
Container [    1] - inside the container!
[root@container egon]# echo $$ #查看該容器的初始程序(init)ID為1,而全局的init程序的ID也為1,證明了二者的隔離性
1
[root@container egon]# hostname #因為我們在pid.c文件中加入了CLONE_NEWUTS,所以此時的主機名也是隔離的,看到的是自己的主機名
container
[root@container egon]# ipcs -q #因為我們在pid.c文件中也加入了CLONE_NEWIPC,所以此時的IPC也是隔離的,看不到全局新建的那個IPC隊列

------ Message Queues --------
key        msqid      owner      perms      used-bytes   messages   

 ps:centos7之后使用systemd代替init,此處我們說的初始程序指的就是這二者,是一個意思

    說明:在傳統的UNIX系統中,PID為1的進程是init,地位非常特殊。他作為所有進程的父進程,有很多特權(比如:屏蔽信號等),另外,其還會為檢查所有進程的狀態,我們知道,如果某個子進程脫離了父進程(父進程沒有wait它),那么init就會負責回收資源並結束這個子進程。所以,要做到進程空間的隔離,首先要創建出PID為1的進程,最好就像chroot那樣,把子進程的PID在容器內變成1。

但是,我們會發現,在子進程的shell里輸入ps,top等命令,我們還是可以看得到所有進程。說明並沒有完全隔離。這是因為,像ps, top這些命令會去讀/proc文件系統,所以,因為/proc文件系統在父進程和子進程都是一樣的,所以這些命令顯示的東西都是一樣的。

所以,我們還需要對文件系統進行隔離,這就需要用到mount命名空間了

2.4 Mount命名空間(系統調用CLONE_NEWNS)

進程運行時可以將掛載點與系統分離,使用這個功能時,我們可以達到 chroot 的功能,而在安全性方面比 chroot 更高。

文件名:fs.c

#define _GNU_SOURCE 
#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <sched.h>
#include <signal.h>
#include <unistd.h>

/* 定義一個給 clone 用的棧,棧大小1M */
#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",10); 
    /* 重新mount proc文件系統到 /proc下 */ 
    system("mount -t proc proc /proc"); 
    execv(container_args[0], container_args); 
    printf("Something's wrong!\n"); 
    return 1; 
} 
 
int main() 
{ 
    printf("Parent [%5d] - start a container!\n", getpid()); 
    /* 啟用Mount Namespace - 增加CLONE_NEWNS參數 */ 
    int container_pid = clone(container_main, container_stack+STACK_SIZE,  
            CLONE_NEWUTS | CLONE_NEWIPC | CLONE_NEWPID | CLONE_NEWNS | SIGCHLD, NULL); 
    waitpid(container_pid, NULL, 0); 
    printf("Parent - container stopped!\n"); 
    return 0; 
} 

我們基於上次pid容器,在沒有mount隔離情況下查看/proc、ps aux、top等信息

[egon@www ~]$ sudo ./pid
Parent [ 6231] - start a container!
Container [    1] - inside the container!
[root@container egon]# ls /proc/
1    116   132   148  165   18   197  213  230  248  265   282  36    5005  57    63   73   83   938        diskstats    locks         sysrq-trigger
10   117   133   149  166   180  198  214  231  249  266   283  37    51    58    64   731  84   94         dma          mdstat        sysvipc
100  118   134   15   167   181  199  215  232  25   267   284  38    514   59    640  74   841  95         driver       meminfo       timer_list
101  119   135   150  168   182  2    216  233  250  268   285  39    515   5939  641  745  85   957        execdomains  misc          timer_stats
102  12    136   151  169   183  20   217  234  251  2682  29   3944  517   60    642  75   86   96         fb           modules       tty
103  120   137   152  17    184  200  218  235  252  2684  293  3946  52    6047  643  76   863  960        filesystems  mounts        uptime
104  121   138   153  170   185  201  219  236  253  269   294  3982  520   6048  644  77   864  97         fs           mpt           version
105  122   139   154  171   186  202  22   237  254  27    295  40    53    6052  645  78   87   98         interrupts   mtrr          vmallocinfo
106  123   14    155  172   187  203  220  238  255  270   296  41    532   6053  646  780  871  99         iomem        net           vmstat
......省略n行  
[root@container egon]# ps aux
USER        PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root          1  0.0  0.6  44000  6548 ?        Ss   10:24   0:02 /usr/lib/systemd/systemd --switched-root --system --deserialize 21
root          2  0.0  0.0      0     0 ?        S    10:24   0:00 [kthreadd]
root          3  0.0  0.0      0     0 ?        S    10:24   0:00 [ksoftirqd/0]
root          5  0.0  0.0      0     0 ?        S<   10:24   0:00 [kworker/0:0H]
root          7  0.0  0.0      0     0 ?        S    10:24   0:00 [migration/0]
root          8  0.0  0.0      0     0 ?        S    10:24   0:00 [rcu_bh]
root          9  0.0  0.0      0     0 ?        S    10:24   0:00 [rcuob/0]
root         10  0.0  0.0      0     0 ?        S    10:24   0:00 [rcuob/1]
root         11  0.0  0.0      0     0 ?        S    10:24   0:00 [rcuob/2]
root         12  0.0  0.0      0     0 ?        S    10:24   0:00 [rcuob/3]
root         13  0.0  0.0      0     0 ?        S    10:24   0:00 [rcuob/4]
root         14  0.0  0.0      0     0 ?        S    10:24   0:00 [rcuob/5]
root         15  0.0  0.0      0     0 ?        S    10:24   0:00 [rcuob/6]
root         16  0.0  0.0      0     0 ?        S    10:24   0:00 [rcuob/7]
root         17  0.0  0.0      0     0 ?        S    10:24   0:00 [rcuob/8]
root         18  0.0  0.0      0     0 ?        S    10:24   0:00 [rcuob/9]
root         19  0.0  0.0      0     0 ?        S    10:24   0:00 [rcuob/10]
root         20  0.0  0.0      0     0 ?        S    10:24   0:00 [rcuob/11]
root         21  0.0  0.0      0     0 ?        S    10:24   0:00 [rcuob/12]
root         22  0.0  0.0      0     0 ?        S    10:24   0:00 [rcuob/13]
root         23  0.0  0.0      0     0 ?        S    10:24   0:00 [rcuob/14]
root         24  0.0  0.0      0     0 ?        S    10:24   0:00 [rcuob/15]
......省略n行

初次之外還有top命令運行的截圖

測試開辟一個新的MOUNT名稱空間/容器container,驗證MOUNT的隔離性:

[egon@www ~]$ gcc -o fs fs.c #編譯
[egon@www ~]$ sudo ./fs #進入mount容器
Parent [ 6554] - start a container!
Container [    1] - inside the container!
[root@container egon]#    #此處便是新的容器了
[root@container egon]# ls /proc/ #瀏覽/proc內容,發現少了好多
1          bus       crypto     execdomains  iomem     keys        loadavg  modules  pagetypeinfo  slabinfo  sysrq-trigger  uptime
13         cgroups   devices    fb           ioports   key-users   locks    mounts   partitions    softirqs  sysvipc        version
acpi       cmdline   diskstats  filesystems  irq       kmsg        mdstat   mpt      sched_debug   stat      timer_list     vmallocinfo
asound     consoles  dma        fs           kallsyms  kpagecount  meminfo  mtrr     scsi          swaps     timer_stats    vmstat
buddyinfo  cpuinfo   driver     interrupts   kcore     kpageflags  misc     net      self          sys       tty            zoneinfo
[root@container egon]# ps aux #查看進程信息發現只能兩個進程:一個初始進程id為1,另外一個就算ps命令本身
USER        PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root          1  0.0  0.2 115384  2092 pts/0    S    11:35   0:00 /bin/bash
root         14  0.0  0.1 139500  1632 pts/0    R+   11:35   0:00 ps aux

除此之外執行top命令,發現包括top命令本身,也是只要兩個進程

 

需要強調的一點是:在通過CLONE_NEWNS創建mount namespace后,父進程會把自己的文件結構復制給子進程中。而子進程中新的namespace中的所有mount操作都只影響自身的文件系統,而不對外界產生任何影響。這樣可以做到比較嚴格地隔離。

並且我們完全可以根據自己的需要來為容器定制mount選項。

Docker的 Mount Namespace

下面就讓我們來模擬制作一個鏡像,模仿Docker的Mount Namespace

步驟一:

對於chroot來說,chroot 目錄,然后切入到目錄對應的名稱空間下,同理,我們也需要為我們的mount namespace提供一個目錄(即鏡像),於是我們在/home/egon下新建目錄rootfs

rootfs的目錄結構參照linux根目錄的結構

[root@www ~]# for i in `ls /`;do mkdir /home/egon/rootfs/$i -p;done
[root@www ~]# ls /home/egon/rootfs/
bin  boot  data  dev  etc  home  lib  lib64  media  mnt  opt  proc  root  run  sbin  srv  sys  tmp  usr  var

步驟二 :

把一些我們需要在命名空間內使用的命令拷貝到/home/egon/rootfs/bin以及/home/egon/rootfs/usr/bin目錄下,需要注意的是:/bin/sh命令必須被拷貝,且要被拷貝到/home/egon/rootfs/bin下,否則無法chroot

#新增目錄
[root@www ~]# mkdir /home/egon/rootfs/usr/libexec
[root@www ~]# mkdir /home/egon/rootfs/usr/bin

#拷貝命令
[root@www ~]# cp -r /bin/*  /home/egon/rootfs/bin/
[root@www ~]# cp -r /usr/bin/*  /home/egon/rootfs/usr/bin/

#拷貝命令依賴的庫,可以ldd /bin/ls來查看ls命令用來的庫文件,然后定向拷貝,此處我們就簡單粗暴的使用*拷貝所有了
[root@www ~]# cp -r /lib/*  /home/egon/rootfs/lib/
[root@www ~]# cp -r /lib64/*  /home/egon/rootfs/lib64/
[root@www ~]# cp -r /usr/libexec/* /home/egon/rootfs/usr/libexec/

#拷貝命令依賴的一些配置文件
[root@www ~]# cp -r /etc/* /home/egon/rootfs/etc/

步驟三:

我們還可以為命名空間定制一些配置文件

[root@www ~]# mkdir /home/egon/conf
[root@www ~]# echo 'egon_hostname' >> /home/egon/conf/hostname #定義hostname文件,用來掛載到命名空間中的/etc/hostname
[root@www ~]# echo '1.1.1.1 egon_hostname' >> /home/egon/conf/hosts #定義hosts文件,用來掛載到命名空間中的/etc/hosts
[root@www ~]# echo 'nameserver 202.110.110.213' >> /home/egon/conf/resolv.conf #定義resolv.conf文件,用來掛載到命名空間中的/etc/resolv.conf

同理,我們也可以我新的命名空間定制一些目錄

[root@www ~]# mkdir /tmp/t1 #本文最終會將該目錄掛載到命名空間中的/mnt目錄
[root@www ~]# touch /tmp/t1/egon_test.txt

步驟四:

文件名:newns.c

#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>

/* 定義一個給 clone 用的棧,棧大小1M */
#define STACK_SIZE (1024 * 1024) 
static char container_stack[STACK_SIZE];

char* const container_args[] = {
    "/bin/bash",
    "-l",
    NULL
};

int container_main(void* arg) 
{ 
    printf("Container [%5d] - inside the container!\n", getpid()); 
 
    sethostname("container",10); 
 
    /* remount "/proc" to make sure the "top" and "ps" show container's information */
    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"); 
    } 
    /*  
     * 模仿Docker的從外向容器里mount相關的配置文件  
     * 你可以查看:/var/lib/docker/containers/<container_id>/目錄, 
     * 你會看到docker的這些文件的。 
     */ 
    if (mount("conf/hosts", "rootfs/etc/hosts", "none", MS_BIND, NULL)!=0 || 
          mount("conf/hostname", "rootfs/etc/hostname", "none", MS_BIND, NULL)!=0 || 
          mount("conf/resolv.conf", "rootfs/etc/resolv.conf", "none", MS_BIND, NULL)!=0 ) { 
        perror("conf"); 
    } 
    /* 模仿docker run命令中的 -v, --volume=[] 參數干的事 */ 
    if (mount("/tmp/t1", "rootfs/mnt", "none", MS_BIND, NULL)!=0) { 
        perror("mnt"); 
    } 
 
    /* chroot 隔離目錄 */
    if ( chdir("./rootfs") != 0 || chroot("./") != 0 ){ 
        perror("chdir/chroot"); 
    }
 
    execv(container_args[0], container_args); 
    perror("exec1111"); 
    printf("Something's wrong!\n"); 
    return 1; 
} 
 
int main() 
{ 
    printf("Parent [%5d] - start a container!\n", getpid()); 
    int container_pid = clone(container_main, container_stack+STACK_SIZE,  
            CLONE_NEWUTS | CLONE_NEWIPC | CLONE_NEWPID | CLONE_NEWNS | SIGCHLD, NULL); 
    waitpid(container_pid, NULL, 0); 
    printf("Parent - container stopped!\n"); 
    return 0; 
} 

步驟五:

[egon@www ~]$ gcc -o newns newns.c
[egon@www ~]$ sudo ./newns              #進行新的命名空間
Parent [ 2848] - start a container!
Container [    1] - inside the container!   #基於之前所做,我們已然實現pid隔離
bash-4.2#                                             #chroot進了一個新的命名空間
bash-4.2# pwd                                      #chroot ./rootfs的效果
/
bash-4.2# hostname                             #查看主機名發現實現了主機名隔離
container
bash-4.2# ipcs -q                                  #ipc同樣也是隔離的

------ Message Queues --------
key        msqid      owner      perms      used-bytes   messages    

bash-4.2# ps aux
USER        PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root          1  0.0  0.1  11768  1860 pts/0    S    20:55   0:00 /bin/bash -l
root         28  0.0  0.1  35884  1480 pts/0    R+   20:57   0:00 ps aux
bash-4.2# 
bash-4.2# 
bash-4.2# 
bash-4.2# 
bash-4.2# 
bash-4.2# 
bash-4.2# mount
proc on /proc type proc (rw,relatime)
sysfs on /sys type sysfs (rw,relatime,seclabel)
none on /tmp type tmpfs (rw,relatime,seclabel)
udev on /dev type devtmpfs (rw,relatime,seclabel,size=490432k,nr_inodes=122608,mode=755)
devpts on /dev/pts type devpts (rw,relatime,seclabel,mode=600,ptmxmode=000)
shm on /dev/shm type tmpfs (rw,relatime,seclabel)
tmpfs on /run type tmpfs (rw,relatime,seclabel)
/dev/mapper/centos-root on /etc/hosts type xfs (rw,relatime,seclabel,attr2,inode64,noquota)
/dev/mapper/centos-root on /etc/hostname type xfs (rw,relatime,seclabel,attr2,inode64,noquota)
/dev/mapper/centos-root on /etc/resolv.conf type xfs (rw,relatime,seclabel,attr2,inode64,noquota)
/dev/mapper/centos-root on /mnt type xfs (rw,relatime,seclabel,attr2,inode64,noquota)
proc on /proc type proc (rw,relatime)
none on /tmp type tmpfs (rw,relatime,seclabel)
shm on /dev/shm type tmpfs (rw,relatime,seclabel)
tmpfs on /run type tmpfs (rw,relatime,seclabel)
/dev/mapper/centos-root on /etc/hosts type xfs (rw,relatime,seclabel,attr2,inode64,noquota)
/dev/mapper/centos-root on /etc/hostname type xfs (rw,relatime,seclabel,attr2,inode64,noquota)
/dev/mapper/centos-root on /etc/resolv.conf type xfs (rw,relatime,seclabel,attr2,inode64,noquota)
/dev/mapper/centos-root on /mnt type xfs (rw,relatime,seclabel,attr2,inode64,noquota)
bash-4.2# cat /etc/hostname #驗證步驟三所述
testhostname
bash-4.2# cat /etc/hosts    #同上
123
bash-4.2# cat /etc/resolv.conf #同上
123
bash-4.2# ls /mnt/             #同上
egon_test.txt

 

 

 

 

 

2.5 Network命名空間

用於隔離網絡資源(/proc/net、IP 地址、網卡、路由等)。后台進程可以運行在不同命名空間內的相同端口上,用戶還可以虛擬出一塊網卡。

每個網絡命名空間都有自己的路由表,它自己的iptables設置提供nat和過濾。Linux網絡命名空間還提供了在網絡命名空間內運行進程的功能。

2.6 User命名空間

同進程 ID 一樣,用戶 ID 和組 ID 在命名空間內外是不一樣的,並且在不同命名空間內可以存在相同的 ID。

 

 

 

 

 

 

 

 

 

 

 

 

參考鏈接:

https://lwn.net/Articles/531114/

http://www.opencloudblog.com/?p=42

http://os.51cto.com/art/201609/517640.htm

http://os.51cto.com/art/201609/517641.htm


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2026 CODEPRJ.COM