Docker镜像浅谈
先抛出几个我在学习过程中产生的几个问题.
1. 容器镜像是什么, 和装系统时的镜像有什么关系?
2. 容器镜像的作用是什么?
3. 不同版本的ubuntu镜像有什么区别, 比如说 ubuntu:18.04和ubuntn:16.04 的区别?
4. Docker镜像为什么要分层? 每层都包含什么? 什么时候会创建新的一层 ? 每层的权限是如何配置的? 只有最底层的镜像层是只读的吗?1 Docker镜像之初见
1.1 什么是镜像
使用docker pull
可以看到我们仅仅是pull了一个ubuntu18.04的镜像, docker pull的结果却出现了多行, 这是因为镜像是分成多层的. 具体为什么要分层? 每一层又是什么? 下文我们会谈到.
由于docker 18.04采用的是overlayfs2作为默认的存储驱动, 所以下载的镜像都存储在/var/lib/docker/overlay2 中.
可以看到这里的目录个数和镜像层数是一致. 并且除了0c开头的那一层, 其他目录都是有diff和work两个子目录 . 0c开头的目录就是整个镜像最底层的只读层
l目录下全部都是指向这些diff目录的软链接.让我们进入0c.../diff中看看, 可以看到在diff目录中是一套完整的根目录. 所以说本质上, 根目录就是一个rootfs.
跟装系统时的镜像没有一点点关系.
1.2 构建Docker镜像
本节来说说如何使用, 使用Dockerfile构建自己的镜像, dockfile由一系列的命令构成。我目前用过的有FROM RUN CMD ADD COPY命令。
From用来制定一个基础镜像层。
RUN命令会在一个新的镜像层上执行参数中所指定的任何命令, 并commit命令的执行结果。
CMD命令在我初次使用时, 与RUN有所混淆. 但是其实二者是完全不同的, CMD只是指定了运行容器时的初始命令, 但是在镜像构建时没有commit任何命令的结果。
举例来说, 下图是一个Dockerfile. 在其中我们先在一个新的镜像层创建一个shell脚本, 然后在CMD中运行这个脚本。
可以看到容器执行CMD中指定的命令.
2 镜像的特性
2.1 分层特性
在我们使用docker run命令启动一个容器时, 就会在现有的只读镜像层之上在创建一个可读写的容器层. 当我们将这些容器删除时, 这些容器层也会被删除.
但是容器层之下的镜像层却不会改变.
图盗自(docker reference)
通过这种共享相同的镜像层的方式, 容器即实现了一定的隔离(容器层之间), 又节省了空间.
2.2 写时拷贝(COW)
如果容器需要读底层的文件或者目录, 那么直接读取底层的文件. 但是如果容器需要修改底层的文件, 那么容器层就会拷贝一个底层的文件.
虽说都是COW策略, 但是实现起来, 不同的存储驱动也是有所不同的.
以overlayfs/overlayfs2为例:
1. 搜索所有的镜像层, 找到要修改的文件, 这个过程从最新的一层直到最底层. 另外搜索结果会放到缓存中, 加速下一次的查找。
2. 执行copy_up操作, 将文件拷贝到容器层
3. 修改文件的拷贝, 同时底层的文件对于容器层是不可见的.
只说了修改和读取, 那么当容器层要删除一个镜像层的文件时, 会发生什么呢? 让我们看看。
先直接在底层镜像层中创建一个文件, 还记得上文中说到的那个只有diff目录和link文件的目录吗?那个就是最底层的镜像层。
我们直接在diff/tmp/目录中创建一个文件, 文件名为thisIsAFileCreateImageLayer
然后运行一个容器, 进入tmp目录, 可以看到这个文件对于容器是可见的.
在容器层删除该文件
在镜像层看看
由上图可以看到在镜像层, 该文件是依然存在的, 这是因为在容器层, 在容器层删除文件或者目录
时, overlayfs采用了whiteouts和opaque技术.
Whiteouts是一个设备号为0/0的字符设备文件. 当上层目录中发现一个whiteout文件时, 在上层目录中读取底层的同名的文件时, 底层的文件就会被忽略.
如下图所示, 在新创建的容器层中我们可以看到一个设备号为0,0的字符设备文件.
接下来我来尝试解答一下我之前的疑问:
其实前两个问题已经得到了解答, 先说说第三个问题
3. 不同版本的ubuntu镜像有什么区别, 比如说 ubuntu:18.04和ubuntn:16.04 的区别?
不同版本的ubuntu的区别我目前知道的区别是软件库的版本不同.
4. Docker镜像为什么要分层? 每层都包含什么? 什么时候会创建新的一层 ? 每层的权限是如何配置的? 只有最底层的镜像层是只读的吗? 分层实现了空间最小化的隔离, 在运行一个新的容器时会创建一个新的读写层, 在使用Dockerfile构建新的镜像时会在已有的镜像层的基础上创建新的一层.