Docker是如何实现隔离的

标签: docker 隔离 | 发表时间:2020-01-21 01:45 | 作者:木木匠
出处:https://juejin.im/welcome/backend

概述

容器化技术在当前云计算、微服务等体系下大行其道,而 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
    8 root      0:00 ps -ef
复制代码

使用 top命令查看进程资源

  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

  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 
#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_NEWPIDCLONE_NEWNS 表示Linux NameSpace的调用类别,分别表示创建新的进程命名空间和 挂载命名空间。

  • CLONE_NEWPID会让执行的程序内部重新编号 PID,也就是从 1号进程开始

  • CLONE_NEWNS 会克隆新的挂载环境出来,通过在子进程内部重新挂载 proc文件夹,可以屏蔽父进程的进程信息。

我们执行一下这段程序来看看效果。

编译

  gcc container.c -o container
复制代码

执行

  [[email protected] luozhou]# ./container 
宿主机进程[ 6061] - 开始一个容器!
容器进程[    1] ----进入容器!
复制代码

这里我们看到输出在宿主机看来,这个程序的 PID6061,在克隆的子进程来看,它的 PID1,我们执行 ps -ef 查看一下进程列表

  [[email protected] 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的内容就找到了我们挂载的地址啦。我们进入目录看看结果

  [[email protected] 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)

复制代码

我们看到上面挂载的目录有包括 cpumemory 那我们猜测大概就是在这个文件夹下面配置限制信息的了。我们跑一个容器来验证下,执行命令:

  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%,关于这两个参数的详细说明可以点击 这里

进程top进程top

我们查看进程消耗情况发现 刚刚启动的容器资源确实被限制在20%,说明 Docker 的CPU限制参数起作用了,那对应在我们的 cgroup 文件夹下面是怎么设置的呢? 同样,这里的配置肯定是和容器实例id挂钩的,我的文件路径是在 /sys/fs/cgroup/cpu/system.slice/docker-5bbf589ae223b347c0d10b7e97cd1461ef82149a6d7fb144e8b01fcafecad036.scope下, 5bbf589ae223b347c0d10b7e97cd1461ef82149a6d7fb144e8b01fcafecad036 就是我们启动的容器id了。

切换到上面的文件夹下,查看我们设置的参数:

  [[email protected]]# cat cpu.cfs_period_us
100000
[[email protected]]# 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技术由于是一个普通的宿主机进程,所以具有性能优势,而虚拟机由于完全虚拟系统,所以具备了高隔离性和安全性的优势,两者互有优缺点。不过容器化是当下的趋势,相信随着技术的成熟,目前的隔离不彻底的问题也能解决,容器化走天下不是梦。

参考

  1. people.redhat.com/vgoyal/pape…
  2. docs.docker.com/v17.09/engi…
  3. lwn.net/Articles/25…

相关 [docker 隔离] 推荐:

Docker是如何实现隔离的

- - 掘金后端
容器化技术在当前云计算、微服务等体系下大行其道,而 Docker 便是容器化技术的典型,对于容器化典型的技术,我们有必要弄懂它,所以这篇文章,我会来分析下 Docker 是如何实现隔离技术的,Docker 与虚拟机又有哪些区别呢. 接下来,我们开始逐渐揭开它的面纱. 我们开始运行一个简单的容器,这里以 busybox镜像为例,它是一个常用的Linux工具箱,可以用来执行很多Linux命令,我们以它为镜像启动容器方便来查看容器内部环境.

Docker & Flatpak

- - IT瘾-dev
目前最流行的技术莫过于Docker,Docker和Docker衍生的东西用到了很多很酷的技术,目前deepin应用软件发布转变成flatpak,这些看似风牛马不相及的技术方案,实际都使用了一个共同的底层技术——Namespace,假如没有namespace支持,这些技术实现都将成为空中楼阁. 一句话总结,无论是Docker、sysmted-nspawn还是flatpak,都是在namespace基础上,针对不同的场景,生出的不同的解决方案.

docker初体验之docker-tomcat

- - BlogJava-首页技术区
docker已经是现在最热的容器技术,最近也去体验了一下,在daocloud注册了一个账号,并开始本机实战docker. daocloud免费有两个容器可用,体验送T恤,邀请送书,这里我分享一个daocloud的邀请码 https://account.daocloud.io/signup?invite_code=mxeq2jkmcur37vz6ven8,daocloud是非常棒的容器云平台,使用体验好,问题响应也及时,绑定微信还送一个额外容器.

kubernetes移除Docker?

- -
两周前,Kubernetes在其最新的Changelog中宣布1.20之后将要弃用dockershime,也就说Kubernetes将不再使用Docker做为其容器运行时. 这一消息持续发酵,掀起了不小的波澜,毕竟Kubernetes+Docker的经典组合是被市场所认可的,大量企业都在使用. 看上去这个“弃用”的决定有点无厘头,那么为什么Kubernetes会做出这样的决定.

Docker应用场景

- - 灯火阑珊
Flynn:一个使用go语言编写的开源PaaS平台,目标是简化分布式环境中应用的部署和维护,可以通过git push命令,将应用部署到Docker,从而省去复杂的配置和操作. CoreOS:一种新的架构体系重新设计的Linux发型版,可以运行在既有的硬件活着云服务器上. CoreOS不提供类似yum或apt的包管理工具,用户不需要在CoreOS中安装软件,而是让程序都在Docker容器中运行.

docker使用场景

- - 开源软件 - ITeye博客
Docker应用容器相对于 VM 有以下几个优点:. 1、启动速度快,容器通常在一秒内可以启动,而 VM 通常要更久. 2、资源利用率高,一台普通PC 可以跑上千个容器,你跑上千个 VM 试试. 3、性能开销小, VM 通常需要额外的 CPU 和内存来完成 OS 的功能,这一部分占据了额外的资源. 因为VM 的 Hypervisor 需要实现对硬件的虚拟化,并且还要搭载自己的操作系统,自然在启动速度和资源利用率以及性能上有比较大的开销.

Docker 监控实战

- - SegmentFault 最新的文章
如今,越来越多的公司开始使用 Docker 了,现在来给大家看几组数据:. 2 / 3 的公司在尝试了 Docker 后最终使用了它. 也就是说 Docker 的转化率达到了 67%,而转化市场也控制在 60 天内. 越大型的公司越早开始使用 Docker. 研究发现主机数量越多的公司,越早开始使用 Docker.

Docker入门例子

- - 开源软件 - ITeye博客
Docker 提供了一个可以运行应用程序的容器. Docker 容器并不包含一个单独的操作系统,而是基于已有的基础设施中操作系统提供的功能来运行的. 2 Docker安装与启动. #将docker加入开机启动. 3 Docker的14个基础命令. 检查Docker的安装是否正确. 运行"Hello World"例子.

Docker认识基础

- - CSDN博客推荐文章
作者:chszs,版权所有,未经同意,不得转载. 博主主页: http://blog.csdn.net/chszs. Docker是一个C/S架构的容器引擎,它包括镜像、容器和库这三个重要的概念. Docker是一个开源平台,它包含容器引擎和Docker Hub注册服务器. 1)Docker容器引擎.

Docker 调试技巧

- - 行业应用 - ITeye博客
摘要: 『重用』容器名 但我们在编写/调试Dockerfile的时候我们经常会重复之前的command,比如这种docker run --name jstorm-zookeeper zookeeper:3.4,然后就容器名就冲突了. 但我们在编写/调试Dockerfile的时候我们经常会重复之前的command,比如这种.