Google Cloud 上の Spring Native

1. 概要

この Codelab では、Spring Native プロジェクトについて、それを使用するアプリを構築し、Google Cloud にデプロイする方法を学習します。

そのコンポーネント、プロジェクトの最近の履歴、いくつかのユースケース、そしてプロジェクトで使用するために必要な手順についてご説明します。

Spring Native プロジェクトは現時点では試験運用版であるため、開始するには特定の構成が必要です。ただし、SpringOne 2021 で発表されたように、Spring Native は Spring Framework 6.0 と Spring Boot 3.0 に一流のサポートに統合される予定です。そのため、Spring Native プロジェクトは、リリースの数か月前。

ジャストインタイム コンパイルは、長時間実行されるプロセスなどに対して非常に最適化されていますが、事前コンパイルされたアプリケーションのパフォーマンスがさらに向上するユースケースもあります。これについては、Codelab で説明します。

方法を学ぶ対象

  • Cloud Shell を使用する
  • Cloud Run API を有効にします。
  • Spring Native アプリを作成してデプロイする
  • そのようなアプリを Cloud Run にデプロイする

必要なもの

アンケート

このチュートリアルをどのように使用されますか?

通読するのみ 通読し、演習を行う

Java に対するご経験についてお答えください。

初心者 中級者 上級者

Google Cloud サービスのご利用についてどのように評価されますか?

初心者 中級者 上級者

2. 背景

Spring Native プロジェクトでは、いくつかのテクノロジーを使用して、ネイティブ アプリケーションのパフォーマンスをデベロッパーに提供します。

Spring Native を完全に理解するには、これらのコンポーネント テクノロジーのいくつか、これらによって可能になること、そしてここで連携する方法を理解しておくことをおすすめします。

AOT コンパイル

デベロッパーがコンパイル時に javac を通常どおり実行すると、.java ソースコードがバイトコードで記述された .class ファイルにコンパイルされます。このバイトコードは Java 仮想マシンによってのみ解釈されるため、JVM がコードを実行するには、他のマシンでこのコードを解釈する必要があります。

このプロセスが Java の署名ポータビリティをもたらします。「一度書けば、どこでも実行できる」ことができますが、ネイティブ コードを実行する場合に比べてコストがかかります。

幸いなことに、JVM のほとんどの実装では、ジャストインタイム コンパイルを使用してこの解釈コストを軽減します。これは、関数の呼び出し回数をカウントすることで実現でき、しきい値(デフォルトでは 10,000)を渡すのに十分な頻度で呼び出される場合、実行時にネイティブ コードにコンパイルされ、それ以上の高コストな解釈は行われなくなります。

事前コンパイルは逆のアプローチを採用し、到達可能なすべてのコードをコンパイル時にネイティブの実行可能ファイルにコンパイルします。これにより、ポータビリティと引き換えにメモリ効率と実行時にパフォーマンスが向上します。

5042e8e62a05a27.png

これはもちろんトレードオフであり、そうする価値は必ずしもない。ただし、AOT コンパイルは次のような場合に役立ちます。

  • 起動時間が重要な短いアプリケーション
  • メモリの制約が大きく、JIT のコストが高すぎる可能性がある環境

面白いことに、AOT コンパイルは JDK 9 では試験運用版の機能として導入されましたが、この実装は維持に多額の費用がかかり、追いつかないこともあったため、削除されました Java 17 で導入された、GraalVM を使用するデベロッパーが優先されます。

GraalVM

GraalVM は、高度に最適化されたオープンソースの JDK ディストリビューションで、起動時間の短縮、AOT ネイティブ イメージのコンパイル、ポリグロット機能を備えており、デベロッパーは複数の言語を単一のアプリケーションに混在させることができます。

GraalVM の開発は継続的に行われており、新しい機能の獲得と既存の機能の改善を継続的に行っています。そのため、デベロッパーの皆様は常に最新情報にご注目ください。

最近のマイルストーンには、次のものがあります。

  • 新しいユーザー フレンドリーなネイティブ イメージのビルド出力(2021-01-18
  • Java 17 のサポート(2022-01-18
  • デフォルトで多層コンパイルを有効にし、多言語でのコンパイル時間を短縮しました(2021-04-20

Spring Native

簡単に言うと、Spring Native では、GraalVM のネイティブ イメージ コンパイラを使用して Spring アプリケーションをネイティブの実行可能ファイルに変換できます。

このプロセスでは、コンパイル時にアプリの静的分析を行い、エントリ ポイントから到達可能なアプリケーション内のすべてのメソッドを見つけます。

基本的に、これは「クローズド ワールド」という概念です。この概念は、コンパイル時にすべてのコードを認識し、新しいコードを実行時に読み込むことができないことを前提としています。

ネイティブ画像生成はメモリを大量に消費するプロセスであり、通常のアプリのコンパイルより時間がかかり、Java の特定の側面に制限が課されていることに注意してください。

アプリケーションを Spring Native と連携させるために、コードを変更する必要がない場合もあります。ただし、状況によっては、正常に動作するために特定のネイティブ構成が必要になる場合があります。その場合、Spring Native ではこのプロセスを簡素化するためにネイティブ ヒントを提供することがよくあります。

3. 設定/事前作業

Spring Native の実装を開始する前に、ネイティブ バージョンと比較できるパフォーマンス ベースラインを確立するために、アプリを作成してデプロイする必要があります。

1. プロジェクトの作成

まず、start.spring.io からアプリを取得します。

curl https://start.spring.io/starter.zip -d dependencies=web \
           -d javaVersion=11 \
           -d bootVersion=2.6.4 -o io-native-starter.zip

このスターター アプリは、Spring Boot 2.6.4 を使用します。これは、Spring Native プロジェクトが執筆時点でサポートしている最新バージョンです。

GraalVM 21.0.3 のリリース以降、このサンプルでも Java 17 を使用できます。このチュートリアルでは引き続き、関連する構成を最小限に抑えるために Java 11 を使用します。

コマンドラインで zip を入手したら、プロジェクトのサブディレクトリを作成し、そこに解凍します。

mkdir spring-native

cd spring-native

unzip ../io-native-starter.zip

2. コードの変更

プロジェクトを立ち上げたら、すぐに生命の兆しが見え始め、スプリング ネイティブのパフォーマンスを公表したプロジェクトを発表します。

DemoApplication.java を以下のように編集します。

package com.example.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.event.EventListener;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.time.Duration;
import java.time.Instant;

@RestController
@SpringBootApplication
public class DemoApplication {
    private static Instant startTime;
    private static Instant readyTime;

    public static void main(String[] args) {
        startTime = Instant.now();
                SpringApplication.run(DemoApplication.class, args);
    }

    @GetMapping("/")
    public String index() {
        return "Time between start and ApplicationReadyEvent: "
                + Duration.between(startTime, readyTime).toMillis()
                + "ms";
    }

    @EventListener(ApplicationReadyEvent.class)
    public void ready() {
                readyTime = Instant.now();
    }
}

ここまでで、ベースライン アプリの準備が整いました。次に、ネイティブ アプリケーションに変換する前の起動時間を把握するために、自由にイメージを作成してローカルで実行してみましょう。

イメージをビルドするには:

mvn spring-boot:build-image

docker images demo を使用して、ベースライン画像のサイズを把握することもできます。6ecb403e9af1475e.png

アプリを実行する手順は次のとおりです。

docker run --rm -p 8080:8080 demo:0.0.1-SNAPSHOT

3. ベースライン アプリをデプロイする

アプリができたので、デプロイして時間を記録します。後でネイティブ アプリの起動時間と比較します。

アプリケーションの種類に応じて、さまざまなホスティングを行います。

この例はシンプルなウェブ アプリケーションであるため、Cloud Run を利用してシンプルにすることができます。

ご自身のマシンで作業している場合は、gcloud CLI ツールがインストールされ、更新されていることを確認してください。

Cloud Shell で作業している場合は、ソース ディレクトリで次のコマンドを実行します。

gcloud run deploy

4.アプリケーション構成

1. Maven リポジトリの構成

このプロジェクトはまだ試験運用版段階にあるため、Maven の中央リポジトリでは利用できない試験運用版のアーティファクトを検出できるようにアプリを構成する必要があります。

この作業を行うには、以下の要素を pom.xml に追加します。これは、をお好みのエディタで行うことができます。

次のリポジトリと pluginRepositories セクションを Pom に追加します。

<repositories>
    <repository>
        <id>spring-release</id>
        <name>Spring release</name>
        <url>https://repo.spring.io/release</url>
    </repository>
</repositories>

<pluginRepositories>
    <pluginRepository>
        <id>spring-release</id>
        <name>Spring release</name>
        <url>https://repo.spring.io/release</url>
    </pluginRepository>
</pluginRepositories>

2. 依存関係を追加する

次に、Spring ネイティブ依存関係を追加します。これは、Spring アプリケーションをネイティブ イメージとして実行するために必要です。注: Gradle を使用している場合、このステップは必要ありません

<dependencies>
    <!-- ... -->
    <dependency>
        <groupId>org.springframework.experimental</groupId>
        <artifactId>spring-native</artifactId>
        <version>0.11.2</version>
    </dependency>
</dependencies>

3. プラグインの追加と有効化

AOT プラグインを追加して、ネイティブ画像の互換性とフットプリントを改善します(詳細)。

<plugins>
    <!-- ... -->
    <plugin>
        <groupId>org.springframework.experimental</groupId>
        <artifactId>spring-aot-maven-plugin</artifactId>
        <version>0.11.2</version>
        <executions>
            <execution>
                <id>generate</id>
                <goals>
                    <goal>generate</goal>
                </goals>
            </execution>
        </executions>
    </plugin>
</plugins>

ここでは、spring-boot-maven-plugin を更新して、ネイティブ イメージのサポートを有効にし、paketo ビルダーを使用してネイティブ イメージを構築します。

<plugins>
    <!-- ... -->
    <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
        <configuration>
            <image>
                <builder>paketobuildpacks/builder:tiny</builder>
                <env>
                    <BP_NATIVE_IMAGE>true</BP_NATIVE_IMAGE>
                </env>
            </image>
        </configuration>
    </plugin>
</plugins>

小さなビルダーのイメージは、複数のオプションのうちの 1 つにすぎません。追加のライブラリやユーティリティがほとんどなく、攻撃対象領域を最小限に抑えることができるため、私たちのユースケースに適しています。

たとえば、一般的な C ライブラリへのアクセスを必要とするアプリを作成する場合や、アプリの要件が不明な場合、フルビルダーが適しているかもしれません。

5. ネイティブ アプリをビルドして実行する

設定がすべて完了したら、イメージを作成して、ネイティブのコンパイル済みアプリを実行できます。

ビルドを実行する前に、次の点にご注意ください。

  • 通常のビルド(数分)より時間がかかります d420322893640701.png
  • このビルドプロセスでは、大量のメモリ(数ギガバイト)が必要になる場合がありますcda24e1eb11fdbea.png
  • このビルドプロセスでは、Docker デーモンにアクセスする必要があります。
  • この例では手動でプロセスを実行していますが、ネイティブ ビルド プロファイルを自動的にトリガーするようにビルドフェーズを設定することもできます。

イメージをビルドするには:

mvn spring-boot:build-image

ビルドが完了したら、ネイティブ アプリの動作を確認できます。

アプリを実行する手順は次のとおりです。

docker run --rm -p 8080:8080 demo:0.0.1-SNAPSHOT

この時点で、ネイティブ アプリケーションの方程式を両面から見ていくことができます。

コンパイル時に多少の時間と追加のメモリ使用を捨てましたが、その引き換えに、はるかに速く起動し、ワークロードに応じてメモリを大幅に消費できるアプリケーションを入手しました。

docker images demo を実行してネイティブ画像のサイズを元のサイズと比較すると、劇的な減少が見られます。

e667f65a011c1328.png

また、より複雑なユースケースでは、実行時にアプリが実行する内容を AOT コンパイラに通知するために追加の変更が必要になります。そのため、予測可能なワークロード(バッチジョブなど)は、非常に適している場合がありますが、その他のブランド効果測定には適している場合もあります。

6. ネイティブ アプリをデプロイする

アプリを Cloud Run にデプロイするには、Artifact Registry などのパッケージ マネージャーにネイティブ イメージを取得する必要があります。

1. Docker リポジトリの準備

このプロセスは、リポジトリを作成することによって開始できます。

gcloud artifacts repositories create native-image-docker-repo --repository-format=docker \
--location=us-central1 --description="Repository for our native images"

次に、新しいレジストリにpush されることを認証済みにします。

gcloud CLI を使用すると、このプロセスを大幅に簡素化できます。

gcloud auth configure-docker us-central1-docker.pkg.dev

2. イメージを Artifact Registry に push する

次に、イメージにタグを付けます。

export PROJECT_ID=$(gcloud config list --format 'value(core.project)')

docker tag  demo:0.0.1-SNAPSHOT \
us-central1-docker.pkg.dev/$PROJECT_ID/native-image-docker-repo/quickstart-image:tag2

その後、docker push を使用して Artifact Registry に送信できます。

docker push us-central1-docker.pkg.dev/$PROJECT_ID/native-image-docker-repo/quickstart-image:tag2

3. Cloud Run へのデプロイ

これで、Artifact Registry に保存したイメージを Cloud Run にデプロイする準備が整いました。

gcloud run deploy --image us-central1-docker.pkg.dev/$PROJECT_ID/native-image-docker-repo/quickstart-image:tag2

アプリをネイティブ イメージとしてビルドしてデプロイしたので、アプリケーションを実行するインフラストラクチャのコストを適切に活用できます。

ベースライン アプリのスタートアップ時間をこの新しいネイティブ アプリと比較してみましょう。

6DDE63D35959B1BB.PNG

7. 概要/クリーンアップ

Google Cloud に Spring Native アプリケーションを構築してデプロイしました。

このチュートリアルが Spring Native プロジェクトをよりよく理解し、将来的に必要になった際の参考になれば幸いです。

省略可: サービスをクリーンアップまたは無効にする

この Codelab 用に Google Cloud プロジェクトを作成した場合でも、既存のプロジェクトを再利用する場合も、使用したリソースからの不要な料金が発生しないように注意してください。

作成した Cloud Run サービスの削除または無効化、ホストしたイメージの削除、プロジェクト全体のシャットダウンを行えます。

8. 参考情報

Spring Native プロジェクトは現在、新しい試験的なプロジェクトですが、先行ユーザーが問題のトラブルシューティングを行い、関与できるように、豊富なリソースが提供されています。

参考情報

このチュートリアルに関連するオンライン リソースは次のとおりです。

ライセンス

この作業はクリエイティブ・コモンズの表示 2.0 汎用ライセンスにより使用許諾されています。