使用 Java SE 订阅企业性能包创建多平台容器映像
客座作者 Albert Attard(2023 年 6 月 12 日)每个 Java 版本都附带许多功能,例如 Java 9 中的 JEP 254:紧凑字符串 和 Java 15 中的 JEP 377:ZGC:可扩展低延迟垃圾回收器(生产) 等。由于这些更改从这些版本开始才可用,因此无法升级其 Java 应用程序的开发人员无法使用这些功能。
Java SE 订阅企业性能包 (EPP) 将 Java 17 中的改进引入 Java 8 系列应用程序。Java EPP 旨在成为任何 Java 8 后端应用程序的替代品。采用 Java EPP 的客户在减少垃圾回收时间、降低内存占用和提高吞吐量方面取得了显著收益。
由于容器映像是运行现代应用程序的常见方式,因此本文旨在向您展示如何创建与 amd64 和 arm64 架构兼容的多平台 Java EPP 容器映像。使用此方法,您可以在不同的架构上运行相同的 Java 代码,从而使您能够将工作负载移动到提供最佳性能的平台。
先决条件
Java EPP 包含在 Oracle Java SE 订阅中,并且还可根据 Java SE OTN 许可证 下载,用于开发、原型制作等。要尝试此处显示的示例,您需要以下内容
- Oracle 帐户,用于根据 OTN 许可证 下载 Java EPP 二进制文件,或 Oracle Java SE 订阅。
- 支持创建多平台容器映像的与 docker 兼容的构建工具,例如 buildx 和 BuildKit。
- 用于推送容器映像的容器注册表。本示例使用 在 OCI 中创建的自定义容器注册表,但任何容器注册表都可以。
下载 Java EPP 二进制文件
您可以从以下位置下载 Java EPP 二进制文件
您应该为每个架构(amd64 和 arm64)下载 .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 文件
使用您首选的构建工具(例如 Gradle、Maven、Ant 等)创建应用程序 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 容器映像具有多平台支持,并且适用于 amd64 和 arm64 架构。
您可以使用 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
由于我们的目标是支持 amd64 和 arm64 架构,因此我们将使用多阶段 docker 构建,其中为每个架构定义一个中间阶段。这类似于 Java 多态性,因为 docker 守护程序将根据目标架构使用正确的阶段。 Oracle Linux 9 基础映像 将在我们的示例中用作基础映像,但您可以选择任何同时支持 amd64 和 arm64 架构的 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
包含目标架构的名称,例如 amd64
和 arm64
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"]
我们可以继续构建此容器映像。
构建并发布多平台容器映像
如先决条件部分中所述,您将需要以下内容
- 支持amd64和arm64架构的buildx上下文
- 将发布映像的容器注册表
首先,您应该验证是否存在同时支持amd64和arm64架构的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/arm64
和linux/amd64
的上下文
$ docker buildx create \
--name multi-platform-builder \
--driver docker-container \
--bootstrap
使用docker buildx use
命令将新上下文设置为当前构建上下文
$ docker buildx use multi-platform-builder
最后,针对linux/amd64
和linux/arm64
平台构建容器映像,并将其推送到您的容器注册表,并具有语义版本1.0.0
$ docker build \
--platform linux/amd64,linux/arm64 \
--tag iad.ocir.io/xxxxxxxxxxxx/epp_multi_platform_containers:1.0.0 \
--push
.
检查新创建的容器映像,并验证是否支持amd64和arm64架构
$ 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 用户指南