容器化技术在当前云计算、微服务等体系下大行其道,而 Docker 即是容器化技术的典型,对于容器化典型的技术,咱们有必要弄懂它,因此这篇文章,我会来分析下 Docker 是如何实现隔离技术的,Docker 与虚拟机又有哪些区别呢?接下来,咱们开始逐渐揭开它的面纱。node
咱们开始运行一个简单的容器,这里以busybox
镜像为例,它是一个经常使用的Linux工具箱,能够用来执行不少Linux命令,咱们以它为镜像启动容器方便来查看容器内部环境。 执行命令:docker
docker run -it --name demo_docker busybox /bin/sh
复制代码
这条命令的意思是:启动一个busybox
镜像的 Docker 容器,-it
参数表示给容器提供一个输出/输出的交互环境,也就是TTY。/bin/sh
表示容器交互运行的命令或者程序。安全
执行成功后咱们就会进入到了 Docker 容器内部,咱们执行ps -ef
查看进程bash
/ # ps -ef
PID USER TIME COMMAND
1 root 0:00 /bin/sh
8 root 0:00 ps -ef
复制代码
使用top
命令查看进程资源dom
Mem: 1757172K used, 106080K free, 190676K shrd, 129872K buff, 998704K cached
CPU: 0.0% usr 0.2% sys 0.0% nic 99.6% idle 0.0% io 0.0% irq 0.0% sirq
Load average: 0.00 0.01 0.05 2/497 9
PID PPID USER STAT VSZ %VSZ CPU %CPU COMMAND
1 0 root S 1300 0.0 1 0.0 /bin/sh
9 1 root R 1292 0.0 3 0.0 top
复制代码
而咱们在宿主机查看下当前执行容器的进程ps -ef|grep busybox
ide
root 5866 5642 0 01:19 pts/4 00:00:00 /usr/bin/docker-current run -it --name demo_docker busybox /bin/sh
root 5952 5759 0 01:20 pts/11 00:00:00 grep --color=auto busybox
复制代码
这里咱们能够知道,对于宿主机 docker run
执行命令启动的只是一个进程,它的pid
是5866。而对于容器程序自己来讲,它被隔离了,在容器内部都只能看到本身内部的进程,那 Docker 是如何作到的呢?它实际上是借助了Linux内核的Namespace
技术来实现的,这里我结合一段C程序来模拟一下进程的隔离。函数
#define _GNU_SOURCE
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <stdio.h>
#include <sched.h>
#include <signal.h>
#include <unistd.h>
#include <sys/mount.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("容器进程[%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
文件夹,能够屏蔽父进程的进程信息。性能
咱们执行一下这段程序来看看效果。
编译
gcc container.c -o container
复制代码
执行
[root@host1 luozhou]# ./container
宿主机进程[ 6061] - 开始一个容器!
容器进程[ 1] ----进入容器!
复制代码
这里咱们看到输出在宿主机看来,这个程序的PID
是6061
,在克隆的子进程来看,它的PID
是1
,咱们执行ps -ef
查看一下进程列表
[root@host1 luozhou]# ps -ef
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 01:46 pts/2 00:00:00 /bin/bash
root 10 1 0 01:48 pts/2 00:00:00 ps -ef
复制代码
咱们发现确实只有容器内部的进程在运行了,再执行top
命令
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
1 root 20 0 115576 2112 1628 S 0.0 0.1 0:00.00 bash
11 root 20 0 161904 2124 1544 R 0.0 0.1 0:00.00 top
复制代码
结果也只有2个进程的信息。
这就是容器隔离进程的基本原理了,Docker主要就是借助 Linux 内核技术Namespace来作到隔离的,其实包括我后面要说到文件的隔离,资源的隔离都是在新的命名空间下经过mount
挂载的方式来隔离的。
了解完进程的隔离,相信大家已经对 Docker 容器的隔离玩法就大概的印象了,咱们接下来看看,Docker 内部的文件系统如何隔离,也就是你在 Docker 内部执行 ls
显示的文件夹和文件如何来的。
咱们仍是之前面的 Docker 命令为例,执行ls
bin dev etc home proc root run sys tmp usr var
复制代码
咱们发现容器内部已经包含了这些文件夹了,那么这些文件夹哪里来的呢?咱们先执行docker info
来看看咱们的 Docker 用到的文件系统是什么?
Server Version: 1.13.1
Storage Driver: overlay2
复制代码
个人版本是1.13.1,存储驱动是overlay2
,不一样的存储驱动在 Docker 中表现不同,可是原理相似,咱们来看看 Docker 如何借助overlay2
来变出这么多文件夹的。咱们前面提到过,Docker都是经过mount 去挂载的,咱们先找到咱们的容器实例id.
执行docker ps -a |grep demo_docker
c0afd574aea7 busybox "/bin/sh" 42 minutes ago Up 42 minutes
复制代码
咱们再根据咱们的容器ID 去查找挂载信息,执行cat /proc/mounts | grep c0afd574aea7
shm /var/lib/docker/containers/c0afd574aea716593ceb4466943bbd13e3a081bf84da0779ee43600de0df384b/shm tmpfs rw,context="system_u:object_r:container_file_t:s0:c740,c923",nosuid,nodev,noexec,relatime,size=65536k 0 0
复制代码
这里出现了一个挂载信息,可是这个记录不是咱们的重点,咱们须要找到overlay2
的挂载信息,因此这里咱们还须要执行一个命令:cat /proc/mounts | grep system_u:object_r:container_file_t:s0:c740,c923
overlay /var/lib/docker/overlay2/9c9318031bc53dfca45b6872b73dab82afcd69f55066440425c073fe681109d3/merged overlay rw,context="system_u:object_r:container_file_t:s0:c740,c923",relatime,lowerdir=/var/lib/docker/overlay2/l/FWESUOVO6DYTXBBJIQBPUWLN6K:/var/lib/docker/overlay2/l/XPKQU6AMUX3AKLAX2BR6V4JQ3R,upperdir=/var/lib/docker/overlay2/9c9318031bc53dfca45b6872b73dab82afcd69f55066440425c073fe681109d3/diff,workdir=/var/lib/docker/overlay2/9c9318031bc53dfca45b6872b73dab82afcd69f55066440425c073fe681109d3/work 0 0
shm /var/lib/docker/containers/c0afd574aea716593ceb4466943bbd13e3a081bf84da0779ee43600de0df384b/shm tmpfs rw,context="system_u:object_r:container_file_t:s0:c740,c923",nosuid,nodev,noexec,relatime,size=65536k 0 0
复制代码
这里overlay
挂载并无和容器id关联起来,因此咱们直接根据容器id是找不到 overlay
挂载信息的,这里借助了context
去关联的,因此咱们经过context
的内容就找到了咱们挂载的地址啦。咱们进入目录看看结果
[root@host1 l]# ls /var/lib/docker/overlay2/9c9318031bc53dfca45b6872b73dab82afcd69f55066440425c073fe681109d3/merged
bin dev etc home proc root run sys tmp usr var
复制代码
咱们发现这个和咱们容器的目录是一致的,咱们在这个目录下建立一个新的目录,而后看看容器内部是否是会出现新的目录。
上面的图片验证了容器内部的文件内容和挂载的/var/lib/docker/overlay2/ID/merged
下是一致的,这就是Docker文件系统隔离的基本原理。
玩过 Docker 的同窗确定知道,Docker 仍是能够限制资源使用的,好比 CPU 和内存等,那这部分是如何实现的呢? 这里就涉及到Linux的另一个概念Cgroups
技术,它是为进程设置资源限制的重要手段,在Linux 中,一切皆文件,因此Cgroups
技术也会体如今文件中,咱们执行mount -t cgroup
就能够看到Cgroups
的挂载状况
cgroup on /sys/fs/cgroup/systemd type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,xattr,release_agent=/usr/lib/systemd/systemd-cgroups-agent,name=systemd)
cgroup on /sys/fs/cgroup/devices type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,devices)
cgroup on /sys/fs/cgroup/net_cls,net_prio type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,net_prio,net_cls)
cgroup on /sys/fs/cgroup/hugetlb type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,hugetlb)
cgroup on /sys/fs/cgroup/perf_event type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,perf_event)
cgroup on /sys/fs/cgroup/freezer type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,freezer)
cgroup on /sys/fs/cgroup/blkio type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,blkio)
cgroup on /sys/fs/cgroup/cpu,cpuacct type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,cpuacct,cpu)
cgroup on /sys/fs/cgroup/pids type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,pids)
cgroup on /sys/fs/cgroup/memory type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,memory)
cgroup on /sys/fs/cgroup/cpuset type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,cpuset)
复制代码
咱们看到上面挂载的目录有包括 cpu
和memory
那咱们猜想大概就是在这个文件夹下面配置限制信息的了。咱们跑一个容器来验证下,执行命令:
docker run -d --name='cpu_set_demo' --cpu-period=100000 --cpu-quota=20000 busybox md5sum /dev/urandom
复制代码
这个命令表示咱们须要启动一个容器,这个容器一直产生随机数进行md5计算来消耗CPU,--cpu-period=100000 --cpu-quota=20000
表示限制 CPU 使用率在20%,关于这两个参数的详细说明能够点击这里
咱们查看进程消耗状况发现 刚刚启动的容器资源确实被限制在20%,说明 Docker 的CPU限制参数起做用了,那对应在咱们的cgroup
文件夹下面是怎么设置的呢? 一样,这里的配置确定是和容器实例id挂钩的,个人文件路径是在/sys/fs/cgroup/cpu/system.slice/docker-5bbf589ae223b347c0d10b7e97cd1461ef82149a6d7fb144e8b01fcafecad036.scope
下,5bbf589ae223b347c0d10b7e97cd1461ef82149a6d7fb144e8b01fcafecad036
就是咱们启动的容器id了。
切换到上面的文件夹下,查看咱们设置的参数:
[root@host1]# cat cpu.cfs_period_us
100000
[root@host1]# cat cpu.cfs_quota_us
20000
复制代码
发现这里咱们的容器启动设置参数同样,也就是说经过这里的文件值来限制容器的cpu使用状况。这里须要注意的是,不一样的Linux版本 Docker Cgroup 文件位置可能不同,有些是在/sys/fs/cgroup/cpu/docker/ID/
下。
通过前面的进程、文件系统、资源限制分析,详细各位已经对 Docker 的隔离原理有了基本的认识,那么它和传统的虚拟机技术有和区别呢?这里贴一个网上的Docker和虚拟机区别的图
这张图应该能够清晰的展现了虚拟机技术和 Docker 技术的区别了,虚拟机技术是彻底虚拟出一个单独的系统,有这个系统去处理应用的各类运行请求,因此它实际上对于性能来讲是有影响的。而 Docker 技术 彻底是依赖 Linux 内核特性 Namespace 和Cgroup 技术来实现的,本质来讲:你运行在容器的应用在宿主机来讲仍是一个普通的进程,仍是直接由宿主机来调度的,相对来讲,性能的损耗就不多,这也是 Docker 技术的重要优点。
Docker 技术因为 仍是一个普通的进程,因此隔离不是很完全,仍是共用宿主机的内核,在隔离级别和安全性上没有虚拟机高,这也是它的一个劣势。
这篇文章我经过实践来验证了 Docker 容器技术在进程、文件系统、资源限制的隔离原理,最后也比较了虚拟机和 Docker 技术的区别,总的来讲 Docker技术因为是一个普通的宿主机进程,因此具备性能优点,而虚拟机因为彻底虚拟系统,因此具有了高隔离性和安全性的优点,二者互有优缺点。不过容器化是当下的趋势,相信随着技术的成熟,目前的隔离不完全的问题也能解决,容器化走天下不是梦。