Namespace
Linux Namespace 是 Linux 提供的一种内核级别环境隔离的方法。这种隔离机制和 chroot 很类似,chroot 是把某个目录修改为根目录,从而无法访问外部的内容。Linux Namesapce 在此基础之上,提供了对 UTS、IPC、Mount、PID、Network、User 等的隔离机制,如下所示。
分类 | 系统调用参数 | 相关内核版本 |
---|---|---|
Mount Namespaces | CLONE_NEWNS | Linux 2.4.19 |
UTS Namespaces | CLONE_NEWUTS | Linux 2.6.19 |
IPC Namespaces | CLONE_NEWIPC | Linux 2.6.19 |
PID Namespaces | CLONE_NEWPID | Linux 2.6.19 |
Network Namespaces | CLONE_NEWNET | 始于Linux 2.6.24 完成于 Linux 2.6.29 |
User Namespaces | CLONE_NEWUSER | 始于 Linux 2.6.23 完成于 Linux 3.8) |
★Linux Namespace 官方文档:Namespaces in operation
”
namespace 有三个系统调用可以使用:
clone()
--- 实现线程的系统调用,用来创建一个新的进程,并可以通过设计上述参数达到隔离。unshare()
--- 使某个进程脱离某个 namespacesetns(int fd, int nstype)
--- 把某进程加入到某个 namespace
下面使用这几个系统调用来演示 Namespace 的效果,更加详细地可以看 DOCKER基础技术:LINUX NAMESPACE(上)、 DOCKER基础技术:LINUX NAMESPACE(下)。
UTS Namespace
UTS Namespace 主要是用来隔离主机名的,也就是每个容器都有自己的主机名。我们使用如下的代码来进行演示。注意:假如在容器内部没有设置主机名的话会使用主机的主机名的;假如在容器内部设置了主机名但是没有使用 CLONE_NEWUTS 的话那么改变的其实是主机的主机名。
#define _GNU_SOURCE
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/mount.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 container_main(void* arg) {
printf("Container [%5d] - inside the container!\n", getpid());
sethostname("container_dawn", 15);
execv(container_args[0], container_args);
printf("Something's wrong!\n");
return 1;
}
int main() {
printf("Parent [%5d] - start a container!\n", getpid());
int container_id = clone(container_main, container_stack + STACK_SIZE,
CLONE_NEWUTS | SIGCHLD, NULL);
waitpid(container_id, NULL, 0);
printf("Parent - container stopped!\n");
return 0;
}

PID Namespace
每个容器都有自己的进程环境中,也就是相当于容器内进程的 PID 从 1 开始命名,此时主机上的 PID 其实也还是从 1 开始命名的,就相当于有两个进程环境:一个主机上的从 1 开始,另一个容器里的从 1 开始。
为啥 PID 从 1 开始就相当于进程环境的隔离了呢?因此在传统的 UNIX 系统中,PID 为 1 的进程是 init,地位特殊。它作为所有进程的父进程,有很多特权。另外,其还会检查所有进程的状态,我们知道如果某个进程脱离了父进程(父进程没有 wait 它),那么 init 就会负责回收资源并结束这个子进程。所以要想做到进程的隔离,首先需要创建出 PID 为 1 的进程。
但是,【kubernetes 里面的话】
int container_main(void* arg) {
printf("Container [%5d] - inside the container!\n", getpid());
sethostname("container_dawn", 15);
execv(container_args[0], container_args);
printf("Something's wrong!\n");
return 1;
}
int main() {
printf("Parent [%5d] - start a container!\n", getpid());
int container_id = clone(container_main, container_stack + STACK_SIZE,
CLONE_NEWUTS | CLONE_NEWPID | SIGCHLD, NULL);
waitpid(container_id, NULL, 0);
printf("Parent - container stopped!\n");
return 0;
}

如果此时你在子进程的 shell 中输入 ps、top 等命令,我们还是可以看到所有进程。这是因为,ps、top 这些命令是去读 /proc 文件系统,由于此时文件系统并没有隔离,所以父进程和子进程通过命令看到的情况都是一样的。
IPC Namespace
常见的 IPC 有共享内存、信号量、消息队列等。当使用 IPC Namespace 把 IPC 隔离起来之后,只有同一个 Namespace 下的进程才能相互通信,因为主机的 IPC 和其他 Namespace 中的 IPC 都是看不到了的。而这个的隔离主要是因为创建出来的 IPC 都会有一个唯一的 ID,那么主要对这个 ID 进行隔离就好了。
想要启动 IPC 隔离,只需要在调用 clone 的时候加上 CLONE_NEWIPC 参数就可以了。
int container_main(void* arg) {
printf("Container