Docker 多阶段构建
云原生应用以容器镜像作为发布形式。云原生应用的构建过程负责把源代码转换成可以直接运行的镜像。这个转换过程根据编程语言而有所不同:
- 解释型语言,如 JavaScript 和 Python,一般并不需要执行额外的构建步骤,可以直接添加源代码。
- 编译型语言,如 Java、Go 和 Rust,则需要首先对源代码进行编译,再把结果添加到镜像中。Go 和 Rust 可以直接编译成原生可执行文件,而 Java 则可以编译成字节代码运行在 JVM 上,也可以通过 GraalVM 进一步编译成原生可执行文件。
对编译型语言来说,整个构建过程至少是分成两步的。编译过程所需要的工具和第三方依赖比较多,而运行时所需要的依赖则很少。以 Java 为例,编译过程可能需要用到 Git、Maven、Gradle、JDK 和 GraalVM 等工具,而运行时则只需要 JDK 或 JRE。编译成原生可执行文件的 Java 应用,连 JDK 或 JRE 都不需要。
为了解决编译和运行阶段的差异性,一般有两种做法:
- 第一种做法是由持续集成服务器来进行编译,容器镜像中直接使用编译的结果。以 Maven 为例,在构建完成之后,只需要把生成的
target
目录下的 JAR 文件复制到镜像中即可。 - 第二种做法是把编译过程也在容器中进行。
第二种做法在某些情况下是必须的。比如,有的镜像注册中心,仅支持构建镜像,并不提供持续集成相关的服务。第二种做法需要用到 Docker 的多阶段构建支持。在多阶段构建中,每一阶段可以有各自的基础镜像,以及构建时执行的操作。前一阶段的结果可以被后面的阶段来直接使用。
下面是 Quarkus 应用的多阶段构建的一个示例。该构建过程分成两个阶段:
- 第一个阶段使用的是 Quarkus 提供的 Maven 镜像,把源代码复制到镜像中,再使用 Maven 命令来构建。
FROM
指令后的AS
为构建阶段指定名称。 - 第二个阶段使用 OpenJDK 11 的基础镜像,并复制第一阶段中产生的文件。
COPY
指令的--from=build
表示复制文件的来源是第一阶段产生的build
镜像,复制文件的路径也来自build
镜像。
FROM quay.io/quarkus/centos-quarkus-maven:21.3.0-java11 AS build
USER root
RUN mkdir /build
COPY src/ /build/src/
COPY pom.xml /build/
RUN cd /build && mvn -B -ntp package
FROM registry.access.redhat.com/ubi8/openjdk-11-runtime:1.10
ENV LANG='en_US.UTF-8' LANGUAGE='en_US:en'
ENV JAVA_OPTIONS="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager"
COPY --from=build --chown=185 /build/target/quarkus-app/lib/ /deployments/lib/
COPY --from=build --chown=185 /build/target/quarkus-app/*.jar /deployments/
COPY --from=build --chown=185 /build/target/quarkus-app/app/ /deployments/app/
COPY --from=build --chown=185 /build/target/quarkus-app/quarkus/ /deployments/quarkus/
EXPOSE 8080
USER 185
ENTRYPOINT [ "java", "-jar", "/deployments/quarkus-run.jar" ]
通过多阶段构建,全部的构建过程都由 Docker 来完成,简化了应用构建的配置。