Docker特性与原理解析

文章假设你已经熟悉了Docker的基本命令和基本知识

首先看看Docker提供了哪些特性:

  • 交互式Shell:Docker可以分配一个虚拟终端并关联到任何容器的标准输入上,例如运行一个一次性交互shell
  • 文件系统隔离:每个进程容器运行在完全独立的根文件系统里
  • 写时复制:采用写时复制方式创建根文件系统,这让部署变得极其快捷,并且节省内存和硬盘空间
  • 资源隔离:可以使用cgroup为每个进程容器分配不同的系统资源
  • 网络隔离:每个进程容器运行在自己的网络命名空间里,拥有自己的虚拟接口和IP地址
  • 日志记录:Docker将会收集和记录每个进程容器的标准流(stdout/stderr/stdin),用于实时检索或批量检索
  • 变更管理:容器文件系统的变更可以提交到新的映像中,并可重复使用以创建更多的容器。无需使用模板或手动配置

从以上特性分别看实现原理

1. 交互式Shell

首先我们允许一个交互式的容器

$docker run -i -t <image name> /bin/bash

这样就建立了一个到容器内的交互式连接,看到的是如下的命令行:

root@df3880b17407:/#

这里我们启动了一个容器,以bash作为其根进程.

root@df3880b17407:/# ps aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.0 18164 2020 ? S 06:06 0:00 /bin/bash

可以看到,在这个容器中,bash 的 PID为 1,而实体机平常情况下,是这样的:

root@ubuntu:~# ps aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.1 24716 2612 ? Ss Sep04 0:01 /sbin/init

大家都知道,所有进程的共同祖先都是 PID=1的进程

所以在容器中,所有以后创建的进程都是通过/bin/bash 创建的,PID=1的 bash是容器中所有进程的祖先

理解了这点后,对容器的理解就很简单了.

2. 文件系统隔离

对于一个正在运行的容器,其文件系统都是一个从根目录开始的虚拟文件系统,在容器中看到的是这样的:

root@df3880b17407:/# ll /
total 68
drwxr-xr-x 2 root root 4096 Jul 22 22:51 bin
drwxr-xr-x 2 root root 4096 Apr 10 22:12 boot
drwxr-xr-x 3 root root 4096 Jul 22 22:49 dev
drwxr-xr-x 85 root root 4096 Sep 5 06:49 etc
drwxr-xr-x 2 root root 4096 Apr 10 22:12 home
drwxr-xr-x 16 root root 4096 Jul 22 22:50 lib
drwxr-xr-x 2 root root 4096 Aug 12 03:30 lib64
drwxr-xr-x 2 root root 4096 Jul 22 22:48 media
drwxr-xr-x 2 root root 4096 Apr 10 22:12 mnt
drwxr-xr-x 2 root root 4096 Jul 22 22:48 opt
dr-xr-xr-x 356 root root 0 Sep 5 06:06 proc
drwx------ 2 root root 4096 Jul 22 22:51 root
drwxr-xr-x 7 root root 4096 Sep 5 07:23 run
drwxr-xr-x 2 root root 4096 Aug 12 03:30 sbin
drwxr-xr-x 2 root root 4096 Jul 22 22:48 srv
dr-xr-xr-x 13 root root 0 Sep 5 06:06 sys
drwxrwxrwt 2 root root 4096 Sep 5 06:55 tmp
drwxr-xr-x 20 root root 4096 Sep 5 06:11 usr
drwxr-xr-x 19 root root 4096 Sep 5 06:11 var

其实真是情况是这样的,容器中的文件系统都是挂载到了真是系统中的一个目录下面.

/var/lib/docker/containers/<image-long-id>/rootfs

这个配置是怎么来的呢,其实所有容器的管理都是通过lxc来管理的,lxc的配置文件放在

/var/lib/docker/containers/<image-long-id>/config.lxc

文件中有字段表示容器挂载到哪个文件目录, 比如我的是这样的:

lxc.rootfs = /var/lib/docker/containers/df3880b17407575cd642a6b7da3c7e417a55fad5bbd63152f89921925626d2b6/rootfs

打开看一下,一目了然:

root@ubuntu:/var/lib/docker/containers/df3880b17407575cd642a6b7da3c7e417a55fad5bbd63152f89921925626d2b6/rootfs# ll
total 84
drwxr-xr-x 53 root root 4096 Sep 5 00:23 ./
drwx------ 4 root root 4096 Sep 5 00:53 ../
drwxr-xr-x 2 root root 4096 Jul 22 15:51 bin/
drwxr-xr-x 2 root root 4096 Apr 10 15:12 boot/
drwxr-xr-x 3 root root 4096 Jul 22 15:49 dev/
drwxr-xr-x 85 root root 4096 Sep 4 23:49 etc/
drwxr-xr-x 2 root root 4096 Apr 10 15:12 home/
drwxr-xr-x 16 root root 4096 Jul 22 15:50 lib/
drwxr-xr-x 2 root root 4096 Aug 11 20:30 lib64/
drwxr-xr-x 2 root root 4096 Jul 22 15:48 media/
drwxr-xr-x 2 root root 4096 Apr 10 15:12 mnt/
drwxr-xr-x 2 root root 4096 Jul 22 15:48 opt/
drwxr-xr-x 2 root root 4096 Apr 10 15:12 proc/
drwx------ 2 root root 4096 Jul 22 15:51 root/
drwxr-xr-x 7 root root 4096 Sep 5 00:23 run/
drwxr-xr-x 2 root root 4096 Aug 11 20:30 sbin/
drwxr-xr-x 2 root root 4096 Jul 22 15:48 srv/
drwxr-xr-x 2 root root 4096 Mar 12 18:41 sys/
drwxrwxrwt 2 root root 4096 Sep 4 23:55 tmp/
drwxr-xr-x 20 root root 4096 Sep 4 23:11 usr/
drwxr-xr-x 19 root root 4096 Sep 4 23:11 var/

这些就是容器中的真实目录了,容器中对于目录的操作都是操作了这个host机器的真实目录。

对于不同的容器,挂载点是不一样的,而容器不能穿越根目录上一级去访问, 所以这里对每一个容器都做到了文件系统隔离。

3. 写时复制

我们把每一个

/var/lib/docker/containers/<image-long-id>

看做是一个容器的配置目录的话,可以看到在配置目录下面有一个 rw/目录,打开看有些什么

total 36
drwxr-xr-x 9 root root 4096 Sep 5 00:23 ./
drwx------ 4 root root 4096 Sep 5 00:53 ../
drwxr-xr-x 6 root root 4096 Sep 4 23:49 etc/
drwxr-xr-x 2 root root 4096 Sep 5 00:23 run/
drwxrwxrwt 2 root root 4096 Sep 4 23:55 tmp/
drwxr-xr-x 7 root root 4096 Sep 4 23:11 usr/
drwxr-xr-x 5 root root 4096 Sep 4 23:11 var/
-r--r--r-- 1 root root 0 Sep 4 23:06 .wh..wh.aufs
drwx------ 2 root root 4096 Sep 4 23:06 .wh..wh.orph/
drwx------ 2 root root 4096 Sep 4 23:11 .wh..wh.plnk/

里面是一些不完整的根目录,这不能说明什么,但是我们在container中写入文件后,看看其中的变化

在容器中执行以下命令

root@df3880b17407:/# touch /opt/x

在 /opt 下我们生成了一个文件

再看看

root@ubuntu:/var/lib/docker/containers/df3880b17407575cd642a6b7da3c7e417a55fad5bbd63152f89921925626d2b6/rw# ll
total 40
drwxr-xr-x 10 root root 4096 Sep 5 01:00 ./
drwx------ 4 root root 4096 Sep 5 00:53 ../
drwxr-xr-x 6 root root 4096 Sep 4 23:49 etc/
drwxr-xr-x 2 root root 4096 Sep 5 01:00 opt/
drwxr-xr-x 2 root root 4096 Sep 5 00:23 run/
drwxrwxrwt 2 root root 4096 Sep 4 23:55 tmp/
drwxr-xr-x 7 root root 4096 Sep 4 23:11 usr/
drwxr-xr-x 5 root root 4096 Sep 4 23:11 var/
-r--r--r-- 1 root root 0 Sep 4 23:06 .wh..wh.aufs
drwx------ 2 root root 4096 Sep 4 23:06 .wh..wh.orph/
drwx------ 2 root root 4096 Sep 4 23:11 .wh..wh.plnk/

是的,在host机器上新生成了 opt/ 目录,这里做到了容器的写时复制

4. 资源隔离

以系统的三大进程间通信的消息队列来看

初始状态在 ghost, ipcs -q 查看消息队列

root@ubuntu:~/codes/msq# ipcs -q

------ Message Queues --------
key msqid owner perms used-bytes messages

在ghost 创建一个, 这里楼主自己写的代码创建的消息队列:

root@ubuntu:~/codes/msq# ./main 1
Create msq with key id 65536root@ubuntu:~/codes/msq# ipcs -q ------ Message Queues --------
key msqid owner perms used-bytes messages
0x00000001 65536 root 666 0 0

然后在容器中查看 ipcs -q

root@df3880b17407:/# ipcs -q

------ Message Queues --------
key msqid owner perms used-bytes messages

可以看到系统资源是隔离的,这里只是说了一部分,其实还包括了可以通过cgoup对其做CPU和Memory的Quota管理.

默认情况下是使用了所有CPU和内存的,但是可以在config.lxc增加如下配置设置CPU等,具体可以参考lxc的文档

lxc.cgroup.cpu.shares=512 lxc.cgroup.cpuset.cpus=1.2

资源隔离的原理就在于利用cgroup,将不同进程的使用隔离开,假设每个容器都是以bash启动的,那么在容器内部,每个子进程都只能使用当前bash下面的资源,对于其他的系统资源是隔离的.子进程的访问权限由父进程决定

5.网络隔离

在安装好docker后,会默认初始化一个 docker0的网桥

docker0   Link encap:Ethernet  HWaddr ee:8c:1f:8b:d7:59
inet addr:172.17.42.1 Bcast:0.0.0.0 Mask:255.255.0.0
...

在host机器上,会为每一个容器生成一个默认的网卡类似这样的 vethdBVa1H veth*

这个网卡的一端连接在容器的eth0,一端连接到docker0.这样就实现了每个容器有一个单独的IP.

这里如果需要容器访问外网,需要将eth0设置为混杂模式:

$ifconfig eth0 promisc

这样看来,容器会从172.17.0.0/24 这个网段选择一个IP作为eth0的IP,这样,容器就可以和外部通过 docker0网桥通信了.

在容器内部监听一个端口 python -m SimpleHTTPServer 80 >> /tmp/log.log &

从ghost访问 telnet 172.17.0.2 80

在容器中看到如下:

Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN 2823/python
tcp 0 0 172.17.0.2:80 172.17.42.1:46142 TIME_WAIT -

在host上看到

Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 172.17.42.1:46142 172.17.0.2:80 ESTABLISHED 10244/telnet

如果需要外部能够访问容器,需要做端口映射规则,和配置虚拟机一样的道理, 只不过这里可以看到的是,80端口并没有占用了本地端口,而是在容器内部做了监听,外部是通过docker0 桥接过去的,每个容器间也做到了端口和网络隔离.

6.日志记录

不多说,在 /var/lib/docker/containers/<image-long-id>.log

7.变更管理

Docker的变更管理看做是git的版本管理好了。

生成镜像的时候,未做改动的部分就是上一个版本的镜像的引用,如果做了改动,就是一个新的文件。

将刚才操作的容器做成镜像

docker commit <image-id> <REPOSITORY>

此时的镜像多出来的部分,比如我在这个镜像中安装了Python,那么多出来的部分作为新文件,其他部分任然是上一个版本的引用。

你可以搭建自己的镜像服务器,push到自己的镜像服务器,从其他机器拉下来后直接可以运行。

以上,一点愚见,希望指正.

[原][Docker]特性与原理解析的更多相关文章

  1. Docker镜像构建原理解析(不装docker也能构建镜像)

    在devops流程里面 构建镜像是一个非常重要的过程,一般构建镜像是写dockerfile文件然后通过docker client来构建的image. docker client 会先检查本地有没有im ...

  2. 一篇不一样的docker原理解析

    转自:https://zhuanlan.zhihu.com/p/22382728 https://zhuanlan.zhihu.com/p/22403015 在学习docker的过程中,我发现目前do ...

  3. 高性能JavaScript模板引擎原理解析

    随着 web 发展,前端应用变得越来越复杂,基于后端的 javascript(Node.js) 也开始崭露头角,此时 javascript 被寄予了更大的期望,与此同时 javascript MVC ...

  4. Docker容器的原理与实践 (下)

    欢迎访问网易云社区,了解更多网易技术产品运营经验. Docker原理分析 Docker架构 镜像原理 镜像是一个只读的容器模板,含有启动docker容器所需的文件系统结构及内容Docker以镜像和在镜 ...

  5. Web APi之过滤器执行过程原理解析【二】(十一)

    前言 上一节我们详细讲解了过滤器的创建过程以及粗略的介绍了五种过滤器,用此五种过滤器对实现对执行Action方法各个时期的拦截非常重要.这一节我们简单将讲述在Action方法上.控制器上.全局上以及授 ...

  6. Web APi之过滤器创建过程原理解析【一】(十)

    前言 Web API的简单流程就是从请求到执行到Action并最终作出响应,但是在这个过程有一把[筛子],那就是过滤器Filter,在从请求到Action这整个流程中使用Filter来进行相应的处理从 ...

  7. JavaScript 模板引擎实现原理解析

    1.入门实例 首先我们来看一个简单模板: <script type="template" id="template"> <h2> < ...

  8. 3D游戏常用技巧Normal Mapping (法线贴图)原理解析——高级篇

    1.概述 上一篇博客,3D游戏常用技巧Normal Mapping (法线贴图)原理解析——基础篇,讲了法线贴图的基本概念和使用方法.而法线贴图和一般的纹理贴图一样,都需要进行压缩,也需要生成mipm ...

  9. 爱加密Android APk 原理解析

    转载请标明出处:http://blog.csdn.net/u011546655/article/details/45921025 爱加密Android APK加壳原理解析 一.什么是加壳? 加壳是在二 ...

随机推荐

  1. AndroidTips-052:.aar文件依赖

    aar aar 文件是android 类库项目的输出文件,其中可以包含普通的.class,清单,以及android项目特有的资源文件. 使用方式 将.aar文件放在在自己项目的libs目录下 在gra ...

  2. FREERTOS 手册阅读笔记

    郑重声明,版权所有! 转载需说明. FREERTOS堆栈大小的单位是word,不是byte. 根据处理器架构优化系统的任务优先级不能超过32,If the architecture optimized ...

  3. 0.Win8.1,Win10,Windows Server 2012 安装 Net Framework 3.5

    后期会在博客首发更新:http://dnt.dkill.net 网站部署之~Windows Server | 本地部署:http://www.cnblogs.com/dunitian/p/482280 ...

  4. 一个表缺失索引发的CPU资源瓶颈案例

    背景 近几日,公司的应用团队反应业务系统突然变慢了,之前是一直比较正常.后与业务部门沟通了解详情,得知最近生意比较好,同时也在做大的促销活动,使得业务数据处理的量出现较大的增长,最终系统在处理时出现瓶 ...

  5. PHP之时间和日期函数

    // 时间日期函数 Time <?php date_default_timezone_set('UTC'); // 获取当前时间的时间戳 $time0 = mktime(); $time1 = ...

  6. MFC单文档程序添加HTML帮助支持

    1.在App类 构造函数中添加 EnableHtmlHelp(); 2.在Frame类中,添加消息影射: ON_COMMAND(ID_HELP_FINDER, CFrameWnd::OnHelpFin ...

  7. Android使用静默安装时碰见的问题

    升级时碰见的异常 private void installPackage(String appName,final File apk) { if (!apk.exists()) { setHasNew ...

  8. 字符型图片验证码识别完整过程及Python实现

    字符型图片验证码识别完整过程及Python实现 1   摘要 验证码是目前互联网上非常常见也是非常重要的一个事物,充当着很多系统的 防火墙 功能,但是随时OCR技术的发展,验证码暴露出来的安全问题也越 ...

  9. 如果你发现mysql的外键约束不管用了

    不知为何我机子上的mysql竟然默认关闭外键约束,导致我试了好多遍都可以插入非法值,以下语句可以开启约束 SET foreign_key_checks = 1; (0则关闭) 备忘

  10. Marmoset Toolbag中的角色布光技巧 by Joe”EarthQuake”Wilson

    Sagat by Tim “spacemonkey” Appleby 有言在先 首先,我要感谢才华横溢的Tim“spacemonkey Appleby允许本教程中使用他那个极其NB的Sagat模型.不 ...