Java 8 から Java 11 への App Engine アプリの移行

Java 8 ランタイムのサポートに加えて、App Engine により Java 11 ランタイムを使用したアプリケーションのホストもサポートされています。既存の App Engine アプリをスタンダード環境の Java 8 から Java 11 に移行する方法を学びます。

Java 8 ランタイムと Java 11 ランタイムの主な違い

  • Java 11 ランタイムから、App Engine スタンダード環境には Memcache やタスクキューなどの App Engine バンドル サービスが含まれなくなりました。代わりに Google Cloud が提供するスタンドアロン プロダクトが、Java 11 ランタイムのほとんどのバンドル サービスに相当します。これらの各 Google Cloud プロダクトは、汎用的な Cloud Java クライアント ライブラリを提供しています。Google Cloud で個別のプロダクトとして提供されないバンドル サービス(画像処理、検索、メッセージングなど)については、この移行ガイドに記載されているサードパーティ プロバイダまたはその他の回避策を使用できます。

    バンドルされた App Engine サービスを削除すると、Java 11 ランタイムで汎用的な方法で完全な Java 開発を行うことができます。Java 11 ランタイムでは、完全に移植可能な標準の Java アプリを作成し、App Engine を含む任意の標準 Java 環境で実行できます。

  • Java 11 ランタイムでは、構成ファイルは XML 形式ではなく YAML 形式になります。たとえば、appengine-web.xml ファイルではなくapp.yaml ファイルでアプリの設定を指定します。appengine-web.xml ファイルを手動で app.yaml ファイルに変換する必要があります。詳細については、app.yaml ファイルの作成をご覧ください。

    他のタイプの App Engine 構成ファイルを YAML に変換する方法については、XML から YAML へのファイル形式の移行をご覧ください。

  • Java 11 ランタイムでは、PORT 環境変数で指定されたポート(推奨)またはポート 8080 で HTTP リクエストに応答するように構成されたウェブサーバーをパッケージしている場合、Java フレームワークを実行できます。たとえば、Java 11 ランタイムでは、Spring Boot UberJAR をそのまま実行できます。

app.yaml ファイルの作成

Java 11 ランタイムでは、アプリケーション設定は app.yaml ファイルに保存されます。既存のアプリケーションから appengine-web.xml ファイルを削除し、app.yaml ファイルに置き換える必要があります。

app.yaml ファイルで必要な要素は runtime 要素のみです。

runtime: java11
# No need for an entrypoint with single fatjar with correct manifest class-path entry.

App Engine が、その他すべての設定のデフォルト値を提供します。たとえば、アプリで使用できるメモリと CPU リソースを決定する F1 インスタンス クラスや、アプリの新しいインスタンスを作成する方法とタイミングを決定する自動スケーリングの設定が含まれます。

gcloud app deploy コマンドで、アプリをデプロイするときに app.yaml ファイルを作成できます。App Engine が作成する app.yaml ファイルには runtime エントリのみが含まれ、アプリはすべての設定にデフォルト値を使用します。

デフォルト設定をオーバーライドする必要がある場合は、app.yaml ファイルを作成して必要な設定を指定します。詳しくは、app.yaml ファイルのリファレンスをご覧ください。

Maven プロジェクトの場合、app.yaml ファイルの標準の場所は src/main/appengine ディレクトリです。App Engine Maven プラグインによって、JAR アーティファクトとこの app.yaml ファイルを含む適切な target/appengine-staging ディレクトリが作成され、デプロイの準備が整います。

Maven プロジェクトの構造のサンプルを次に示します。

MyDir/
  pom.xml
  [index.yaml]
  [cron.yaml]
  [dispatch.yaml]
  src/main/
    appengine/
      app.yaml
    java/com.example.mycode/
      MyCode.java

プロジェクト ディレクトリに複数の JAR ファイルがある場合や、カスタム エントリポイントを指定する場合は、app.yaml ファイルの entrypoint 要素で指定する必要があります。

WAR ファイルを JAR ファイルに再パッケージ化する

Java 11 ランタイムへ移行するには、App Engine Java 8 ウェブ アプリケーションを実行可能な JAR ファイルに再パッケージ化する必要があります。

アプリケーションには、PORT 環境変数で指定されたポート(通常は 8081)の HTTP リクエストに応答するウェブサーバーを起動する Main クラスが必要です。

例:

import com.sun.net.httpserver.HttpServer;
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetSocketAddress;

public class Main {

  public static void main(String[] args) throws IOException {
    // Create an instance of HttpServer bound to port defined by the
    // PORT environment variable when present, otherwise on 8080.
    int port = Integer.parseInt(System.getenv().getOrDefault("PORT", "8080"));
    HttpServer server = HttpServer.create(new InetSocketAddress(port), 0);

    // Set root URI path.
    server.createContext("/", (var t) -> {
      byte[] response = "Hello World from Google App Engine Java 11.".getBytes();
      t.sendResponseHeaders(200, response.length);
      try (OutputStream os = t.getResponseBody()) {
        os.write(response);
      }
    });

    // Start the server.
    server.start();
  }
}

WAR 移行の例

次の手順では、App Engine Java 8 hello-world アプリケーションを Java 11 ランタイムで実行する JAR として再パッケージ化する方法を説明します。

この移行では appengine-simple-jetty-main アーティファクトを使用します。これにより、Main クラスにシンプルな Jetty ウェブサーバーが与えられます。Jetty ウェブサーバーは WAR ファイルを読み込み、実行可能な JAR ファイルにパッケージ化します。

  1. ローカルマシンに埋め込み型 Jetty サーバー アーティファクトのクローンを作成します。

    git clone https://github.com/GoogleCloudPlatform/java-docs-samples
    

    または、zip ファイルとしてサンプルをダウンロードして解凍します。

  2. サンプルコードが入っているディレクトリに移動します。

    cd java-docs-samples/appengine-java11/appengine-simple-jetty-main/
    
  3. 依存関係をローカルにインストールします。

    mvn install
    
  4. プロジェクト pom.xml ファイルに次のコードを追加します。

    • appengine-simple-jetty-main 依存関係
      <dependency>
        <groupId>com.example.appengine.demo</groupId>
        <artifactId>simple-jetty-main</artifactId>
        <version>1</version>
        <scope>provided</scope>
      </dependency>
    • maven-dependencyプラグイン
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-dependency-plugin</artifactId>
        <version>3.1.2</version>
        <executions>
          <execution>
            <id>copy</id>
            <phase>prepare-package</phase>
            <goals>
              <goal>copy-dependencies</goal>
            </goals>
            <configuration>
              <outputDirectory>
                ${project.build.directory}/appengine-staging
              </outputDirectory>
            </configuration>
          </execution>
        </executions>
      </plugin>
      App Engine は ${build.directory}/appengine-staging ディレクトリに配置されたファイルをデプロイします。maven-dependency プラグインをビルドに追加すると、指定した依存関係が正しいフォルダにインストールされます。
  5. entrypoint 要素を app.yaml ファイルに作成して appengine-simple-jetty-main オブジェクトを呼び出し、WAR ファイルを引数として渡します。たとえば、helloworld-servlet サンプル app.yaml ファイルをご覧ください。

    runtime: java11
    entrypoint: 'java -cp "*" com.example.appengine.demo.jettymain.Main helloworld.war'
  6. アプリケーションをローカルで実行するには、次のことを行います。

    1. アプリケーションをパッケージ化します。

      mvn clean package
      
    2. WAR ファイルを引数にしてサーバーを起動します。

      たとえば、java-docs-samples/appengine-java11/appengine-simple-jetty-main/ フォルダから次のコマンドを実行すると、helloworld-servlet サンプルの中のサーバーを起動できます。

      mvn exec:java -Dexec.args="../helloworld-java8/target/helloworld.war"
      
    3. ウェブブラウザに次のアドレスを入力します。

      http://localhost:8080

  7. アプリケーションをデプロイするには:

    gcloud ツール

    gcloud app deploy

    Maven プラグイン

    mvn package appengine:deploy -Dapp.deploy.projectId=PROJECT_ID

    PROJECT_ID を Cloud プロジェクトの ID に置き換えます。pom.xml ファイルですでにプロジェクト ID を指定している場合は、実行するコマンドに -Dapp.deploy.projectId プロパティを含める必要がありません。

フレームワークの柔軟性

Java 11 ランタイムには、ウェブサービス フレームワークは含まれていません。つまり、サーブレット ベースのフレームワークに限定されません。

Google Cloud GitHub リポジトリには、一般的な Java ウェブ フレームワークを使用した hello world サンプルがあります。

App Engine Java SDK からの移行

Java 11 ランタイムは、App Engine Java SDK をサポートしていません。App Engine Java 11 ランタイムを使用するために、既存の App Engine Java 8 アプリとデプロイ プロセス加える必要がある変更を以下に示します。

App Engine 固有の API からの移行

App Engine 固有の API で提供されるほとんどの機能は現在、Java 用 Google Cloud クライアント ライブラリで提供されています。詳しくは、下記のおすすめの代替方法をご覧ください。

Blobstore

データを格納および取得するには、Cloud クライアント ライブラリを介して Cloud Storage を使用します。開始するには、Cloud Storage の使用をご覧ください。

データストア

Datastore API のような NoSQL の Key-Value データベースの代わりに、Datastore モードで Cloud Firestore を使用します。Firestore は Datastore の最新バージョンです。Datastore モードは、主に App Engine アプリで使用されるデータベースにおすすめです。

画像

画像の配信には、Cloud Storage から行う方法、直接配信する方法、サードパーティのコンテンツ配信ネットワーク(CDN)を使用する方法があります。

画像のサイズ変更、変換、操作を行うには、ImageJ2imgscalrthumbnailator などの画像処理ライブラリを使用します。こうしたサードパーティ ライブラリを使用するには、ライブラリを依存関係として追加し、ライブラリの API を呼び出すようにコードを更新します。

また、App Engine Images サービスでは、サービス提供 URL を使用して画像のサイズ変更を処理することで、アプリケーションへの動的リクエストを回避する機能も提供されます。同様の機能は、サイズが変更された画像を事前に生成し、Cloud Storage にアップロードして配信することでも実現できます。あるいは、画像のサイズ変更を提供するサードパーティ コンテンツ配信ネットワーク(CDN)サービスを利用することもできます。

ログ

Cloud Logging を使用するようにアプリを更新することをおすすめします。Cloud Logging は、App Engine ロギングと同じ機能をサポートしています。たとえば、重大度によってアプリが書き込むメッセージをフィルタリングしたり、それらのメッセージを特定のリクエストに関連付けることができます。代わりに、JSON オブジェクトで構造化された特定のデータを含むログメッセージを書き込むことで、フィルタリングと相関を有効にできます。

詳細については、ログの書き込みと表示をご覧ください。

メール

メールを送信するには、SendGridMailgunMailjet などのサードパーティのメール プロバイダを使用します。これらのサービスはいずれも、アプリケーションからメールを送信するための API を提供しています。

Memcache

アプリケーション データをキャッシュに保存するには、Memorystore for Redis を使用します。

モジュール

アプリケーションが実行しているサービスの情報を取得および変更するには、環境変数と App Engine Admin API を組み合わせて使用します。

サービス情報 アクセス方法
現在のアプリケーション ID GAE_APPLICATION 環境変数
現在のプロジェクト ID GOOGLE_CLOUD_PROJECT 環境変数
現在のサービス名 GAE_SERVICE 環境変数
現在のサービス バージョン GAE_VERSION 環境変数
現在のインスタンス ID GAE_INSTANCE 環境変数
デフォルトのホスト名 Admin API の apps.get メソッド
サービスのリスト Admin API の apps.services.list メソッド
特定のサービスのバージョンのリスト Admin API の apps.services.versions.list メソッド
特定のサービスのデフォルト バージョン(トラフィック分割を含む) Admin API の apps.services.get メソッド
特定のバージョンで実行中インスタンスのリスト Admin API の apps.services.versions.instances.list メソッド

アプリケーションで実行中のサービスについての詳細は、Java 11 ランタイム環境をご覧ください。

名前空間

Namespaces API では、テナントごとに一意の名前空間文字列を指定するだけで、マルチテナント アプリでテナント間でデータを分割できました。

Datastore は直接マルチテナンシーをサポートしていますが、他の Google Cloud サービスはサポートしていません。マルチテナントのアプリで他の Google Cloud サービスを使用する場合は、手動でマルチテナンシーを処理する必要があります。完全に分離されたサービス インスタンスを得るには、Cloud Resource Manager API を使ってプログラムで新規プロジェクトを作成し、プロジェクトでリソースにアクセスできます。

OAuth

App Engine OAuth サービスを使用して OAuth 2.0 トークンを確認する代わりに、OAuth 2.0 APIoauth2.tokeninfo メソッドを使用します。

Compute Engine 上で ElasticSearch のような全文検索データベースをホストし、サービスからアクセスします。

タスクキュー

Cloud Tasks REST API、RPC API、または Google Cloud クライアント ライブラリを使用して非同期コードを実行するタスクをキューに入れ、Java 11 App Engine 標準サービスをプッシュ ターゲットとして使用します。詳細については、タスクキューから Cloud Tasks への移行をご覧ください。

pull キューを使用する多くのケース、たとえば、別個のワーカーによって pull されて処理されるタスクやメッセージをキューに追加する場合などにおいて、機能が類似し、配信が保証される Pub/Sub は、代替案として適しています。次の Cloud Tasks API を操作するサンプル プログラムをご覧ください。

ユーザー認証

Users API の代わりに、次のような HTTP ベースの任意の認証メカニズムを使用します。

XML から YAML へのファイル形式の移行

Cloud SDK では、次のファイル形式はサポートされません。

  • cron.xml
  • datastore-index.xml
  • dispatch.xml
  • queue.xml

次の例は、xml ファイルを yaml ファイルに移行する方法を示しています。

自動的にファイルを移行する

xml ファイルを自動的に移行するには:

  1. Cloud SDK バージョン 226.0.0 以降が必要です。 最新バージョンに更新するには、次のコマンドを実行します。

    gcloud components update
    
  2. 移行するファイルごとに、次のいずれかのサブコマンド(cron-xml-to-yamldatastore-indexes-xml-to-yamldispatch-xml-to-yamlqueue-xml-to-yaml)とファイル名を指定します。

    gcloud beta app migrate-config queue-xml-to-yaml MY-QUEUE-XML-FILE.xml
    
  3. 本番環境にデプロイする前に、変換後のファイルを手動でダブルチェックします。

    xml ファイルから yaml ファイルへの正常な変換例については、手動でファイルを移行するのタブをご覧ください。

手動でファイルを移行する

xml ファイルを yaml ファイルに手動で移行するには:

cron.yaml

オブジェクト一覧を含む cron オブジェクトを持つ cron.yaml ファイルを作成します。各オブジェクトは以下に示すように、cron.xml ファイル中の各 <cron> タグ属性に対応するフィールドを持っています。

変換後のcron.yamlファイル:

cron:
- url: '/recache'
  schedule: 'every 2 minutes'
  description: 'Repopulate the cache every 2 minutes'
- url: '/weeklyreport'
  schedule: 'every monday 08:30'
  target: 'version-2'
  timezone: 'America/New_York'
  description: 'Mail out a weekly report'

元の cron.xml ファイル

<?xml version="1.0" encoding="UTF-8"?>
<cronentries>
  <cron>
    <url>/recache</url>
    <description>Repopulate the cache every 2 minutes</description>
    <schedule>every 2 minutes</schedule>
  </cron>
  <cron>
    <url>/weeklyreport</url>
    <description>Mail out a weekly report</description>
    <schedule>every monday 08:30</schedule>
    <timezone>America/New_York</timezone>
    <target>version-2</target>
  </cron>
</cronentries>

詳細については、cron.yaml リファレンス ドキュメントをご覧ください。

dispatch.yaml

オブジェクト一覧を含む dispatch オブジェクトを持つ dispatch.yaml ファイルを作成します。各オブジェクトは以下に示すように、dispatch.xml ファイル中の各 <dispatch> タグ属性に対応するフィールドを持っています。

変換後のdispatch.yamlファイル:

dispatch:
- url: '*/favicon.ico'
  module: default
- url: 'simple-sample.uc.r.appspot.com/'
  module: default
- url: '*/mobile/*'
  module: mobile-frontend

元の dispatch.xml ファイル

<?xml version="1.0" encoding="UTF-8"?>
<dispatch-entries>
  <dispatch>
      <url>*/favicon.ico</url>
      <module>default</module>
  </dispatch>
  <dispatch>
      <url>simple-sample.uc.r.appspot.com/</url>
      <module>default</module>
  </dispatch>
  <dispatch>
      <url>*/mobile/*</url>
      <module>mobile-frontend</module>
  </dispatch>
</dispatch-entries>

詳細については、dispatch.yaml リファレンス ドキュメントをご覧ください。

index.yaml

オブジェクト一覧を含む indexes オブジェクトを持つ index.yaml ファイルを作成します。各オブジェクトは以下に示すように、datastore-indexes.xml ファイル中の各 <datastore-index> タグ属性に対応するフィールドを持っています。

変換後のindex.yamlファイル:

indexes:
- ancestor: false
  kind: Employee
  properties:
  - direction: asc
    name: lastName
  - direction: desc
    name: hireDate
- ancestor: false
  kind: Project
  properties:
  - direction: asc
    name: dueDate
  - direction: desc
    name: cost

元の datastore-index.xml ファイル

<?xml version="1.0" encoding="utf-8"?>
<datastore-indexes
 autoGenerate="true">
   <datastore-index kind="Employee" ancestor="false">
       <property name="lastName" direction="asc" />
       <property name="hireDate" direction="desc" />
   </datastore-index>
   <datastore-index kind="Project" ancestor="false">
       <property name="dueDate" direction="asc" />
       <property name="cost" direction="desc" />
   </datastore-index>
</datastore-indexes>

詳細については、index.yaml リファレンス ドキュメントをご覧ください。

queue.yaml

オブジェクト一覧を含む queue オブジェクトを持つ queue.yaml ファイルを作成します。各オブジェクトは以下に示すように、queue.xml ファイル中の各 <queue> タグ属性に対応するフィールドを持っています。

変換後のqueue.yamlファイル:

queue:
- name: fooqueue
  mode: push
  rate: 1/s
  retry_parameters:
    task_retry_limit: 7
    task_age_limit: 2d
- name: barqueue
  mode: push
  rate: 1/s
  retry_parameters:
    min_backoff_seconds: 10
    max_backoff_seconds: 200
    max_doublings: 0

元の queue.xml ファイル

<queue-entries>
  <queue>
    <name>fooqueue</name>
    <rate>1/s</rate>
    <retry-parameters>
      <task-retry-limit>7</task-retry-limit>
      <task-age-limit>2d</task-age-limit>
    </retry-parameters>
  </queue>
  <queue>
    <name>barqueue</name>
    <rate>1/s</rate>
    <retry-parameters>
      <min-backoff-seconds>10</min-backoff-seconds>
      <max-backoff-seconds>200</max-backoff-seconds>
      <max-doublings>0</max-doublings>
    </retry-parameters>
  </queue>
<queue-entries>