UTS
(UNIX 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
命令进行查看时,就会发现产生的变化。
进程间通信(Inter-Process Communication
,IPC
)涉及的 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 隔离。