在学习Docker 是时候, 了解到Docker 容器隔离,觉得很神奇,但是Docker 到底是如何实现隔离的 自己并不了解.
我们开始运行一个简单的容器,这里以busybox
镜像为例,它是一个常用的Linux工具箱,可以用来执行很多Linux命令,我们以它为镜像启动容器方便来查看容器内部环境。 执行命令:
docker run -it --name demo_docker busybox /bin/sh
这条命令的意思是:启动一个busybox
镜像的 Docker 容器,-it
参数表示给容器提供一个输出/输出的交互环境,也就是TTY。/bin/sh
表示容器交互运行的命令或者程序。
执行成功后我们就会进入到了 Docker 容器内部,我们执行ps -ef
查看进程
/ # ps -ef
PID USER TIME COMMAND
1 root 0:00 /bin/sh
6 root 0:00 ps -ef
使用top
命令查看进程资源
Mem: 1784592K used, 97536K free, 968K shrd, 109436K buff, 855756K cached
CPU: 2.4% usr 2.8% sys 0.0% nic 94.5% idle 0.2% io 0.0% irq 0.0% sirq
Load average: 0.26 0.34 0.21 3/252 7
PID PPID USER STAT VSZ %VSZ CPU %CPU COMMAND
1 0 root S 1316 0.0 0 0.0 /bin/sh
7 1 root R 1308 0.0 0 0.0 top
而我们在宿主机查看下当前执行容器的进程ps -ef|grep busybox
[root@VM-0-15-centos ~]# ps -ef|grep busybox
root 2261 1194 0 14:45 pts/3 00:00:00 grep --color=auto busybox
root 31292 30445 0 14:42 pts/0 00:00:00 /usr/bin/docker-current run -it --name demo_docker busybox /bin/sh
这里我们可以知道,对于宿主机 docker run
执行命令启动的只是一个进程,它的pid
是2261。而对于容器程序本身来说,它被隔离了,在容器内部都只能看到自己内部的进程,那 Docker 是如何做到的呢?它其实是借助了Linux内核的Namespace
技术来实现的,这里我结合一段C程序来模拟一下进程的隔离。
#define _GNU_SOURCE
#include
#include
#include
#include
#include
#include
#include
#include
/* 定义一个给 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("容器进程[%5d] ----进入容器!\n",getpid());
mount("proc", "/proc", "proc", 0, NULL);
/**执行/bin/bash */
execv(container_args[0], container_args);
printf("出错啦!\n");
return 1;
}
int main()
{
printf("宿主机进程[%5d] - 开始一个容器!\n",getpid());
/* 调用clone函数 */
int container_pid = clone(container_main, container_stack+STACK_SIZE, CLONE_NEWPID | CLONE_NEWNS | SIGCHLD, NULL);
/* 等待子进程结束 */
waitpid(container_pid, NULL, 0);
printf("宿主机 - 容器结束!\n");
return 0;
}
考虑到很多同学对C语言不是很熟悉,我这里简单解释下这段程序,这段程序主要就是执行clone()
函数,去克隆一个进程,而克隆执行的程序就是我们的container_main
函数,接着下一个参数就是栈空间,然后CLONE_NEWPID
和CLONE_NEWNS
表示Linux NameSpace的调用类别,分别表示创建新的进程命名空间和 挂载命名空间。
CLONE_NEWPID
会让执行的程序内部重新编号PID
,也就是从1
号进程开始CLONE_NEWNS
会克隆新的挂载环境出来,通过在子进程内部重新挂载proc
文件夹,可以屏蔽父进程的进程信息我们执行一下这段程序来看看效果。
[root@VM-0-15-centos docker]# gcc container.c -o container
[root@VM-0-15-centos docker]# ls
container container.c
[root@VM-0-15-centos docker]# ./container
宿主机进程[14599] - 开始一个容器!
容器进程[ 1] ----进入容器!
这里我们看到输出在宿主机看来,这个程序的PID
是14599
,在克隆的子进程来看,它的PID
是1
,我们执行ps -ef
查看一下进程列表
[root@VM-0-15-centos docker]# ps -ef
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 14:51 pts/6 00:00:00 /bin/bash
root 28 1 0 14:52 pts/6 00:00:00 ps -ef
我们发现确实只有容器内部的进程在运行了,再执行top
命令
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
1 root 20 0 116320 2792 1660 S 0.0 0.1 0:00.02 bash
29 root 20 0 159876 2056 1516 R 0.0 0.1 0:00.00 top
结果也只有2个进程的信息。
这就是容器隔离进程的基本原理了,Docker主要就是借助 Linux 内核技术Namespace来做到隔离的,其实包括我后面要说到文件的隔离,资源的隔离都是在新的命名空间下通过mount
挂载的方式来隔离的。
转自Docker是如何实现隔离的 - 木木匠的个人空间 - OSCHINA - 中文开源技术交流社区