Docker入门篇(三):Docker 镜像 & UnionFS

777 阅读6分钟

我们在最开始尝试下载tomcat镜像时,就发现它的大小达到了反常的600MB!看来这个镜像显然不仅仅包含着tomcat应用本身。我们将在这一章节中具体讨论,Docker的镜像部分。

十张图带你深入了解容器和镜像

镜像是什么

Docker镜像是一种轻量级,可以执行的独立软件包,用于打包软件运行环境,或者基于某种运行环境的软件。一个镜像内包含了某个软件所需要的所有内容,包括了代码,运行时,库,环境变量和配置文件

联合文件系统UnionFS

它是一种分层,轻量级,并且高性能的文件系统。它将对文件系统的修改作为一次提交来一层层的在原先的文件系统中进行叠加;同时它可以将不同的目录挂载到同一个虚拟文件系统下(unite several directories into to a single virtual filesystem)。

Union文件系统是Docker镜像的基础。镜像可以通过分层进行继承,基于基础镜像,可以制作各种具体的应用镜像。

一个镜像文件实际上是由多个文件系统组成,但是镜像为我们提供了一个统一的视角,让我们凭直觉认为:我们操作的是一个整体的文件系统。

因此,当我们从阿里云仓库中下载centostomcat镜像时,并不是下载了一个镜像,而是同时下载了很多分镜像,然后向我们展现了最外层的镜像ID。

Docker镜像的加载原理

典型的Linux系统包含了两大部分:

bootfs(boot file system)。它又有两个重要部分:bootloader和kernel(运行Linux所必须的内核)。bootfs的任务就是利用内部的bootloader引导kernel加载到机器内存当中,随后,这个bootfs就会被系统卸载。

rootfs(root file system)。它运行在bootfs之上,包含了典型Linux系统中的/bin,/bin等标准目录。并且随着发行版本的不同(Ubuntu,CentOS),在其之上安装的软件会存在一些区别。

也就是说,无论是Ubuntu,还是CentOS,由于它们都是源于Linux内核开发,所以它们的bootfs是相同的。唯一的区别就体现在rootfs上。

Docker正是敏锐地发现了这一点:干脆就让宿主机来提供bootfs层(主要是提供Linux内核),每个镜像只保留rootfs即可。另外,一个精简的OS系统,只要包括最基本的命令,工具,和程序库就可以了。(简略到甚至连vim都没有安装)

这里要补充一点,Docker是一个基于Linux环境运行的工具。也就是说它默认宿主机也是Linux系统(这是镜像和虚拟机共享bootfs的前提)。尽管Docker为Windows和Mac提供了工具,但是主场仍然在Linux系统。因为在实际环境中,开发人员基本上都选择Linux作为服务器的OS。

因此,我们从Docker中下载到的centos镜像只有区区200MB

🌎深入了解Docker镜像CSDN

在运行Tomcat之前

作为Java程序员,我们都知道Tomcat的运行有一个前提条件:那就是安装JDK。而JDK当然也要运行在OS之上。于是就形成了这样的关系(这个OS由宿主机的kernel驱动):

一个区区Tomcat当然不会占用太大空间,但是如果要将JDK和centos也算进去,一来二去,最终打包成的tomcat镜像文件也就远比tomcat本身庞大的多了。

Docker使用了UnionFS的分层原理,将内部的文件一层一层打包,并在最外层覆盖了一层Tomcat应用。这个最外层又通常称之为容器层,而容器层之下的都归类于镜像层

Docker选择UnionFS的理由

最主要的原因就是:共享资源

举个例子:多个镜像如果都是基于一个base镜像(假设它只有最简单的CentOS镜像)构建而来,那么宿主机只需要保存一份base镜像即可,而不需要在每个镜像内部都铺上独立的CentOS镜像。

制作一个新的镜像

在介绍Docker Hub时,我们就抛出了一个问题:它和GitHub是如此的类似,甚至还包含了pull,push等指令。那会不会有commit呢?

答案是肯定的。我们正是通过commit命令方式在本地将某个容器改造成属于自己的镜像。语句为:

$ docker commit -m=${msg} -a=${author} ${containerID} ${newImageName}[:${label}]

其中:${msg}为这个新镜像的描述信息;${author}标注这个镜像的作者;${containerID}表示要将目前的哪个容器制作成镜像。${newImageName}为新的镜像名称,注意,必须全部为小写

:${label}仍为可选项,如果没有显式指定,则指派为latest

我们用一个实例来演示:从阿里云仓库中下载一个tomcat镜像并创建一个容器tomcat9,然后在此之上做一点小改动,然后通过commit命令将这个容器重新打包成一份DIY版的tomcat镜像。

#直接从镜像中下载一个新的tomcat镜像,如果之前没有下载的话。
$ docker pull tomcat

在获取这个镜像之后,我们就创建一个容器,并在此之上做一些改动。注意,以守护进程的方式创建容器,否则会阻塞终端。(注:tomcat9只是笔者的命名。)

$ docker run -d --name tomcat9 tomcat

hub.docker.com/_/tomcat 里面介绍:通过进入容器Tomcat目录查看,有一个空的webapps和 webapps.dist 目录,你需要将这个webapps 目录删除,并将webapps.dist 目录重命名为 webapps 目录。否则会报错:404。

为了能修改容器内的文件让tomcat正确运行,但是又不能直接进入到容器内(这样会阻塞终端),这时应该采取什么方式呢?没错,正是exec命令。

我们首先使用ls查看tomcat9容器的内容:

$ docker exec tomcat9 ls -l

果然像官方文档所说的,该目录下有一个webappswebapps.dist文件夹。我们对其进行替换:

$ docker exec tomcat9 rm -rf webapps
$ docker exec tomcat9 mv webapps.dist webapps

在这个容器的"bug"修复完毕后,我们对这个容器重新进行打包。其中,18c15d7c3792是笔者的运行环境中,tomcat9的容器ID。

$ docker commit -a="lijunhu" -m="I have fixed 404 bug" 18c15d7c3792 runnable_tomcat:1.0

在执行完毕后,我们通过docker images命令就能看到打包好的runnable_tomcat镜像了。

我们重新用这个镜像生成容器:

docker run -d -p 10000:8080 --name tomcat_9 runnable_tomcat:1.0

注意,外部访问这个tomcat容器经历了两个过程:首先需要访问Linux虚拟机(Docker的宿主机)的10000端口访问到Docker容器,然后再转接到8080端口访问正在容器内运行的tomcat9服务器。

还有一个小细节:由于我们显式标注了label标记为1.0,所以在创建容器的时候就不能再省略该标签了。(Docker会默认缺省的标签是latest

因此,外部要访问容器内的tomcat服务器,要访问虚拟机的10000端口,而不是8080端口

我们在真实的物理机中访问运行在虚拟机内的Docket容器内的tomcat(注,hadoop102是笔者为虚拟机配置的主机名,并且已经通过hosts文件配置了主机名与ip地址的映射):

hadoop102:10000 

如果暂时没有对hosts文件进行配置,也可以使用IP地址替换。或者选择直接使用虚拟机内的浏览器访问(虚拟机需要安装图形化界面):

localhost:10000

当我们的浏览器显示这个页面时,就说明我们的所有配置步骤都成功了。