#|Docker学习(理论基础之Docker镜像分层 | 容器和镜像的关系 | 写时复制与用时分配)

前言

本讲是从Docker系列讲解课程,单独抽离出来的一个小节,重点介绍:镜像分层的意义,一起来探讨一下吧!
概述 #|Docker学习(理论基础之Docker镜像分层 | 容器和镜像的关系 | 写时复制与用时分配)
文章图片

1.站在镜像分层的角度考虑,什么是镜像?
就是把业务代码+可运行环境、进行整体的打包得到的一个只读文件。每迭代一个版本,打一次包。需要哪个版本,可以直接拉取运行。
再具体点:
镜像是一种轻量级、可执行的独立软件包、用来打包软件运行环境和基于运行环境开发的软件,它包含了某个软件所需的所有内容、包括代码、运行时、库、环境变量和配置文件。
所有的应用、直接打包docker镜像、就可以直接运行使用。
举例说明:
通过docker pull 命令,下载下的tomcat 或 nginx镜像,是不是可以通过docker run 启动后,直接通过浏览器访问,与之配套的资源已经自动构建好。
2.为什么要镜像分层?好处是什么?
一起回顾一个码头的场景:
古代的码头是不是要手挑肩扛,把货物一件件往小船上运?弊端很明显:工人可以多少私吞一点,效率也比较低。
现在呢?都是一个大大的集装箱,一个大大的货轮。好处也很明显:效率高、集装箱货物之间彼此相互不影响。
反思:码头上,每个集装箱,可以把他们理解为单独的一个镜像,Docker是什么?它就是那个大大的货轮。
#|Docker学习(理论基础之Docker镜像分层 | 容器和镜像的关系 | 写时复制与用时分配)
文章图片

上面的货物,有PHP的、Nginx的、Tomcat,多种镜像、(可以在基准镜像上迭代)多个版本(每个版本只需做增量操作),每个镜像都可以拉出来独立运行,相互都不影响,是不是很Nice!
3.如何创建docker镜像 docker官方仓库里面有大量的镜像,可以从中直接pull命令拉取。另外这些镜像都是原厂维护,可以得到及时的更新和修护。
我还可以通过编写Dockerfile,用FROM指定基准镜像后,自定义一些服务,然后重新bulid镜像,最后把它打包成一个自定义的新镜像。
一、现实中的镜像 1.所有镜像都起始于一个基础镜像层 该基础镜像,在Dockerfile中的体现就是FROM关键字后面的内容。
当进行修改或增加新的内容时,就会在当前镜像层之上,创建新的镜像层。无论这个镜像有多少层,对内每层的中间镜像都是各自独立的,对外他们又是一个统一的整体。
#|Docker学习(理论基础之Docker镜像分层 | 容器和镜像的关系 | 写时复制与用时分配)
文章图片

对内是一层一层的(每家每户),对外统一视图(都是中国公民),都是只读的:
#|Docker学习(理论基础之Docker镜像分层 | 容器和镜像的关系 | 写时复制与用时分配)
文章图片

注:图片中的大背景框,就是基准镜像,类似于大轮船。
2.镜像与容器的使用原理 Docker镜像是只读的,不能直接在上面运行服务的,怎么办?这就出现了Container容器。当容器启动时,一个新的可写层加载到镜像的顶部,这一层就是我们通常说的容器层,所有写的操作都是基于容器的,容器之下的都叫镜像层(提供底层支持)。
#|Docker学习(理论基础之Docker镜像分层 | 容器和镜像的关系 | 写时复制与用时分配)
文章图片

3.怎么使用这些镜像 1.我们在Dockerfile里构建了第一层镜像,它的基准镜像FROM: Centos:9.0,其他什么就不做,然后docker build -t myimage/centos:v1 此时它只有第一层。
2.一个新需求,需要在该镜像基础上,添加一个Nginx服务。于是我们就找了一个合适的版本,下载到宿主机内,然后在Dockerfile中,用ADD命令把Nginx添加到容器,其他什么就不做,然后docker build -t myimage/centos:v2 此时它就有两层了。
3.突然发现需要给Nginx服务打个补丁,于是,我们又通过copy命令,把补丁文件添加给容器内部。其他什么都不做,然后docker build -t myimage/centos:v3 此时就有了第三层。
#|Docker学习(理论基础之Docker镜像分层 | 容器和镜像的关系 | 写时复制与用时分配)
文章图片

实际工作中,一个Dockerfile文件,一次性就可以定义很多层,上面是一种精简的表述。
二、镜像层数图解和查看方式 理论上,一个容器的镜像,最多可以有127层,怎么查看镜像的层数?
#|Docker学习(理论基础之Docker镜像分层 | 容器和镜像的关系 | 写时复制与用时分配)
文章图片

通过docker Inspect 命令,寻找RootFS代码片段,在Layers中,有多少行(sha256)就代表镜像有多少层。
docker images #获取镜像id docker inspect hello-world #根据镜像ID,查看镜像详细信息

#|Docker学习(理论基础之Docker镜像分层 | 容器和镜像的关系 | 写时复制与用时分配)
文章图片

"RootFS": { "Type": "layers", "Layers": [ "sha256:e07ee1baac5fae6a26f30cabfe54a36d3402f96afda318fe0a96cec4ca393359" "sha256:e07ss1baac5fae6a26f3dcabfe54a36d3436f96afda318fe0a96cec4cad3e353" ] },

疑问:这些层是怎么来的?
在Dockerfile一般,每一行的添加ADD、COPY、RUN,或者创touch建新的(配置)文件的命令,都会触发新建一层镜像,这层镜像一般会被称为中间层镜像(它类似游戏的存档,可以随时回来玩儿,也类似于一种缓存机制,使得后续镜像可以复用),直接通过docker images命令是看不到的(docker image ls -a 可以看到)。
中间层镜像存在的意义:加速镜像构建、重复利用资源。
注:这些知识点,将在下面实战环节,有详细的介绍,要耐心一点哦!
三、容器/镜像核心技术 1.写时复制: copy on write
a)当修改容器内文件时,只会在容器层保存变化,并不会修改镜像
b)当容器查找文件的时候,首先会在容器层查找,如果容器层没有的话,那么就会从镜像层的最上层,依次向下层镜像中查找。
业务场景:如果即将被修改的文件,仅存在于镜像层,却不存在于容器层时。
文件会从镜像层复制到容器层,修改后会被保存在容器层,这就叫写时复制。
好处:这样不会影响基于该镜像的其他容器。
2.用时分配 业务场景:如果即将被修改的文件,既不存在于镜像层,也不存在于容器层时。
只有该文件被真正写入到容器时,才会给该容器分配存储空间,当容器重启后,会将这些空间释放(创建的这些文件会被删除),当然也可以在重启容器前通过docker commit命令存档,持久化文件。
注:如果你对docker commit与docker build的关系感兴趣,点击进入点击进入(第六章)。
四、实战演示 1.容器构建过程演示
cd test#切换到测试目录 mkdir docker_layer#创建Dockerfile文件的根目录 cd docker_layer #切换到Dockerfile的根目录 vim Dockerfile#创建Dockerfile

1.手写一个Dockerfile,里面只打印内容
注意:上面第二章节,已经提及,RUN/COPY/ADD命令,会创建中间层镜像,下面你即将看到。
FROMnginx:alpine RUN ["echo","aaaa"] RUN ["echo","bbbb"] RUN ["echo","cccc"] RUN ["echo","dddd"]

2.docker build -t构建镜像,版本号1.0
docker build -t succ.com/docker_layer:1.0 .#构建镜像(尾部有个点)

#|Docker学习(理论基础之Docker镜像分层 | 容器和镜像的关系 | 写时复制与用时分配)
文章图片

(图4-2)
3.docker images -a查看镜像和中间层镜像
docker images docker images -a

#|Docker学习(理论基础之Docker镜像分层 | 容器和镜像的关系 | 写时复制与用时分配)
文章图片

(图4-3)
反思1:为什么docker images 可以看到nginx镜像,这个镜像在build之前是不存在的。
答案:我们的基准镜像的FROM nginx:alpine ,所以在build的时候(上图4-2第7行),会自动从nginx远程仓库,拉取nginx对应版本的镜像,该镜像是基准镜像。
疑问:(图4-3)中的succ.com/docker_layer 镜像,又是从哪儿来的?
它是docker build 命令通过Dockerfile文件构建而来,它是nginx镜像为依托,构建而来的镜像。
疑问:(图4-3),通过docker ps -a,看到的中间层镜像又是怎么回事儿?
这一点,在上面的第二章节的尾部,有提及过,此处主要是印证中间层镜像的存在,它存在的意义就是加速镜像构建、重复利用资源,在下面的示例中会进一步演示。
注意:如上图4-3,可以很清晰的看到docker ps 命令默认是看不到中间层镜像的,只有添加了-a参数才可以看到,需要提醒的是中间层镜像不可以随意删除,除非也希望删除镜像本身。
2.容器快照复用演示 至此,在上面的案例中,在succ.com/docker_layer 镜像v1.0版本中,通过docker images -a,已经看到有3个临时的中间层镜像。
下面就继续演示,中间层镜像的复用(后面的镜像会把它们当做缓存,不会再重新创建新的中间层镜像)!
4.重新编辑Dockfile,仅修改后两行
把上面Dockerfile中的内容,进修改最下面两行,具体如下:
FROMnginx:alpine
RUN ["echo","aaaa"]
RUN ["echo","bbbb"]
RUN ["echo","cccc"]
RUN ["echo","dddd"]
修改为 FROMnginx:alpine
RUN ["echo","aaaa"]
RUN ["echo","bbbb"]
RUN ["echo","eeee"]
RUN ["echo","gggg"]
5.docker build -t重新构建镜像,版本号2.0
docker build -t succ.com/docker_layer:2.0 .#构建镜像

#|Docker学习(理论基础之Docker镜像分层 | 容器和镜像的关系 | 写时复制与用时分配)
文章图片

(图4-4)
不知道你对上图中的结果,惊不惊喜,意不意外。
在本次构建2.0镜像的时候,有2个需要注意的地方。
1、使用了缓存;2、又生成了新的中间层镜像,刚才中间层镜像是3个,现在docker images -a ,应该可以看到有所增加。
6.再次查看中间层镜像的个数
#|Docker学习(理论基础之Docker镜像分层 | 容器和镜像的关系 | 写时复制与用时分配)
文章图片

(图4-5)
果然:中间层镜像又增加了1个,在上(图4-3)中看到的是3个。
3.延伸:如何删除中间层镜像 理论上,不建议删除中间层镜像,因为它是对应版本镜像的一部分,删除后会引发一些错误。当对应版本的真实镜像被删除的同时,中间层镜像会随之被删除。
docker rmi b9ba84b655cf#删除V2版本的镜像 docker images -a#可以看到,中间层镜像又只剩下V1版本的3个了

#|Docker学习(理论基础之Docker镜像分层 | 容器和镜像的关系 | 写时复制与用时分配)
文章图片

4.小节
通过docker build v2版本,发现两个现象,1 在V1的基础上构建V2时,使用了V1的中间层镜像缓存;2 因为在V2版本build之前,修改了两行Dockerfile内容,这两行都是RUN命令,由此V2构建成功后,中间层镜像由V1版本的3个,变成了4个,又多了一个。
五、总结
本讲重点讲了镜像的分层,前面三个章节主要是讲述理论和概念部分。第四章节讲实战,以此来验证理论部分。最终我们可以看到了,镜像的分层(V2增量构建在V1基础之上)及其构建方式,只需要修改Dockerfile后重新用docker build命令重新构建即可。
【#|Docker学习(理论基础之Docker镜像分层 | 容器和镜像的关系 | 写时复制与用时分配)】在构建镜像的过程中,后面的镜像会尽可能的利用前面镜像的中间层镜像,做为缓存,来构建新版本的镜像,好处不言而喻了,加速构建、节省资源,新版本镜像属于增量操作(仅需在原镜像基础上,告诉基准镜像自己做了哪些变动)。
注:RUN /ADD /COPY都会触发构建中间层镜像,本讲实战仅演示了RUN命令,剩余的两个,自己查资料,尝试一下吧!提示,可以做一些简单的文件复制(从宿主机到容器内)
尾言
本讲内容是从 Docker入门到进阶里面抽离出来的内容,重点介绍了Docker镜像分层,虽然看起来不是很重要,但是它对于你深刻的去了解Docker,还是很重要的。
如果感觉文章还不错,对你有帮助,动动小手,点个赞吧~~,啦啦啦,啦啦啦~~
附注 你还可能对以下内容感兴趣,一起附注给爱学习的你
1、Linux环境下Docker的快速安装、Windows10+专业版环境下安装Docker
2、如何获取Docker的最新版本 | 如何获取Tomcat/JDK/Nginx指定版本镜像
3、配置阿里云镜像加速器,提高镜像下载速度
4、Dockerfile八大核心命令 | Dockerfile构建自己的镜像
5、Docker容器 | Dockerfile优化
6、Docker容器的生命周期 | kill和stop | pause 和 unpause

    推荐阅读