Docker 特性与原理详细介绍与解析
Docker特性与原理
首先看看Docker提供了哪些特性:
- 交互式Shell:Docker可以分配一个虚拟终端并关联到任何容器的标准输入上,例如运行一个一次性交互shell
- 文件系统隔离:每个进程容器运行在完全独立的根文件系统里
- 写时复制:采用写时复制方式创建根文件系统,这让部署变得极其快捷,并且节省内存和硬盘空间
- 资源隔离:可以使用cgroup为每个进程容器分配不同的系统资源
- 网络隔离:每个进程容器运行在自己的网络命名空间里,拥有自己的虚拟接口和IP地址
- 日志记录:Docker将会收集和记录每个进程容器的标准流(stdout/stderr/stdin),用于实时检索或批量检索
- 变更管理:容器文件系统的变更可以提交到新的映像中,并可重复使用以创建更多的容器。无需使用模板或手动配置
从以上特性分别看实现原理
1.交互式Shell
首先我们允许一个交互式的容器
$dockerrun-i-t<imagename>/bin/bash
这样就建立了一个到容器内的交互式连接,看到的是如下的命令行:
root@df3880b17407:/#
这里我们启动了一个容器,以bash作为其根进程.
root@df3880b17407:/#psaux USERPID%CPU%MEMVSZRSSTTYSTATSTARTTIMECOMMAND root10.00.0181642020?S06:060:00/bin/bash
可以看到,在这个容器中,bash的PID为1,而实体机平常情况下,是这样的:
root@ubuntu:~#psaux USERPID%CPU%MEMVSZRSSTTYSTATSTARTTIMECOMMAND root10.00.1247162612?SsSep040:01/sbin/init
大家都知道,所有进程的共同祖先都是PID=1的进程
所以在容器中,所有以后创建的进程都是通过/bin/bash创建的,PID=1的bash是容器中所有进程的祖先理解了这点后,对容器的理解就很简单了.
2.文件系统隔离
对于一个正在运行的容器,其文件系统都是一个从根目录开始的虚拟文件系统,在容器中看到的是这样的:
root@df3880b17407:/#ll/ total68 drwxr-xr-x2rootroot4096Jul2222:51bin drwxr-xr-x2rootroot4096Apr1022:12boot drwxr-xr-x3rootroot4096Jul2222:49dev drwxr-xr-x85rootroot4096Sep506:49etc drwxr-xr-x2rootroot4096Apr1022:12home drwxr-xr-x16rootroot4096Jul2222:50lib drwxr-xr-x2rootroot4096Aug1203:30lib64 drwxr-xr-x2rootroot4096Jul2222:48media drwxr-xr-x2rootroot4096Apr1022:12mnt drwxr-xr-x2rootroot4096Jul2222:48opt dr-xr-xr-x356rootroot0Sep506:06proc drwx------2rootroot4096Jul2222:51root drwxr-xr-x7rootroot4096Sep507:23run drwxr-xr-x2rootroot4096Aug1203:30sbin drwxr-xr-x2rootroot4096Jul2222:48srv dr-xr-xr-x13rootroot0Sep506:06sys drwxrwxrwt2rootroot4096Sep506:55tmp drwxr-xr-x20rootroot4096Sep506:11usr drwxr-xr-x19rootroot4096Sep506:11var
其实真是情况是这样的,容器中的文件系统都是挂载到了真是系统中的一个目录下面.
/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 total84 drwxr-xr-x53rootroot4096Sep500:23./ drwx------4rootroot4096Sep500:53../ drwxr-xr-x2rootroot4096Jul2215:51bin/ drwxr-xr-x2rootroot4096Apr1015:12boot/ drwxr-xr-x3rootroot4096Jul2215:49dev/ drwxr-xr-x85rootroot4096Sep423:49etc/ drwxr-xr-x2rootroot4096Apr1015:12home/ drwxr-xr-x16rootroot4096Jul2215:50lib/ drwxr-xr-x2rootroot4096Aug1120:30lib64/ drwxr-xr-x2rootroot4096Jul2215:48media/ drwxr-xr-x2rootroot4096Apr1015:12mnt/ drwxr-xr-x2rootroot4096Jul2215:48opt/ drwxr-xr-x2rootroot4096Apr1015:12proc/ drwx------2rootroot4096Jul2215:51root/ drwxr-xr-x7rootroot4096Sep500:23run/ drwxr-xr-x2rootroot4096Aug1120:30sbin/ drwxr-xr-x2rootroot4096Jul2215:48srv/ drwxr-xr-x2rootroot4096Mar1218:41sys/ drwxrwxrwt2rootroot4096Sep423:55tmp/ drwxr-xr-x20rootroot4096Sep423:11usr/ drwxr-xr-x19rootroot4096Sep423:11var/
这些就是容器中的真实目录了,容器中对于目录的操作都是操作了这个host机器的真实目录。
对于不同的容器,挂载点是不一样的,而容器不能穿越根目录上一级去访问,所以这里对每一个容器都做到了文件系统隔离。
3.写时复制
我们把每一个
/var/lib/docker/containers/<image-long-id>
看做是一个容器的配置目录的话,可以看到在配置目录下面有一个rw/目录,打开看有些什么
total36 drwxr-xr-x9rootroot4096Sep500:23./ drwx------4rootroot4096Sep500:53../ drwxr-xr-x6rootroot4096Sep423:49etc/ drwxr-xr-x2rootroot4096Sep500:23run/ drwxrwxrwt2rootroot4096Sep423:55tmp/ drwxr-xr-x7rootroot4096Sep423:11usr/ drwxr-xr-x5rootroot4096Sep423:11var/ -r--r--r--1rootroot0Sep423:06.wh..wh.aufs drwx------2rootroot4096Sep423:06.wh..wh.orph/ drwx------2rootroot4096Sep423:11.wh..wh.plnk/
里面是一些不完整的根目录,这不能说明什么,但是我们在container中写入文件后,看看其中的变化
在容器中执行以下命令
root@df3880b17407:/#touch/opt/x
在/opt下我们生成了一个文件
再看看
root@ubuntu:/var/lib/docker/containers/df3880b17407575cd642a6b7da3c7e417a55fad5bbd63152f89921925626d2b6/rw#ll total40 drwxr-xr-x10rootroot4096Sep501:00./ drwx------4rootroot4096Sep500:53../ drwxr-xr-x6rootroot4096Sep423:49etc/ drwxr-xr-x2rootroot4096Sep501:00opt/ drwxr-xr-x2rootroot4096Sep500:23run/ drwxrwxrwt2rootroot4096Sep423:55tmp/ drwxr-xr-x7rootroot4096Sep423:11usr/ drwxr-xr-x5rootroot4096Sep423:11var/ -r--r--r--1rootroot0Sep423:06.wh..wh.aufs drwx------2rootroot4096Sep423:06.wh..wh.orph/ drwx------2rootroot4096Sep423:11.wh..wh.plnk/
是的,在host机器上新生成了opt/目录,这里做到了容器的写时复制
4.资源隔离
以系统的三大进程间通信的消息队列来看
初始状态在ghost,ipcs-q查看消息队列
root@ubuntu:~/codes/msq#ipcs-q ------MessageQueues-------- keymsqidownerpermsused-bytesmessages
在ghost创建一个,这里楼主自己写的代码创建的消息队列:
root@ubuntu:~/codes/msq#./main1 Createmsqwithkeyid65536root@ubuntu:~/codes/msq#ipcs-q ------MessageQueues-------- keymsqidownerpermsused-bytesmessages 0x0000000165536root66600
然后在容器中查看ipcs-q
root@df3880b17407:/#ipcs-q ------MessageQueues-------- keymsqidownerpermsused-bytesmessages
可以看到系统资源是隔离的,这里只是说了一部分,其实还包括了可以通过cgoup对其做CPU和Memory的Quota管理.
默认情况下是使用了所有CPU和内存的,但是可以在config.lxc增加如下配置设置CPU等,具体可以参考lxc的文档
lxc.cgroup.cpu.shares=512lxc.cgroup.cpuset.cpus=1.2
资源隔离的原理就在于利用cgroup,将不同进程的使用隔离开,假设每个容器都是以bash启动的,那么在容器内部,每个子进程都只能使用当前bash下面的资源,对于其他的系统资源是隔离的.子进程的访问权限由父进程决定
5.网络隔离
在安装好docker后,会默认初始化一个docker0的网桥
docker0Linkencap:EthernetHWaddree:8c:1f:8b:d7:59 inetaddr:172.17.42.1Bcast:0.0.0.0Mask:255.255.0.0 ...
在host机器上,会为每一个容器生成一个默认的网卡类似这样的vethdBVa1Hveth*
这个网卡的一端连接在容器的eth0,一端连接到docker0.这样就实现了每个容器有一个单独的IP.
这里如果需要容器访问外网,需要将eth0设置为混杂模式:
$ifconfigeth0promisc
这样看来,容器会从172.17.0.0/24这个网段选择一个IP作为eth0的IP,这样,容器就可以和外部通过docker0网桥通信了.
在容器内部监听一个端口python-mSimpleHTTPServer80>>/tmp/log.log&
从ghost访问telnet172.17.0.280
在容器中看到如下:
ActiveInternetconnections(serversandestablished) ProtoRecv-QSend-QLocalAddressForeignAddressStatePID/Programname tcp000.0.0.0:800.0.0.0:*LISTEN2823/python tcp00172.17.0.2:80172.17.42.1:46142TIME_WAIT-
在host上看到
ActiveInternetconnections(serversandestablished) ProtoRecv-QSend-QLocalAddressForeignAddressStatePID/Programname tcp00172.17.42.1:46142172.17.0.2:80ESTABLISHED10244/telnet
如果需要外部能够访问容器,需要做端口映射规则,和配置虚拟机一样的道理,只不过这里可以看到的是,80端口并没有占用了本地端口,而是在容器内部做了监听,外部是通过docker0桥接过去的,每个容器间也做到了端口和网络隔离.
6.日志记录
不多说,在/var/lib/docker/containers/<image-long-id>.log下
7.变更管理
Docker的变更管理看做是git的版本管理好了。
生成镜像的时候,未做改动的部分就是上一个版本的镜像的引用,如果做了改动,就是一个新的文件。
将刚才操作的容器做成镜像
dockercommit<image-id><REPOSITORY>
此时的镜像多出来的部分,比如我在这个镜像中安装了Python,那么多出来的部分作为新文件,其他部分任然是上一个版本的引用。
你可以搭建自己的镜像服务器,push到自己的镜像服务器,从其他机器拉下来后直接可以运行。
感谢阅读,希望能帮助到大家,谢谢大家对本站的支持!