Docker 学习篇(四)
1、Docker 镜像(image)
(1)、镜像是什么?
镜像是一种轻量级、可执行的独立软件包,用来打包软件运行环境和基于运行环境开发的软件,它包含运行某个软件所需的所有内容,包括代码、运行时所需的环境、库和配置文件。
(2)、UnionFS(联合文件系统,Union File System)
联合文件系统是一种分层、轻量级并且高性能的文件系统,联合文件系统的内部,是一个文件套着另一个文件,很像俄罗斯套娃,如图1所示。
(3)、镜像的分层结构
Docker 镜像的内部结构其实就是一个联合文件系统,是一层一层的。首先我们讲解一下 Docker 中的 base 镜像,base 镜像最内层的是 bootfs(boot file system),稍外层的是 rootfs(root file system),见图2。
bootfs:在 linux/unix 操作系统中,bootfs 主要包含 boot loader(boot加载器) 和 kernel(操作系统内核),boot loader 主要是负责加载 kernel。linux 刚启动时会加载 bootfs 文件系统,bootfs 中的 boot loader 将整个 kernel 加载到内存,此时内存的使用权由 bootfs 转交给 kernel,之后 bootfs 会被卸载掉,从而释放出它占用的内存。
rootfs:rootfs 包含着典型 linux 系统的目录结构,比如 /dev, /proc, /bin, /etc, /lib, /usr, /tmp 等标准目录和文件。
从上面的讲解可以看出,base 镜像其实就是一个最小安装的 Linux 发行版,对于 base 镜像来说,底层直接用宿主机的 kernel 就行(上篇文章讲过,Docker 和宿主机共用内核和内存),只需要根据 Linux 操作系统的不同提供不同的 rootfs 即可。比如 Ubuntu 和 CentOS 的 Linux kernel 差别不大,但是它们的 rootfs 却并不相同,Ubuntu 有它自己的 rootfs, CentOS 也有它自己的rootfs,见图3。
(4)、为什么 Docker 镜像要采用这种分层结构呢?
Docker 镜像采用分层结构,最大的好处就是能够实现资源共享,节省资源开销。
有多个镜像都是从 base 镜像构建而来,那么宿主机磁盘上只需保存一份 base 镜像,同时内存也只需加载一份 base 镜像,就可以为所有容器服务了。
举个栗子:比如我现在用 CentOS 操作系统的 ISO 做了一个 rootfs,然后又在里面安装了Java环境(JDK),用来部署我的应用 A。此时,我的另一个同事在发布他的应用 B 时,希望能够直接使用我安装过 Java 环境的rootfs,而不是重复这个流程,镜像的分层结构就能够实现这个需求。
(5)、使用几个例子来验证镜像是分层的
(a)docker pull redis,见图 5 的下载过程,我们可以看到镜像是一层一层下载的。
(b)平时我们在 windows 上安装的 centos 都是好几个G,为什么我们通过docker pull centos 从阿里云上拉取的 centos 镜像才200多兆(见图6)?
原因如下,对于不同的 linux 发行版本,kernel 差别不大,可以共用,主要区别是 rootfs。我们是在 centos 虚拟机上做的演示,本地已经安装过 centos,即本地已经有 kernel 了。通过 docker pull centos 下载镜像时,centos 镜像可以和本地的 centos 共用 kernel,所以我们不需要下载 bootfs,只下载 rootfs 就可以了。即通过 docker pull centos 下载镜像时,实际上只下载 rootfs,因此 docker 下载的 centos 镜像只有200M。
当我们下载 ubuntu镜像,也是此理,因为 ubuntu 的 kernel 和 centos 类似,ubuntu 镜像的 bootfs 可以使用本地 centos 的 kernel。
(c)平时我们在windows环境安装tomcat只有几十兆,但是我们通过docker pull tomcat拉去下来的tomcat镜像足足有几百兆,这是怎么回事?
原因如下,tomcat 运行时需要 jdk,所以 tomcat 镜像里面一定要包括 jdk,此外 tomcat 有自己的内核,不能和本机的 centos 共用 kernel(对于不同的镜像,bootfs 基本是一致的,注意这是基本,不要以为 docker 中所有的镜像都是和宿主机共用内核的,也有 tomcat 这种例外,无法共用,必须自己生成自己的内核),所以 tomcat 镜像里除了 rootfs,还需要包括 bootfs (包含 kernel ),所以才这么大。
2、Docker 容器(container)
容器(container)的定义和镜像(image)几乎一模一样,也是一堆层的统一视角,唯一区别在于容器的最上面那一层是可读可写的。
容器 = 镜像(可读) + 可读可写层,综合图10和图11,从里层到外层分别是 base image,其他 image,最外层加上一个可读可写层,组成最终的 container。
3、docker commit,根据容器创建镜像
(1)docker commit 命令
根据容器创建镜像,并提交至仓库,具体命令如下:docker commit -m="提交的描述信息" -a="作者" 容器ID [REPOSITORY[:TAG]]。
比如我们从仓库下载了 tomcat 镜像,把 tomcat 镜像运行起来,运行起来的 tomcat 就是一个容器,我们在当前这个容器上做一些自定义和调整,然后使用上述命令对这个容器执行 commit 操作,就会形成一个新的镜像,并提交至仓库。
类似于你买了一辆车(原始的tomcat镜像),这辆车原来并没有导航雷达,我们回来对车进行了重新组装,安上了导航雷达,形成一辆新的车(新的tomcat镜像)。
(2)演示 docker commit 操作
(a)docker pull tomcat,从仓库下载 tomcat 镜像。
(b)运行 tomcat 镜像,生成 tomcat 容器,docker run -it -p 8888(docker对外暴漏的端口):8080(tomcat端口) tomcat。
我们看 tomcat 启动日志,出现如图14的 startup 日志,表示 tomcat 启动成功。
或者在本机的centos上访问 localhost:8888,出现 tomcat 那只猫,也表示 tomcat 启动成功,见图15。
(c)接下来我们修改上一步生成的 tomcat 容器。我们删除 tomcat 容器里面的官方说明文档(即 tomcat 目录中的 docs 文件夹,也即 tomcat运行界面中的 Documentation 模块,见图16),生成一个新的容器。
具体步骤如下所示:
第一步:获取生成的 tomcat 容器的ID,docker ps,见图 15,我们可以知道 tomcat 容器的容器 ID 为 1f19ddcbddf6。
第二步:进入到这个 tomcat 容器中,并删除 docs 文件夹。
1、docker exec -it 1f19ddcbddf6 /bin/bash,docker exec -it 容器ID baseShell,使用该命令可以进入正在运行的容器。
2、pwd,显示/usr/local/tomcat。
3、ls -l,显示tomcat目录里的各种文件夹,包括webapps。
4、cd webapps.dist,ls -l,cd 到 webapps.dist 目录,ls 发现 webapps.dist 目录中有一个 docs 文件夹,我们把它删除掉。
5、rm -rf docs,删除掉 docs 文件夹。
此时我们访问 localhost:8888,点击 Documentation,报错 404 Not Found(如果第一次没有变得话,说明有缓存,我们把缓存清一下,再点击)。
(d)当前的 tomcat 容器是一个没有文档内容的容器,以它为模板 commit 一个没有 docs 的tomcat 新镜像 zhouxuejiao/tomcat02。
docker commit -a="zhouxuejiao" -m="del tomcat docs" 1f19ddcbddf6 zhouxuejiao/tomcat02:1.2,使用这个命令,根据我们修改后的容器,会生成一个新的镜像。
docker images,使用这个命令查看所有镜像,发现有我们要生成的新镜像 zhouxuejiao/tomcat02。
(e)验证新镜像是否创建成功。
当我们运行新的 tomcat 镜像,并且在 tomcat 运行界面中点击 Documentation 时会报 404 错误的话,说明我们创建的这个新镜像成功了。
docker run -it -p 7777:8080 453122812ee3(新镜像的镜像ID),运行新的镜像。
访问ocalhost:7777,点击 Documentation,报错 404 Not Found,说明我们的新镜像创建成功啦,见图23、24。
今天中秋,祝大家中秋快乐啊!记得吃月饼哦