一文彻底搞懂Docker中的namespace
Posted 神技圈子
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了一文彻底搞懂Docker中的namespace相关的知识,希望对你有一定的参考价值。
什么是namespace
namespace是对全局系统资源的一种封装隔离。这样可以让不同namespace的进程拥有独立的全局系统资源。这样改变一个namespace的系统资源只会影响当前namespace中的进程,对其它namespace中的资源没有影响。以前Linux也有一个。之前有一个系统调用chroot和namespace类似。
namespace分类
chroot内部不能访问外部的内容,namespce在此基础上提供了UTS、IPC、mount、PID、network、User等隔离机制。
分类 | 参数 | 隔离内容 |
---|---|---|
Mount namespaces | CLONE_NEWNS | Mount points (since Linux 2.4.19) |
UTS namespaces | CLONE_NEWUTS | Hostname and NIS domain name (since Linux 2.6.19) |
IPC namespaces | CLONE_NEWIPC | System V IPC, POSIX message queues (since Linux 2.6.19) |
PID namespaces | CLONE_NEWPID | Process IDs (since Linux 2.6.24) |
Network namespaces | CLONE_NEWNET | Network devices, stacks, ports, etc. (since Linux 2.6.24) |
User namespaces | CLONE_NEWUSER | User and group IDs (started in Linux 2.6.23 and completed in Linux 3.8) |
查看进程所属的namespace
Linux中每个进程都有一个/proc/[pid]/ns这样一个目录,这里面包含了该进程所属的namespace信息。如下图所示
从结果可以看到,对于每种类型的namespace,进程都会同一个namespace ID 相关联。
就ipc这个namespace类型来说,ipc -> ipc:[4026532560]中,ipc是namespace类型,4026532560是inode number。假设两个进程的ipc namespace下的inode number一样,说明这两个进程属于同一个namespace。对于其它的namespace类型也一样。
相关系统调用
clone
clone调用主要创建一个新进程并放入到namespace中。
int clone(int (*child_func)(void *), void *child_stack
, int flags, void *arg);
flags:
指定一个或者多个上面的CLONE_NEW*(当然也可以包含跟namespace无关的flags),
这样就会创建一个或多个新的不同类型的namespace,
并把新创建的子进程加入新创建的这些namespace中。
setns
setns调用将当前进程加入到已有的namespace中
int setns(int fd, int nstype);
fd:
指向/proc/[pid]/ns/目录里相应namespace对应的文件,
表示要加入哪个namespace
nstype:
指定namespace的类型(上面的任意一个CLONE_NEW*):
1. 如果当前进程不能根据fd得到它的类型,如fd由其他进程创建,
并通过UNIX domain socket传给当前进程,
那么就需要通过nstype来指定fd指向的namespace的类型
2. 如果进程能根据fd得到namespace类型,比如这个fd是由当前进程打开的,
那么nstype设置为0即可
unshare
使当前进程退出指定的namespace,并加入到新创建的namespace中。
int unshare(int flags);
flags:
指定一个或者多个上面的CLONE_NEW*,
这样当前进程就退出了当前指定类型的namespace并加入到新创建的namespace
unshare和clone都是创建并加入到namespace,它们的区别是clone是将本进程写入到namespace,而unshare是创建子进程让子进程。
UTS namespace
UTS namespace 是用来隔离系统的hostname以及NIS domain name。UTS的由来就是struct utsname结构体。hostname和domainname可以通过sethostname和setdomainname系统函数来设置,以及gethostname和getdomainname函数来获取。
#include <iostream>
#include <sys/types.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
using namespace std;
#define STACK_SIZE (1024 * 1024)
static char child_stack[STACK_SIZE];
char *const child_args[] = "/bin/bash", NULL;
int ChildFunc(void *arg)
cout << "Container - inside the container, it's pid is " << getpid() << endl;
::sethostname("container_001", 13);
execv(child_args[0],child_args);
return 1;
#define NO_EXIT(code, msg); if(code==-1)perror(msg); exit(-1);
int main()
cout << "parent pid is: " << getpid() << endl;
pid_t child_pid = ::clone(ChildFunc, child_stack+STACK_SIZE, CLONE_NEWUTS|SIGCHLD, NULL);
NO_EXIT(child_pid, "clone");
::waitpid(child_pid, nullptr, 0);
return 0;
编译后运行结果如下
可以看到父进程和子进程不在同一个UTS namespace。
再来开一个将当前进程加入到namespace的例子
#include <iostream>
#include <sys/types.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
#include <fcntl.h>
using namespace std;
#define NO_EXIT(code, msg); if(code==-1)perror(msg); exit(-1);
int main(int argc, char **argv)
//获取namespace对应的fd
int fd = open(argv[1], O_RDONLY);
NO_EXIT(fd, "open");
int ret =setns(fd, 0);
NO_EXIT(ret, "setns");
execv("/bin/bash", NULL);
return 0;
重新打开个shell窗口看下hostname
在上一个程序的shell窗口查看namespace的inode number(应用程序和其所属的namespace在同一namespace)。
继续回到第二个窗口运行程序,可以看到bash提示符里面的hostname以及UTS namespace的inode number和第一个shell窗口的都一样
IPC namespace
IPC全称 Inter-Process Communication,是Unix/Linux下进程间通信的一种方式,IPC有共享内存、信号量、消息队列等方法。所以,为了隔离,我们也需要把IPC给隔离开来,这样,只有在同一个Namespace下的进程才能相互通信。要启动IPC隔离,我们只需要在调用clone时加上CLONE_NEWIPC参数就可以了。
下面来看一个例子
首先创建一个Queue,执行ipcmk创建队列,并用ipcs查看结果。正常情况下子进程也能看到该队列。
这个两个命令解释如下
- ipcmk :创建shared memory segments, message queues, 和semaphore arrays
- ipcs : 查看shared memory segments, message queues, 和semaphore arrays的相关信息
对创建UTS程序进行修改,添加CLONE_NEWIPC参数
运行结果如下:
运行程序后可以看到IPC已经被隔离
PID namespace
PID namespaces用来隔离进程的ID空间,使得不同pid namespace里的进程ID可以重复且相互之间不影响。
PID namespace可以嵌套,也就是说有父子关系,当前创建的所有新namespace都是当前namespace的子namespace,父namespace可以看到所有孙子的namespace中的进程信息。
继续来看一个例子吧:
#include <iostream>
#include <sys/types.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
using namespace std;
#define STACK_SIZE (1024 * 1024)
static char child_stack[STACK_SIZE];
char *const child_args[] = "/bin/bash", NULL;
int ChildFunc(void *arg)
cout << "Container - inside the container, it's pid is " << getpid() << endl;
::sethostname("container_001", 13);
execv(child_args[0],child_args);
return 1;
#define NO_EXIT(code, msg); if(code==-1)perror(msg); exit(-1);
int main()
//添加了CLONE_NEWPID参数
pid_t child_pid = ::clone(ChildFunc, child_stack+STACK_SIZE, CLONE_NEWUTS|CLONE_NEWPID|SIGCHLD, NULL);
NO_EXIT(child_pid, "clone");
::waitpid(child_pid, nullptr, 0);
return 0;
运行结果如下
结果打印出来看到PID为1,1就是init进程,它是所有进程的父进程。如果子进程脱离了父进程,那么init就会负责回收资源并结束这个子进程。所以,要做到进程隔离,首先就得创建PID为1的进程(类似chroot)。但是这并没有完全隔离,比如/proc 文件系统父子进程还是一样的。
mount namespace
Mount namespace用来隔离文件系统的挂载点, 使得不同的mount namespace拥有自己独立的挂载点信息,不同的namespace之间不会相互影响,这对于构建用户或者容器自己的文件系统目录非常有用。
Mount namespace是第一个被加入Linux的namespace,由于当时没想到还会引入其它的namespace,所以取名为CLONE_NEWNS,而没有叫CLONE_NEWMOUNT。
下面例子继续对示例代码进行修改
#include <iostream>
#include <sys/types.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
using namespace std;
#define STACK_SIZE (1024 * 1024)
static char child_stack[STACK_SIZE];
char *const child_args[] = "/bin/bash", NULL;
int ChildFunc(void *arg)
cout << "Container - inside the container, it's pid is " << getpid() << endl;
::sethostname("container_001", 13);
system("mount -t proc proc /proc");
execv(child_args[0],child_args);
return 1;
#define NO_EXIT(code, msg); if(code==-1)perror(msg); exit(-1);
int main()
cout << "parent pid is: " << getpid() << endl;
pid_t child_pid = ::clone(ChildFunc, child_stack+STACK_SIZE, CLONE_NEWUTS|CLONE_NEWPID|CLONE_NEWNS|SIGCHLD, NULL);
NO_EXIT(child_pid, "clone");
::waitpid(child_pid, nullptr, 0);
return 0;
可以看到程序运行后用ps查看只有两个进程了,而进程1就是/bin/bash进程。子进程中新namespace的所有mount操作都只影响自身的文件系统,做到了真正意义上的隔离。
以上是关于一文彻底搞懂Docker中的namespace的主要内容,如果未能解决你的问题,请参考以下文章