全国协议5人面授小班,企业级独立开发考核,转业者的IT软件工程师基地 登录/注册 | 如何报名
当前位置: 服务端相关   >  容器核心技术–Namespace
admin · 更新于 2021-08-06

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 能不能让我们直接观察到呢?

让我们进入 Linux 环境,执行如下操作:

# 进入/proc/目录cd /proc/# 查看当前目录下有哪些文件或目录ls# 随便进入一个以数字(进程号)命名的目录,比如1cd 1# 查看ns(Namespace)目录下的内容ls -al ns
代码块
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

当前目录下红色的链接,就是这个进程对应的 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 Namespace */
        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);
代码块
  • 1
  • 2

这段代码 调用 clone 实现线程的系统调用,用来创建一个新的进程,并可以通过设计上述参数达到隔离。

执行下面的操作

# 安装可能需要的依赖sudo dnf install -y libcap-devel# 编译这个文件cc container.c -o container# 运行./container
代码块
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

执行我们编译好的container程序后,发现我们处于一个新的环境的终端中,你可以在这里验证你的猜测,比如查看当前环境的进程 ps,当前登录的用户 whoami,网络状况 ip a等等,使用exit 可以退出回到原来的环境。
我们确实通过系统调用,创建了一个与宿主机资源隔离的容器环境。

3. 小结

本节我们介绍了 Namespace 机制,和它的 8 种隔离类型,并实现了一具有命名空间隔离功能的“容器”,在这个过程中,希望大家对容器和 Namespace 机制有了更深入的理解。


为什么选择汉码未来