http://www.tuicool.com/articles/7v2ai22

【编者的话】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: 主机名(本文介绍)
  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 。

原文链接: https://blog.jtlebi.fr/2013/12 ... -uts/


Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐