分层结构
镜像是一种轻量级、可执行的独立软件包,用来打包软件运行环境和基于运行环境开发的软件,它包含运行某个软件所需的所有内容,包括代码、运行时、库、环境变量和配置文件。
[root@iZbp1dnapcnoxqoyi0jwqaZ ~]# docker images REPOSITORY TAG IMAGE ID CREATED SIZE tomcat latest feba8d001e3f 2 weeks ago 649MB centos latest 300e315adb2f 4 weeks ago 209MB
docker镜像、容器的基石是Union 文件系统(联合文件系统)。
Union文件系统(Union Filesystem)是一种分层、轻量级并且高性能的文件系统,它支持对文件系统的修改作为一次提交来一层层的叠加,这个特性使得镜像可以通过分层实现和继承;同时可以将不同目录挂载到同一个虚拟文件系统下。
在Docker镜像分为基础镜像和父镜像,没有父镜像的镜像被称为基础镜像。用户是基于基础镜像来制作各种不同的应用镜像。这些应用镜像共享同一个基础镜像层,提高了存储效率。
当用户通过升级程序到新版本,改变了一个Docker镜像时,一个新的镜像层会被创建。因此,用户不用替换整个原镜像或者完全重新建立新镜像,只需要添加新层即可。在用户分发镜像的时,也只需要分发被改动的新层内容(增量部分),这让Docker的镜像管理变得十分轻松级和快速。
也就是说我们想生成一套虚拟环境不用从零开始了,而只要在一个相对完善的基础环境之上来创建我们的虚拟环境就可以了,比如我们想生成一个具有tomcat环境的镜像,只要在一个装有jdk环境的镜像之上来创建就可以了
docker这种分层的文件系统,最大的好处就是共享资源。
多数镜像都从相同的基础镜像构建而来,那么宿主机只需在磁盘上保存一份基础镜像,同时内存中也只需加载一份 base 镜像,就可以为所有容器服务了。而且镜像的每一层都可以被共享。
这也是为什么在一个新的docker环境中,刚开始pull镜像会很慢,后面的都很快。
镜像加载原理
- docker的镜像实际上由一层一层的文件系统组成,这种层级的文件系统就是上文说到的UnionFS,包括bootfs与rootfs。
bootfs(boot file system):在Docker镜像的最底层是bootfs,这一层与我们典型的Linux/Unix系统是一样的,包含bootloader(boot加载器)和kernel(内核)。当boot加载完成之后整个内核就都在内存中了,此时内存的使用权已由bootfs转交给内核,此时系统也会卸载bootfs。
rootfs (root file system) :在bootfs之上。包含的就是典型 Linux 系统中的 /dev, /proc, /bin, /etc 等标准目录和文件。rootfs就是各种不同的操作系统发行版,比如Ubuntu,Centos等等。
- 那么docker的rootfs与传统意义的rootfs不同之处到底是什么呢?
传统的Linux加载bootfs时会先将rootfs设为read-only,然后在系统自检之后将rootfs从read-only改为read-write。然后我们就可以在rootfs上进行写和读的操作了。但docker的镜像却不是这样,他在bootfs自检完毕之后并不会把rootfs的read-only改为read-write。而是利用union mount(UnionFS的一种挂载机制)将一个或多个read-only的rootfs加载到之前的read-only 的rootfs层之上。并在加载了这么多层的rootfs之后,仍然让它看起来只像一个文件系统,在docker的体系里把union mount的这些read-only层的rootfs叫做docker的镜像(image)。请注意,此时的每一层rootfs都是read-only的,也就是说我们此时还不能对其进行操作,那么我们怎样对其进行读写操作呢?
答案是将docker镜像进行实例化,就是上文说的从镜像(image)变成容器(container)的过程,当镜像被实例化为容器之后,系统会为在一层或是多层的read-only的rootfs之上分配一层空的read-write的rootfs。而这个分配的动作就是由docker run命令发起的。
- 虚拟机上的操作系统都是好几个G,为什么docker的centos镜像和ubuntu镜像才这么点大小?
[root@iZbp1dnapcnoxqoyi0jwqaZ ~]# docker images REPOSITORY TAG IMAGE ID CREATED SIZE tomcat latest feba8d001e3f 3 weeks ago 649MB centos latest 300e315adb2f 4 weeks ago 209MB ubuntu latest f643c72bc252 6 weeks ago 72.9MB
对于不同的linux发行版, bootfs基本是一致的, 只是rootfs会有差别, 因此不同的发行版可以公用bootfs;并且rootfs可以很小,只需要包括最基本的命令、工具和程序库。
所以,当宿主机是linux时,docker使用centos或者ubuntu镜像,直接用了宿主机的kernel,自己只需要提供 rootfs 就行了。
同理,当宿主机是linux时,docker使用windows镜像,由于无法共用宿主机的kernel,镜像就会很大:
[root@iZbp1dnapcnoxqoyi0jwqaZ ~]# docker images REPOSITORY TAG IMAGE ID CREATED SIZE tomcat latest feba8d001e3f 3 weeks ago 649MB centos latest 300e315adb2f 4 weeks ago 209MB ubuntu latest f643c72bc252 6 weeks ago 72.9MB toolboc/windows95 latest 07e6027f1db1 2 years ago 1.6GB
容器commit为镜像的操作
镜像和容器的关系就像类与实例,可以用docker run通过镜像运行一个容器;那么当我们完成了对容器的修改,想要提交为一个镜像,如何实现?
docker commit -a=“作者” -m=“提交的描述信息” 容器ID 要创建的镜像名:标签
- 运行一个容器:
[root@izbp13m488196e5hna361rz ~]# docker run -d -p 8888:8080 consol/tomcat-7.0 5aedc1d14c649115a1266da163177f04fe45ff9210d34f26271269b4bd8cea78 [root@izbp13m488196e5hna361rz ~]# docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 5aedc1d14c64 consol/tomcat-7.0 "/bin/sh -c /opt/tom…" 8 seconds ago Up 7 seconds 8778/tcp, 0.0.0.0:8888->8080/tcp compassionate_wu
-d 表示后台启动
-p 8888:8080表示docker宿主机对外暴露的端口是8888,docker容器内部端口为8080
浏览器8888端口访问tomcat的主页,验证成功
- 根据需求修改这个容器:
这里就把tomcat主页上的successfully改成fail
[root@izbp13m488196e5hna361rz ~]# docker exec -it 5aedc1d14c64 grep 'successfully' /opt/tomcat/webapps/ROOT/index.jsp <h2>If you're seeing this, you've successfully installed Tomcat. Congratulations!</h2> [root@izbp13m488196e5hna361rz ~]# docker exec -it 5aedc1d14c64 sed -i "s/successfully/fail/g" /opt/tomcat/webapps/ROOT/index.jsp [root@izbp13m488196e5hna361rz ~]# docker exec -it 5aedc1d14c64 grep 'fail' /opt/tomcat/webapps/ROOT/index.jsp <h2>If you're seeing this, you've fail installed Tomcat. Congratulations!</h2>
刷新浏览器8888端口的页面,验证成功
- 以这个容器为模板commit一个镜像:
[root@izbp13m488196e5hna361rz ~]# docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 5aedc1d14c64 consol/tomcat-7.0 "/bin/sh -c /opt/tom…" 48 minutes ago Up 48 minutes 8778/tcp, 0.0.0.0:8888->8080/tcp compassionate_wu [root@izbp13m488196e5hna361rz ~]# docker commit -a="nick" -m="this is mytomcat" 5aedc1d14c64 nicktomcat:1.0 sha256:bad52e056690fcef071735f30e2406f29d45ccabfeedc5bf49a012a0f487c90e [root@izbp13m488196e5hna361rz ~]# docker images REPOSITORY TAG IMAGE ID CREATED SIZE nicktomcat 1.0 bad52e056690 9 seconds ago 601MB nginx latest f6d0b4767a6c 5 days ago 133MB tomcat latest feba8d001e3f 4 weeks ago 649MB consol/tomcat-7.0 latest 7c34bafd1150 5 years ago 601MB
新镜像为 nicktomcat:1.0
- 以新镜像运行一个容器并验证:
[root@izbp13m488196e5hna361rz ~]# docker images REPOSITORY TAG IMAGE ID CREATED SIZE nicktomcat 1.0 bad52e056690 2 minutes ago 601MB nginx latest f6d0b4767a6c 5 days ago 133MB tomcat latest feba8d001e3f 4 weeks ago 649MB consol/tomcat-7.0 latest 7c34bafd1150 5 years ago 601MB [root@izbp13m488196e5hna361rz ~]# docker run -d -p 80:8080 nicktomcat:1.0 deda27438748e5c5038e7454bc3a44c47199abc1ee5dfc19075026fe21723000 [root@izbp13m488196e5hna361rz ~]# docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES deda27438748 nicktomcat:1.0 "/bin/sh -c /opt/tom…" 5 seconds ago Up 4 seconds 8778/tcp, 0.0.0.0:80->8080/tcp determined_saha
这次对外暴露的端口为80,用浏览器验证
以上简单模拟了使用docker完成版本迭代的流程:
- pull一个官方镜像consol/tomcat-7.0
- 以consol/tomcat-7.0镜像run一个容器
- 在容器中根据业务的需求修改
- 修改完成后,容器commit为新镜像nicktomcat
- 在别的环境,可以直接使用nicktomcat新镜像来运行容器,该容器已实现业务需求