Linux命名空间学习教程(一) UTS


【编者的话】Docker核心解决的问题是利用LXC来实现类似VM的功能,从而利用更加节省的硬件资源提供给用户更多的计算资源。而 LXC所实现的隔离性主要是来自内核的命名空间, 其中pid、net、ipc、mnt、uts 等命名空间将容器的进程、网络、消息、文件系统和hostname 隔离开。本文是Linux命名空间系列教程的第一篇,通过一个简单的例子介绍了Linux容器以及UTS命名空间。DockerOne在撸代码的基础上进行了校对和整理。

我在OVH工作的时候,曾经为一款“即将发布”的产品添加安全机制,而这项工作需要用到Linux的命名空间(Linux namespace)。在学习过程中我发现,Linux的命名空间很强大,但是这方面的文档却非常少。

大多数人都应该听说过LXC——LinuX Containers,它是一个加强版的Chroot。简单的说,LXC就是将不同的应用隔离开来,这有点类似于chroot,chroot是将应用隔离到一个虚拟的私有root下,而LXC在这之上更进了一步。LXC内部依赖Linux内核的3种隔离机制(isolation infrastructure):
  1. Chroot
  2. Cgroups
  3. Namespaces


我本可以将这个系列命名为《如何构建你自己的LXC》,从而赢得更高的Google排名,但这样做可能会显得我太狂妄。事实上,LXC所做的事情远不止隔离,它还包含模板管理、冻结以及其它更多的功能。这个系列将会为你解开LXC的神秘面纱。

在这个系列中,我们将使用最简单的C程序启动/bin/bash ,并逐步为它加入各种隔离机制。

好了,现在我们开始吧。

有意思的是,Linux的容器工具并没有提供一个类似黑盒般神秘的容器解决方案,而是提供单独隔离构件(isolation building block),统称为Namespaces。每一个新版本都会有新的构件发布出来。这样你就可以跟据你的特定应用的情况,选择所需要的构件。

Linux的3.12内核支持6种Namespace:
  1. UTS: hostname(本文介绍)
  2. IPC: 进程间通信 (之后的文章会讲到)
  3. PID: "chroot"进程树(之后的文章会讲到)
  4. NS: 挂载点,首次登陆Linux(之后的文章会讲到)
  5. NET: 网络访问,包括接口(之后的文章会讲到)
  6. USER: 将本地的虚拟user-id映射到真实的user-id(之后的文章会讲到)


如下是一个程序的完整骨架,用于从子进程启动/bin/bash程序(为了保持例子简单,所以去掉了错误检查):
main-0-template.c

define _GNU_SOURCE

include <sys/types.h>

include <sys/wait.h>

include <stdio.h>

include <sched.h>

include <signal.h>

include <unistd.h>

define STACK_SIZE (1024 * 1024)

static char child_stack[STACK_SIZE];
char* const child_args[] = {
"/bin/bash",
NULL
};

int child_main(void* arg) {
printf(" - World !\n");
execv(child_args[0], child_args);
printf("Ooops\n");
return 1;
}

int main() {
printf(" - Hello ?\n");
int child_pid = clone(child_main, child_stack + STACK_SIZE, SIGCHLD, NULL);
waitpid(child_pid, NULL, 0);
return 0;
}

请注意,这里我们使用的是clone而不是fork系统调用。这正是魔法(即将)发生的地方。
jean-tiare@jeantiare-Ubuntu:~/blog$ gcc -Wall main.c -o ns && ./ns
- Hello ?
- World !
jean-tiare@jeantiare-Ubuntu:~/blog$ # inside the container
jean-tiare@jeantiare-Ubuntu:~/blog$ exit
jean-tiare@jeantiare-Ubuntu:~/blog$ # outside the container

OK,帅呆了。不过,假如没有注释,你很难注意到我们在子进程的/bin/bash中。事实上,当我在写这篇文章的时候,好几次不小心退出父进程的shell。

现在做一些修改,如直接修改主机名(the hostname with 0% env vars tricks),是不是会很牛逼?就单单用Namespace?很简单,我们只要:
  1. clone系统调用中使用CLONE_NEWUTS的标志
  2. 在子进程中调用sethostname


main-1-uts.c
// (needs root privileges (or appropriate capabilities))
//[...]
int child_main(void* arg) {
printf(" - World !\n");
sethostname("In Namespace", 12);
execv(child_args[0], child_args);
printf("Ooops\n");
return 1;
}

int main() {
printf(" - Hello ?\n");
int child_pid = clone(child_main, child_stack+STACK_SIZE,
    CLONE_NEWUTS | SIGCHLD, NULL);
waitpid(child_pid, NULL, 0);
return 0;
}

运行一下:
jean-tiare@jeantiare-Ubuntu:~/blog$ gcc -Wall main.c -o ns && sudo ./ns
- Hello ?
- World !
root@In Namespace:~/blog$ # inside the container
root@In Namespace:~/blog$ exit
jean-tiare@jeantiare-Ubuntu:~/blog$ # outside the container

事情就是这个样子(至少这篇文章中是这样)!namespace真是太TM容易上手了:clone,设置特定的CLONE_NEW*标志,设置新的env,搞定!

你希望更加深入?有兴趣可以阅读一下excellent LWN article series on namespaces

原文链接:Introduction to Linux namespaces – Part 1: UTS(翻译:孙科 审校:李颖杰)

-----------------------------------------
Linux命名空间学习教程(一) UTS
Linux命名空间学习教程(二) IPC
Linux命名空间学习教程(三) PID
Linux命名空间学习教程(四)NS(FS)
Linux命名空间学习教程(五)NET

1 个评论

谢谢翻译,正在学这一部分的内容。sethostname需要root权限,刚开始没有用sudo,没有显示In Namespace

要回复文章请先登录注册