Dockerfile 运行 层与脚本

Dockerfile RUN layers vs script

Docker 版本 19.03.12,构建 48a66213fe

所以在 docker 文件中,如果我有以下几行:

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

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

如果我想要一个更干净的(相对于可追踪的)Docker文件,如果我将这些行放在 .sh 脚本中可以调用该脚本(即 COPY 后跟 运行 语句) .每次构建步骤 运行,即使 .sh 脚本中没有任何变化**?** 在这里寻找一些问题。

我在想,无论什么包是稳定的,都有一个单独的 RUN <those packages> 即在一层和依赖/经常更改的行中,即可以使用用户定义的(docker 构建时 CLI级别参数)将它们保存在单独的 运行 层中(因此我可以有效地使用层缓存)。

想知道您是否认为保持更清洁的 Docker 文件(调用 运行 some.sh)会比可追踪的 Docker 文件(所有内容都列在Docker文件是什么造就了这张图片)。

谢谢。

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

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

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

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

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

例如,在下图中,构建器阶段使用的指令多于严格要求以获得更好的缓存。即使模板文件被复制到第一阶段,即使它根本没有被使用,因为它只在 运行 时间被读取和使用。但是通过这种方式,最后阶段可以通过单个 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"]

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

Will the build step run each time, even though nothing is changes inside .sh script**?**

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

如前一段所述,使用脚本至少会多花费 1 个 COPY 或 ADD 指令,如果使用 运行 指令,则引入一个本可以避免的额外层。

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

你能观察到什么?

  • 特别是在“经典”Docker 构建系统上,每个 RUN 命令都成为一个图像层。在你的例子中,你 RUN yum install && ... && <some cleanup>;如果这被拆分为多个 RUN 命令,那么 un-cleaned-up 内容将作为图像的一部分提交并占用 space,即使它在后面的层中被删除。

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

  • 作为一个更具体的例子,有一个偶然的模式,其中图像安装一些 development-only 包,运行s 一个安装步骤,然后卸载这些包。 Alpine-based 示例可能看起来像

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

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

    (multi-stage 构建可能是实现需要 build-only 工具的相同目标的更简单方法,但不包括在最终图像中。)

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

而且...仅此而已。如果您认为将安装步骤保留在单独的脚本中更易于维护(也许您有某种方法可以在 non-Docker 上下文中使用相同的脚本),那就去做吧。我通常会默认保留 RUN 命令中详细说明的步骤,并且通常会尽量将这些设置步骤保留为 light-weight。