Docker层和虚悬镜像(dangling image)介绍

Docker层和虚悬镜像(dangling image)介绍原文发表于 kubernetes 中文社区 为作者原创原文地址当你拉取 Docker 镜像时 你会注意到它被拉取成不同的层 另外 当你创建自己的 Docker 镜像时 也会创建多个层 在本文中 我们将更好地理解 Docker 层 1 什么是 Docker 层 Docker 镜像由几层组成 每层都对应 Dockerfile 中的特定指定 Docker 层创建指令有 RUN COPY ADD 其他指令将创建

原文发表于kubernetes中文社区,为作者原创 原文地址

当你拉取Docker镜像时,你会注意到它被拉取成不同的层。另外,当你创建自己的Docker镜像时,也会创建多个层。在本文中,我们将更好地理解Docker层。

1.什么是Docker层?

Docker镜像由几层组成。每层都对应 Dockerfile中的特定指定。Docker层创建指令有: RUNCOPYADD。其他指令将创建中间层,并且不会影响镜像的大小。

我们看一个例子:创建一个Spring Boot MVC应用程序, 并且在Maven构建中创建Docker镜像。这些资源可从 GitHub获得。

我们使用的 feature/dockerbenchsecurity 分支,它是 master 分支中更安全的版本。

Dockerfile如下:

FROM openjdk:10-jdk VOLUME /tmp RUN useradd -d /home/appuser -m -s /bin/bash appuser USER appuser 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"] 

我们使用mvn clean install 命令构建的应用程序,还将创建Docker镜像。为了简洁起见,我们没有列出openjdk:10-jdk镜像中所有图层的拉取 。

Image will be built as mydeveloperplanet/mykubernetesplanet:0.0.3-SNAPSHOT Step 1/8 : FROM openjdk:10-jdk Pulling from library/openjdk Image 16e82e17faef: Pulling fs layer ... Image a9448aba0bc3: Pull complete Digest: sha256:9f17cd5e487b6561b752f1be6a3c4a90cc1aad65 Status: Downloaded newer image for openjdk:10-jdk ---> b11e88dd885d Step 2/8 : VOLUME /tmp ---> Running in c3a6 Removing intermediate container c3a6 ---> b6f9ca000de6 Step 3/8 : RUN useradd -d /home/appuser -m -s /bin/bash appuser ---> Running in e6e7 Removing intermediate container e6e7 ---> 04f6b Step 4/8 : USER appuser ---> Running in 697b663dadbb Removing intermediate container 697b663dadbb ---> eaf6b8af5709 Step 5/8 : HEALTHCHECK --interval=5m --timeout=3s CMD curl -f http://localhost:8080/actuator/health/ || exit 1 ---> Running in f420b9d060c5 Removing intermediate container f420b9d060c5 ---> 77f95436a3ff Step 6/8 : ARG JAR_FILE ---> Running in 60b9d25ad2ac Removing intermediate container 60b9d25ad2ac ---> 135fa7df95ac Step 7/8 : COPY ${JAR_FILE} app.jar ---> 63cb Step 8/8 : ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"] ---> Running in a Removing intermediate container a ---> 8e2b049f9783 Successfully built 8e2b049f9783 Successfully tagged mydeveloperplanet/mykubernetesplanet:0.0.3-SNAPSHOT 

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

我们来看一下我们的Docker镜像:

$ docker image ls REPOSITORY TAG IMAGE ID CREATED SIZE mydeveloperplanet/mykubernetesplanet 0.0.3-SNAPSHOT 8e2b049f9783 About a minute ago 1GB openjdk 

看看我们的mykubernetesplanet 镜像的构建历史记录 :

$ docker history 8e2b049f9783 IMAGE CREATED CREATED BY SIZE COMMENT 8e2b049f9783 About a minute ago /bin/sh -c #(nop) ENTRYPOINT ["java" "-Djav… 0B 63cb 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 appuser 0B 04f6b 2 minutes ago /bin/sh -c useradd -d /home/appuser -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。Dockerfile中只有 RUN and COPY 指令会影响Docker镜像的大小。openjdk:10-jdk镜像的各层 也被列举出,显示为missing ,这意味着这些层可以建立在不同的系统上,并且在本地不可用。

2.重新创建Docker镜像

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

Image will be built as mydeveloperplanet/mykubernetesplanet:0.0.3-SNAPSHOT Step 1/8 : FROM openjdk:10-jdk Pulling from library/openjdk Digest: sha256:9f17cd5e487b6561b752f1be6a3c4a90cc1aad65 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 mydeveloperplanet/mykubernetesplanet:0.0.3-SNAPSHOT 

从上面我们注意到,镜像第一层与我们先前的版本相同–层ID相同。在日志中,我们注意到docker层是从缓存中提取的。

在步骤7中,docker使用新ID创建了一个新层。因为我们确实创建了一个新的JAR文件,Docker也识别它是一个新文件,因此创建了一个新层。

在步骤8中,还创建了一个新层,因为它是建立在新层之上的。

让我们再次列出Docker镜像:

$ docker image ls REPOSITORY TAG IMAGE ID CREATED SIZE mydeveloperplanet/mykubernetesplanet 0.0.3-SNAPSHOT e5d7b72aad05 13 seconds ago 1GB <none> <none> 8e2b049f9783 5 minutes ago 1GB openjdk 10-jdk b11e88dd885d 2 months ag 

0.0.3-SNAPSHOT 中可以看到上次docker构建的镜像ID。而且,上个版本镜像ID和标签都已被删除,并以none 关键字表示 。这称为 虚悬镜像(dangling image) 。我们将在本文结尾处对虚悬镜像(dangling image) 进行更详细的说明。

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

$ docker history e5d7b72aad05 IMAGE CREATED CREATED BY SIZE COMMENT e5d7b72aad05 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文件。

$ docker image ls REPOSITORY TAG IMAGE ID CREATED SIZE mydeveloperplanet/mykubernetesplanet 0.0.3-SNAPSHOT eced642d4f5c 30 seconds ago 1GB <none> <none> e5d7b72aad05 3 minutes ago 1GB <none> <none> 8e2b049f9783 8 minutes ago 1GB openjdk 10-jdk b11e88dd885d 2 months ago 987MB $ 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:1db53310e… 17.4MB 135fa7df95ac 9 minutes ago /bin/sh -c #(nop) ARG JAR_FILE 0B ... 

3.镜像有多大?

通过docker image ls命令的输出 ,我们注意到两个虚悬镜像(dangling image),大小为1 GB。这对存储有什么影响?

首先,我们需要知道镜像数据的存储位置。使用以下命令可以检索存储位置:

$ docker image inspect eced642d4f5c ... "GraphDriver": { 
    "Data": { 
    "LowerDir": "/var/lib/docker/overlay2/655be8bea8e54c31ebb7e3adf05db227d194a49c1e2f95552d593d623e024b92/diff:/var/lib/docker/overlay2/993f77b91a487e19befee23c8a17791d71096d348c54c38fba3dc8478/diff:/var/lib/docker/overlay2/d62d6ca8ce1960d057e11d163de5a337de06455ef/diff:/var/lib/docker/overlay2/cabdf4de81557a8047e3670bd2eecb5449de7de8fe9dfd4ad0c81d7dd2c61e9d/diff:/var/lib/docker/overlay2/062bf99d6a563ee2ef7824ec02ff5cd09fb8721cb23f6a55f8927edc2607f9c1/diff:/var/lib/docker/overlay2/ba024c24b20771dbf409fe13225cf675fcadace1c7be000/diff:/var/lib/docker/overlay2/d15f4477bbebd1224c9ea09cd767f7db7429ffb1e8aa79b01ab77506/diff:/var/lib/docker/overlay2/ead6625bc49875d0aba886b24ff0e1e204adcfc4029bc688d/diff:/var/lib/docker/overlay2/05e003c0522caa3ce09814ff2167da1e53ec83481fef0ce6e6/diff", "MergedDir": "/var/lib/docker/overlay2/205b55ee2f0e06394b6dccd18f53bf0646ff60452ffb/merged", "UpperDir": "/var/lib/docker/overlay2/205b55ee2f0e06394b6dccd18f53bf0646ff60452ffb/diff", "WorkDir": "/var/lib/docker/overlay2/205b55ee2f0e06394b6dccd18f53bf0646ff60452ffb/work" }, "Name": "overlay2" }, ... 

可以看到,我们的Docker镜像存储在 /var/lib/docker/overlay2。我们可以通过查看overlay2 目录的大小,来了解它占用的存储空间:

$ du -sh -m overlay2 1059 overlay2 

openjdk:10-jdk 镜像大小是987 MB,JAR文件为17.4 MB,总大小应约为987 MB + 3 * 17.4 MB(两个虚悬镜像(dangling image) 和一个真实的镜像)。这大约是1,040 MB。

可以看出,我们不能简单地添加所有Docker镜像的大小来确认实际存储大小。

其中的差异是由于存在中间镜像。

这些可以显示如下:

$ docker images -a REPOSITORY TAG IMAGE ID CREATED SIZE mydeveloperplanet/mykubernetesplanet 0.0.3-SNAPSHOT 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> 63cb 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> 04f6b 7 days ago 987MB <none> <none> b6f9ca000de6 7 days ago 987MB openjdk 10-jdk b11e88dd885d 2 months ago 

4. 如何去掉虚悬镜像(dangling image)

虚悬镜像(dangling image) ,我们不需要它们,它们还占用存储空间。我们如何去掉呢?

首先,列出悬空的镜像:

$ docker images -f dangling=true REPOSITORY TAG IMAGE ID CREATED SIZE <none> <none> e5d7b72aad05 7 days ago 1GB <none> <none> 8e2b049f9783 7 days ago 1GB 

我们可以使用以下 docker rmi 命令删除镜像:

$ docker rmi e5d7b72aad05 Deleted: sha256:e5d7b72aad054100d142d99467ca2ef3bc2a0994fb589f9fc7ff004afe Deleted: sha256:409f2fee0cde9b5144f8e92887b61e49f3ccbd2b0e601fd3b9be32ff47 Deleted: sha256:2162a2af22ee26f7ac9bd95cdc9714b8fbfbeb892ff827be15c7795b 

或者,你可以使用 docker image prune 命令来执行此操作。

现在已经删除了虚悬镜像(dangling image),让我们看一下overlay2 目录的大小 :

$ du -sh -m overlay2 1026 overlay2 

我们节省了33 MB。

似乎还不算什么,但是当你经常构建Docker镜像时,随着时间的流逝,它会显著增长。

5.总结

在本文中,我们尝试更好地理解Docker层。我们注意到构建Docker镜像时会创建中间层,如果我们不定期清理中间层,那么虚悬镜像(dangling image)会一直保留在我们的系统中。我们还查看了系统上Docker镜像的大小。

译文链接: https://dzone.com/articles/docker-layers-explained

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/209639.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月19日 上午8:46
下一篇 2026年3月19日 上午8:46


相关推荐

  • docker停止运行中的容器(docker关闭容器)

    问题描述:centos启动一个容器添加了-d参数,但是dockerps或者dockerps-a查看却已经退出了shell>dockerrun-dcentosa44b2b88559b68a2221c9574490a0e708bff49d88ca21f9e59d3eb245c7c0547shell>dockerps退出原因1、docker容器运行必须有一…

    2022年4月10日
    732
  • Dockerfile add_在docker中使用ubuntu

    Dockerfile add_在docker中使用ubuntu前言Dockerfile中提供了两个非常相似的命令COPY和ADD,本文尝试解释这两个命令的基本功能,以及其异同点,然后总结其各自适合的应用场景。Build上下文的概念在使用dock

    2022年8月6日
    7
  • docker启动镜像容器命令_镜像删除

    docker启动镜像容器命令_镜像删除一、查看当前docker中下载的镜像,如下图,当前我的Docker容器中存在两个镜像,tomcat、mysql二、启动镜像(因启动命令参数过多,同时各种镜像启动时可以增加额外的参数,本次以启动mysql5.6为例)dockerrun-p本机映射端口:镜像映射端口-d–name启动镜像名称-e镜像启动参数镜像名称:镜像版本号参数释义:-p本机端口和容器启动端口映射-d后台运行–name容器名称-e镜像启动参数例:

    2026年2月19日
    3
  • docker离线安装并导入镜像「建议收藏」

    docker离线安装并导入镜像「建议收藏」实际项目开发过程中,客户环境大多是全内网环境,无法连接互联网。这样docker就不能yum在线联网安装,所需要的镜像也不能在线pull下载。这时就需要进行离线安装docker及镜像。

    2026年4月14日
    5
  • docker(7)docker-compose容器集群编排「建议收藏」

    docker(7)docker-compose容器集群编排「建议收藏」前言实际工作中我们部署一个应用,一般不仅仅只有一个容器,可能会涉及到多个,比如用到数据库,中间件MQ,web前端和后端服务,等多个容器。我们如果一个个去启动应用,当项目非常多时,就很难记住了,所有

    2022年7月31日
    10
  • docker-compose 集群_docker redis 集群

    docker-compose 集群_docker redis 集群前言实际工作中我们部署一个应用,一般不仅仅只有一个容器,可能会涉及到多个,比如用到数据库,中间件MQ,web前端和后端服务,等多个容器。我们如果一个个去启动应用,当项目非常多时,就很难记住了,所有

    2022年8月6日
    6

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号