Docker引擎分层解析

简介: 当我们拉取Docker Image时,如果仔细观察的话,你就会发现:它被拉成不同的层。另外,当然,我们创建自己的Docker Image时,也会创建多个层。在本文中,我们将尝试更好地去探究Docker层次的秘密。

      当我们拉取Docker Image时,如果仔细观察的话,你就会发现:它被拉成不同的层。另外,当然,我们创建自己的Docker Image时,也会创建多个层。在本文中,我们将尝试更好地去探究Docker层次的秘密。

      Docker Image由多层组成。每层都对应于Dockerfile中的某些指令。以下说明创建了一个图层:RUN、COPY、ADD。 让我们来看一个例子,我们将使用预先创建的Spring Boot MVC应用程序,并在Maven构建中创建Docker镜像。Dockerfile文件如下:


[administrator@JavaLangOutOfMemory luga ]% vi Dockerfile
FROM openjdk:10-jdk
VOLUME /tmp
RUN useradd -d /home/luga -m -s /bin/bash luga
USER luga
HEALTHCHECK --interval=5m --timeout=3s CMD curl -f http://localhost:8080/actuator/health/ || exit 1
ARG JAR_FILE
COPY ${JAR_FILE} app.jar
ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"]

      使用docker build 进行构建Docker镜像,当然,也可以使用mvn clean install命令行进行操作。如下图所示:(因涉及项目中信息,故对部分数据进行参数化处理)


[administrator@JavaLangOutOfMemory luga ]% docker build -f Dockerfile -t luga-demo-springboot .
[+] Building 2718.8s (9/9) FINISHED                                                                                                                                   
 => [internal] load build definition from Dockerfile                                                                                                             0.0s
 => => transferring dockerfile: 358B                                                                                                                             0.0s
 => [internal] load .dockerignore                                                                                                                                0.0s
 => => transferring context: 2B                                                                                                                                  0.0s
 => [internal] load metadata for docker.io/library/openjdk:10-jdk                                                                                               13.2s
 => [auth] library/openjdk:pull token for registry-1.docker.io                                                                                                   0.0s
 => [1/3] FROM docker.io/library/openjdk:10-jdk@sha256:9f17c917630d5e95667840029487b6561b752f1be6a3c4a90c4716907c1aad65                                       2703.4s
 => => resolve docker.io/library/openjdk:10-jdk@sha256:9f17c917630d5e95667840029487b6561b752f1be6a3c4a90c4716907c1aad65                                          0.0s
 => => sha256:9f17c917630d5e95667840029487b6561b752f1be6a3c4a90c4716907c1aad65 2.37kB / 2.37kB                                                                   0.0s
 => => sha256:923d074ef1f4f0dceef68d9bad8be19c918d9ca8180a26b037e00576f24c2cb4 2.00kB / 2.00kB                                                                   0.0s
 => => sha256:b11e88dd885d8b2621d448f3d2099068d181c5c778c2ab0cf0f61b573fa429b7 4.99kB / 4.99kB                                                                   0.0s
 => => sha256:16e82e17faef9e90ceefcd8175e9899edce768aa6008cc16dd1e3fe7d3b88bb8 49.26MB / 49.26MB                                                                39.8s
 => => sha256:117dc02416a34c62e28a030f27828f2f31af6b8b1f02c85b009a1ffb390d01dc 7.38MB / 7.38MB                                                                  21.3s
 => => sha256:7e4c717259ac9c550efbbf41c6fe0dc9598046f4bfd4b398deb63f7a0c19cb3f 9.78MB / 9.78MB                                                                  51.9s
 => => sha256:7a518b8f48be0323544739e175909b24be833b4a2bf39939f91fbcc2ab0e48a4 50.64MB / 50.64MB                                                                42.5s
 => => sha256:add32d44f708eff65a3ad250f4c87b8c3c912f0240c8a0acd76a88df6cd3ebdc 883.70kB / 883.70kB                                                              44.2s
 => => sha256:a0158fa0854313538e2807a9098aa49227ea46a44c3fc9120e9dc74e45baa408 237B / 237B                                                                      43.4s
 => => extracting sha256:16e82e17faef9e90ceefcd8175e9899edce768aa6008cc16dd1e3fe7d3b88bb8                                                                       12.9s
 => => sha256:9eb8cb7aab2603e0ca5015d7f512794de6fdf25375be1f135a3b890c5a17a298 131B / 131B                                                                      44.2s
 => => sha256:a9448aba0bc3432cb23b86d5c0fe808e4f0c8b7fdbe46fa26cdada8bcd582aa9 378.28MB / 378.28MB                                                            2654.1s
 => => extracting sha256:117dc02416a34c62e28a030f27828f2f31af6b8b1f02c85b009a1ffb390d01dc                                                                        2.1s
 => => extracting sha256:7e4c717259ac9c550efbbf41c6fe0dc9598046f4bfd4b398deb63f7a0c19cb3f                                                                        3.3s
 => => extracting sha256:7a518b8f48be0323544739e175909b24be833b4a2bf39939f91fbcc2ab0e48a4                                                                       14.2s
 => => extracting sha256:add32d44f708eff65a3ad250f4c87b8c3c912f0240c8a0acd76a88df6cd3ebdc                                                                        0.4s
 => => extracting sha256:a0158fa0854313538e2807a9098aa49227ea46a44c3fc9120e9dc74e45baa408                                                                        0.0s
 => => extracting sha256:9eb8cb7aab2603e0ca5015d7f512794de6fdf25375be1f135a3b890c5a17a298                                                                        0.0s
 => => extracting sha256:a9448aba0bc3432cb23b86d5c0fe808e4f0c8b7fdbe46fa26cdada8bcd582aa9                                                                       47.3s
 => [internal] load build context                                                                                                                                0.3s
 => => transferring context: 1.36MB                                                                                                                              0.2s
 => [2/3] RUN useradd -d /home/luga -m -s /bin/bash luga                                                                                                         1.6s
 => [3/3] COPY  app.jar                                                                                                                                          0.1s
 => exporting to image                                                                                                                                           0.1s
 => => exporting layers                                                                                                                                          0.1s
 => => writing image sha256:6bc6d7e6e0b2deaca0451ebd5d78fcbd43036ddf917dc17e29a741565d9070ff                                                                     0.0s
 => => naming to docker.io/library/luga-demo-springboot                                                                                                          0.0s

      这里会发生什么?我们注意到已经创建了图层,并且大多数图层都被删除(删除中间容器)。那么,为什么说删除中间容器而不删除中间层呢?那是因为构建步骤是在中间容器中执行的。完成构建步骤后,可以删除中间容器。除此之外,层是只读的。一层包含前一层和当前层之间的差异。在这些层的顶层,有一个可写层(当前层),称为容器层。如前所述,只有特定的指令才能创建新层。让我们看一下我们的Docker镜像:


[administrator@JavaLangOutOfMemory luga ] % docker image ls
REPOSITORY                  TAG                                              IMAGE ID       CREATED         SIZE
luga-demo-springboot        latest                                           6bc6d7e6e0b2   21 hours ago    989MB
nacos/nacos-server          latest                                           9c0b55a5ab2c   34 hours ago    935MB
grafana/grafana             latest                                           13afb861111c   2 days ago      187MB
mysql                       5.7                                              cc8775c0fe94   4 days ago      449MB
prom/prometheus             latest                                           53fd5ed1cd48   10 days ago     173MB
openzipkin/zipkin           latest                                           9b4acc3eb019   3 weeks ago     150MB
redis                       latest                                           ef47f3b6dc11   5 weeks ago     104MB
tomee                       latest                                           9e19954eefcc   5 weeks ago     336MB
... ...

[administrator@JavaLangOutOfMemory luga ] % docker histoty 6bc6d7e6e0b2
IMAGE           CREATED               CREATED BY                                      SIZE    COMMENT
6bc6d7e6e0b2   About a minute ago    /bin/sh -c #(nop) ENTRYPOINT ["java" "-Djav…    0B
63c18567012b    About a minute ago    /bin/sh -c #(nop) COPY file:2a5b71774c60e0f6…   17.4MB
135fa7df95ac    About a minute ago    /bin/sh -c #(nop) ARG JAR_FILE                  0B
77f95436a3ff    2 minutes ago         /bin/sh -c #(nop) HEALTHCHECK &{["CMD-SHELL…    0B
eaf6b8af5709    2 minutes ago         /bin/sh -c #(nop) USER luga                  0B
04f6b2716819    2 minutes ago         /bin/sh -c useradd -d /home/luga -m -s /b…   399kB
b6f9ca000de6    2 minutes ago         /bin/sh -c #(nop) VOLUME [/tmp]                 0B
b11e88dd885d    2 months ago          /bin/sh -c #(nop) CMD ["jshell"]                0B
<missing>       2 months ago          /bin/sh -c set -ex; if [ ! -d /usr/share/m…     697MB
<missing>       2 months ago          /bin/sh -c #(nop) ENV JAVA_DEBIAN_VERSION=1…    0B
...

     从上面的命令行,我们注意到,中间容器的大小确实为0B,与预期的一样。Dockerfile中只有RUN和COPY命令会影响Docker镜像的大小。openjdk:10-jdkimage的层也被列出,并由丢失的关键字识别。这仅意味着这些层建立在不同的系统上,并且在本地不可用。

     如果我们在不对源代码进行任何更改的情况下再次运行Maven构建,会发生什么情况?


Image will be built as luga-demo-springboot:latest
Step 1/8 : FROM openjdk:10-jdk
Pulling from library/openjdk
t: sha256:9f17c917630d5e95667840029487b6561b752f1be6a3c4a90c4716907c1aad65
Status: Image is up to date for openjdk:10-jdk
---> b11e88dd885d
Step 2/8 : VOLUME /tmp
---> Using cache
---> b6f9ca000de6
... ...
Step 6/8 : ARG JAR_FILE
---> Using cache
---> 135fa7df95ac
Step 7/8 : COPY ${JAR_FILE} app.jar
---> 409f2fee0cde
Step 8/8 : ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"]
---> Running in 75f07955bbc8
Removing intermediate container 75f07955bbc8
---> e5d7b72aad05
Successfully built e5d7b72aad05
Successfully tagged luga-demo-springboot:latest


     我们注意到第一层与我们先前的版本相同。图层ID相同。在日志中,我们注意到图层是从缓存中提取的。在步骤7中,使用新ID创建新层。我们确实创建了一个新的JAR文件,Docker将其解释为一个新文件,因此创建了一个新层。在步骤8中,还创建了一个新层,因为它是建立在新层之上的。

      让我们再次列出Docker镜像:


[administrator@JavaLangOutOfMemory luga ] %docker image ls
REPOSITORY                              TAG               IMAGE ID        CREATED           SIZE
luga-demo-springboot                  latest              d7u9b72aad85    13 seconds ago    1GB
<none>                          <none>        8e2b049f9783    5 minutes ago     1GB
openjdk                                 10-jdk            b11e88dd885d    2 months ago      987MB


     我们的标签latest已收到我们上一个版本的图片ID。我们的旧图片ID的存储库和标签已删除,使用none关键字表示。这称为悬空图像。我们将在本文结尾处对此进行更详细的说明。

     当我们查看新创建的镜像构建历史时,我们注意到两个顶层是新的,就像构建日志中一样:


[administrator@JavaLangOutOfMemory luga ] %docker history d7u9b72aad85
IMAGE           CREATED           CREATED BY                                      SIZE     COMMENT
d7u9b72aad85    38 seconds ago    /bin/sh -c #(nop) ENTRYPOINT ["java" "-Djav…    0B
409f2fee0cde    42 seconds ago    /bin/sh -c #(nop) COPY file:4b04c6500d340c9e…   17.4MB
135fa7df95ac    6 minutes ago     /bin/sh -c #(nop) ARG JAR_FILE                  0B
...

     当我们更改源代码时,结果是相同的,因为在这种情况下,还会生成一个新的JAR文件。


[administrator@JavaLangOutOfMemory luga ] %docker image ls
REPOSITORY                              TAG               IMAGE ID        CREATED           SIZE
luga-demo-springboot                    latest            eced642d4f5c    30 seconds ago    1GB
<none>                            <none>      d7u9b72aad85    3 minutes ago     1GB
<none>                            <none>      8e2b049f9783    8 minutes ago     1GB
openjdk                                 10-jdk            b11e88dd885d    2 months ago      987MB
[administrator@JavaLangOutOfMemory luga ] %docker history eced642d4f5c
IMAGE           CREATED               CREATED BY                                      SIZE    COMMENT
eced642d4f5c    About a minute ago    /bin/sh -c #(nop) ENTRYPOINT ["java" "-Djav…    0B
44a9097b8bad    About a minute ago    /bin/sh -c #(nop) COPY file:1d5276778b53310e…   17.4MB
135fa7df95ac    9 minutes ago         /bin/sh -c #(nop) ARG JAR_FILE                  0B
...

      现在大小如何?

      让我们仔细看看docker image ls命令的最新输出。我们注意到两个悬挂的图像,大小为1 GB。但这对存储真正意味着什么?首先,我们需要知道图像数据的存储位置。使用以下命令以检索存储位置:


[administrator@JavaLangOutOfMemory luga ] %ocker image inspect eced642d4f5c
  ...
  "GraphDriver": {
    "Data": {
      "LowerDir": "/var/lib/docker/overlay2/655be8bea8e54c31ebb7e3adf05db227d194a49c1e2f95552d593d623e024b92/diff:/var/lib/docker/overlay2/993f77b91a487e19b3696836efee23c8a17791d71096d348c54c38fba3dc8478/diff:/var/lib/docker/overlay2/d62d6ca8ce1960d057e11d163d458563628e5a337de06455e714900f72005589/diff:/var/lib/docker/overlay2/cabdf4de81557a8047e3670bd2eecb5449de7de8fe9dfd4ad0c81d7dd2c61e9d/diff:/var/lib/docker/overlay2/062bf99d6a563ee2ef7824ec02ff5cd09fb8721cb23f6a55f8927edc2607f9c1/diff:/var/lib/docker/overlay2/ba024c24b20771dbf409f501423273e13225cf675f30896720cadace1c7be000/diff:/var/lib/docker/overlay2/d15f4477b53508127bebd1224c9ea09cd767f7db7429ffb1e8aa79b01ab77506/diff:/var/lib/docker/overlay2/ea434348d6625bc49875d0aba886b24ff0e1e204a350099981dcfc4029bc688d/diff:/var/lib/docker/overlay2/05e003c0522c7049110aa3ce09814ff2167da1e53ec83481fef03324011ce6e6/diff",
      "MergedDir": "/var/lib/docker/overlay2/205b55ee2f0e06394b6d17067338845410609887ccd18f53bf0646ff60452ffb/merged",
      "UpperDir": "/var/lib/docker/overlay2/205b55ee2f0e06394b6d17067338845410609887ccd18f53bf0646ff60452ffb/diff",
      "WorkDir": "/var/lib/docker/overlay2/205b55ee2f0e06394b6d17067338845410609887ccd18f53bf0646ff60452ffb/work"
    },
    "Name": "overlay2"
  },
  ...

      我们的Docker映像存储在/ var / lib / docker / overlay2中。我们可以简单地检索overlay2目录的大小,以了解分配的存储空间:


[administrator@JavaLangOutOfMemory luga ] %du -sh -

m

overlay2

1059 overlay2

     openjdk:10-jdk镜像为987 MB。我们镜像中的JAR文件为17.4 MB。总大小应约为987 MB + 3 * 17.4 MB(两个悬挂的镜像和一个真实的镜像)。这大约是1,040 MB。我们可以得出结论,Docker镜像有某种智能存储,我们不能简单地添加所有Docker镜像的大小来检索实际存储大小。差异是由于存在中间图像。这些可以显示如下:


[administrator@JavaLangOutOfMemory luga ] %docker images -a
REPOSITORY                                      TAG                   IMAGE ID        CREATED       SIZE
luga-demo-springboot                          latest                  eced642d4f5c    7 days ago    1GB
<none>                                  <none>            44a9097b8bad    7 days ago    1GB
<none>                                  <none>            e5d7b72aad05    7 days ago    1GB
<none>                                  <none>            409f2fee0cde    7 days ago    1GB
<none>                                  <none>            8e2b049f9783    7 days ago    1GB
<none>                                  <none>            63c18567012b    7 days ago    1GB
<none>                                  <none>            135fa7df95ac    7 days ago    987MB
<none>                                  <none>            77f95436a3ff    7 days ago    987MB
<none>                                  <none>            eaf6b8af5709    7 days ago    987MB
<none>                                  <none>            04f6b2716819    7 days ago    987MB
<none>                                  <none>            b6f9ca000de6    7 days ago    987MB
openjdk                                           10-jdk              b11e88dd885d    2 months ago  987MB

      我们如何优化这些悬而未决的镜像呢? 我们不再需要它们,它们仅分配存储空间。我们可以使用docker rmi命令删除经常不用的镜像,或者,可以使用docker image prune命令执行此操作,以将其释放。

      在本文中,我们试图更好地理解Docker层。我们注意到,如果我们不定期清洁中间层,则会继续创建中间层,并且悬挂的镜像仍然保留在我们的系统中。

相关文章
|
存储 安全 数据建模
Docker数据卷深入解析与操作技巧
通过巧妙使用Docker数据卷,我们就能实现数据的灵活管理和无缝迁移,享受容器化的便捷,同时确保数据的安全与高效共享。
313 6
|
NoSQL Java Linux
《docker高级篇(大厂进阶):2.DockerFile解析》包括:是什么、DockerFile构建过程解析、DockerFile常用保留字指令、案例、小总结
《docker高级篇(大厂进阶):2.DockerFile解析》包括:是什么、DockerFile构建过程解析、DockerFile常用保留字指令、案例、小总结
792 76
|
Ubuntu NoSQL 开发工具
《docker基础篇:4.Docker镜像》包括是什么、分层的镜像、UnionFS(联合文件系统)、docker镜像的加载原理、为什么docker镜像要采用这种分层结构呢、docker镜像commit
《docker基础篇:4.Docker镜像》包括是什么、分层的镜像、UnionFS(联合文件系统)、docker镜像的加载原理、为什么docker镜像要采用这种分层结构呢、docker镜像commit
753 70
|
安全 持续交付 Docker
Docker:重塑现代软件交付的容器引擎
Docker:重塑现代软件交付的容器引擎
|
Kubernetes Cloud Native 持续交付
Docker:轻量级容器化技术解析
Docker:轻量级容器化技术解析
|
存储 Kubernetes 调度
Kubernetes、Docker和Containerd的关系解析
总的来说,Docker、Containerd和Kubernetes之间的关系可以用一个形象的比喻来描述:Docker就像是一辆装满货物的卡车,Containerd就像是卡车的引擎,而Kubernetes就像是调度中心,负责指挥卡车何时何地送货。
676 12
|
Kubernetes Linux 虚拟化
入门级容器技术解析:Docker和K8s的区别与关系
本文介绍了容器技术的发展历程及其重要组成部分Docker和Kubernetes。从传统物理机到虚拟机,再到容器化,每一步都旨在更高效地利用服务器资源并简化应用部署。容器技术通过隔离环境、减少依赖冲突和提高可移植性,解决了传统部署方式中的诸多问题。Docker作为容器化平台,专注于创建和管理容器;而Kubernetes则是一个强大的容器编排系统,用于自动化部署、扩展和管理容器化应用。两者相辅相成,共同推动了现代云原生应用的快速发展。
4999 11
|
负载均衡 网络协议 算法
Docker容器环境中服务发现与负载均衡的技术与方法,涵盖环境变量、DNS、集中式服务发现系统等方式
本文探讨了Docker容器环境中服务发现与负载均衡的技术与方法,涵盖环境变量、DNS、集中式服务发现系统等方式,以及软件负载均衡器、云服务负载均衡、容器编排工具等实现手段,强调两者结合的重要性及面临挑战的应对措施。
679 4
|
安全 持续交付 Docker
深入理解并实践容器化技术——Docker 深度解析
深入理解并实践容器化技术——Docker 深度解析
783 2
|
运维 持续交付 虚拟化
深入解析Docker容器化技术的核心原理
深入解析Docker容器化技术的核心原理
518 1