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

linhaifeng發表於2017-04-01

一 介紹

    如果把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

相關文章