使用 Java SE 订阅企业性能包创建多平台容器映像

每个 Java 版本都附带许多功能,例如 Java 9 中的 JEP 254:紧凑字符串 和 Java 15 中的 JEP 377:ZGC:可扩展低延迟垃圾回收器(生产) 等。由于这些更改从这些版本开始才可用,因此无法升级其 Java 应用程序的开发人员无法使用这些功能。

Java SE 订阅企业性能包 (EPP) 将 Java 17 中的改进引入 Java 8 系列应用程序。Java EPP 旨在成为任何 Java 8 后端应用程序的替代品。采用 Java EPP 的客户在减少垃圾回收时间、降低内存占用和提高吞吐量方面取得了显著收益。

由于容器映像是运行现代应用程序的常见方式,因此本文旨在向您展示如何创建与 amd64arm64 架构兼容的多平台 Java EPP 容器映像。使用此方法,您可以在不同的架构上运行相同的 Java 代码,从而使您能够将工作负载移动到提供最佳性能的平台。

先决条件

Java EPP 包含在 Oracle Java SE 订阅中,并且还可根据 Java SE OTN 许可证 下载,用于开发、原型制作等。要尝试此处显示的示例,您需要以下内容

  1. Oracle 帐户,用于根据 OTN 许可证 下载 Java EPP 二进制文件,或 Oracle Java SE 订阅
  2. 支持创建多平台容器映像的与 docker 兼容的构建工具,例如 buildx 和 BuildKit
  3. 用于推送容器映像的容器注册表。本示例使用 在 OCI 中创建的自定义容器注册表,但任何容器注册表都可以。

下载 Java EPP 二进制文件

您可以从以下位置下载 Java EPP 二进制文件

您应该为每个架构(amd64arm64)下载 .tar.gz 文件,并将它们放在名为 binaries 的目录中。您可以使用 tree 命令验证 ./binaries 目录中是否存在二进制文件

$ tree './binaries'

./binaries
├── jdk-8u371-perf-linux-aarch64.tar.gz
└── jdk-8u371-perf-linux-x64.tar.gz

注意 Java EPP 二进制文件在其文件名中有一个独特的 -perf


您无需提取这些二进制文件。这些文件将在容器映像创建期间复制到容器映像中并提取。

生成应用程序 JAR 文件

使用您首选的构建工具(例如 GradleMavenAnt 等)创建应用程序 JAR 文件。您可以将应用程序打包为单个 fat JAR 文件或多个 JAR 文件。

不同的构建工具在不同的默认输出目录中创建 JAR 文件(并复制依赖项)。例如,Gradle 使用 ./build/libs 目录,而 Maven 使用 ./target 目录作为其各自的输出目录。为了不依赖于构建工具,我们将假设所有应用程序 JAR 文件都位于 ./jars 目录中。

在此示例中,应用程序主类位于包 demo 中,我们将使用以下命令启动应用程序

$ java -classpath './jars/*' demo.Main

创建多架构 Dockerfile

容器映像是打包、分发和运行现代应用程序的常用方式。开发人员将他们的代码及其所有依赖项打包到容器映像中,并通过容器注册表(例如 Oracle Container Registry)分发,以便在运行时直接访问容器托管环境。容器映像的用户不必担心如何配置应用程序或使用哪个版本的 Java,因为所有内容都封装在容器映像中。

为了简化在不同架构中采用技术,许多容器映像都内置了多平台支持,例如 container-registry.oracle.com/java/jdk-no-fee-term:17.0.7-oraclelinux8 容器映像。

Docker 通过其 buildx 插件--platform 选项简化了多平台支持。使用这些功能,我们的容器映像的用户可以指向同一标记,然后 docker 将为其架构选择正确的容器映像。例如,JDK 17 容器映像具有多平台支持,并且适用于 amd64arm64 架构。

您可以使用 inspect 选项来检查图像标记下的所有受支持架构

$ docker buildx imagetools \
  inspect container-registry.oracle.com/java/jdk-no-fee-term:17.0.7-oraclelinux8

Name:      container-registry.oracle.com/java/jdk-no-fee-term:17.0.7-oraclelinux8
MediaType: application/vnd.docker.distribution.manifest.list.v2+json
Digest:    sha256:6eb6accdd2afb3118d6487b358a928ec9c6d3fb9abd30107c9f15d0e05634e18

Manifests:
  Name:      container-registry.oracle.com/java/jdk-no-fee-term:17.0.7-oraclelinux8@sha256:d93847cfa8a7e66ea7fea7b6aaf12b225e5e4c9fdef3a6a3544c8ce3aaa79acc
  MediaType: application/vnd.docker.distribution.manifest.v2+json
  Platform:  linux/arm64

  Name:      container-registry.oracle.com/java/jdk-no-fee-term:17.0.7-oraclelinux8@sha256:ff56f1ff813ff71e5bbe2804e1eedd3cd42983e87e3b5ff8caf8a62da6bf9027
  MediaType: application/vnd.docker.distribution.manifest.v2+json
  Platform:  linux/amd64

由于我们的目标是支持 amd64arm64 架构,因此我们将使用多阶段 docker 构建,其中为每个架构定义一个中间阶段。这类似于 Java 多态性,因为 docker 守护程序将根据目标架构使用正确的阶段。 Oracle Linux 9 基础映像 将在我们的示例中用作基础映像,但您可以选择任何同时支持 amd64arm64 架构的 64 位 Linux 发行版。


注意 您应该知道 Java EPP 仅在 64 位 Linux 上运行。


Dockerfile 包含四个阶段,我们将一次构建一个阶段,并随着构建过程描述每个阶段。请随时跳过以查看最终示例。

1⋅ 创建仅包含操作系统的基本阶段

FROM container-registry.oracle.com/os/oraclelinux:9 AS base
WORKDIR /opt

容器镜像构建工具将根据所需架构使用正确的容器镜像。例如,在为amd64架构构建容器镜像时,将使用该操作系统的amd64版本。

2⋅ 为arm64架构添加一个扩展基本操作系统阶段的阶段

在此阶段,我们将仅执行arm64架构所需的配置。复制arm64 Java EPP 二进制文件 (./binaries/jdk-8u371-perf-linux-aarch64.tar.gz) 并将其解压到 /opt/jdk 目录中。

FROM base AS base-arm64
COPY ./binaries/jdk-8u371-perf-linux-aarch64.tar.gz jdk-8u371-perf-linux-aarch64.tar.gz
RUN tar xvfz jdk-8u371-perf-linux-aarch64.tar.gz \
       && rm jdk-8u371-perf-linux-aarch64.tar.gz \
       && mv jdk1.8.0_371 jdk

如果您将 jdk-8u371-perf-linux-aarch64.tar.gz 二进制文件保存在其他位置,则应更新路径。

请注意,阶段名称 base-arm64 中包含架构名称。在构建此容器镜像的arm64变体时,容器镜像构建工具将根据其名称选择此阶段。

我们的 Dockerfile 现在包含两个阶段。

FROM container-registry.oracle.com/os/oraclelinux:9 AS base
WORKDIR /opt


FROM base AS base-arm64
COPY ./binaries/jdk-8u371-perf-linux-aarch64.tar.gz jdk-8u371-perf-linux-aarch64.tar.gz
RUN tar xvfz jdk-8u371-perf-linux-aarch64.tar.gz \
       && rm jdk-8u371-perf-linux-aarch64.tar.gz \
       && mv jdk1.8.0_371 jdk

3⋅ 为amd64架构添加一个扩展基本操作系统阶段的阶段

这与前一阶段类似,但有两个关键区别。此阶段名为 base-amd64,并且它使用amd64 Java EPP 二进制文件 (./binaries/jdk-8u371-perf-linux-aarch64.tar.gz)。

FROM base AS base-amd64
COPY ./binaries/jdk-8u371-perf-linux-x64.tar.gz jdk-8u371-perf-linux-x64.tar.gz
RUN tar xvfz jdk-8u371-perf-linux-x64.tar.gz \
       && rm jdk-8u371-perf-linux-x64.tar.gz \
       && mv jdk1.8.0_371 jdk

在这两个阶段中,docker 守护程序将 Java EPP 解压到 /opt/jdk 目录中。虽然这不是必须的,但将两个版本解压到同一目录路径可以简化后续阶段,因为这些阶段可以假定在 /opt/jdk 目录下找到了 Java。

Dockerfile 现在有三个阶段。

FROM container-registry.oracle.com/os/oraclelinux:9 AS base
WORKDIR /opt


FROM base AS base-arm64
COPY ./binaries/jdk-8u371-perf-linux-aarch64.tar.gz jdk-8u371-perf-linux-aarch64.tar.gz
RUN tar xvfz jdk-8u371-perf-linux-aarch64.tar.gz \
       && rm jdk-8u371-perf-linux-aarch64.tar.gz \
       && mv jdk1.8.0_371 jdk


FROM base AS base-amd64
COPY ./binaries/jdk-8u371-perf-linux-x64.tar.gz jdk-8u371-perf-linux-x64.tar.gz
RUN tar xvfz jdk-8u371-perf-linux-x64.tar.gz \
       && rm jdk-8u371-perf-linux-x64.tar.gz \
       && mv jdk1.8.0_371 jdk

到目前为止,我们有一个使用 Java EPP 的通用多平台容器镜像。


注意 JAVA_HOME 环境变量未设置,java 可执行文件不在 PATH 中(将在下一阶段配置)。另一种方法是在此阶段和下一阶段之间插入另一个阶段,在该阶段中我们定义 JAVA_HOME 环境变量并将 java 可执行文件放在 PATH 中。


4⋅ 创建一个在构建时扩展前一阶段的最终阶段

docker buildx 插件在全局范围内提供 多个平台参数 (ARG),例如 TARGETARCH。请记住,仅在使用 docker buildx 插件时才可以使用这些参数。

参数 TARGETARCH 包含目标架构的名称,例如 amd64arm64

FROM base-${TARGETARCH}

在为amd64架构构建容器镜像时,上述指令将评估为

FROM base-amd64

在为arm64架构构建容器镜像时,也会发生类似的评估。

该阶段的其余部分相当标准。

FROM base-${TARGETARCH}
WORKDIR /opt/app
ENV JAVA_HOME "/opt/jdk"
ENV PATH "${PATH}:${JAVA_HOME}/bin"

COPY ./jars .

ENTRYPOINT ["java", "-classpath", "./*", "demo.Main"]

最终的 Dockerfile 类似于

FROM container-registry.oracle.com/os/oraclelinux:9 AS base
WORKDIR /opt


FROM base AS base-arm64
COPY ./binaries/jdk-8u371-perf-linux-aarch64.tar.gz jdk-8u371-perf-linux-aarch64.tar.gz
RUN tar xvfz jdk-8u371-perf-linux-aarch64.tar.gz \
    && rm jdk-8u371-perf-linux-aarch64.tar.gz \
    && mv jdk1.8.0_371 jdk


FROM base AS base-amd64
COPY ./binaries/jdk-8u371-perf-linux-x64.tar.gz jdk-8u371-perf-linux-x64.tar.gz
RUN tar xvfz jdk-8u371-perf-linux-x64.tar.gz \
    && rm jdk-8u371-perf-linux-x64.tar.gz \
    && mv jdk1.8.0_371 jdk


FROM base-${TARGETARCH}
WORKDIR /opt/app
ENV JAVA_HOME "/opt/jdk"
ENV PATH "${PATH}:${JAVA_HOME}/bin"

COPY ./jars .

ENTRYPOINT ["java", "-classpath", "./*", "demo.Main"]

我们可以继续构建此容器映像。

构建并发布多平台容器映像

先决条件部分中所述,您将需要以下内容

  1. 支持amd64arm64架构的buildx上下文
  2. 将发布映像的容器注册表

首先,您应该验证是否存在同时支持amd64arm64架构的buildx上下文。您可以使用docker buildx ls命令列出所有可用的上下文

$ docker buildx ls

NAME/NODE                 DRIVER/ENDPOINT                           STATUS  BUILDKIT PLATFORMS
...
multi-platform-builder *  docker-container
  multi-platform-builder0 unix:///~/.colima/docker.sock running v0.11.5  linux/arm64, linux/amd64, linux/amd64/v2
...

如果上下文不可用,您可以使用docker buildx create命令创建一个同时支持linux/arm64linux/amd64的上下文

$ docker buildx create \
  --name multi-platform-builder \
  --driver docker-container \
  --bootstrap

使用docker buildx use命令将新上下文设置为当前构建上下文

$ docker buildx use multi-platform-builder

最后,针对linux/amd64linux/arm64平台构建容器映像,并将其推送到您的容器注册表,并具有语义版本1.0.0

$ docker build \
  --platform linux/amd64,linux/arm64 \
  --tag iad.ocir.io/xxxxxxxxxxxx/epp_multi_platform_containers:1.0.0 \
  --push
  .

检查新创建的容器映像,并验证是否支持amd64arm64架构

$ docker buildx imagetools \
  inspect iad.ocir.io/xxxxxxxxxxxx/epp_multi_platform_containers:1.0.0

Name:      iad.ocir.io/xxxxxxxxxxxx/epp_multi_platform_containers:1.0.0
MediaType: application/vnd.oci.image.index.v1+json
Digest:    sha256:870e5bd6d83a718a17c460fc9b25741295b581c0f321ffe9371ef827c35121a8

Manifests:
  Name:        iad.ocir.io/xxxxxxxxxxxx/epp_multi_platform_containers:1.0.0@sha256:3c8669dd275f8dd556c33b1b75890b21d690a5c3bd32879a6a92090a3bccca71
  MediaType:   application/vnd.oci.image.manifest.v1+json
  Platform:    linux/amd64

  Name:        iad.ocir.io/xxxxxxxxxxxx/epp_multi_platform_containers:1.0.0@sha256:bde47574310fcca1b29122d592dee291674540fc6083a0b03debfae9d2ef9ed9
  MediaType:   application/vnd.oci.image.manifest.v1+json
  Platform:    linux/arm64
...

发布容器映像后,您可以运行它

$ docker run iad.ocir.io/xxxxxxxxxxxx/epp_multi_platform_containers:1.0.0

最后的想法

本文将 Java EPP 与多平台容器映像相结合,以使用现代分发渠道交付高性能 Java 8 应用程序。此方法使您能够通过 Java EPP 使用 Java 17 优势运行 Java 8 应用程序。


参考

[1] Java EPP 用户指南

[2] 完整示例(不含 Java EPP 二进制文件)