最近基于linux容器技术的docker很是火热,作为做云计算方面探究的程序屌当然不能错过,以前研究过 cloudfoundry自身的容器warden实现的大致过程,当时也没有把一些收获记录下来,这次借docker的势头, 重新回去研究一下基于cgroup的容器技术。
linux容器的底层原理都是基于cgroup的,docker是使用go语言来编写的,而且封装的比较高级,所以底层 一些的LXC容器更容易去接近cgroup的实现。cgroup之所以能够做到资源隔离的效果,当然和linux内核提供 的namespace机制是分不开的。
何为namespace?
我们在写c++代码的时候一般都会有这么一句话using namespace std;这样可以使我们在调用标准库函数 的时候免去函数所在的域如std::cout,这使得cout函数调用不会产生歧义,c++使用namespace来杜绝函数 或者变量带来的名称冲突问题。
而linux的namespace可以被理解为进程的命名空间划分,使得不同namespace空间下面的进程处于几乎完全 隔离的状况,它们之间是完全透明的,linux container就是把各个不同的进程组设置在不同的namespace 下来实现他们的互不干扰。我们日常所使用的机器处于全局的namespace下,所以我们并不能感觉到其存在, 而当进入到一个linux容器后,感觉在另一台机器上操作一样。
linux容器技术与传统的虚拟化技术有很大差别,一台宿主机上的各个容器更像是一个个独立的进程组,这些 进程组去共享宿主机的cpu,内存,网卡等硬件资源,所以linux container是操作系统级的虚拟化技术。
namespace的作用理清了,那么怎么去创建一个namespace呢,这就需要clone这个系统调用。
clone系统调用
通过字面判断是一个进程clone出令一个进程,而我们可以通过参数来控制这两个进程属于不同的namespace, clone函数原型:
int clone(int (*fn)(void *), void *child_stack, int flags, void *arg);
int (*fn)(void *)这是子进程的执行函数循环
void *child_stack 可以通过这个参数指定子进程的运行栈
void *arg 父进程传递给子进程的参数
clone的参数中能将父子进程放进两个namespace的就是int flags,我们来引用cloudfoundry源码中的一段 代码如下:
long pagesize;
void *stack;
int flags = 0;
pid_t pid;
pagesize = sysconf(_SC_PAGESIZE);
stack = alloca(pagesize);
assert(stack != NULL);
/* Point to top of stack (it grows down) */
stack = stack + pagesize;
/* Setup namespaces */
flags |= CLONE_NEWIPC;
flags |= CLONE_NEWNET;
flags |= CLONE_NEWNS;
flags |= CLONE_NEWPID;
flags |= CLONE_NEWUTS;
pid = clone(child_run, stack, flags, w);
从这段代码中可以将clone这个系统调用解释的很清楚:
CLONE_NEWIPC 新的IPC namespace,这就意味着不同namepsace下的进程不能直接通信
CLONE_NEWNET 新的network namespace, 将各个容器的network分离
CLONE_NEWNS 新的mount namespace,新进程的文件系统将独立
CLONE_NEWPID 新的PID namespace,新的PID空间
CLONE_NEWUTS 新的UTS namespace,生成新的uname返回值
前面已经说过linux容器是一个个互不干扰的进程组,通过clone出来的进程将成为容器的第一个进程,这 与普通的fork是完全不同的。