文章目录
Linux Namespace
参考文章 1:DOCKER基础技术:LINUX NAMESPACE(上)
参考文章 2:DOCKER基础技术:LINUX NAMESPACE(下)
官方文档:《Namespaces in operation》
Linux Namespace 是 Linux 提供的一种内核级别环境隔离的方法。Unix 中有一个名为 chroot 的系统调用(通过修改根目录把用户 jail 到一个特定目录下),chroot 提供了一种简单的隔离模式:chroot 内部的文件系统无法访问外部的内容。Linux Namespace 在此基础上,提供了对 UTS、IPC、Mount、PID、Network、User 等六种隔离机制。
基础程序
本文中后续程序都是在基础程序基础上形成的,基础程序如下。
#define _GNU_SOURCE
#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <sched.h>
#include <signal.h>
#include <unistd.h>
/* 定义一个给 clone 用的栈,栈大小1M */
#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[%d] - inside the container!\n", getpid());
/* 直接执行一个shell,以便我们观察这个进程空间里的资源是否被隔离了 */
execv(container_args[0], container_args);
printf("Something's wrong!\n");
return 1;
}
int main()
{
printf("Parent[%d] - start a container!\n", getpid());
/* 调用clone函数,其中传出一个函数,还有一个栈空间的(为什么传尾指针,因为栈是反着的) */
int container_pid = clone(container_main, container_stack+STACK_SIZE, SIGCHLD, NULL);
/* 等待子进程结束 */
waitpid(container_pid, NULL, 0);
printf("Parent - container stopped!\n");
return 0;
}
UTS Namespace
UTS 实现主机名隔离,在程序中使用如下:
int container_main(void* arg)
{
...
sethostname("container", 10); /* 设置主机名 */
printf("Something's wrong!\n");
return 1;
}
int main()
{
...
/* 调用clone函数,其中传出一个函数,还有一个栈空间的(为什么传尾指针,因为栈是反着的) */
int container_pid = clone(container_main, container_stack+STACK_SIZE, CLONE_NEWUTS |
SIGCHLD, NULL); /* 加上 UTS 参数 */
...
}
IPC Namespace
IPC 全称 Inter-Process Communication,是Unix/Linux下进程间通信的一种方式,IPC有共享内存、信号量、消息队列等方法。IPC 隔离实现如下:
int main()
{
...
/* 调用clone函数,其中传出一个函数,还有一个栈空间的(为什么传尾指针,因为栈是反着的) */
int container_pid = clone(container_main, container_stack+STACK_SIZE, CLONE_NEWUTS |
CLONE_NEWIPC | SIGCHLD, NULL); /* 加上 PID 参数 */
...
}
检验 IPC 隔离,clone 时先不指定 CLONE_NEWIPC 参数。使用
ipcmk -Q
创建一个 IPC 队列,使用ipcs -q
查看队列创建是否成功,如下图所示。运行未指定 CLONE_NEWIPC 参数的程序,使用
ipcs -q
查看能否看到刚才创建的队列,结果如下,发现能够访问到。指定 CLONE_NEWIPC 参数后重新编译执行,继续使用
ipcs -q
查看,结果如下,已经看不到创建的 IPC 队列了,实现了 IPC 隔离。
PID Namespace
PID Namespace 将子进程 PID 设置为 1。在传统的UNIX系统中,PID为1的进程是init,地位非常特殊。他作为所有进程的父进程,有很多特权(比如:屏蔽信号等)。另外,其还会为检查所有进程的状态,如果某个子进程脱离了父进程(父进程没有wait它),那么init就会负责回收资源并结束这个子进程。所以,要做到进程空间的隔离,首先要创建出PID为1的进程,最好就像 chroot 那样,把子进程的 PID 在容器内变成 1 。
在 clone
时将 CLONE_NEWPID
传入即可, 可在程序内使用 getpid()
获取进程 PID 并打印,对比使用 CLONE_NEWPID
前后,子进程的 PID 有无变化。
Mount Namespace
加上 CLONE_NEWPID
子进程内使用 ps
、top
等命令依然可以查看所有进程,因为子进程仍旧和父进程共享 /proc 文件系统,在 clone
时传入 CLONE_NEWNS
标志位,且在子进程内重新挂载 /proc 文件系统可以解决这一问题,修改如下(这里通过实践发现原文中两个小错误,一是如果不在父进程内重新 mount -t proc proc /proc
,子进程结束后再在系统中使用 ps 就会报错;二是很奇怪,即使不指定 CLONE_NEWNS
标志,单纯的在子进程内 mount 也能实现同样功能。)
int container_main(void* arg)
{
...
sethostname("container", 10); /* 设置主机名 */
system("mount -t proc proc /proc");
...
}
int main()
{
...
/* 调用clone函数,其中传出一个函数,还有一个栈空间的(为什么传尾指针,因为栈是反着的) */
int container_pid = clone(container_main, container_stack+STACK_SIZE, CLONE_NEWUTS | CLONE_NEWPID |
CLONE_NEWIPC | CLONE_NEWNS | SIGCHLD, NULL); /* 加上 NS */
/* 等待子进程结束 */
waitpid(container_pid, NULL, 0);
system("mount -t proc proc /proc"); /* 不加这行,退出后使用 ps 报错 */
...
}
Network Namespace
- 待完成
User Namespace
- 待完成