首页 > 解决方案 > 为 Dockerfile 的最终 CMD 步骤创建中间容器和新图像快照的优势是什么

问题描述

当我们从 Dockerfile 构建镜像时,Dockerfile 中的每个步骤都会导致创建一个中间容器,该容器是从上一步的镜像创建的,并运行该步骤的指令。但是到了CMD的最后一步,在倒数第二步的上面运行CMD命令新建的镜像也会有同样的启动命令CMD吧?

例子:

Step 1. FROM alpine
Step 2. RUN apk --update add redis 
Step 3. EXPOSE 6379
Step 4. CMD ["redis-server"]

来自 Alpine 的第 1 步将有图像 I1

第 2 步将使用 I1 和 RUN 命令启动容器 C1,作为启动创建新映像 I2(删除中间容器 C1)

第 3 步将使用镜像 I2 以 EXPOSE 命令作为启动运行一个新的容器 C2,并创建一个新的镜像 I3(删除中间容器 C2)

第 4 步将使用镜像 I3 启动一个新容器 C3 并运行启动命令 CMD 将创建一个新镜像 I4(删除中间容器 C3)

最后 I4 将作为最终图像呈现。现在这个镜像 I4 也将与镜像 I3 相同的 CMD 启动命令。

我的问题是为什么我们必须运行容器 C3 并创建映像 I4?为什么不使用启动命令 CMD 将其留在映像 I3 上,该命令将在用户尝试使用 I3 创建容器时运行

标签: docker

解决方案


它不运行这些容器,这只发生在 RUN 步骤中。也没有添加新层,可以添加文件系统层的唯一步骤是 RUN、COPY 和 ADD。这是对图像配置 json 元数据的修改。图像清单包含对此配置 json 的引用,因此您会为每次配置更改获得一个新的图像摘要。使用经典的构建过程,生成元数据的简单方法是创建一个容器,但正如这里的输出所示,它没有运行,状态仅为created

$ DOCKER_BUILDKIT=0 docker build --no-cache --rm=false -f df.intermediate .
Sending build context to Docker daemon  20.99kB
Step 1/4 : FROM alpine
latest: Pulling from library/alpine
ba3557a56b15: Pull complete
Digest: sha256:a75afd8b57e7f34e4dad8d65e2c7ba2e1975c795ce1ee22fa34f8cf46f96a3be
Status: Downloaded newer image for alpine:latest
 ---> 28f6e2705743
Step 2/4 : RUN apk --update add redis
 ---> Running in dadc1e8d5044
fetch https://dl-cdn.alpinelinux.org/alpine/v3.13/main/x86_64/APKINDEX.tar.gz
fetch https://dl-cdn.alpinelinux.org/alpine/v3.13/community/x86_64/APKINDEX.tar.gz
(1/1) Installing redis (6.0.11-r0)
Executing redis-6.0.11-r0.pre-install
Executing redis-6.0.11-r0.post-install
Executing busybox-1.32.1-r3.trigger
OK: 8 MiB in 15 packages
 ---> 32a040f16a0c
Step 3/4 : EXPOSE 6379
 ---> Running in 7272af442111
 ---> 6d8a0569afdf
Step 4/4 : CMD ["redis-server"]
 ---> Running in 7853be69241a
 ---> 8027f666c955
Successfully built 8027f666c955

$ docker container ls -a
CONTAINER ID   IMAGE                           COMMAND                  CREATED          STATUS                     PORTS                                        NAMES
7853be69241a   6d8a0569afdf                    "/bin/sh -c '#(nop) …"   9 seconds ago    Created                                                                 awesome_mclean
7272af442111   32a040f16a0c                    "/bin/sh -c '#(nop) …"   9 seconds ago    Created                                                                 pensive_dewdney
dadc1e8d5044   28f6e2705743                    "/bin/sh -c 'apk --u…"   11 seconds ago   Exited (0) 9 seconds ago                                                nice_greider

重要的是,图像是引用其他层和配置元数据的 json,并且您不会复制所有层,您只会创建对相同层的额外引用,因此只有几 kb 可以跟踪指向另一个配置文件的另一个 json 文件. 您可以在推送到注册表时看到:

$ docker push localhost:5000/test/intermediate:latest
The push refers to repository [localhost:5000/test/intermediate]
c2b5fcbe3ebc: Pushed
cb381a32b229: Pushed
latest: digest: sha256:f8171dae4c1e77313ce3a6c7496e4c641a5636f55ce1bb15fe4dd6f114680239 size: 739

查看图像配置的历史记录部分,它显示这些步骤没有任何层数据(empty_layer: true):

$ regctl image inspect localhost:5000/test/intermediate
{            
  "created": "2021-03-18T13:39:41.050804558Z",
  "architecture": "amd64",
  "os": "linux",                                     
  "config": {                                    
    "ExposedPorts": {
      "6379/tcp": {}                                 
    },       
    "Env": [                                        
      "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
    ],                                               
    "Cmd": [                                            
      "redis-server"
    ]                
  },                                                                  
  "rootfs": {
    "type": "layers",                                             
    "diff_ids": [
      "sha256:cb381a32b2296e4eb5af3f84092a2e6685e88adbc54ee0768a1a1010ce6376c7",       
      "sha256:c2b5fcbe3ebc0a260ba84a593ab0b7fd9f584242a98170bb0d0b5b89c353d2f0"
    ]        
  }, 
  "history": [                                                         
    {                 
      "created": "2021-02-17T21:19:54.634967707Z",                                       
      "created_by": "/bin/sh -c #(nop) ADD file:80bf8bd014071345b1c0364eeb0a5e48f3fb0d87f9c31cb990e57caa652b59b8 in / "
    },
    {                                                                  
      "created": "2021-02-17T21:19:54.811094842Z",
      "created_by": "/bin/sh -c #(nop)  CMD [\"/bin/sh\"]",                              
      "empty_layer": true
    },
    {
      "created": "2021-03-18T13:39:40.595419392Z",
      "created_by": "/bin/sh -c apk --update add redis"
    },                                                
    {        
      "created": "2021-03-18T13:39:40.818725501Z",
      "created_by": "/bin/sh -c #(nop)  EXPOSE 6379",
      "empty_layer": true
    },
    {              
      "created": "2021-03-18T13:39:41.050804558Z",
      "created_by": "/bin/sh -c #(nop)  CMD [\"redis-server\"]",
      "empty_layer": true            
    }                     
  ]                       
}

Docker 会分别跟踪其中的每一个,因为它会影响缓存。仅当您从相同的先前状态开始时,重用先前构建中的缓存步骤才有效。

请注意,还有其他方法可以生成此元数据,而 buildkit 无需创建或运行容器即可执行此操作:

$ docker build --no-cache --progress=plain -f df.intermediate .
#1 [internal] load build definition from df.intermediate
#1 sha256:f3ec60f1563d5476385c136badb851fd417c0ae26caf5f61c3e931b8b8b08b86
#1 transferring dockerfile: 121B done
#1 DONE 0.0s      
                      
#2 [internal] load .dockerignore
#2 sha256:586943d6f8ffae008b8774450d592ae867dbda8f418150cb140924142d97a698
#2 transferring context: 49B done
#2 DONE 0.0s                 
                  
#3 [internal] load metadata for docker.io/library/alpine:latest
#3 sha256:d4fb25f5b5c00defc20ce26f2efc4e288de8834ed5aa59dff877b495ba88fda6
#3 DONE 0.0s                             
                        
#4 [1/2] FROM docker.io/library/alpine   
#4 sha256:665ba8b2cdc0cb0200e2a42a6b3c0f8f684089f4cd1b81494fbb9805879120f7
#4 resolve docker.io/library/alpine:latest done  
#4 DONE 0.0s 
             
#5 [2/2] RUN apk --update add redis  
#5 sha256:655ace4fb184eee9174a1a3911e13e4086750316878b55acc3f07cd5dd0ff2d6
#5 0.510 fetch https://dl-cdn.alpinelinux.org/alpine/v3.13/main/x86_64/APKINDEX.tar.gz
#5 0.724 fetch https://dl-cdn.alpinelinux.org/alpine/v3.13/community/x86_64/APKINDEX.tar.gz
#5 0.946 (1/1) Installing redis (6.0.11-r0)  
#5 0.960 Executing redis-6.0.11-r0.pre-install 
#5 1.042 Executing redis-6.0.11-r0.post-install
#5 1.048 Executing busybox-1.32.1-r3.trigger
#5 1.060 OK: 8 MiB in 15 packages   
#5 DONE 1.2s   

#6 exporting to image
#6 sha256:e8c613e07b0b7ff33893b694f7759a10d42e180f2b4dc349fb57dc6b71dcab00
#6 exporting layers 0.0s done 
#6 writing image sha256:8126afb6ee91f75f6d5ce4c59ae5b66981a708715a217a36e98ccf423c6d025d done
#6 DONE 0.0s 

在上面,您可以看到#5执行 RUN,然后立即写入结果。


推荐阅读