
Dockerfiles中的多阶段构建功能允许您创建具有更高级别缓存和更少保护的小型容器映像。 在本文中,我将向您展示一些高级模板-不仅仅是在构建和运行之间复制文件。 它们使您可以实现功能的最大效率。 但是,如果您是多级装配领域的初学者,那么首先,可能不应该阅读该用户手册 。
版本兼容性
在v17.05版本中向Docker添加了多阶段构建支持。 所有模板均可用于任何后续版本,但由于使用BuildKit服务器端的链接器 ,某些模板的效率更高。 假设BuildKit有效地跳过了未使用的阶段,并在可能的情况下同时创建了阶段(我分别强调了这些示例)。 BuildKit目前已作为构建的实验后端添加到Moby中 ,并且应在Docker CE v18.06中可用。 它也可以独立使用或作为img项目的一部分使用。
阶段继承
多阶段构建增加了几个新的语法概念。 首先,您可以将以FROM
命令开头的阶段命名为AS stagename
并使用COPY
中的--from=stagename
从该阶段复制文件。 实际上, FROM
命令和--from
标签有很多共同点,并非毫无--from
,它们具有相同的名称。 两者都采用相同的参数,可以识别它,或者从这一点开始一个新的阶段,或者将其用作复制文件的源。 也就是说,要在当前阶段使用原始图像质量中的上一个阶段,不仅可以使用--from=stagename
,还可以采用阶段名称FROM stagename
。 如果在Dockerfile的多个命令中使用相同的公共部分,这将很有用:它减少了公共代码并简化了其维护,将子步骤分开。 因此,重建一个阶段不会影响其他阶段的程序集缓存。 因此,调用docker build
时,可以使用--target
标签单独组装每个阶段。
FROM ubuntu AS base RUN apt-get update && apt-get install git FROM base AS src1 RUN git clone … FROM base as src2 RUN git clone …
在此示例中,BuildKit的第二阶段和第三阶段是同时构建的。
直接使用图片
您可以使用--from
标签直接使用映像,而不是在以前仅支持映像引用的FROM
命令中使用汇编阶段名称。 原来是直接从这些图像复制文件。 例如,以下代码中的linuxkit/ca-certificatesimage
直接将TLS CA根复制到当前步骤。
FROM alpine COPY --from=linuxkit/ca-certificates / /
通用图片别名
构建阶段不一定包含任何命令; 它可能包含单个FROM
行。 如果您在多个地方使用该图像,这将简化阅读并使它变得更容易,这样,如果您需要更新整个图像,则只需要更改一行即可。
FROM alpine:3.6 AS alpine FROM alpine RUN … FROM alpine RUN …
在此示例中,使用高山图像的每个地方实际上都被分配为alpine:3.6
,而不是alpine:latest
。 是时候升级到alpine:3.7
,您将需要更改一行,并且毫无疑问:现在,程序集的所有元素都使用了更新版本。
在别名中使用build参数时,这一点尤为重要。 以下示例与上一个示例相似,但是允许用户通过设置--build-arg ALPINE_VERSION=value
选项来重新定义涉及高山图像的程序集的所有实例。 请记住:必须在第一个构建阶段之前确定FROM
命令中使用的任何参数。
ARG ALPINE_VERSION=3.6 FROM alpine:${ALPINE_VERSION} AS alpine FROM alpine RUN …
在“-from”中使用构建参数
COPY
的--from
标签中指定的值不得包含程序集参数。 例如,以下示例无效。
// THIS EXAMPLE IS INTENTIONALLY INVALID FROM alpine AS build-stage0 RUN … FROM alpine ARG src=stage0 COPY --from=build-${src} . .
这是由于必须在组装开始之前确定阶段之间的依赖性。 这样就不需要对所有团队进行持续评估。 例如,在alpine
图像中定义的环境变量会影响--from
值的评估。 之所以可以评估FROM
命令的参数,是因为这些参数是在任何阶段开始之前全局定义的。 幸运的是,正如我们之前所发现的,使用一个FROM
命令定义别名的阶段并对其进行引用就足够了。
ARG src=stage0 FROM alpine AS build-stage0 RUN … FROM build-${src} AS copy-src FROM alpine COPY --from=copy-src . .
现在,如果覆盖程序集参数src
,则将切换最后一个COPY
元素的初始步骤。 请注意:如果不再使用某些步骤,则只有基于BuildKit的链接器才能有效地跳过它们。
使用构建参数的条件
我们被要求在Dockerfile中添加对IF/ELSE
样式条件的支持。 我们尚不知道是否会添加类似内容,但将来我们将尝试在BuildKit中使用客户端支持。 同时,为了实现类似的行为,您可以使用当前的多阶段概念(进行一些计划)。
// THIS EXAMPLE IS INTENTIONALLY INVALID FROM alpine RUN … ARG BUILD_VERSION=1 IF $BUILD_VERSION==1 RUN touch version1 ELSE IF $BUILD_VERSION==2 RUN touch version2 DONE RUN …
前面的示例显示了使用IF/ELSE
记录条件的伪代码。 要在当前的多阶段构建中实现类似的行为,您可能需要将各种分支条件定义为单独的步骤,并使用参数选择正确的依赖路径。
ARG BUILD_VERSION=1 FROM alpine AS base RUN … FROM base AS branch-version-1 RUN touch version1 FROM base AS branch-version-2 RUN touch version2 FROM branch-version-${BUILD_VERSION} AS after-condition FROM after-condition RUN …
Dockerfile的最后一步基于after-condition
阶段,即图像别名(基于BUILD_VERSION
build BUILD_VERSION
识别)。 根据BUILD_VERSION
的值,选择中间部分的此阶段。
请注意:只有基于BuildKit的链接器可以跳过未使用的分支。 在以前版本的链接器中,将构建所有阶段,但是在创建最终映像之前,将丢弃它们的结果。
最低生产阶段的开发/测试助手
最后,让我们看一个结合先前模板的示例,以演示如何创建一个Dockerfile,该Dockerfile创建一个最小的生产映像,然后可以使用其内容来测试和创建开发映像。 让我们从基本的Dockerfile示例开始:
FROM golang:alpine AS stage0 … FROM golang:alpine AS stage1 … FROM scratch COPY --from=stage0 /binary0 /bin COPY --from=stage1 /binary1 /bin
创建最小的生产映像时,这是一个相当常见的选择。 但是,如果您还需要获取替代的开发人员映像或在最后阶段使用这些二进制文件运行测试该怎么办? 首先想到的只是在测试和开发阶段复制相似的二进制文件。 问题是这样的:不能保证您将以相同的组合测试所有生产二进制文件。 在最后阶段,可能会有所更改,但是您会忘记在其他阶段进行类似的更改,或者在复制二进制文件的方式上出错。 最后,我们不是测试单独的二进制文件,而是最终图像。
另一种选择是在生产阶段之后确定开发和测试阶段,并复制生产阶段的全部内容。 然后对生产步骤使用一个FROM
命令,使默认生产步骤再次成为最后一步。
FROM golang:alpine AS stage0 … FROM scratch AS release COPY --from=stage0 /binary0 /bin COPY --from=stage1 /binary1 /bin FROM golang:alpine AS dev-env COPY --from=release / / ENTRYPOINT ["ash"] FROM golang:alpine AS test COPY --from=release / / RUN go test … FROM release
默认情况下,此Dockerfile将继续构建最小的默认映像,例如,带有选项--target=dev-env
将创建一个映像,该映像的外壳程序包含最终版本的所有二进制文件。
希望这对您有所帮助,并建议如何创建更高效的多阶段Dockerfile。 如果您参加DockerCon2018并想了解有关多阶段构建,Dockerfiles,BuildKit或任何相关主题的更多信息,请注册Hallway Track Linker或关注Docker平台内部会议上的Contribute和Collaborate或Black Belt轨道。