关于 Docker 系列其他文章的传送门
-
《 关于 Docker 中镜像与存储卷的概念和详细使用方法 》 您当前所在位置

一、聊聊 Docker 镜像¶
容器技术的火热,离不开 docker 的推动,但是容器技术并不是 docker 公司发明的,这家公司最大的创新其实是镜像技术,而镜像技术其实早就已经存在,但是 docker 却创造了一种叫分层镜像的机制,这种分层镜像技术,就好像一把助燃剂,让容器技术如干柴烈火般的燃烧起来
在 docker 出现之前,人们想要使用容器,需要操作 namespace、cgroup、lxc 等机制,过程及其繁琐,而且对比虚拟机来说,安装速度并没有多大优势,而 docker 出现后,人们只需要从指定的镜像仓库下载镜像,在本地启动即可使用容器
这种镜像非常精简,只有程序本身和其依赖的文件,启动速度极快,究其根本还是因为镜像分层的技术加持,那什么是镜像分层呢?
镜像分层的原理¶
docker分层¶
什么是分层呢,如下图所示:

一个 docker 镜像由多个可读的镜像层组成,然后运行的容器会在这个 docker 的镜像上面多加一层可写的容器层,任何的对文件的更改都只存在此容器层。因此任何对容器的操作均不会影响到镜像
如何实现¶
至于容器如何获取镜像层文件而又不影响到是镜像层的呢? docker 是这样实现的…
如果需要获取某个文件,那么容器曾会从上到下去下一层的镜像层去获取文件,如果该层文件不存在,那么就会去下一镜像层去寻找,直到最后一层。 对于用户而言,用户面向的是一个叠加后的文件系统

docker 就是利用之前提到过的一个叫做 copy-on-write (CoW) 的策略来保证 base 镜像的安全性,以及更高的性能和空间利用率。,而任何对于文件的操作都会记录在容器层,例如说修改文件,容器层会把在镜像层找到的文件拷贝到容器层然后进行修改,删除文件则会在容器层内记录删除文件的记录

联合文件系统(UnionFS)¶
分层镜像这种特殊的机制,构建于一个叫联合文件系统(UnionFS)之上,这是一种分层、轻量级并且高性能的文件系统,它支持对文件系统的修改作为一次提交来一层层的叠加,同时可以将不同目录挂载到同一个虚拟文件系统下 (unite several directories into a single virtual filesystem)
联合文件系统是 Docker 镜像的基础。镜像可以通过分层来进行继承,基于镜像(base 镜像),可以制作各种具体的应用镜像
另外,不同 Docker 容器就可以共享一些基础的文件系统层,同时再加上自己独有的改动层,大大提高了存储的效率
Docker 中使用的 AUFS(AnotherUnionFS)就是一种联合文件系统。 AUFS 支持为每一个成员目录(类似 Git 的分支)设定只读(readonly)、读写(readwrite)和写出(whiteout-able)权限, 同时 AUFS 里有一个类似分层的概念, 对只读权限的分支可以逻辑上进行增量地修改 (不影响只读部分的)
Docker 目前支持的联合文件系统包括 OverlayFS, AUFS, Btrfs, VFS, ZFS 和 Device Mapper
各 Linux 发行版 Docker 推荐使用的存储驱动如下表。
| Linux 发行版 | Docker 推荐使用的存储驱动 |
|---|---|
| Docker CE on Ubuntu | overlay2 (16.04 +) |
| Docker CE on Debian | overlay2 (Debian Stretch), aufs, devicemapper |
| Docker CE on CentOS | overlay2 |
| Docker CE on Fedora | overlay2 |
在可能的情况下,推荐使用 overlay2 存储驱动,overlay2 是目前 Docker 默认的存储驱动,以前则是 aufs。你可以通过配置来使用以上提到的其他类型的存储驱动
关于 base 镜像¶
base 镜像有两层含义:
- 不依赖其他镜像,从 scratch 构建
- 其他镜像可以之为基础进行扩展
所以,能称作 base 镜像的通常都是各种 Linux 发行版的 Docker 镜像,比如 Ubuntu, Debian, CentOS 等
base 镜像提供的是最小安装的 Linux 发行版
我们大部分镜像都将是基于 base 镜像构建的。所以,通常使用的是官方发布的 base 镜像。可以在 docker hub 里找到
比如 centos: https://hub.docker.com/_/centos
我们可以自己构建 docker base 镜像,也可以直接使用已有的 base 镜像。比如 centos, 我们可以直接从 docker hub 上拉取
拉取:
1 | docker pull centos |
查看:
1 | docker images centos |
可以看到最新的 centos 镜像只有 200mb ,是不是觉得太小了?这是因为 docker 镜像在运行的时候直接使用 docker 宿主机器的 kernel
众所周知:
Linux 操作系统由 内核空间 和 用户空间 组成
内核空间是 kernel ,用户空间是 rootfs ,不同 Linux 发行版的区别主要是 rootfs
比如 Ubuntu 14.04 使用 upstart 管理服务,apt 管理软件包;而 CentOS 7 使用 systemd 和 yum。这些都是用户空间上的区别,Linux kernel 差别不大
所以 Docker 可以同时支持多种 Linux 镜像,模拟出多种操作系统环境
综述¶
可能会有人问为什么要这么去做呢?我觉得有两大好处:
- 基本上每个软件都是基于某个镜像去运行的,因此一旦某个底层环境出了问题,就不需要去修改全部基于该镜像的软件的镜像,只需要修改底层环境的镜像
- 这个好处也是最大好处,就是可以共享资源,其他相同环境的软件镜像都共同去享用同一个环境镜像,而不需要每个软件镜像要去创建一个底层环境
镜像的使用¶
常用参数¶
1 | image: # 镜像类分组 |
示例:
1 | docker container commit -p -a "monster <admin@monster.com>" -c 'CMD ["/bin/sh","-c","/bin/sh"]' centos_monster centos:sh |
搜索镜像¶
下载镜像时,可以使用官方的镜像仓库 Docker Hub,或者国内的镜像加速器,如阿里云(需要注册账号)、DaoCloud等
这张图是关于 DockerHub 上镜像的说明


也可以使用 docker 客户端搜索
1 | 语法 |

下载镜像¶
使用参数 pull

查看本地已有镜像:
1 | root@node1 ~] docker images |
更改镜像标签¶
1 | root@node1 [10:35:10 PM] [~] -> docker image tag centos:7 centos:monster #把 centos:7 改成 centos:monster |
指定镜像启动容器¶
优先使用本地镜像,没有的话从指定互联网镜像仓库下载到本地并启动
这里使用刚才改好的镜像
1 | #容器名 #镜像名 |
打包镜像¶
1. 做记号¶
在刚才启动的容器内创建 11 到 20 的文件夹做记号
1 | [root@49364c3bf90e /] mkdir {11..20} |
2. 使用快捷键剥离容器 shell¶
ctrl + p+ctrl + q
3. 查看容器的状态是 UP¶
1 | root@node1 [10:45:18 PM] [~] -> docker ps -a |
4. 把容器打包成镜像¶
1 | docker container commit -p -a "monster <admin@monster.com>" -c 'CMD ["/bin/sh","-c","/bin/sh"]' centos_monster centos:sh |
5. 查看镜像¶
刚才打包的镜像 centos:sh
1 | root@node1 [11:11:36 PM] [~] -> docker images |
6. 启动容器¶
指定从 centos:sh 启动容器
发现 shell 已经不是 bash 了,之前创建的 11 到 20 文件夹也存在,说明是刚才制作的镜像
1 | root@node1 [11:11:39 PM] [~] -> docker run -it --rm centos:sh |
上传镜像到 DockerHub¶
我们把刚才修改过的镜像上传到 DockerHub 仓库,需要先注册
如果是阿里云的话,方法类似,但是上传时候的地址是私有的,登录阿里云官网查看详细说明(需注册)
1. 创建一个仓库¶

2. 更改镜像的 TAG¶
必须是这种格式 HubID/IMAGENAME:TAG
1 | root@node1 [11:52:13 PM] [~] -> docker image tag centos:sh monster0303/centos:sh |
3. 登录 DockerHub¶
1 | root@node1 [11:57:24 PM] [~] -> docker login |
4. 上传镜像¶
1 | root@node1 [11:59:05 PM] [~] -> docker push monster0303/centos:sh |
5. 查看¶
从 DockerHub 上已经可以看到了,大功告成!!

二、存储卷的引入¶
1. 为什么有存储卷¶
Docker 的镜像由多个只读层叠加而成,启动容器时,Docker 会加载只读镜像层并在镜像栈顶部添加一个读写层
如果运行中的容器修改了现有的一个已经存在的文件,那该文件将会从读写层下面的只读层复制到读写层,该文件的只读版本仍然存在,只是已经被读写层中该文件的副本所隐藏,此即 写时复制 (COW) 机制
关闭并重启容器,其数据不受影响,但删除 Docker 容器,则其更改将会全部丢失
这样存在的问题:
- 存储于联合文件系统中,不易于宿主机访问;
- 容器间数据共享不便
- 删除容器其数据会丢失
- 无法迁移
2. 解决方案¶
既然镜像存在这么严重的问题,那在生产环境怎么保证数据的稳定性呢?以后用编排系统动态管理容器时怎么办呢?
解决方案就是:卷( volume )
卷是容器上的一个或多个 目录,此类目录可绕过联合文件系统,与宿主机上的某目录 绑定 (关联)

Docker 有两种类型的卷,每种类型都在容器中存在一个挂载点,但其在宿主机上的位置有所不同
| 类型 | 含义 | 区别 |
|---|---|---|
| Bind mount volume | 指向主机文件系统上用户指定位置的卷 | 在宿主机上的目录是用户指定的,在容器上的目录也是指定的 |
| Docker-managed volume | Docker 守护进程在主机文件系统中 Docker 拥有的一部分中创建托管卷 | 容器上的目录是用户指定的,但是在宿主机上的目录是在固定的目录下自动生成的 |
3. 卷参数¶
1 | docker run |
4. 示例¶
先创建容器 c1,指定容器内目录挂载点为 /www_data,宿主机目录随机
1 | root@node1 [06:47:35 PM] [~] -> docker run --name c1 -it --volume /www_data centos |
查看容器内 / 目录下确实创建了 www_data 目录
1 | [root@862a1d8070e3 /]# ls / |
接下来在挂载目录中写一个 test.txt 文件
1 | [root@862a1d8070e3 /]# echo "test page " > www_data/test.txt |
回到宿主机,查看挂载点在哪个目录下
1 | root@node1 [06:51:38 PM] [~] -> docker container inspect -f {{.Mounts}} c1 |
在挂载点内,就能看到容器内创建的 test.txt 文件了
1 | root@node1 [06:47:35 PM] [~] -> cat /var/lib/docker/volumes/e9d29b27d26fd5a7573b0da088ac42ad9d80e2742c5ecee2519e8a12980d852f/_data/test.txt |
指定宿主机挂载目录也是类似的效果,而 “从其他容器克隆卷” 的选项,就是会从指定的容器卷克隆一份出来作为自己的卷用,可以理解成虚拟机里的模板,从模板创建虚拟机类似
5. jq¶
Docker 中的信息大部分是使用 JONS 格式存储,不方便阅读,比如刚才查看 Mounts 时,阅读体验并不友好,为了便于阅读,可以使用 jq 格式化输出
jq 格式化输出 JONS 的文档
1 | jq '.' # 不做任何更改,直接美化输出 |
需要注意的是,jq 不支持过滤后的 JONS,可能因为无法判断语法了
所以可以先格式化输出 再过滤
1 | root@node1 [06:47:35 PM] [~] -> docker inspect 862a1d8070e3 | jq '.'| grep -i mounts -A 10 |
参考文章
https://yeasy.gitbooks.io/docker_practice/underly/ufs.html
http://blog.sbb.fun/blog/docker/docker3.html
https://www.cnblogs.com/woshimrf/p/docker-container-lawyer.html
https://www.itcodemonkey.com/article/13255.html