【Docker篇】Docker镜像加载原理,UnionFS(联合文件系统),镜像Commit

Docker镜像

1. 镜像是什么

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

举个例子:

你开发了一款软件,假设叫做app,这个app运行起来需要jdk,tomcat等。那么镜像会将软件运行环境(jdk,tomcat)和基于运行环境开发的软件(app)同时打包。

在这里插入图片描述

2.Docker镜像加载原理

在聊Docker镜像加载原理之前,我们首先要了解什么是UnionFS。什么是UnionFS呢?其实就是联合文件系统。

2.1 UnionFS(联合文件系统)

UnionFS(联合文件系统):Union文件系统(UnionFS)是一种分层、轻量级并且高性能的文件系统,它支持对文件系统的修改作为一次提交来一层层的叠加,同时可以将不同目录挂载到同一个虚拟文件系统下(unite several directories into a single virtual filesystem)。Union 文件系统是 Docker 镜像的基础。镜像可以通过分层来进行继承,基于基础镜像(没有父镜像),可以制作各种具体的应用镜像。

特性:一次同时加载多个文件系统,但从外面看起来,只能看到一个文件系统,联合加载会把各层文件系统叠加起来,这样最终的文件系统会包含所有底层的文件和目录

在这里插入图片描述

有多少人是这个表情?别着急,这里你只要知道有这么个文件系统,而且在Docker中使用到了就行,继续往下看。

2.2 Docker镜像加载原理

docker的镜像实际上由一层一层的文件系统组成,这种层级的文件系统通过UnionFS构成了一个完整的镜像。

bootfs(boot file system)主要包含bootloader和kernel,bootloader主要是引导加载kernel,Linux刚启动时会加载bootfs文件系统,在Docker镜像的最底层是bootfs。这一层与我们典型的Linux/Unix系统是一样的,包含boot加载器和内核。当boot加载完成之后整个内核就都在内存中了,此时内存的使用权已由bootfs转交给内核,此时系统也会卸载bootfs。

rootfs (root file system) ,在bootfs之上。包含的就是典型 Linux 系统中的 /dev, /proc, /bin, /etc 等标准目录和文件。rootfs就是各种不同的操作系统发行版,比如Ubuntu,Centos等等。

在这里插入图片描述
在这里插入图片描述

啥意思呢?其实就是我们看到的镜像只包含了rootfs的部分,bootfs的部分只是作为底层的引导作用,在镜像生成完会被卸载,并不存在于镜像中。这也就是为啥我们使用Docker制作的Centos镜像只要几百兆,而使用虚拟机安装的Centos需要好几个G。
在这里插入图片描述
在这里插入图片描述

对于一个精简的OS,rootfs 可以很小,只需要包含最基本的命令,工具和程序库就可以了,因为底层直接用Host的kernel,自己只需要提供rootfs就可以了。由此可见对于不同的linux发行版,bootfs基本是一致的,rootfs会有差别,因此不同的发行版可以公用bootfs。

3. 分层理解

我们这里下载一个tomcat镜像来看一下。

[root@jiangnan ~]# docker pull tomcat
Using default tag: latest
latest: Pulling from library/tomcat
0e29546d541c: Pull complete 
9b829c73b52b: Pull complete 
cb5b7ae36172: Pull complete 
6494e4811622: Pull complete 
668f6fcc5fa5: Pull complete 
dc120c3e0290: Pull complete 
8f7c0eebb7b1: Pull complete 
77b694f83996: Pull complete 
0f611256ec3a: Pull complete 
4f25def12f23: Pull complete 
Digest: sha256:9dee185c3b161cdfede1f5e35e8b56ebc9de88ed3a79526939701f3537a52324
Status: Downloaded newer image for tomcat:latest
docker.io/library/tomcat:latest
[root@jiangnan ~]# 

在这里插入图片描述

我们发现,对于我们来说,tomcat不是完整的吗,但是当我们下载镜像的时候它是一层一层下载的。

刚才我们使用了docker pull tomcat默认下载的就是最新版本,我们现在再来下载一个指定版本,比如8.5

[root@jiangnan ~]# docker pull tomcat:8.5
8.5: Pulling from library/tomcat
0e29546d541c: Already exists 
9b829c73b52b: Already exists 
cb5b7ae36172: Already exists 
6494e4811622: Already exists 
668f6fcc5fa5: Already exists 
dc120c3e0290: Already exists 
8f7c0eebb7b1: Already exists 
77b694f83996: Already exists 
b7c26350ecc2: Pull complete 
7365b3b02e1b: Pull complete 
Digest: sha256:421c2a2c73f3e339c787beaacde0f7bbc30bba957ec653d41a77d08144c6a028
Status: Downloaded newer image for tomcat:8.5
docker.io/library/tomcat:8.5
[root@jiangnan ~]# 

这里我们发现也是使用了分层下载,但是第一次下载,每一层都是Pull complete,而第二次下载有的层却是Already exists,Already exists就是说这一层已经有了,不需要下载了,我只下载了没有的层。

思考:为什么Docker镜像要采用这种分层的结构呢?

从两次下载tomcat我们可以看到,对于已经存在的层就没有必要再次下载了,而是实现了资源的共享。我们可以引申为所有的镜像都是如此,多个镜像都从相同的Base镜像构建而来,那么宿主机只需在磁盘上保留一份base镜像,同时内存中也只需要加载一份base镜像,这样就可以为所有的容器服务了,而且镜像的每一层都可以被共享。

当然,我们通过docker inspect 可以看得更加清晰,主要看layer(层)部分

[root@jiangnan ~]# docker image inspect tomcat:latest

"RootFS": {
    
    
            "Type": "layers",
            "Layers": [
                "sha256:11936051f93baf5a4fb090a8fa0999309b8173556f7826598e235e8a82127bce",
                "sha256:31892cc314cb1993ba1b8eb5f3002c4e9f099a9237af0d03d1893c6fcc559aab",
                "sha256:8bf42db0de72f74f4ef0c1d1743f5d54efc3491ee38f4af6d914a6032148b78e",
                "sha256:26a504e63be4c63395f216d70b1b8af52263a5289908df8e96a0e7c840813adc",
                "sha256:f9e18e59a5651609a1503ac17dcfc05856b5bea21e41595828471f02ad56a225",
                "sha256:832e177bb5008934e2f5ed723247c04e1dd220d59a90ce32000b7c22bd9d9b54",
                "sha256:3bb5258f46d2a511ddca2a4ec8f9091d676a116830a7f336815f02c4b34dbb23",
                "sha256:59c516e5b6fafa2e6b63d76492702371ca008ade6e37d931089fe368385041a0",
                "sha256:bd2befca2f7ef51f03b757caab549cc040a36143f3b7e3dab94fb308322f2953",
                "sha256:3e2ed6847c7a081bd90ab8805efcb39a2933a807627eb7a4016728f881430f5f"
            ]
        }
[root@jiangnan ~]# docker image inspect tomcat:8.5

"RootFS": {
    
    
            "Type": "layers",
            "Layers": [
                "sha256:11936051f93baf5a4fb090a8fa0999309b8173556f7826598e235e8a82127bce",
                "sha256:31892cc314cb1993ba1b8eb5f3002c4e9f099a9237af0d03d1893c6fcc559aab",
                "sha256:8bf42db0de72f74f4ef0c1d1743f5d54efc3491ee38f4af6d914a6032148b78e",
                "sha256:26a504e63be4c63395f216d70b1b8af52263a5289908df8e96a0e7c840813adc",
                "sha256:f9e18e59a5651609a1503ac17dcfc05856b5bea21e41595828471f02ad56a225",
                "sha256:832e177bb5008934e2f5ed723247c04e1dd220d59a90ce32000b7c22bd9d9b54",
                "sha256:3bb5258f46d2a511ddca2a4ec8f9091d676a116830a7f336815f02c4b34dbb23",
                "sha256:59c516e5b6fafa2e6b63d76492702371ca008ade6e37d931089fe368385041a0",
                "sha256:af88ee51802bd5af6bdd8d04fba5cc1766d66fa35daa759d834145e3e205a285",
                "sha256:534054ff942e53faa42e4df2a49e80cc8cb3473d90657446624b10598c8da8c1"
            ]
        }

发现他们的layer(层)确实有些是相同的。

3.1 引申理解

所有的 Docker 镜像都起始于一个基础镜像层,当进行修改或增加新的内容时,就会在当前镜像层之上,创建新的镜像层。

举一个简单的例子,假如基于 Ubuntu Linux 16.04 创建一个新的镜像,这就是新镜像的第一层;如果在该镜像中添加 Python包,就会在基础镜像层之上创建第二个镜像层;如果继续添加一个安全补丁,就会创建第三个镜像层。

该镜像当前已经包含 3 个镜像层,如下图所示。

在这里插入图片描述

在添加额外的镜像层的同时,镜像始终保持是当前所有镜像的组合,理解这一点非常重要。下图中举了一个简单的例子,每个镜像层包含 3 个文件,而镜像包含了来自两个镜像层的 6 个文件。

在这里插入图片描述

上图中的镜像层跟之前图中的略有区别,主要目的是便于展示文件。

下图中展示了一个稍微复杂的三层镜像,在外部看来整个镜像只有 6 个文件,这是因为最上层中的文件
7 是文件 5 的一个更新版本。

在这里插入图片描述

这种情况下,上层镜像层中的文件覆盖了底层镜像层中的文件。这样就使得文件的更新版本作为一个新镜像层添加到镜像当中。

下图展示了与系统显示相同的三层镜像。所有镜像层堆叠并合并,对外提供统一的视图。

在这里插入图片描述
在这里插入图片描述

说白了就是:镜像是分层的,从基础镜像开始,一层一层叠加起来的。上一层始终是以下面的层为基础,下面的层相对于上面的层就是一个完整的镜像,我们所有的操作都是在最上面的一层进行。在外面看来你是一个镜像,但是在内是多个层(镜像)叠加起来的。比如我的一个镜像有3层,制作的时候从第1层开始有了第2层,这两层合起来供第三层使用,那对于第3层来说,第1、2层合起来是不是就是一个完整的镜像呢?但是这3层合起来相对于第4层(假如有)就又是一个完整的镜像。以此类推,都是一样的。

又点绕,后面我们在制作镜像的时候在给大家细说一下。

这样可以得到镜像的一个特点

Docker镜像都是只读的,当容器启动时,一个新的可写层被加载到镜像的顶部!
这一层就是我们通常说的容器层,容器之下的都叫镜像层!

容器和镜像的关系,我在前面的博客中有讲到,可以去看看。

4. 镜像Commit

docker commit 从容器创建一个新的镜像。

我们知道了,运行态的镜像叫做容器,我们可以在里面做一些修改,添加,删除等操作。做完操作之后,我觉得这是一个很好的模板,我下次可以直接拿来使用,怎么做呢,使用docker commit将当前容器创建一个镜像出来,下次要使用的时候直接使用即可。

举例:

前面我们下载了一个tomcat镜像,但是当我运行起来之后,页面显示404,这不是不能访问,而是没有资源,什么意思呢?默认的tomcat中webapps中是空的。

[root@jiangnan ~]# docker run -it -p 8081:8080 tomcat:latest
Using CATALINA_BASE:   /usr/local/tomcat
Using CATALINA_HOME:   /usr/local/tomcat
Using CATALINA_TMPDIR: /usr/local/tomcat/temp
Using JRE_HOME:        /usr/local/openjdk-11
Using CLASSPATH:       /usr/local/tomcat/bin/bootstrap.jar:/usr/local/tomcat/bin/tomcat-juli.jar
Using CATALINA_OPTS:   
NOTE: Picked up JDK_JAVA_OPTIONS:  --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.io=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.base/java.util.concurrent=ALL-UNNAMED --add-opens=java.rmi/sun.rmi.transport=ALL-UNNAMED
21-Feb-2022 15:11:00.005 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Server version name:   Apache Tomcat/10.0.14
21-Feb-2022 15:11:00.029 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Server built:          Dec 2 2021 22:01:36 UTC
21-Feb-2022 15:11:00.029 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Server version number: 10.0.14.0
21-Feb-2022 15:11:00.029 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log OS Name:               Linux
21-Feb-2022 15:11:00.029 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log OS Version:            3.10.0-1127.19.1.el7.x86_64
21-Feb-2022 15:11:00.029 INFO [main]
# webapps下是空的,肯定包404
[root@jiangnan ~]# docker ps
CONTAINER ID   IMAGE           COMMAND             CREATED          STATUS          PORTS                                       NAMES
677a42e9efab   tomcat:latest   "catalina.sh run"   21 seconds ago   Up 21 seconds   0.0.0.0:8081->8080/tcp, :::8081->8080/tcp   strange_ptolemy
[root@jiangnan ~]# docker exec -it 677a42e9efab /bin/bash
root@677a42e9efab:/usr/local/tomcat# ls
BUILDING.txt	 LICENSE  README.md	 RUNNING.txt  conf  logs	    temp     webapps.dist
CONTRIBUTING.md  NOTICE   RELEASE-NOTES  bin	      lib   native-jni-lib  webapps  work
root@677a42e9efab:/usr/local/tomcat# cd webapps
root@677a42e9efab:/usr/local/tomcat/webapps# ls
root@677a42e9efab:/usr/local/tomcat/webapps# 

在这里插入图片描述
在这里插入图片描述

为了避免再报404,我们将webapps.dist下的文件复制到webapps下,再次访问。

在这里插入图片描述

这不就完美了,为了避免404的尴尬,我想把我现在这个tomcat提交成一个新的镜像,下次直接使用不就好了,于是我用了docker commit

语法:

docker commit -a="作者名" -m="备注信息" 容器id 新的镜像名:版本

这里注意镜像的名字不能有大写,否则报错:invalid reference format

# 先试用ctrl + p + q退出,其实后台还在运行
docker commit -a="jiangnan" -m="myTomcat" 677a42e9efab tomcat02:1.1
[root@jiangnan ~]# docker ps
CONTAINER ID   IMAGE           COMMAND             CREATED         STATUS         PORTS                                       NAMES
677a42e9efab   tomcat:latest   "catalina.sh run"   5 minutes ago   Up 5 minutes   0.0.0.0:8081->8080/tcp, :::8081->8080/tcp   strange_ptolemy
[root@jiangnan ~]# docker commit -a="jiangnan" -m="myTomcat" 677a42e9efab tomcat02:1.1
sha256:b4249b680e8da80841a52bcc8360211cf38652aaa20d5d64dd5bcfec581d60c9
[root@jiangnan ~]# docker images
REPOSITORY   TAG       IMAGE ID       CREATED         SIZE
tomcat02     1.1       b4249b680e8d   9 seconds ago   684MB
tomcat       8.5       2d2bccf89f53   2 months ago    678MB
tomcat       latest    fb5657adc892   2 months ago    680MB
centos       latest    5d0da3dc9764   5 months ago    231MB
[root@jiangnan ~]# 

发现这里多了一个tomcat02,话不多说,直接启动看效果。

[root@jiangnan ~]# docker run -it -p 8082:8080 tomcat02:1.1
Using CATALINA_BASE:   /usr/local/tomcat
Using CATALINA_HOME:   /usr/local/tomcat
Using CATALINA_TMPDIR: /usr/local/tomcat/temp
Using JRE_HOME:        /usr/local/openjdk-11
Using CLASSPATH:       /usr/local/tomcat/bin/bootstrap.jar:/usr/local/tomcat/bin/tomcat-juli.jar
Using CATALINA_OPTS:   
NOTE: Picked up JDK_JAVA_OPTIONS:  --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.io=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.base/java.util.concurrent=ALL-UNNAMED --add-opens=java.rmi/sun.rmi.transport=ALL-UNNAMED
21-Feb-2022 15:20:45.874 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Server version name:   Apache Tomcat/10.0.14
21-Feb-2022 15:20:45.881 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Server built:          Dec 2 2021 22:01:36 UTC
21-Feb-2022 15:20:45.882 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Server version number: 10.0.14.0
21-Feb-2022 15:20:45.882 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log OS Name:               Linux
21-Feb-2022 15:20:45.882 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log OS Version:            3.10.0-1127.19.1.el7.x86_64
21-Feb-2022 15:20:45.882 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Architecture:          amd64
21-Feb-2022 15:20:45.882 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Java Home:             /usr/local/openjdk-11
21-Feb-2022 15:20:45.882 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log JVM Version:           11.0.13+8

在这里插入图片描述

404没有了,很完美。

在这里插入图片描述

5. 总结

  1. 镜像是一种轻量级、可执行的独立软件包,是一种分层结构的系统。
  2. 使用分层结构+UnionFS(联合文件系统)可以实现资源共享,资源整合。
  3. 镜像的非运行态叫做镜像,运行态称之为容器,最上层可读可写。
  4. 容器通过commit可以形成新的镜像,相当于给容器做了一次快照。

我的微信公众号先已开通,各位小伙伴们可以关注我,后面文章会进行同步,方便查看。
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_45842494/article/details/123058639
今日推荐