【Docker 内核详解】namespace 资源隔离(二):UTS namespace & IPC namespace

namespace 资源隔离(二):UTS namespace & IPC namespace

1.UTS namespace

UTSUNIX Time-sharing System),UTS namespace 提供了 主机名域名 的隔离,这样每个 Docker 容器就可以拥有独立的主机名和域名了,在网络上可以被视作一个独立的节点,而非宿主机上的一个进程。Docker 中,每个镜像基本都以自身所提供的服务名称来命名镜像的 hostname,且不会对宿主机产生任何影响,其原理就是利用了 UTS namespace

下面通过代码来感受一下 UTS 隔离的效果,首先需要一个程序的骨架。打开编辑器创建 uts.c 文件,输入如下代码。

#define _GNU_SOURCE
#include 
#include 
#include 
#include 
#include 
#include 

#define STACK_SIZE (1024 * 1024)

static char child_stack[STACK_SIZE];
char* const child_args[] = {
	"/bin/bash",
	NULL
};

int child_main(void* args){
	printf("在子进程中!\n");
	execv(child_args[0], child_args);
	return 1;
}

int main(){
	printf("程序开始:\n");
	int child_pid = clone(child_main, child_stack + STACK_SIZE, SIGCHLD, NULL);
	waitpid(child_pid, NULL, 0);
	printf("已退出\n";
	return 0;
}

编译并运行上述代码,执行如下命令,效果如下。

-Wall 是 GCC 编译器的一个编译选项,它会开启编译器的所有警告选项。当开启 -Wall 选项后,编译器会对代码中可能存在的潜在问题发出警告,例如未使用变量、变量未初始化、类型转换等。这样可以让开发者更好地发现潜在的问题并进行修复。

root@local:~# gcc -Wall uts.c -o uts.o && ./uts.o
程序开始:
在子进程中!
root@local:~# exit
exit
已退出
root@local:~#

下面将修改代码,加入 UTS 隔离。运行代码需要 root 权限,以防止普通用户任意修改系统主机名导致 set-user-ID 相关的应用运行出错。

// [...]
int child_main(void* arg) {
	printf("在子进程中!\n");
	sethostname("NewNamespace", 12);
	execv(child_args[0], child_args);
	return 1;
}

int main(){
	//[...]
	int child_pid = clone(child_main, child_stack + STACK_SIZE, CLONE_NEWUTS | SIGCHLD, NULL);
	//[...]
}

再次运行,可以看到 hostname 已经变化。

root@local:~# gcc -Wall namespace.c -o main.o && ./main.o
程序开始:
在子进程中!
root@NewNamespace:~# exit
exit
已退出
root@local:~#           <-- 回到原来的hostname

值得一提的是,也许有读者会尝试不加 CLONE_NEWUTS 参数运行上述代码,发现主机名同样改变了,并且输入 exit 后主机名也恢复了,似乎并没有区别。实际上,不加 CLONE_NEWUTS 参数进行隔离时,由于使用 sethostname 函数,所以宿主机的主机名被修改了。而看到 exit 退出后主机名还原,是因为 bash 只在刚登录时读取一次 UTS,不会实时读取最新的主机名。当重新登录或者使用 uname 命令进行查看时,就会发现产生的变化。

2.IPC namespace

进程间通信Inter-Process CommunicationIPC)涉及的 IPC 资源包括常见的 信号量消息队列共享内存。申请 IPC 资源就申请了一个全局唯一的 32 32 32 位 ID,所以 IPC namespace 中实际上包含了 系统 IPC 标识符 以及 实现 POSIX 消息队列的文件系统。在同一个 IPC namespace 下的进程彼此可见,不同 IPC namespace 下的进程则互相不可见。

IPC namespace 在实现代码上与 UTS namespace 相似,只是标识位有所变化,需要加上 CLONE_NEWIPC 参数。主要改动如下,其他部分不变,程序名称改为 ipc.c

// [...]
int child_pid = clone(child_main, child_stack + STACK_SIZE, CLONE_NEWIPC | CLONE_NEWUTS | SIGCHLD, NULL);
// [...]

首先在 shell 中使用 ipcmk -Q 命令创建一个 message queue

root@local:~# ipcmk-Q
Message queue id: 32769

通过 ipcs- q 可以查看到已经开启的 message queue,序号为 32769 32769 32769

root@local:~# ipcs -q
------ Message Queues ----
key         msqid   owner   perms  used-bytes  messages
0x4cf5e29f  32769   root    644    0           0

然后可以编译运行加入了 IPC namespace 隔离的 ipc.c,在新建的子进程中调用的 shell 中执行 ipcs -q 查看 message queue

root@local:~# gcc -wall ipc.c -o ipc.o && ./ipc.o
程序开始:
在子进程中!
root@NewNamespace:~# ipcs -q
------ Message Queues ------ 
key msqid owner perms used-bytes messages
root@NewNamespace:~# exit
exit
已退出

从结果显示中可以发现,子进程找不到原先声明的 message queue了,已经实现了 IPC 的隔离。

目前使用 IPC namespace 机制的系统不多,其中比较有名的有 PostgreSQL。Docker 当前也使用 IPC namespace 实现了容器与宿主机、容器与容器之间的 IPC 隔离。

你可能感兴趣的:(#,Docker,docker,容器,运维,namespace,UTS,IPC,资源隔离)