1. Namespace 详解
1.1 Mount Namespace
Mount Namespace 用来隔离文件系统的挂载点,不同的 Mount namespace 拥有各自独立的挂载点信息。在 Docker 这样的容器引擎中,Mount namespace 的作用就是保证容器中看到的文件系统的视图。
1.2 UTS Namespace
UTS Namespace 用来隔离系统的主机名、hostname 和 NIS 域名。
1.3 IPC Namespace
IPC 就是在不同进程间传递和交换信息。IPC Namespace 使得容器内的所有进程,进行的数据传输、共享数据、通知、资源共享等范围控制在所属容器内部,对宿主机和其他容器没有干扰。
1.4 PID Namespace
PID namespaces用来隔离进程的 ID 空间,使得不同容器里的进程 ID 可以重复,相互不影响。
1.5 Network Namespace
Network namespace 用来隔离网络,每个 namespace 可以有自己独立的网络栈,路由表,防火墙规则等。
1.6 user namespace
user namespace 是例子中讲到的,控制用户 UID 和 GID 在容器内部和宿主机上的一个映射,主要用来管理权限。
1.7 Time namespace
这个 Namespace 允许操作系统为进程设定不同的系统时间。
1.8 Cgroup Namespace
这个 Namespace 用来限制 CGroup 根目录下不同层级目录的权限,使得 CGROUP 根目录下的子目录的进程无法影响到父目录。
2. 实践出真知
2.1 感受 Namespace
讲了那么多理论,Namespace 能不能让我们直接观察到呢?
当前目录下红色的链接,就是这个进程对应的 Namespace。
有兴趣的读者可以看看其他不同进程的 Namespace,比对下是否有差异。如果你找到某个进程的Namespace 与其他的不一致,就说明这个进程指定了 Namespace 隔离。
2.2 使用 Namespace 自制简易容器
将以下代码保存到/root/test/container.c
#define _GNU_SOURCE#include <stdio.h>#include <stdlib.h>#include <sys/types.h>#include <sys/wait.h>#include <sys/mount.h>#include <sys/capability.h>#include <stdio.h>#include <sched.h>#include <signal.h>#include <unistd.h>#define STACK_SIZE (1024 * 1024)static char container_stack[STACK_SIZE];char* const container_args[] = {
"/bin/bash",
NULL};int pipefd[2];void set_map(char* file, int inside_id, int outside_id, int len) {
FILE* mapfd = fopen(file, "w");
if (NULL == mapfd) {
perror("open file error");
return;
}
fprintf(mapfd, "%d %d %d", inside_id, outside_id, len);
fclose(mapfd);}void set_uid_map(pid_t pid, int inside_id, int outside_id, int len) {
char file[256];
sprintf(file, "/proc/%d/uid_map", pid);
set_map(file, inside_id, outside_id, len);}void set_gid_map(pid_t pid, int inside_id, int outside_id, int len) {
char file[256];
sprintf(file, "/proc/%d/gid_map", pid);
set_map(file, inside_id, outside_id, len);}int container_main(){
char ch;
close(pipefd[1]);
read(pipefd[0], &ch, 1);
sethostname("container",10);
mount("proc", "/proc", "proc", 0, NULL);
mount("none", "/tmp", "tmpfs", 0, "");
execv(container_args[0], container_args);
return 1;}int main(){
const int gid=getgid(), uid=getuid();
pipe(pipefd);
int container_pid = clone(container_main, container_stack+STACK_SIZE,
CLONE_NEWCGROUP|CLONE_NEWIPC|CLONE_NEWUTS | CLONE_NEWPID | CLONE_NEWNS | CLONE_NEWUSER | SIGCHLD, NULL);
set_uid_map(container_pid, 0, uid, 1);
set_gid_map(container_pid, 0, gid, 1);
close(pipefd[1]);
waitpid(container_pid, NULL, 0);
return 0;}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
我们不用读懂这个代码,只需要留意下 main 主函数中这部分
int container_pid = clone(container_main, container_stack+STACK_SIZE,
CLONE_NEWCGROUP|CLONE_NEWIPC|CLONE_NEWNET|CLONE_NEWUTS | CLONE_NEWPID | CLONE_NEWNS | CLONE_NEWUSER | SIGCHLD, NULL);
这段代码 调用 clone
实现线程的系统调用,用来创建一个新的进程,并可以通过设计上述参数达到隔离。
sudo dnf install -y libcap-develcc container.c -o container./container
执行我们编译好的container程序后,发现我们处于一个新的环境的终端中,你可以在这里验证你的猜测,比如查看当前环境的进程 ps
,当前登录的用户 whoami
,网络状况 ip a
等等,使用exit
可以退出回到原来的环境。
我们确实通过系统调用,创建了一个与宿主机资源隔离的容器环境。
3. 小结
本节我们介绍了 Namespace 机制,和它的 8 种隔离类型,并实现了一具有命名空间隔离功能的“容器”,在这个过程中,希望大家对容器和 Namespace 机制有了更深入的理解。