自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Docker 基礎(chǔ)技術(shù):Linux Namespace(下)

運(yùn)維 系統(tǒng)運(yùn)維
在今天的這篇文章中,主要想向大家介紹Linux的User和Network的Namespace。

[[171780]]

在 Docker基礎(chǔ)技術(shù):Linux Namespace(上篇)中我們了解了,UTD、IPC、PID、Mount 四個namespace,我們模仿Docker做了一個相當(dāng)相當(dāng)山寨的鏡像。在這一篇中,主要想向大家介紹Linux的User和Network的Namespace。

好,下面我們就介紹一下還剩下的這兩個Namespace。

User Namespace

User Namespace主要是用了CLONE_NEWUSER的參數(shù)。使用了這個參數(shù)后,內(nèi)部看到的UID和GID已經(jīng)與外部不同了,默認(rèn)顯示為65534。那是因?yàn)槿萜髡也坏狡湔嬲腢ID所以,設(shè)置上了最大的UID(其設(shè)置定義在/proc/sys/kernel/overflowuid)。

要把容器中的uid和真實(shí)系統(tǒng)的uid給映射在一起,需要修改 /proc/ /uid_map 和/proc/ /gid_map 這兩個文件。這兩個文件的格式為:

ID-inside-ns ID-outside-ns length

其中:

  • 第一個字段ID-inside-ns表示在容器顯示的UID或GID,
  • 第二個字段ID-outside-ns表示容器外映射的真實(shí)的UID或GID。
  • 第三個字段表示映射的范圍,一般填1,表示一一對應(yīng)。

比如,把真實(shí)的uid=1000映射成容器內(nèi)的uid=0

  1. $cat/proc/2465/uid_map 
  2.          0       1000          1 

再比如下面的示例:表示把namespace內(nèi)部從0開始的uid映射到外部從0開始的uid,其最大范圍是無符號32位整形

  1. $cat/proc/$$/uid_map 
  2.          0          0          4294967295 

另外,需要注意的是:

  • 寫這兩個文件的進(jìn)程需要這個namespace中的CAP_SETUID (CAP_SETGID)權(quán)限(可參看Capabilities)
  • 寫入的進(jìn)程必須是此user namespace的父或子的user namespace進(jìn)程。
  • 另外需要滿如下條件之一:1)父進(jìn)程將effective uid/gid映射到子進(jìn)程的user namespace中,2)父進(jìn)程如果有CAP_SETUID/CAP_SETGID權(quán)限,那么它將可以映射到父進(jìn)程中的任一uid/gid。

這些規(guī)則看著都煩,我們來看程序吧(下面的程序有點(diǎn)長,但是非常簡單,如果你讀過《Unix網(wǎng)絡(luò)編程》上卷,你應(yīng)該可以看懂):

  1. #define _GNU_SOURCE 
  2. #include <stdio.h> 
  3. #include <stdlib.h> 
  4. #include <sys/types.h> 
  5. #include <sys/wait.h> 
  6. #include <sys/mount.h> 
  7. #include <sys/capability.h> 
  8. #include <stdio.h> 
  9. #include <sched.h> 
  10. #include <signal.h> 
  11. #include <unistd.h> 
  12. #define STACK_SIZE (1024 * 1024) 
  13. staticcharcontainer_stack[STACK_SIZE]; 
  14. char*constcontainer_args[] = { 
  15.     “/bin/bash”, 
  16.     NULL 
  17. }; 
  18. intpipefd[2]; 
  19. voidset_map(char* file,intinside_id,intoutside_id,intlen) { 
  20.     FILE* mapfd =fopen(file,”w”); 
  21.     if(NULL == mapfd) { 
  22.         perror(“open file error”); 
  23.         return; 
  24.     } 
  25.     fprintf(mapfd,”%d %d %d”, inside_id, outside_id, len); 
  26.     fclose(mapfd); 
  27. voidset_uid_map(pid_t pid,intinside_id,intoutside_id,intlen) { 
  28.     charfile[256]; 
  29.     sprintf(file,”/proc/%d/uid_map”, pid); 
  30.     set_map(file, inside_id, outside_id, len); 
  31. voidset_gid_map(pid_t pid,intinside_id,intoutside_id,intlen) { 
  32.     charfile[256]; 
  33.     sprintf(file,”/proc/%d/gid_map”, pid); 
  34.     set_map(file, inside_id, outside_id, len); 
  35. intcontainer_main(void* arg) 
  36.     printf(“Container [%5d] – inside the container!\n”, getpid()); 
  37.     printf(“Container: eUID = %ld;  eGID = %ld, UID=%ld, GID=%ld\n”, 
  38.             (long) geteuid(), (long) getegid(), (long) getuid(), (long) getgid()); 
  39.     /* 等待父進(jìn)程通知后再往下執(zhí)行(進(jìn)程間的同步) */ 
  40.     charch; 
  41.     close(pipefd[1]); 
  42.     read(pipefd[0], &ch, 1); 
  43.     printf(“Container [%5d] – setup hostname!\n”, getpid()); 
  44.     //set hostname 
  45.     sethostname(“container”,10); 
  46.     //remount “/proc” to make sure the “top” and “ps” show container’s information 
  47.     mount(“proc”,”/proc”,”proc”, 0, NULL); 
  48.     execv(container_args[0], container_args); 
  49.     printf(“Something’s wrong!\n”); 
  50.     return1; 
  51. intmain() 
  52.     constintgid=getgid(), uid=getuid(); 
  53.     printf(“Parent: eUID = %ld;  eGID = %ld, UID=%ld, GID=%ld\n”, 
  54.             (long) geteuid(), (long) getegid(), (long) getuid(), (long) getgid()); 
  55.     pipe(pipefd); 
  56.     printf(“Parent [%5d] – start a container!\n”, getpid()); 
  57.     intcontainer_pid = clone(container_main, container_stack+STACK_SIZE, 
  58.             CLONE_NEWUTS | CLONE_NEWPID | CLONE_NEWNS | CLONE_NEWUSER | SIGCHLD, NULL); 
  59.     printf(“Parent [%5d] – Container [%5d]!\n”, getpid(), container_pid); 
  60.     //To map the uid/gid, 
  61.     //   we need edit the /proc/PID/uid_map (or /proc/PID/gid_map) in parent 
  62.     //The file format is 
  63.     //   ID-inside-ns   ID-outside-ns   length 
  64.     //if no mapping, 
  65.     //   the uid will be taken from /proc/sys/kernel/overflowuid 
  66.     //   the gid will be taken from /proc/sys/kernel/overflowgid 
  67.     set_uid_map(container_pid, 0, uid, 1); 
  68.     set_gid_map(container_pid, 0, gid, 1); 
  69.     printf(“Parent [%5d] – user/group mapping done!\n”, getpid()); 
  70.     /* 通知子進(jìn)程 */ 
  71.     close(pipefd[1]); 
  72.     waitpid(container_pid, NULL, 0); 
  73.     printf(“Parent – container stopped!\n”); 
  74.     return0; 

上面的程序,我們用了一個pipe來對父子進(jìn)程進(jìn)行同步,為什么要這樣做?因?yàn)樽舆M(jìn)程中有一個execv的系統(tǒng)調(diào)用,這個系統(tǒng)調(diào)用會把當(dāng)前子進(jìn)程的進(jìn)程空間給全部覆蓋掉,我們希望在execv之前就做好user namespace的uid/gid的映射,這樣,execv運(yùn)行的/bin/bash就會因?yàn)槲覀冊O(shè)置了uid為0的inside-uid而變成#號的提示符。

整個程序的運(yùn)行效果如下:

  1. hchen@ubuntu:~$id 
  2. uid=1000(hchen) gid=1000(hchen)groups=1000(hchen) 
  3. hchen@ubuntu:~$ ./user#<–以hchen用戶運(yùn)行 
  4. Parent: eUID = 1000;  eGID = 1000UID=1000GID=1000 
  5. Parent [ 3262] – start a container! 
  6. Parent [ 3262] – Container [ 3263]! 
  7. Parent [ 3262] – user/groupmappingdone! 
  8. Container [    1] – inside the container! 
  9. Container: eUID = 0;  eGID = 0UID=0GID=0#<—Container里的UID/GID都為0了 
  10. Container [    1] – setuphostname! 
  11. root@container:~# id #<—-我們可以看到容器里的用戶和命令行提示符是root用戶了 
  12. uid=0(root) gid=0(root)groups=0(root),65534(nogroup) 

雖然容器里是root,但其實(shí)這個容器的/bin/bash進(jìn)程是以一個普通用戶hchen來運(yùn)行的。這樣一來,我們?nèi)萜鞯陌踩詴玫教岣摺?/p>

我們注意到,User Namespace是以普通用戶運(yùn)行,但是別的Namespace需要root權(quán)限,那么,如果我要同時使用多個Namespace,該怎么辦呢?一般來說,我們先用一般用戶創(chuàng)建User Namespace,然后把這個一般用戶映射成root,在容器內(nèi)用root來創(chuàng)建其它的Namesapce。

Network Namespace

Network的Namespace比較啰嗦。在Linux下,我們一般用ip命令創(chuàng)建Network Namespace(Docker的源碼中,它沒有用ip命令,而是自己實(shí)現(xiàn)了ip命令內(nèi)的一些功能——是用了Raw Socket發(fā)些“奇怪”的數(shù)據(jù),呵呵)。這里,我還是用ip命令講解一下。

首先,我們先看個圖,下面這個圖基本上就是Docker在宿主機(jī)上的網(wǎng)絡(luò)示意圖(其中的物理網(wǎng)卡并不準(zhǔn)確,因?yàn)閐ocker可能會運(yùn)行在一個VM中,所以,這里所謂的“物理網(wǎng)卡”其實(shí)也就是一個有可以路由的IP的網(wǎng)卡)

 

 

上圖中,Docker使用了一個私有網(wǎng)段,172.40.1.0,docker還可能會使用10.0.0.0和192.168.0.0這兩個私有網(wǎng)段,關(guān)鍵看你的路由表中是否配置了,如果沒有配置,就會使用,如果你的路由表配置了所有私有網(wǎng)段,那么docker啟動時就會出錯了。

當(dāng)你啟動一個Docker容器后,你可以使用ip link show或ip addr show來查看當(dāng)前宿主機(jī)的網(wǎng)絡(luò)情況(我們可以看到有一個docker0,還有一個veth22a38e6的虛擬網(wǎng)卡——給容器用的):

  1. hchen@ubuntu:~$ ip link show 
  2. 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state … 
  3.     link/loopback00:00:00:00:00:00 brd 00:00:00:00:00:00 
  4. 2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc … 
  5.     link/ether00:0c:29:b7:67:7d brd ff:ff:ff:ff:ff:ff 
  6. 3: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 … 
  7.     link/ether56:84:7a:fe:97:99 brd ff:ff:ff:ff:ff:ff 
  8. 5: veth22a38e6: <BROADCAST,UP,LOWER_UP> mtu 1500 qdisc … 
  9.     link/ether8e:30:2a:ac:8c:d1 brd ff:ff:ff:ff:ff:ff 

那么,要做成這個樣子應(yīng)該怎么辦呢?我們來看一組命令:

  1. ## 首先,我們先增加一個網(wǎng)橋lxcbr0,模仿docker0 
  2. brctl addbr lxcbr0 
  3. brctl stp lxcbr0 off 
  4. ifconfiglxcbr0 192.168.10.1/24up#為網(wǎng)橋設(shè)置IP地址 
  5. ## 接下來,我們要創(chuàng)建一個network namespace – ns1 
  6. # 增加一個namesapce 命令為 ns1 (使用ip netns add命令) 
  7. ip netns add ns1 
  8. # 激活namespace中的loopback,即127.0.0.1(使用ip netns exec ns1來操作ns1中的命令) 
  9. ip netnsexecns1   ip linksetdev lo up 
  10. ## 然后,我們需要增加一對虛擬網(wǎng)卡 
  11. # 增加一個pair虛擬網(wǎng)卡,注意其中的veth類型,其中一個網(wǎng)卡要按進(jìn)容器中 
  12. ip link add veth-ns1typeveth peer name lxcbr0.1 
  13. # 把 veth-ns1 按到namespace ns1中,這樣容器中就會有一個新的網(wǎng)卡了 
  14. ip linksetveth-ns1 netns ns1 
  15. # 把容器里的 veth-ns1改名為 eth0 (容器外會沖突,容器內(nèi)就不會了) 
  16. ip netnsexecns1  ip linksetdev veth-ns1 name eth0 
  17. # 為容器中的網(wǎng)卡分配一個IP地址,并激活它 
  18. ip netnsexecns1ifconfigeth0 192.168.10.11/24up 
  19. # 上面我們把veth-ns1這個網(wǎng)卡按到了容器中,然后我們要把lxcbr0.1添加上網(wǎng)橋上 
  20. brctl addif lxcbr0 lxcbr0.1 
  21. # 為容器增加一個路由規(guī)則,讓容器可以訪問外面的網(wǎng)絡(luò) 
  22. ip netnsexecns1     ip route add default via 192.168.10.1 
  23. # 在/etc/netns下創(chuàng)建network namespce名稱為ns1的目錄, 
  24. # 然后為這個namespace設(shè)置resolv.conf,這樣,容器內(nèi)就可以訪問域名了 
  25. mkdir-p/etc/netns/ns1 
  26. echo”nameserver 8.8.8.8″>/etc/netns/ns1/resolv.conf 

上面基本上就是docker網(wǎng)絡(luò)的原理了,只不過,

了解了這些后,你甚至可以為正在運(yùn)行的docker容器增加一個新的網(wǎng)卡:

  1.      
  2. ip link add peerAtypeveth peer name peerB 
  3. brctl addif docker0 peerA 
  4. ip linksetpeerA up 
  5. ip linksetpeerB netns ${container-pid} 
  6. ip netnsexec${container-pid} ip linksetdev peerB name eth1 
  7. ip netnsexec${container-pid} ip linkseteth1 up ; 
  8. ip netnsexec${container-pid} ip addr add ${ROUTEABLE_IP} dev eth1 ; 

上面的示例是我們?yōu)檎谶\(yùn)行的docker容器,增加一個eth1的網(wǎng)卡,并給了一個靜態(tài)的可被外部訪問到的IP地址。

這個需要把外部的“物理網(wǎng)卡”配置成混雜模式,這樣這個eth1網(wǎng)卡就會向外通過ARP協(xié)議發(fā)送自己的Mac地址,然后外部的交換機(jī)就會把到這個IP地址的包轉(zhuǎn)到“物理網(wǎng)卡”上,因?yàn)槭腔祀s模式,所以eth1就能收到相關(guān)的數(shù)據(jù),一看,是自己的,那么就收到。這樣,Docker容器的網(wǎng)絡(luò)就和外部通了。

當(dāng)然,無論是Docker的NAT方式,還是混雜模式都會有性能上的問題,NAT不用說了,存在一個轉(zhuǎn)發(fā)的開銷,混雜模式呢,網(wǎng)卡上收到的負(fù)載都會完全交給所有的虛擬網(wǎng)卡上,于是就算一個網(wǎng)卡上沒有數(shù)據(jù),但也會被其它網(wǎng)卡上的數(shù)據(jù)所影響。

這兩種方式都不夠完美,我們知道,真正解決這種網(wǎng)絡(luò)問題需要使用VLAN技術(shù),于是Google的同學(xué)們?yōu)長inux內(nèi)核實(shí)現(xiàn)了一個IPVLAN的驅(qū)動,這基本上就是為Docker量身定制的。

Namespace文件

上面就是目前Linux Namespace的玩法。 現(xiàn)在,我來看一下其它的相關(guān)東西。

讓我們運(yùn)行一下上篇中的那個pid.mnt的程序(也就是PID Namespace中那個mount proc的程序),然后不要退出。

  1. $ sudo ./pid.mnt 
  2. [sudo] passwordforhchen: 
  3. Parent [ 4599] – start a container! 
  4. Container [    1] – inside the container! 

我們到另一個shell中查看一下父子進(jìn)程的PID:

  1. hchen@ubuntu:~$ pstree -p 4599 
  2. pid.mnt(4599)───bash(4600) 

我們可以到proc下(/proc//ns)查看進(jìn)程的各個namespace的id(內(nèi)核版本需要3.8以上)。

下面是父進(jìn)程的:

  1. hchen@ubuntu:~$sudols-l/proc/4599/ns 
  2. total 0 
  3. lrwxrwxrwx 1 root root 0  4月  7 22:01 ipc -> ipc:[4026531839] 
  4. lrwxrwxrwx 1 root root 0  4月  7 22:01 mnt -> mnt:[4026531840] 
  5. lrwxrwxrwx 1 root root 0  4月  7 22:01 net -> net:[4026531956] 
  6. lrwxrwxrwx 1 root root 0  4月  7 22:01 pid -> pid:[4026531836] 
  7. lrwxrwxrwx 1 root root 0  4月  7 22:01 user -> user:[4026531837] 
  8. lrwxrwxrwx 1 root root 0  4月  7 22:01 uts -> uts:[4026531838] 

下面是子進(jìn)程的:

  1. hchen@ubuntu:~$sudols-l/proc/4600/ns 
  2. total 0 
  3. lrwxrwxrwx 1 root root 0  4月  7 22:01 ipc -> ipc:[4026531839] 
  4. lrwxrwxrwx 1 root root 0  4月  7 22:01 mnt -> mnt:[4026532520] 
  5. lrwxrwxrwx 1 root root 0  4月  7 22:01 net -> net:[4026531956] 
  6. lrwxrwxrwx 1 root root 0  4月  7 22:01 pid -> pid:[4026532522] 
  7. lrwxrwxrwx 1 root root 0  4月  7 22:01 user -> user:[4026531837] 
  8. lrwxrwxrwx 1 root root 0  4月  7 22:01 uts -> uts:[4026532521] 

我們可以看到,其中的ipc,net,user是同一個ID,而mnt,pid,uts都是不一樣的。如果兩個進(jìn)程指向的namespace編號相同,就說明他們在同一個namespace下,否則則在不同namespace里面。

這些文件還有另一個作用,那就是,一旦這些文件被打開,只要其fd被占用著,那么就算PID所屬的所有進(jìn)程都已經(jīng)結(jié)束,創(chuàng)建的namespace也會一直存在。比如:我們可以通過:mount –bind /proc/4600/ns/uts ~/uts 來hold這個namespace。

另外,我們在上篇中講過一個setns的系統(tǒng)調(diào)用,其函數(shù)聲明如下:

  1. intsetns(intfd,intnstype); 

其中第一個參數(shù)就是一個fd,也就是一個open()系統(tǒng)調(diào)用打開了上述文件后返回的fd,比如:

  1. fd = open(“/proc/4600/ns/nts”, O_RDONLY); // 獲取namespace文件描述符 
  2. setns(fd, 0);// 加入新的namespace 
責(zé)任編輯:趙寧寧 來源: H2EX
相關(guān)推薦

2016-09-20 21:32:16

DockerLinux Names

2021-07-10 08:29:13

Docker內(nèi)核Namespace

2021-07-14 10:33:22

Docker內(nèi)核Mount Names

2022-08-04 07:25:22

Docker部署項(xiàng)目

2010-01-11 10:17:12

2015-08-26 11:27:26

DockerDeviceMappe分層鏡像

2022-07-21 11:58:12

Docker

2018-12-20 10:55:29

2011-04-07 15:02:02

LinuxMySQL數(shù)據(jù)庫

2014-09-18 14:13:54

Docker

2021-03-05 18:36:00

Linux網(wǎng)橋Docker

2021-02-03 11:20:41

Docker架構(gòu)容器

2022-08-30 19:11:12

Docker虛擬化技術(shù)

2024-01-10 14:24:32

Docker容器Kafka

2024-02-23 10:11:00

虛擬化技術(shù)

2021-01-09 09:20:08

Linux發(fā)行版Docker

2022-07-26 07:14:52

Docker宿主命令

2024-04-02 09:01:45

2009-07-04 20:16:50

2018-08-06 08:51:32

Linux命令cut
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號