Docker 多阶段构建

云原生应用以容器镜像作为发布形式。云原生应用的构建过程负责把源代码转换成可以直接运行的镜像。这个转换过程根据编程语言而有所不同:

  • 解释型语言,如 JavaScript 和 Python,一般并不需要执行额外的构建步骤,可以直接添加源代码。
  • 编译型语言,如 Java、Go 和 Rust,则需要首先对源代码进行编译,再把结果添加到镜像中。Go 和 Rust 可以直接编译成原生可执行文件,而 Java 则可以编译成字节代码运行在 JVM 上,也可以通过 GraalVM 进一步编译成原生可执行文件。

对编译型语言来说,整个构建过程至少是分成两步的。编译过程所需要的工具和第三方依赖比较多,而运行时所需要的依赖则很少。以 Java 为例,编译过程可能需要用到 Git、Maven、Gradle、JDK 和 GraalVM 等工具,而运行时则只需要 JDK 或 JRE。编译成原生可执行文件的 Java 应用,连 JDK 或 JRE 都不需要。

为了解决编译和运行阶段的差异性,一般有两种做法:

  1. 第一种做法是由持续集成服务器来进行编译,容器镜像中直接使用编译的结果。以 Maven 为例,在构建完成之后,只需要把生成的 target 目录下的 JAR 文件复制到镜像中即可。
  2. 第二种做法是把编译过程也在容器中进行。

第二种做法在某些情况下是必须的。比如,有的镜像注册中心,仅支持构建镜像,并不提供持续集成相关的服务。第二种做法需要用到 Docker 的多阶段构建支持。在多阶段构建中,每一阶段可以有各自的基础镜像,以及构建时执行的操作。前一阶段的结果可以被后面的阶段来直接使用。

下面是 Quarkus 应用的多阶段构建的一个示例。该构建过程分成两个阶段:

  1. 第一个阶段使用的是 Quarkus 提供的 Maven 镜像,把源代码复制到镜像中,再使用 Maven 命令来构建。FROM 指令后的 AS 为构建阶段指定名称。
  2. 第二个阶段使用 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 来完成,简化了应用构建的配置。

版权所有 © 2024 灵动代码