1. 程式人生 > >宋寶華- Linux namespace

宋寶華- Linux namespace

名稱空間是在OS之上實現容器與主機隔離,以及容器之間互相隔離的Linux核心核心技術。根據《Docker 最初的2小時(Docker從入門到入門)》一文,名稱空間本質上就是在不同的工作組裡面封官許願,讓大家在各自的部門裡面都是manager,而且彼此不衝突。本文接下來從細節做一些討論。

由於本文敲的命令既有可能位於主機,又有可能位於新的名稱空間(模擬容器),為了避免搞亂你的腦子,下面主機命令一概採用本顏色,而模擬容器類的命令一概採用本顏色。色盲讀者,敬請諒解。

名稱空間是什麼?

名稱空間(Namespace),它表示著一個識別符號(identifier)的可見範圍。一個識別符號可在多個名稱空間中定義,它在不同名稱空間中的含義是互不相干的。這樣,在一個新的名稱空間中可定義任何識別符號,它們不會與任何已有的識別符號發生衝突,只要已有的定義都處於其他名稱空間中。再次回憶一下這個封官許願圖,大家都是官:


名稱空間是C++、Java裡面常見的概念。比如下面最簡單的程式,在2個獨立的名稱空間裡面各自的函式都是叫func(),func就是一個識別符號(identifier),可以並存於多個名稱空間。

#include <iostream>
using namespace std;

// 第一個名稱空間
namespace first_space{
   void func(){
      cout << "Inside first_space" << endl;
   }
}
// 第二個名稱空間
namespace second_space{
   void func(){
      cout << "Inside second_space" << endl;
   }
}
int main ()
{
 
   // 呼叫第一個名稱空間中的函式
   first_space::func();
   
   // 呼叫第二個名稱空間中的函式
   second_space::func(); 

   return 0;
}
Docker要營造OS級別的虛擬化,需要實現一點,讓每個容器都感覺自己擁有整個的獨立OS,但是實際上,在Docker下,多個容器實際上是運行於相同的OS核心上面:


所以,核心需要提供某種意義上的抽象,讓各個容器感覺自己擁有獨立的OS,讓它們自己執行的時候覺得不是在一個整體的OS裡面執行,而是各個容器感覺自己獨有一個OS,這個OS最好和底層實際的主機資源隔離,才能實現容器執行的平臺無關性。這個抽象可以從這幾個角度展開:

程序的ID(PID)

現在每個容器內部的程序應該擁有獨立的PID,不能在同一個OS的一個大池子裡面(儘管實際上是,但是在容器內部要意識不到)。典型的,在Linux裡面,init程序的PID是1,容器化後,應該每個容器都有一個1以及由1衍生的子程序和子程序的子程序(子子孫孫無窮匱)。但是這個容器內部的1程序,在容器內部它是1,但是最終它肯定是屬於底下那個同一個OS大池子裡面的某一個PID。

類似你在上海呼叫電話號碼88888888,和在武漢呼叫電話號碼88888888,在各自的城市都覺得是88888888,但是在全國(底下唯一的kernel)範圍內則分別是021-88888888和027-88888888。

這種對映關係類似於:


程序間通訊(IPC)

與PID類似,在容器內部的程序間通訊應該被從全域性的Linux的程序間通訊隔離開來。在沒有名稱空間的情況下,Linux System V IPC都會有各自的ID。譬如:

$ ipcs
------ Shared Memory Segments --------
key        shmid      owner      perms      bytes      nattch     status      
0x00000000 524288     baohua     600        524288     2          dest         
0x00000000 327681     baohua     600        1048576    2          dest         
0x00000000 425986     baohua     600        524288     2          dest         
…         
------ Semaphore Arrays --------
key        semid      owner      perms      nsems     
0x002fa327 0          root       666        2         
------ Message Queues --------
key        msqid      owner      perms      used-bytes   messages

但是在各個容器內部,ID與ID之間應該互相隔離。容器內部應該看不到主機的IPC,而一個容器也看不到另外一個容器的IPC。譬如在這臺主機上跑Ubuntu 14.04的bash,目前還沒有發現IPC:

[email protected]:~$ docker run -it --rm ubuntu:14.04 bash
[email protected]:/# ipcs
------ Shared Memory Segments --------
key        shmid      owner      perms      bytes      nattch     status      
------ Semaphore Arrays --------
key        semid      owner      perms      nsems     
------ Message Queues --------
key        msqid      owner      perms      used-bytes   messages    

主機名稱(UTS)

要讓容器各自感覺獨立,那麼從底層的主機名獨立也是很重要的。比如,我的主機名現在可以通過hostname命令獲取:

[email protected]:~/develop/linux$ hostname
baohua-VirtualBox

而執行的Docker內部的主機名則可以用docker run的-h引數指定,現在我們指定為“container”:

[email protected]:~/develop/linux$ docker run -h container -it --rm ubuntu:14.04 bash
[email protected]:/# hostname
container

這樣容器內部的程序,就不覺得自己在“baohua-VirtualBox”這個機器上面跑。

如果我們docker run中不指定hostname,會有一個隨機分配的數值做hostname:

[email protected]:~$ docker run -it --rm ubuntu:14.04 bash
[email protected]:/# hostname
0c7951083f70

使用者(User )

比如我用我的電腦,我是用baohua這個使用者名稱。但是在Docker的容器裡面,為了體現虛擬化的概念,容器肯定要和實際的主機分離,這個時候,容器裡面應該有自己的使用者名稱。

[email protected]:~/develop/linux$ docker run -h container -it --rm ubuntu:14.04 bash
[email protected]:/#

登陸到容器後,我們得到的使用者名稱是root。

看到這個root,我們會疑惑?它是否會擁有類似主機的root許可權,比如甚至都可以跑到sysfs裡面卸掉一個CPU?這個顯然是不可能的:

[email protected]:/sys/devices/system/cpu/cpu1# sh -c 'echo 0 > online'
sh: 1: cannot create online: Read-only file system

因為在容器裡面,sysfs都是隻讀的。實際上,我們並不太希望容器裡面控制真實的主機。這個root許可權發揮的作用,更多的是在容器內部,它針對虛擬化後的資源,擁有的root許可權,比如可以在容器內部執行mount。

下面我們驗證容器內部的root許可權的作用:容器啟動後,我們在根目錄下建立檔案1,並且在其中寫入hello,之後在容器內建立使用者名稱baohua,以baohua這個使用者,再在1裡面寫入hello就不會有許可權:

$ docker run -h container -it --rm ubuntu:14.04 bash
[email protected]:/# touch 1
[email protected]:/# echo hello > 1
[email protected]:/# useradd baohua
[email protected]:/# su baohua
[email protected]:/$ echo hello > 1
bash: 1: Permission denied

掛載(mount)

既然我們強調容器與主機的剝離,我們顯然不應該把主機的檔案系統暴露給容器內部。眾所周知,Linux應用的執行不能沒有根檔案系統以及proc,sys,dev等特殊的檔案系統。所以容器內部也不能不擁有自己的這些檔案。但是另外一方面,容器內部看到的東西和主機看到的應該不一樣,否則主機就直接暴露給了容器,不能體現虛擬化概念。
Linux的mount名稱空間可以實現不同mount 名稱空間的程序看到的檔案系統層次不一樣。也就是說,不同的容器,以及容器與主機之間,可以出現不同目錄結構;當然也可以出現相同的目錄結構,但是他們在磁碟的位置可以不一樣。
另外一個方面,mount()和umount()系統呼叫的影響不再是全域性的而隻影響其呼叫程序指向的名稱空間。所以容器A裡面mount了xxx到目錄yyy,容器B也看不見,當然主機的yyy 目錄也不會指向xxx。

網路(network)

網路名稱空間可以被認為是隔離的擁有單獨網路協議資源(網絡卡、路由轉發表、iptables)的環境,比如在容器裡面可以看到獨立的虛擬網絡卡。以網路埠為例,一個容器裡面佔據掉的某埠,在另外一個容器裡面可以佔據同樣的埠,因為理念上,它們擁有的網絡卡、IP都是可以不同的。當然新的網路名稱空間內,也是需要網絡卡的,我們一般可以增加一對虛擬網絡卡veth peer。veth (虛擬乙太網)裝置是成對的,把veth中的一個放入名稱空間A,另外一個放入名稱空間B,則這2個名稱空間就可以通過這對veth來通訊。當然,這對veth中的一個也可以位於主機。veth的工作原理是:發給veth一端的包可以被另外一個一端收到,這點有些類似具備雙向功能的pipe(但是實際pipe是單向的)。

Linux如何支援名稱空間

地球人都知道,Linux建立一個新的程序可以用fork()、vfork()和clone(),而它們在Linux核心的最底層都九九歸一到do_fork()這個函式。

其中clone()是最特殊的,因為它可以帶上一系列的flags從而實現子程序資源的不同編排。比如pthread_create()建立執行緒的時候,底層就是呼叫clone()並通過一系列CLONE flags來表明子程序要繼續父程序資源,從而以這種輕量級程序的方式來實現所謂執行緒的。
clone(child_stack=0xb6cf1424, flags=CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGHAND|CLONE_THREAD|CLONE_SYSVSEM|CLONE_SETTLS|CLONE_PARENT_SETTID|CLONE_CHILD_CLEARTID, …)
為了支援名稱空間,Linux核心增加了幾個CLONE_NEW開頭的flag,它們是CLONE_NEWNS(針對mount)、CLONE_NEWUTS(針對主機名)、CLONE_NEWIPC、CLONE_NEWPID、CLONE_NEWNET、CLONE_NEWUSER,當我們執行clone()時候,每多帶上一個flag,就表明相應的名稱空間會多建立一份,體現“NEW”這個單詞的含義。
下面我們在一個簡單的C程式中逐步增加各種名稱空間的支援,這個C程式中,我們用clone()建立一個子程序,並在子程序中呼叫exec()執行bash shell。在透過clone()建立子程序時,我們通過不同的clone FLAGS來使得子程序擁有獨特名稱空間。

第一步,無新名稱空間

我們編寫一個最簡單的程式,通過clone()建立一個子程序,子程序再通過exec()函式族執行bash shell(目的是為了方便在子程序中,執行Linux的命令)
#define _GNU_SOURCE
#include <sched.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <signal.h>
#include <stdio.h>

#define STACK_SIZE (1024 * 1024)

#define errExit(msg)    do { perror(msg); exit(EXIT_FAILURE); \
                        } while (0)

static char child_stack[STACK_SIZE];

int child_main(void *arg)
{
	printf("child\n");
	execlp("/bin/bash","bash",NULL,NULL);
	return 1;
}

int main()
{
    pid_t child_pid;
	child_pid = clone(child_main,child_stack+STACK_SIZE,SIGCHLD,NULL);

	if (child_pid == -1)
        	errExit("clone");


	wait(NULL);

	return 0;
}
我們編譯執行它,在子程序的bash shell裡面檢視自身PID,結果是18374,看起來很正常:$ gcc main.c 
[email protected]:~/develop/training/namespace$ ./a.out 
child
[email protected]:~/develop/training/namespace$ echo $$
18374
此時,clone()的時候一個CLONE_NEWxxx都未帶,所以父子程序共享一樣的名稱空間。

第二步,新增PID名稱空間:

只改一行程式碼:
@@ -23,7 +23,7 @@ int child_main(void *arg)
 int main()
 {
        pid_t child_pid;
-       child_pid = clone(child_main,child_stack+STACK_SIZE,SIGCHLD,NULL);
+       child_pid = clone(child_main,child_stack+STACK_SIZE,SIGCHLD | CLONE_NEWPID,NULL);
 
        if (child_pid == -1)
                errExit("clone");

編譯後執行:

$ sudo setcap all+eip ./a.out
[sudo] password for baohua:
[email protected]:~/develop/training/namespace$./a.out

child
[email protected]:~/develop/training/namespace$echo $$
1

前面一步setcap的目的是為了給程式執行CLONE_NEWPID的能力。後面echo $$顯示的結果是1,子程序的bash shell是新的PID名稱空間的init程序。但是在主機環節中,bash的PID是多少呢?執行命令ps --ppid:

$ ps --ppid `pidof a.out`
  PIDTTY          TIME CMD
19094 pts/8    00:00:00 bash

如果這個時候,我們在主機環境進入/proc/19094/ns,會發現其中的pid與主機的其他程序的ns內容不一樣。

第三步,mount名稱空間:

我們現在增加CLONE_NEWNS,然後mount proc等。修改2行程式碼:
@@ -16,6 +16,7 @@ static char child_stack[STACK_SIZE];
 int child_main(void *arg)
 {
        printf("child\n");
+       system("mount -t proc proc /proc");
        execlp("/bin/bash","bash",NULL,NULL);
        return 1;
 }
@@ -23,7 +24,7 @@ int child_main(void *arg)
 int main()
 {
        pid_t child_pid;
-       child_pid = clone(child_main,child_stack+STACK_SIZE,SIGCHLD | CLONE_NEWPID,NULL);
+       child_pid = clone(child_main,child_stack+STACK_SIZE,SIGCHLD | CLONE_NEWPID | CLONE_NEWNS,NULL);
 
        if (child_pid == -1)
                errExit("clone");
執行之,進入bash子程序看proc目錄,發現只有1和20兩個程序
相反的,主機下的/proc卻含有大量的程序:
/proc中內容的不一樣,自然會導致在主機和新名稱空間內部敲ps、top這種命令看到的結果不一樣,因為此類命令就是通過讀/proc中的資料來實現的。下面我們在主機的家目錄(/home/baohua)建立~/test-dir,在裡面建立檔案1,內容寫為hello。並掛載/home/baohua/test-dir到新名稱空間的/mnt目錄。$ mkdir ~/test-dir
$ touch ~/test-dir/1
$ echo hello > ~/test-file

只需要在child_main()函式裡面增加一行程式碼:

mount("/home/baohua/test-dir","/mnt", "none", MS_BIND, NULL);
執行結果如下,我們現在發現在新的程序的名稱空間內/mnt目錄下面有檔案1,而且內容是hello。

sudo ./a.out 
child
[email protected]:~/develop/training/namespace# cd /mnt/
[email protected]:/mnt# ls
1
[email protected]:/mnt# cat 1
hello

而此時轉過頭來看主機的/mnt的內容完全不同(還是老樣子,並沒有因為新的名稱空間內部有重新mount /mnt而發生變化):[email protected]:/$ cd /mnt/
[email protected]:/mnt$ ls
hgfs

第四步:網路名稱空間

現在在前面程式中clone()的flags增加CLONE_NEWNET,修改1行程式碼:

@@ -27,7 +26,8 @@ int child_main(void *arg)
 int main()
 {
       pid_t child_pid;
-      child_pid = clone(child_main,child_stack+STACK_SIZE,SIGCHLD |CLONE_NEWPID | CLONE_NEWNS,NULL);
+      child_pid = clone(child_main,child_stack+STACK_SIZE,SIGCHLD |CLONE_NEWPID |
+               CLONE_NEWNS |CLONE_NEWNET,NULL);
在新的程序的名稱空間內,執行ifconfig和ip link list,可以說網路環境是十分的單純:[email protected]:~/develop/training/namespace#ifconfig
[email protected]:~/develop/training/namespace#ip link list
1: lo: <LOOPBACK> mtu 65536 qdiscnoop state DOWN mode DEFAULT group default
   link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00

而主機裡面由於存在真實網絡卡等,內容十分豐富:

[email protected]:~/develop/training/namespace$ifconfig
docker0  Link encap:Ethernet  HWaddr00:00:00:00:00:00 
         inet addr:172.17.42.1 Bcast:0.0.0.0  Mask:255.255.0.0
         …
eth0     Link encap:Ethernet  HWaddr00:0c:29:ef:11:2f 
         inet addr:192.168.47.128 Bcast:192.168.47.255 Mask:255.255.255.0
         inet6 addr: fe80::20c:29ff:feef:112f/64 Scope:Link
         …
lo       Link encap:Local Loopback 
         inet addr:127.0.0.1 Mask:255.0.0.0
         inet6 addr: ::1/128 Scope:Host
         …
[email protected]:~/develop/training/namespace$ip link list
1: lo: <LOOPBACK,UP,LOWER_UP> mtu65536 qdisc noqueue state UNKNOWN mode DEFAULT group default
   link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP>mtu 1500 qdisc pfifo_fast state UP mode DEFAULT group default qlen 1000
   link/ether 00:0c:29:ef:11:2f brd ff:ff:ff:ff:ff:ff
3: docker0:<NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWNmode DEFAULT group default
   link/ether 00:00:00:00:00:00 brd ff:ff:ff:ff:ff:ff

在新的名稱空間中,只有一個loopback裝置,這個時候還ping不通127.0.0.1因為它還沒有up:

[email protected]:~/develop/training/namespace#ping 127.0.0.1
connect: Network is unreachable

把它up一下再ping:

[email protected]:~/develop/training/namespace#ip link set dev lo up
[email protected]:~/develop/training/namespace#ping 127.0.0.1
PING 127.0.0.1 (127.0.0.1) 56(84) bytes ofdata.
64 bytes from 127.0.0.1: icmp_seq=1 ttl=64time=0.035 ms
64 bytes from 127.0.0.1: icmp_seq=2 ttl=64time=0.022 ms
^C
--- 127.0.0.1 ping statistics ---
2 packets transmitted, 2 received, 0%packet loss, time 999ms
rtt min/avg/max/mdev =0.022/0.028/0.035/0.008 ms

但是,我們在新的名稱空間的loopback裝置,和主機裡面的loopback其實不是一個loopback。

下面檢視a.out子程序bash在主機中的PID是21405(接下來新增虛擬網絡卡的時候需要這個數值):

$ ps --ppid `pidof a.out`
  PIDTTY      STAT   TIME COMMAND
19093 pts/8    S     0:00 ./a.out
21405 pts/8    S+    0:00 bash

新增一對虛擬網絡卡,讓新的名稱空間可以和主機互聯。在主機中敲入如下命令:

$ sudo ip link add name veth0 type vethpeer name veth1 netns 21405

上述命令設定了連線的一對虛擬網路裝置,它是這麼工作的:傳送給veth0的資料包將會被veth1收到,傳送給veth1資料包將會被veth0收到。

我們進入新的名稱空間的bash,敲如下命令,發現新的名稱空間裡面真的多出來veth1虛擬網絡卡!

[email protected]:~/develop/training/namespace#ip link list

1: lo: <LOOPBACK> mtu 65536 qdiscnoop state DOWN mode DEFAULT group default

   link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00

2: veth1: <BROADCAST,MULTICAST> mtu 1500 qdisc noopstate DOWN mode DEFAULT group default qlen 1000

link/ether 3e:7a:86:a3:8b:9d brdff:ff:ff:ff:ff:ff

而主機上面則湧現出了新的veth0網絡卡:

$ ip link list

1: lo: <LOOPBACK,UP,LOWER_UP> mtu65536 qdisc noqueue state UNKNOWN mode DEFAULT group default

   link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00

2: eth0:<BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP modeDEFAULT group default qlen 1000

   link/ether 00:0c:29:ef:11:2f brd ff:ff:ff:ff:ff:ff

3: docker0:<NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWNmode DEFAULT group default

   link/ether 00:00:00:00:00:00 brd ff:ff:ff:ff:ff:ff

24: veth0: <BROADCAST,MULTICAST> mtu 1500 qdisc noopstate DOWN mode DEFAULT group default qlen 1000

    link/etherb2:80:d7:36:b5:84 brd ff:ff:ff:ff:ff:ff

在新名稱空間內執行如下命令:

[email protected]:~/develop/training/namespace#ifconfig veth1 10.1.1.1/24 up

主機上執行如下命令:

$ sudo ifconfig veth0 10.1.1.2/24 up

而後我們會發現在新名稱空間可以ping通10.1.1.2,而主機可以ping通10.1.1.1,這樣就實現了雙向通訊。

第五步:UTS名稱空間

下面我們繼續安置CLONE_NEWUTS 標記,來實現主機名的分裂。修改2行程式碼

@@ -19,6 +19,7 @@ int child_main(void *arg)
       printf("child\n");
       system("mount -t proc none /proc");
       mount("/home/baohua/test-dir", "/mnt","none", MS_BIND, NULL);
+      sethostname("container",10);
       execlp("/bin/bash","bash",NULL,NULL);
       return 1;
 }
@@ -27,7 +28,7 @@ int main()
 {
       pid_t child_pid;
       child_pid = clone(child_main,child_stack+STACK_SIZE,SIGCHLD |CLONE_NEWPID |
-               CLONE_NEWNS |CLONE_NEWNET,NULL);
+               CLONE_NEWNS | CLONE_NEWNET |CLONE_NEWUTS, NULL);
 
       if (child_pid == -1)
                errExit("clone");

編譯執行後,在bash中敲hostname命令,獲取主機名,發現變為了“container”。

[email protected]:~/develop/training/namespace$sudo ./a.out

[sudo] password forbaohua:

child

[email protected]:~/develop/training/namespace#hostname

container

第六步:USER名稱空間

先看如下最簡單的程式,只在clone()時候使用CLONE_NEWUSER:

#define _GNU_SOURCE
#include <sched.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <signal.h>
#include <stdio.h>
 
#define STACK_SIZE (1024 * 1024)
 
#define errExit(msg)    do { perror(msg); exit(EXIT_FAILURE); \
                        } while (0)
 
static char child_stack[STACK_SIZE];
 
static int child_main(void *arg)
{
       printf("child\n");
       execlp("/bin/bash","bash",NULL,NULL);
       return1;
}
 
int main()
{
       pid_tchild_pid;
 
       child_pid= clone(child_main,child_stack+STACK_SIZE,SIGCHLD | CLONE_NEWUSER, NULL);
       if(child_pid == -1)
              errExit("clone");
 
       wait(NULL);
 
       return0;
}
它的執行結果如下,看起來在子程序裡面(新的名稱空間裡面),我們得到的使用者是nobody:

[email protected]:~/develop/training/namespace$gcc user.c

[email protected]:~/develop/training/namespace$./a.out

child

[email protected]:~/develop/training/namespace$

在子程序對應的shell裡面,敲id命令,看一下自身的ID,發現都是65534:

[email protected]:~/develop/training/namespace$id

uid=65534(nobody)gid=65534(nogroup) groups=65534(nogroup)

clone()用了CLONE_NEWUSER的引數後,子程序運行於新的USER名稱空間,內部看到的UID和GID已經與外部不同了,在預設情況下以ID 65534執行。

其實我們可以把主機的ID,與新USER名稱空間的ID進行一個對映,比如我們啟動子程序的時候,實際上是以baohua這個使用者啟動的,則說明bash子程序,在主機對應的使用者是baohua。但是,在新的名稱空間內部,它究竟對映到哪個使用者呢?這個我們可以通過修改程序的/proc/pid/uid_map和/proc/pid/gid_map這2個檔案來進行ID的內外對映。

主機裡面baohua的ID是1000:

$ id baohua

uid=1000(baohua)gid=1000(baohua)groups=1000(baohua),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),108(lpadmin),124(sambashare),131(docker)

我們現在把uid 1000對應的baohua對映到新名稱空間內部的root使用者(uid為0),在主機中執行如下命令:

$ ps --ppid `pidofa.out`

  PID TTY          TIME CMD

27321 pts/6    00:00:00 bash

我們手動進行對映:

$ sudo sh -c 'echo 01000 1 > /proc/27321/uid_map'

$ sudo sh -c 'echo 01000 1 > /proc/27321/gid_map'

之後在子程序再次敲id命令,發現重大不同。

[email protected]:~/develop/training/namespace$id

uid=0(root)gid=0(root) groups=0(root),65534(nogroup)

發現自身的uid、gid變為了0。接下來,只用su -就可以讓shell顯示[email protected]

下面我們用程式實現這個過程:

#define _GNU_SOURCE
#include <sched.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/mount.h>
#include <signal.h>
#include <stdio.h>
 
#define STACK_SIZE (1024 * 1024)
 
#define errExit(msg)    do { perror(msg); exit(EXIT_FAILURE); \
                        } while (0)
 
static char child_stack[STACK_SIZE];
 
static void set_map(char* file, intinside_id, int outside_id)
{
       FILE*mapfd = fopen(file, "w");
       if(NULL == mapfd) {
              perror("openfile error");
              return;
       }
       fprintf(mapfd,"%d %d %d", inside_id, outside_id, 1);
       fclose(mapfd);
}
 
static void set_uid_map(pid_t pid, int inside_id,int outside_id)
{
       charfile[256];
       sprintf(file,"/proc/%d/uid_map", pid);
       set_map(file,inside_id, outside_id);
}
 
static void set_gid_map(pid_t pid, intinside_id, int outside_id)
{
       charfile[256];
       sprintf(file,"/proc/%d/gid_map", pid);
       set_map(file,inside_id, outside_id);
}
 
static int child_main(void *arg)
{
       sleep(1);//wait for 1 second to make certain uid_map and gid_map is written
       printf("child\n");
       system("mount-t proc none /proc");
       mount("/home/baohua/test-dir","/mnt", "none", MS_BIND, NULL);
       sethostname("container",10);
       execlp("/bin/bash","bash",NULL,NULL);
       return1;
}
 
int main()
{
       pid_tchild_pid;
 
       child_pid= clone(child_main,child_stack+STACK_SIZE,SIGCHLD | CLONE_NEWPID |
                     CLONE_NEWNS| CLONE_NEWNET | CLONE_NEWUTS | CLONE_NEWUSER, NULL);
       if(child_pid == -1)
              errExit("clone");
 
       set_uid_map(child_pid,0, getuid());
       set_gid_map(child_pid,0, getgid());
 
       wait(NULL);
 
       return0;
}

上述程式碼中,父程序會通過set_uid_map()和set_gid_map()這2個函式,進行新名稱空間內部的使用者0與主機的使用者1000的對映。由於子程序執行bash之前延遲了1秒,所以我們在子程序進入shell的時候,它已經直接是root使用者了:

$ ./a.out

child

[email protected]:~/develop/training/namespace#

那麼,它針對主機資源的實際許可權是不是root呢,實驗一下它是否可以訪問/dev/sda1:

[email protected]:~/develop/training/namespace#cat /dev/sda1

cat: /dev/sda1:Permission denied

下面我們在bash裡面啟動一些stress程序:

[email protected]:~/develop/training/namespace#stress --cpu 8 --io 4 --vm 2 --vm-bytes 128M --timeout 100000s&

[1] 46

直接在新名稱空間內看ps:


但是我們在主機裡面看ps呢?


我們則發現,所有的stress程序在主機裡面都是對應使用者baohua的,而在新的名稱空間裡面則是root。

所以這個關係類似:

 

相關推薦

- Linux namespace

名稱空間是在OS之上實現容器與主機隔離,以及容器之間互相隔離的Linux核心核心技術。根據《Docker 最初的2小時(Docker從入門到入門)》一文,名稱空間本質上就是在不同的工作組裡面封官許願,讓大家在各自的部門裡面都是manager,而且彼此不衝突。本文接下來從細節做

ARM Linux 3.x的設備樹(Device Tree)

3rd else 命名 number 部分 kernel 傳統 rtc trigge 1. ARM Device Tree起源 Linus Torvalds在2011年3月17日的ARM Linux郵件列表宣稱“this whole ARM thing is a f*

: 關於Linux進程優先級數字混亂的徹底澄清

自己 olt vp8 自己的 進程的優先級 內核 在一起 top命令 eol 宋寶華: 關於Linux進程優先級數字混亂的徹底澄清 原創: 宋寶華 Linux閱碼場 9月20日 https://mp.weixin.qq.com/s/44Gamu17Vkl77OGV2KkRm

Linux中的1024——給閱碼場Linuxer們的節日祝福

1024是程式設計師的狂歡節。基於二進位制的原理,程式設計師通常會把1024當做一個整數而不是1000。程式設計師這個行業處理“bit”,當然這個行業“苦逼”,這也讓我輕鬆地記住了一個單詞——bitter。bitter的意思就是“苦的”,bit後面加er字尾,是人,這裡只是會

: 迭代螺旋法——關於Linux學習方法的血淚建議

在下是一個碼農,也號稱是一個老溼,平生閱碼農無數(吹牛的 ^-^)。經由大量的案例,我能夠理解了為什麼很多碼農學了很多年Linux,還是感覺沒有掌握要領,仍然內心崩潰,最終對Linux吐血而亡,正所謂:人世間最大的痛苦,莫過於,碼農落花有意,而Linux流水無情.....

深入探究Linux/VxWorks裝置樹視訊教程--專題視訊課程

深入探究Linux/VxWorks裝置樹視訊教程—5116人已學習 課程介紹        很多人看了很Linux多裝置樹的資料後,還是不會用裝置樹。該Linux裝置樹視訊課程假設一個全新的電路板,上面有4個ARM核,一片中斷控制器、一個UART、一個GPIO控制器、一個I2

Linux裝置驅動開發詳解》——sysfs檔案系統與linux裝置模型(5.4.2)

以下讀書筆記內容,摘自宋寶華《Linux裝置驅動開發詳解》一書。 1、sysfs檔案系統的簡介 (1)linux2.6以後的核心引進syfs檔案系統,是虛擬檔案系統; (2)產生一個包括所有系統硬體

linux裝置驅動開發詳解》——platform裝置驅動(12.2)

以下讀書筆記,整理於宋寶華《linux裝置驅動開發詳解》一書。 1、piatform匯流排出現的原因 在SOC整合的獨立外設控制器、掛接在soc記憶體空間的外設不依附與此類匯流排(PCI、USB、I

Linux裝置驅動框架裡的設計模式之——模板方法(Template Method)

本文系轉載,著作權歸作者所有。商業轉載請聯絡作者獲得授權,非商業轉載請註明出處。 作者: 宋寶華 來源: 微信公眾號linux閱碼場(id: linuxdev) 前言 《設計模式》這本經典的書裡面定義了20多種設計模式,雖然都是面向物件的,似乎需要C++、Java這樣的語言才能實現,但是根據筆者前面反覆

: CPU是如何訪問到記憶體的?--MMU最基本原理

原帖地址: 假設頁表只有一級 對於一個有MMU的CPU而言,MMU開啟後,CPU是這樣定址的:CPU任何時候,一切時候,發出的地址都是虛擬地址,這個虛擬地址發給MMU後,MMU通過頁表來在頁表裡面查出來這個虛擬地址對應的實體地址是什麼,從而去訪問外面的記憶體條。MMU裡面的頁表地址暫

:Docker 最初的2小時(Docker從入門到入門)

最初的2小時,你會愛上Docker,對原理和使用流程有個最基本的理解,避免滿世界無頭蒼蠅式找資料。本人反對暴風驟雨式多管齊下狂轟濫炸的學習方式,提倡迭代學習法,就是先知道怎麼玩,有個感性認識,再深入學習高階用法,深層原理,一輪輪迭代。堅決反對一上來就搞幾百頁厚的東西把人腦子弄

:Docker 最初的2小時(Docker從入門到入門)【轉】

最初的2小時,你會愛上Docker,對原理和使用流程有個最基本的理解,避免滿世界無頭蒼蠅式找資料。本人反對暴風驟雨式多管齊下狂轟濫炸的學習方式,提倡迭代學習法,就是先知道怎麼玩,有個感性認識,再深入學習高階用法,深層原理,一輪輪迭代。堅決反對一上來就搞幾百頁厚的東西把人腦子弄亂。 Docker是什麼? K

:關於Ftrace的一個完整案例【轉】

Ftrace簡介 Ftrace是Linux進行程式碼級實踐分析最有效的工具之一,比如我們進行一個系統呼叫,出來的時間過長,我們想知道時間花哪裡去了,利用Ftrace就可以追蹤到一級級的時間分佈。 Ftrace案例 寫一個proc模組,包含一個proc的讀和寫的入口。test_proc_show()故意呼

C語言大型軟體設計的面向物件--專題視訊課程

C語言大型軟體設計的面向物件—369人已學習 課程介紹        C語言程式碼一旦達到一定規模,就特別強調良好的架構設計,以保證程式碼的可讀性好、程式碼簡潔以及可複用。此時,軟體設計師勢必要用C語言來做面向物件的設計。不能克服面向物件這一關,也是很多人無法讀懂Linux核

:關於DMA ZONE和dma_alloc_coherent若干誤解的澄清

在32位X86計算機的條件下,ISA實際只可以訪問16MB以下的記憶體。那麼ISA上面假設有個網絡卡,要DMA,超過16MB以上的記憶體,它根本就訪問不到。所以Linux核心乾脆簡單一點,把16MB砍一刀,這一刀以下的記憶體單獨管理。如果ISA的驅動要申請DMA buffer,你帶一個GFP_DMA標記來表明

:關於罪與慾望的深淵

《人民的名義》無疑是繼《潛伏》後最佳的國產電視劇,沒有之一。我們拋開它的反腐敗政治目的不談,電視劇本身關於罪與慾望,關於最深層次人性的揭祕,也深深地震撼每一個人。我反覆地問自己一個問題,如果也同樣置身在這汙穢的官場,我會是誰,我又會走向何處?說白了,我們大多數人都不過是一個凡

:用systemd-nspawn執行最輕量級容器

systemd-nspawn可以建立最輕量級的容器(ns的意思就是namespace),本文的實驗平臺是Ubuntu 16.04,x86_64機器。本文的目的是:在Ubuntu中用systemd-nspawn啟動一個容器,執行Debian作業系統(Jessie),並在主機使用machinectl進行管理。

- KVM最初的2小時(KVM從入門到入不了門)

接著《Docker最初的2小時(Docker從入門到入門)》繼續聊,再花10個小時寫出《KVM最初的2小時(KVM從入門到入不了門)》。坦白講,由於KVM遠遠比Docker要複雜,還是要2小時愛上KVM,這絕非難事,所以很可能入不了門。原則上,我們繼續迭代學習,

Docker 基礎技術之 Linux namespace 詳解

基本 mar $$ 裏的 sta 進程資源 進程間通信 開始 消息隊列 Docker 是“新瓶裝舊酒”的產物,依賴於 Linux 內核技術 chroot 、namespace 和 cgroup。本篇先來看 namespace 技術。 Docker 和虛擬機技術一樣,從操作系

Linux Namespace : UTS

-i 朋友 系統 getpid break copy 分配 war 感覺 UTS namespace 用來隔離系統的 hostname 以及 NIS domain name。UTS 據稱是 UNIX Time-sharing System 的縮寫。 hostname 與 N