前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Docker引擎分层解析

Docker引擎分层解析

作者头像
Luga Lee
发布2021-12-09 20:34:23
4650
发布2021-12-09 20:34:23
举报
文章被收录于专栏:架构驿站

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

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

代码语言:javascript
复制
[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命令行进行操作。如下图所示:(因涉及项目中信息,故对部分数据进行参数化处理)

代码语言:javascript
复制
[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镜像:

代码语言:javascript
复制
[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
... ...
代码语言:javascript
复制
[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构建,会发生什么情况?

代码语言:javascript
复制
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镜像:

代码语言:javascript
复制
[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关键字表示。这称为悬空图像。我们将在本文结尾处对此进行更详细的说明。

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

代码语言:javascript
复制
[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文件。

代码语言:javascript
复制
[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。但这对存储真正意味着什么?首先,我们需要知道图像数据的存储位置。使用以下命令以检索存储位置:

代码语言:javascript
复制
[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目录的大小,以了解分配的存储空间:

代码语言:javascript
复制
m
1059 overlay2

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

代码语言:javascript
复制
[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层。我们注意到,如果我们不定期清洁中间层,则会继续创建中间层,并且悬挂的镜像仍然保留在我们的系统中。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-01-17,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 架构驿站 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档