Mandrel:Quarkusに特化したGraalVMのディストリビューション

Red Hat で Solution Architect として OpenJDK を担当している伊藤ちひろ(@chiroito)です。

この記事は、Red Hat Developerのブログ記事、Mandrel: A specialized distribution of GraalVM for Quarkus | Red Hat Developer の翻訳記事です。


https://developers.redhat.com/sites/default/files/styles/article_feature/public/blog/2021/04/Mandrel_specialized_distribution_GraalVM-01.png?itok=lFJJWIxr

最初にMandrelを発表したとき、なぜRed HatがGraalVMのダウンストリーム・ディストリビューションを必要としているのかを説明しました。私たちは、GraalVMのネイティブイメージ機能、特にQuarkusの文脈で最も興味を持ちました。この記事では、Mandrelとは何か、何ではないかを説明します。また、Mandrelの技術的な特徴を紹介し、QuarkusでMandrelを使用する短いデモンストレーションを行います。

Mandrelとは?

Mandrelは、GraalVMのnative-imageコンポーネントに焦点を当て、Quarkusユーザーが自分のアプリケーションにネイティブイメージを生成するための簡単な方法を提供します。Quarkusを使用している開発者は、Javaのソースコードから、Linux上で動作する無駄のないネイティブなプラットフォーム依存のアプリケーションに至るまで、すべての作業を行えるはずです。この機能は、クラウド・ネイティブアプリケーションの開発モデルにおけるコンテナへのデプロイに不可欠です。

私たちは、Mandrel内でのGraalVMの他の用途については、意図的に約束していません。GraalVMのフルディストリビューションは、ネイティブイメージ以上のものです。多言語サポート、効率的なインタプリタ実装をサポートするTruffleフレームワーク、native-imageのためのLLVMコンパイラバックエンド、HotSpotのC2サーバコンパイラの代替となるlibgraal JITコンパイラ、その他多くの機能を備えています。Mandrelは、GraalVMの機能の小さなサブセットです。私たちは、Quarkusのようなプロジェクトで、native-imageというユースケースをサポートしています。

なぜMandrelなのか?

Mandrelは、GraalVMの不要な部分を取り除き、native-imageに集中できます。メンテナンスやテストのためのコードベースを減らせます。Mandrelは、Labs OpenJDK 11ではなく、アップストリームのOpenJDK 11を使用できます。これはGraalVM CEの基本的なJava開発キットです。Red HatにはOpenJDK for Red Hat customersをサポートするチームがあるので、アップストリームのOpenJDKを使うことはMandrelにとって自然なことでした。また、Red HatはOpenJDKのアップストリームに関わってきた歴史があり、現在はjdk-updates OpenJDK プロジェクトのOpenJDK 11uストリームをリードしています。

これらのプロジェクトにおける私たちのリーダーシップにより、私たちはGraalVM、ひいてはMandrelを改善する安定した変更を行いました。Red Hatはアップストリーム・ファーストの会社なので、最初からパッチをアップストリームに出すようにしています。今のところ、私たちはGraalVM CEとデバッグ能力を増強しました。(-g フラグを使用して)Linux と Windows の両方に対応しています。また、Oracle Labsと協力して、native-imageJDK Flight Recorderサポートの追加を積極的に行っています。統合されれば、開発者はJDK Flight Recorderを使用して、アプリケーションがJVMモードまたはネイティブで実行されているかどうかに関わらず、アプリケーションの動作やパフォーマンス上の課題を観察・追跡できるようになります。

MandrelでのGraalVMコンポーネント

GraalVMのnative-image機能をMandrelで提供するために、Substrate VM フレームワークとその依存関係のみをビルドしています。これには、GraalVM SDKGraalVMコンパイラが含まれます。TruffleSulongなど、その他のGraalVMコンポーネントはMandrelの一部ではなく、我々はそれらをビルドしません。

GraalVMのSDKとコンパイラ以外に、Substrate VMはベースとなるJDKとネイティブ・コンパイラのツール群にも依存しています。MandrelはアップストリームのOpenJDK 11のビルドとGNU Compiler Collection(GCC)を使用してこれらの依存関係を満たしていますが、GraalVMはLabs OpenJDK 11を使用し、GCCとLLVMの両方のツール群をサポートしています。

私たちは、MandrelをOpenJDKの拡張機能と考える傾向があります。ほとんどがJARファイルで、ネイティブなバイナリはほとんどありません。実際、私たちの製品化されたMandrelイメージは、RHELベースの通常の java-11-openjdk-static-libs インストールに加えて、2つのRPMを追加するだけです。

Mandrelのビルド

GraalVMは、Pythonで書かれたツールであるmxを使ってビルドされています。異なるGraalVMコンポーネント間のすべての依存関係はmxスイートによって定義され、ビルドプロセスの一部はmxエクステンションとして定義されています。そのため、mxはMandrelのビルドプロセスにも不可欠な要素となっています。GraalVMとMandrelの両方のビルドをサポートするためにmxスイートを拡張することは、難しいことがわかりました。特に、ビルドするコンポーネントの数を減らしたり、ビルドの手順を分割したりして、異なるビルドシステム上で異なるパーツをビルドすることをサポートするために行った作業を組み合わせることが難しかったです。

現在の解決策は、mandrel-packagingを使用して、Mandrelをビルドしています。このツールは、必要な mx スイートから不要な依存関係を削除し、必要なコマンドを正しい順序で起動します。また、mandrel-packagingツールは、Mandrelを3つのステップでビルドできます。まず、Javaの部品(JARファイルとMavenアーティファクト)を生成し、次にネイティブの部品(GraalVMのスタティックライブラリ)を生成し、最後にOpenJDK 11で組み立て、native imageに対応したランタイムを生成します。

ここでは、OpenJDK 11をベースに、Mandrelの現行バージョンをソースからビルドするための基本的な手順を紹介します。

$ curl -sL https://github.com/AdoptOpenJDK/openjdk11-upstream-binaries/releases/download/jdk-11.0.10%2B9/OpenJDK11U-jdk_x64_linux_11.0.10_9.tar.gz -o jdk.tar.gz
$ curl -sL https://github.com/AdoptOpenJDK/openjdk11-upstream-binaries/releases/download/jdk-11.0.10%2B9/OpenJDK11U-static-libs_x64_linux_11.0.10_9.tar.gz -o jdk-static-libs.tar.gz
$ export JAVA_HOME=$(pwd)/openjdk-11
$ mkdir ${JAVA_HOME}
$ tar xf jdk.tar.gz -C ${JAVA_HOME} --strip-components=1
$ tar xf jdk-static-libs.tar.gz -C ${JAVA_HOME} --strip-components=1
$ git clone --depth 1 --branch 5.279.1 https://github.com/graalvm/mx
$ git clone --depth 1 --branch mandrel-21.0.0.0-Final \
  https://github.com/graalvm/mandrel 
$ git clone --depth 1 --branch mandrel-21.0.0.0-Final \
  https://github.com/graalvm/mandrel-packaging$ $JAVA_HOME/bin/java -ea ./mandrel-packaging/build.java --mx-home $(pwd)/mx
$ ./mandrel-java11-21.0.0.0-Final/bin/native-image --version
GraalVM Version 21.0.0.0-Final (Mandrel Distribution) (Java Version 11.0.10+9)

Oracle Labsとの協力

GraalVM CE 11は、アップストリームのOpenJDK 11をフォークしたLabs OpenJDK 11をベースにしています。これは、libgraalとGraalVM CEをサポートするベースJDKを構築するために使用されています。Labs OpenJDK 11は、GraalVM CEをビルドしているのと同じグループによってメンテナンスされています。

Graal VM CEはLabs OpenJDK 11をベースにしているため、Graal VMチームは、Java 11の最新のJava-level JVM Compiler Interface (JVMCI)の機能強化を利用して、改善できます。この2つのプロジェクトは非常に密接な関係にあるため、開始当初はビルドするJDKとして、Labs OpenJDK 11をアップストリームのOpenJDK 11に置き換えることは不可能でした。我々はOracle Labsと協力してMandrelを実現しました。

私たちは無地のOpenJDK 11で構築したかったし、Oracle Labsはダウンストリームで保守するパッチの数を減らしたかったのです。この作業の結果、一連のパッチがGraalVMのコードベースと上流のOpenJDK 11に統合されました。これは、今後の協力のための素晴らしい基盤となりました。最も重要なことは、OpenJDKのライブラリを静的な亜種としてビルドすることをOpenJDK 11でサポートする必要がありました。Mandrelは、通常のOpenJDKディストリビューションに含まれる共有ライブラリではなく、OpenJDKのスタティック・ライブラリを使用します。これにより、ネイティブなJavaアプリケーションをネイティブなイメージにリンクできます。これらはOpenJDKのライブラリに依存しています。

QuarkusでMandrelを使う

この記事を書いている時点では、QuarkusがMandrelの主な使用者です。ここでは、Javaで書かれたシンプルなRESTEasy Quarkusアプリケーションのネイティブ実行ファイルを生成するためにMandrelを使用する簡単なデモンストレーションを紹介します。

$ echo | mvn io.quarkus:quarkus-maven-plugin:1.12.2.Final:create
$ cd code-with-quarkus
$ ./mvnw -Dnative verify \
         -Dquarkus.native.container-build=true \
         -Dquarkus.native.builder-image=quay.io/quarkus/ubi-quarkus-mandrel:20.3-java11
$ ./target/code-with-quarkus-1.0.0-SNAPSHOT-runner  
__  ____  __  _____   ___  __ ____  ______ 
 --/ __ \/ / / / _ | / _ \/ //_/ / / / __/  
 -/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \    
--\___\_\____/_/ |_/_/|_/_/|_|\____/___/    
2021-03-08 17:57:40,084 INFO  [io.quarkus] (main) code-with-quarkus 1.0.0-SNAPSHOT native (powered by Quarkus 1.12.2.Final) started in 0.014s. Listening on: http://0.0.0.0:8080
2021-03-08 17:57:40,084 INFO  [io.quarkus] (main) Profile prod activated.  
2021-03-08 17:57:40,084 INFO  [io.quarkus] (main) Installed features: [cdi, resteasy]
$ curl -w "\n" http://localhost:8080/hello-resteasy
Hello RESTEasy

まず、シンプルなRESTEasy Quarkusアプリケーションを、code-with-quarkusというディレクトリに作成します。そして、Mavenのラッパー(mvnw)を使ってJavaのソースコードをコンパイルし、定義済みのコンテナイメージを使ってJavaのクラスファイルからネイティブの実行ファイルをビルドします。-DnativeパラメータはMavenにネイティブの実行ファイルを生成するように指示し、-Dquarkus.native.container-build=trueはQuarkusにビルダーイメージを使用してネイティブの実行ファイルをビルドするように指示し、-Dquarkus.native.builder-image=...どのビルダーイメージを使用するかを指示します。この例についての詳細は、Quarkus ガイドを参照してください。

生成されたバイナリ./target/code-with-quarkus-1.0.0-SNAPSHOT-runnerは、最小のUBI 8イメージのような、基本的なLinuxディストリビューション上で動作するのに適したネイティブELFバイナリです。

$ file target/code-with-quarkus-1.0.0-SNAPSHOT-runner
target/code-with-quarkus-1.0.0-SNAPSHOT-runner: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=b43068dce7c114a0e8d018370a9d47e1e257de44, not stripped

図1は、Mandrelのネイティブイメージ実行ファイルを使ってQuarkusアプリケーションをコンテナ化するためのワークフローを示しています。

https://developers.redhat.com/sites/default/files/styles/article_floated/public/blog/2021/04/containerize.jpeg?itok=ChSk4s62

図1:MandrelによるQuarkusアプリケーションのコンテナ化

まとめ

Mandrel は GraalVM のダウンストリームリリースであり、Red Hat が支援するプロジェクトにおける Java アプリケーションのネイティブイメージコンパイルに焦点を当てています。そのため、すべてのユーザーにとってGraalVMのネイティブイメージを置き換えるものではありません。例えば、MandrelはpolyglotやLLVMツール群をサポートしていませんが、これはいくつかのユースケースで必要とされるかもしれません。しかし、Red HatのエンジニアはアップストリームのGraalVMに貢献しており、アップストリームのGraalVMコードを最新の状態に保つよう努力しています。

MandrelはGraalVMのGitHub組織の一部であり、その組織の一部としてメンテナンスされています。それはGraalVMのエコシステムの一部であり、私たちはそれを変えるつもりはありません。とはいえ、MandrelはQuarkusのようなプロジェクトの持続可能性のニーズを満たすことを目的としています。私たちの主な目標は、Quarkusのエコシステムの一部であるMandrelのバージョンを安定的に保ち、そのエコシステムのライフサイクルを通してサポートすることです。そのため、特定のMandrelバージョンは、通常のアップストリームGraalVM CEビルドよりも長く更新されることになるでしょう。また、MandrelはOpenJDK 11に依存しているため、OpenJDKの四半期ごとのセキュリティ修正に合わせて最新の状態を維持する必要があります。

私たちのGitHubページのMandrel releasesを定期的にチェックしてください。現在、LinuxとWindows用のダウンロードがあります。私たちは通常、UBI 8ベースのLinuxコンテナイメージをquay.ioに、GitHubのリリースと同時に、あるいはその後すぐに公開しています。

* 各記事は著者の見解によるものでありその所属組織を代表する公式なものではありません。その内容については非公式見解を含みます。