原文地址:

微信公众号:《鲁智深菜园子》:Docker是如何实现隔离的


# 1、运行一个容器
运行一个简单的容器,这里以busybox镜像为例,它是一个常用的Linux工具箱,可以用来执行很多Linux命令,我们以它为镜像启动容器方便来查看容器内部环境。执行命令:
`docker run -it --name demo_docker busybox /bin/sh`
启动一个busybox镜像的 Docker 容器,-it参数表示给容器提供一个输出/输出的交互环境,也就是TTY。/bin/sh表示容器交互运行的命令或者程序。

2、进程的隔离

执行成功后我们就会进入到了 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 <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;
}

这段程序主要就是执行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挂载的方式来隔离的。

4、文件的隔离

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

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 overlayrw,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 tmpfsrw,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

发现这个和我们容器的目录是一致的。

5、资源的限制

Docker 还是可以限制资源使用的,比如 CPU 和内存等。

在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限制参数起作用了。

这里的配置肯定是和容器实例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/ 下。

7、与传统虚拟机技术的区别

虚拟机技术是完全虚拟出一个单独的系统,有这个系统去处理应用的各种运行请求,所以它实际上对于性能来说是有影响的。而 Docker 技术 完全是依赖 Linux 内核特性 Namespace 和Cgroup 技术来实现的,本质来说:你运行在容器的应用在宿主机来说还是一个普通的进程,还是直接由宿主机来调度的,相对来说,性能的损耗就很少,这也是 Docker 技术的重要优势。

Docker 技术由于 还是一个普通的进程,所以隔离不是很彻底,还是共用宿主机的内核,在隔离级别和安全性上没有虚拟机高,这也是它的一个劣势。

Docker是如何实现隔离的的更多相关文章

  1. 理解Docker(3):Docker 使用 Linux namespace 隔离容器的运行环境

    本系列文章将介绍Docker的有关知识: (1)Docker 安装及基本用法 (2)Docker 镜像 (3)Docker 容器的隔离性 - 使用 Linux namespace 隔离容器的运行环境 ...

  2. 5、Docker 核心原理-资源隔离和限制

    Docker 资源隔离 Docker 是利用linux的LXC技术,内核的Kernel namespace Namespace: PID - 通过PID的namespace隔离,可以嵌套 NET - ...

  3. Docker实践(5)—资源隔离

    Docker使用cgroup实现CPU,内存和磁盘IO等系统资源的限制. CPU Docker现在有2个与CPU资源相关的参数,-c可以指定CPU的占比,--cpuset可以绑定CPU.例如,指定容器 ...

  4. 隔离 docker 容器中的用户

    笔者在前文<理解 docker 容器中的 uid 和 gid>介绍了 docker 容器中的用户与宿主机上用户的关系,得出的结论是:docker 默认没有隔离宿主机用户和容器中的用户.如果 ...

  5. Docker五大优势:持续集成、版本控制、可移植性、隔离性和安全性

    随着Docker技术的不断成熟,越来越多的企业开始考虑使用Docker.Docker有很多的优势,本文主要讲述了Docker的五个最重要优势,即持续集成.版本控制.可移植性.隔离性和安全性. 对于Do ...

  6. docker的底层-隔离的核心

    在了解底层原理之前: 说几个名词: 解耦状态: 所有东西都没有重复,任何东西都没有公用的地方. 半解耦状态:有部分共同的一起用,其他的独立 完全解耦状态: 就是各自都是独立没有重复. kvm:完全解耦 ...

  7. 微服务与Docker介绍

    什么是微服务 微服务应用的一个最大的优点是,它们往往比传统的应用程序更有效地利用计算资源.这是因为它们通过扩展组件来处理功能瓶颈问题.这样一来,开发人员只需要为额外的组件部署计算资源,而不需要部署一个 ...

  8. Docker学习总结之docker入门

    Understanding Docker 以下均翻译自Docker官方文档 ,转载请注明:Vikings翻译. What is Docker? Docker 是一个开源的平台,设计目标是可以方便开发, ...

  9. what is docker

    尽管之前久闻Docker的大名了,但是天资愚钝,对其到底是个啥东西一直摸不清,最近花了一段时间整理了一下,算是整理出一点头绪来. 官网的介绍是这样的: Docker is an open platfo ...

随机推荐

  1. 知乎上看到的一篇讲解Epoll的文章,较形象生动

    作者:蓝形参链接:https://www.zhihu.com/question/20122137/answer/14049112来源:知乎著作权归作者所有.商业转载请联系作者获得授权,非商业转载请注明 ...

  2. JVM 分代GC策略分析

    JVM 分代GC策略分析   我们以Sun HotSpot VM来进行分析,首先应该知道,如果我们没有指定任何GC策略的时候,JVM默认使用的GC策略.Java虚拟机是按照分代的方式来回收垃圾空间,我 ...

  3. HttpMessageConverter那回事

    相信使用过Spring的开发人员都用过@RequestBody.@ResponseBody注解,可以直接将输入解析成Json.将输出解析成Json,但HTTP 请求和响应是基于文本的,意味着浏览器和服 ...

  4. Linux下nginx反向代理负载均衡几种方式以及配置

    下面以ip地址192.168.1.1 和192.168.1.2举例 1.轮询   (1).轮询:每个请求按时间顺序逐一分配到不同的后端服务器,如果后端服务器down掉,能自动剔除.  upstream ...

  5. CentOS7上安装jdk,mysql

    最近笔者的云服务器由于中毒,重装系统了... 所以就记录下所有服务的搭建过程吧 1.安装jdk 在oracle上下载linux系统的jdk,笔者这里使用的是1.8 https://www.oracle ...

  6. Windows 2008 Server R2双网卡负载均衡

    源文档 <http://blog.sina.com.cn/s/blog_6025f5690101apwd.html>

  7. 10步写了个Django网站,正经网站···

      Django做网站只要10步,真的只有10步,不信?咱们来数数--   今天主要讲解用Pycharm编辑器搭建网站,网站功能是 实现在局域网中快速传递大文件! 比如:同事要给你个1G的文件,你丢一 ...

  8. linux之平均负载(学习笔记非原创)

    什么是平均负载 [root@111 ~]# uptime 11:03:33 up 149 days, 17:34, 1 user, load average: 0.08, 0.05, 0.01 最后三 ...

  9. 5.1中repair table

    mysql> repair table xs;+---------+--------+----------+----------+| Table | Op | Msg_type | Msg_te ...

  10. 2021升级版微服务教程7-OpenFeign实战开发和参数调优

    2021升级版SpringCloud教程从入门到实战精通「H版&alibaba&链路追踪&日志&事务&锁」 教程全目录「含视频」:https://gitee.c ...