【问题标题】:Dockerfile RUN layers vs scriptDockerfile RUN 层与脚本
【发布时间】:2022-01-27 18:15:13
【问题描述】:

Docker 版本 19.03.12,构建 48a66213fe

所以在 dockerfile 中,如果我有以下几行:

RUN yum install aaa \
        bbb \
        ccc && \
        <some cmd> && \
        <etc> && \
         <some cleanup> 

这是最佳做法吗?当我调用其他 时,我是否应该将 yum 部分与 分开?

如果我想要一个更干净(而不是可跟踪)的 Dockerfile,如果我将这些行放在 .sh 脚本中,可以调用该脚本(即 COPY 后跟 RUN 语句)。构建步骤是否每次都会运行,即使 .sh 脚本中没有任何变化**?** 在这里寻找一些陷阱。

我在想,无论包是稳定的,都有一个单独的RUN &lt;those packages&gt;,即在一层和依赖/经常更改的行,即可以使用用户定义的(docker build time CLI level args)将它们保持在单独的 RUN层(这样我就可以有效地使用层缓存)。

想知道你是否认为保持一个更干净的 Dockerfile(调用 RUN some.sh)会比一个可追溯的 Dockerfile 效率低(Dockerfile 中列出了所有内容是什么构成了该图像)。

谢谢。

【问题讨论】:

    标签: docker docker-compose dockerfile containers docker-registry


    【解决方案1】:

    就最终的图像文件系统而言,如果您直接使用RUN 命令,或者RUN 一个脚本,或者有多个RUN 命令,您会发现没有区别。层数和命令字符串的大小根本没有任何区别。

    你能观察到什么?

    • 特别是在“经典”Docker 构建系统上,每个RUN 命令都会成为一个镜像层。在你的例子中,你RUN yum install &amp;&amp; ... &amp;&amp; &lt;some cleanup&gt;;如果将其拆分为多个RUN 命令,则未清理的内容将作为图像的一部分提交并占用空间,即使它在后面的层中被删除。

      “更多层”本身并不一定是坏事,除非你有太多的层以至于你达到了内部限制。这里唯一真正的缺点是创建一个包含您打算删除的内容的图层,在这种情况下,它的空间仍将在最终图像中。

    • 作为一个更具体的示例,有时会出现一种模式,即映像会安装一些仅用于开发的软件包、运行安装步骤并卸载软件包。基于 Alpine 的示例可能如下所示

      RUN apk add --virtual .build-deps \
            gcc make \
       && make \
       && make install \
       && apk del .build-deps
      

      在这种情况下,您必须在同一个RUN 命令中运行“安装”和“卸载”;否则 Docker 将创建一个包含仅构建包的层。

      (多阶段构建可能是一种更简单的方法来实现需要仅构建工具的相同目标,但不将它们包含在最终映像中。)

    • RUN 命令的实际文本在docker history 和类似的检查命令中可见。

    而且……就是这样。如果您认为将安装步骤保存在单独的脚本中更易于维护(也许您有一些方法可以在非 Docker 上下文中使用相同的脚本),那就去吧。我通常会默认保留RUN 命令中说明的步骤,并且通常尽量保持这些设置步骤的轻量级。

    【讨论】:

      【解决方案2】:

      我猜这个问题有点基于意见。

      这取决于你追求什么。这最终是在开发体验和优化图像之间进行权衡。

      如果您将所有内容都放在 RUN 指令中,您将在一定程度上减少层数,从而减少图像大小。此外,每一层都存储在注册表中,因此推送和拉取会变得更加耗时和昂贵。 另一方面,这意味着每个小的更改都会导致 RUN 指令中的所有内容再次运行,因为它会使该单层的缓存无效。

      如果您正在使用 RUN 指令创建临时文件,而这些临时文件会被稍后的 RUN 指令删除,那么最好在一条指令中运行这两个命令,而不是使用临时文件创建层。

      对于生产映像,我会选择单个 RUN 指令,因为优化比构建速度和缓存更重要,IMO。如果可以,您还可以使用多阶段,其中第一阶段使用单独的 RUN 指令来利用层缓存。在第二阶段,从第一阶段获取一些人工制品,并积极地将层数保持在最低限度。只有最后阶段会从注册表中推送和拉取。

      例如,在下图中,构建器阶段使用了比严格要求更多的指令来获得更好的缓存。即使模板文件被复制到第一阶段,即使它根本没有在那里使用,因为它只是在运行时读取和使用。但是这样最后阶段可以通过一条 COPY 指令获得输出二进制文件和模板。

      FROM golang as builder
      WORKDIR /src
      COPY go.mod go.sum ./
      RUN go mod download
      COPY *.go /src/
      RUN mkdir -p /dist/templates
      RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -o /dist/run .
      COPY haproxy.cfg.template /dist/templates/
      
      FROM alpine
      WORKDIR /mananger
      COPY --from=builder /dist ./
      ENTRYPOINT ["./run"]
      

      就脚本与 RUN 指令而言,我认为使用 RUN 指令并使用双 & 符号 &amp;&amp; 连接多个命令更为惯用。如果事情变得非常复杂,那么最好使用专用脚本来更好地利用 shell 语法/功能。这取决于你在那里做什么。

      构建步骤是否每次都会运行,即使 .sh 脚本内没有任何变化**?**

      构建步骤只会运行一次并被缓存。只要脚本的内容不会改变,docker 就会使用缓存层。您需要以某种方式将文件放入图像中以预先运行,所以我猜如果文件已更改,则真正的缓存失效已经发生在 COPY 指令中。

      如上一段所述,使用脚本将花费您最少 1 次 COPY 或 ADD 指令,并引入一个额外的层,如果使用了 RUN 指令,则本可以避免。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2021-05-02
        • 2022-01-18
        • 2022-08-11
        • 1970-01-01
        • 1970-01-01
        • 2021-07-04
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多