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

このドキュメントでは、Java 8 アプリケーションを JAR ファイルとして再パッケージ化し、サポートされている Java ランタイムで実行する方法について説明します。サポートされている Java ランタイムを使用するには、Jetty などのサーバーを埋め込むか、Docker を使用してアプリケーションをコンテナ化してカスタム ランタイムを作成します。アプリケーションを完全に書き換える必要はありません。既存の WAR アプリケーションは、最新の Java プラットフォームまたはフレキシブルなクラウド環境で実行できます。デプロイ戦略とインフラストラクチャに最適な方法を、次の中から選択します。

Java 8 ウェブ アプリケーション(WAR ファイル)を準備する

Java 8 アプリケーションをサポートされている JAR ファイルとして再パッケージ化する前に、WAR ファイルをビルドする必要があります。このセクションでは、WAR ファイルをビルドする Java 8 アプリケーションのサンプルについて説明します。手順に沿って Java 8 の hello-world アプリケーションを作成します。

  1. ソース ディレクトリに HelloServlet.java ファイルを作成します。

    
    import java.io.IOException;
    import javax.servlet.ServletException;
    import javax.servlet.annotation.WebServlet;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    @WebServlet("/hello")
    public final class HelloServlet extends HttpServlet {
      /**
       * This method handles GET requests to the /hello endpoint.
       *
       * <p>Subclasses should not override this method.
       *
       * @param request the HttpServletRequest object
       * @param response the HttpServletResponse object
       * @throws ServletException if a servlet-specific error occurs
       * @throws IOException if an I/O error occurs
       */
      @Override
      protected void doGet(
          final HttpServletRequest request, final HttpServletResponse response)
          throws ServletException, IOException {
        response.setContentType("text/html");
        response.getWriter().println("<h1>Hello, World!</h1>");
      }
    }
  2. web.xml デプロイ記述子ファイルを作成して、ウェブ アプリケーションを構成します。

    <?xml version="1.0" encoding="UTF-8"?>
    <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
             version="4.0">
    
        <welcome-file-list>
            <welcome-file>index.jsp</welcome-file>
        </welcome-file-list>
    
    </web-app>
  3. ランディング ページ index.jsp を作成する

    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <html>
    <head>
        <title>Hello App Engine</title>
    </head>
    <body>
        <h1>Welcome to Google App Engine!</h1>
        <p><a href="hello">Say Hello</a></p>
    </body>
    </html>
    
  4. pom.xml ファイルに次のコードを追加して、Java 8 アプリケーションのビルドを定義します。

    • WAR のパッケージ構成:

      <groupId>com.example</groupId>
      <artifactId>HelloWorldApp</artifactId>
      <version>1.0</version>
      <packaging>war</packaging>
      
    • maven.compiler ソースとターゲットがバージョン 1.8 に設定された maven-war-plugin プラグイン:

      <properties>
          <maven.compiler.source>1.8</maven.compiler.source>
          <maven.compiler.target>1.8</maven.compiler.target>
          <java.version>8</java.version>
      </properties>
      
    • javax.servlet-api の依存関係:

      <dependencies>
          <dependency>
              <groupId>javax.servlet</groupId>
              <artifactId>javax.servlet-api</artifactId>
              <version>3.1.0</version>
              <scope>provided</scope>
          </dependency>
      </dependencies>
      
    • Maven の構成:

        <build>
            <plugins>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <version>3.8.1</version> <configuration>
                        <source>${maven.compiler.source}</source>
                        <target>${maven.compiler.target}</target>
                    </configuration>
                </plugin>
      
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-war-plugin</artifactId>
                    <version>3.3.2</version>
                </plugin>
      
            </plugins>
        </build>
      

    プロジェクト ディレクトリの構造は次のようになります。

    ├── pom.xml
    └── src
        └── main
            ├── java
            │   └── com
            │       └── example
            │           └── HelloServlet.java
            └── webapp
                ├── WEB-INF
                │   └── web.xml
                └── index.jsp
    
    
  5. アプリケーションのプロジェクト ディレクトリで mvn install を実行して、ターゲット ディレクトリに WAR ファイル HelloWorldApp-1.0.war を生成します。

Dockerfile を使用してアプリケーションをデプロイする(推奨)

カスタム ランタイムは、App Engine カスタム ランタイムなどのカスタム コンテナをサポートするプラットフォームに適しています。カスタム ランタイムを使用するとランタイム環境を構成できるため、柔軟性が向上します。カスタム ランタイムのデプロイのチュートリアルの例については、App Engine フレキシブル環境でカスタム ランタイム アプリを作成するをご覧ください。

次の手順では、Dockerfile を使用して Java 8 アプリケーションをコンテナ化する方法について説明します。

  1. Java 8 ウェブ アプリケーション(WAR ファイル)を準備する
  2. コンテナ イメージをビルドして Artifact Registry に push する
  3. アプリケーションをデプロイする

コンテナ イメージをビルドして Artifact Registry に push する

このセクションでは、Cloud Build を使用して Docker イメージをビルドし、Artifact Registry リポジトリに push する方法について説明します。アプリケーションのコンテナ イメージを作成する手順は次のとおりです。

  1. ソース ディレクトリに cloudbuild.yaml ファイルを作成して Docker イメージをビルドし、Artifact Registry に push します。

    steps:
    # Step 1: Build the Docker image
    - name: "gcr.io/cloud-builders/docker"
        args:
        - "build"
        - "-t"
        - "$LOCATION-docker.pkg.dev/$PROJECT/$REPOSITORY/SERVICE:VERSION"
        - "."
    
    # Step 2: Push the Docker image to Artifact Registry
    - name: "gcr.io/cloud-builders/docker"
        args:
        - "push"
        - "$LOCATION-docker.pkg.dev/$PROJECT/$REPOSITORY/SERVICE:VERSION"
    
    images:
    - "$LOCATION-docker.pkg.dev/$PROJECT/$REPOSITORY/SERVICE:VERSION"
    

    次のように置き換えます。

    • LOCATION は、アプリをデプロイする Google Cloud リージョンに置き換えます。
    • PROJECT: 実際の Google Cloud プロジェクト ID。
    • REPOSITORY: Artifact Registry リポジトリの名前。
    • IMAGE は、コンテナ イメージの URL に置き換えます。
    • TAG は、コンテナ イメージのタグに置き換えます。
  2. 次の構成の Dockerfile を作成します。

    
    # Use Maven to build the project with JDK 8
    FROM maven:3.8.6-openjdk-8 AS build
    
    # Set working directory
    WORKDIR /app
    
    # Copy the application source code
    COPY . .
    
    # Build the application
    RUN mvn clean package
    
    # Use Jetty as the runtime
    FROM jetty:9.4-jdk8
    
    # Set Jetty working directory
    WORKDIR /var/lib/jetty/webapps
    
    # Copy the built WAR file
    COPY --from=build /app/target/*.war ./ROOT.war
    
    # Expose the default Jetty port
    EXPOSE 8080
    
    # Start Jetty correctly
    CMD ["java", "-Djetty.base=/var/lib/jetty", "-jar", "/usr/local/jetty/start.jar"]
  3. Docker をダウンロードしてインストールし、サンプルアプリをテストして、ローカルマシンで Hello World コンテナを実行します。

  4. アプリケーション コンテナ イメージをビルドして Artifact Registry に push します。

    gcloud builds submit .
    

アプリケーションをデプロイする

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

  1. ソース ディレクトリでカスタム ランタイムを使用するように app.yaml ファイルを構成します。

    runtime: custom
    env: flex
    instance_class: F1
    
    handlers:
      - url: /.*
        script: auto

    プロジェクト ディレクトリの構造は次のようになります。

    ├── Dockerfile
    ├── README.md
    ├── app.yaml
    ├── cloudbuild.yaml
    ├── pom.xml
    └── src
        └── main
            ├── java
            │   └── com
            │       └── example
            │           └── HelloServlet.java
            └── webapp
                ├── WEB-INF
                │   └── web.xml
                └── index.jsp
    
  2. gcloud app deploy コマンドを使用してアプリケーションをデプロイします。

    gcloud app deploy --image-url=REGION-docker.pkg.dev/PROJECT_ID/REPOSITORY/IMAGE:tag
    

    次のように置き換えます。

    • LOCATION は、アプリをデプロイする Google Cloud リージョンに置き換えます。
    • PROJECT: 実際の Google Cloud プロジェクト ID。
    • REPOSITORY: Artifact Registry リポジトリの名前。
    • IMAGE は、コンテナ イメージの URL に置き換えます。
    • TAG は、コンテナ イメージのタグに置き換えます。

埋め込み Java ランタイムを使用する

次の手順は、埋め込みサーバー(Jetty)を使用して App Engine Java 8 アプリケーションを再パッケージ化し、サポートされている Java ランタイムでスタンドアロン JAR として実行する方法です。

  1. 埋め込み Jetty サーバーを作成する
  2. Java 8 ウェブ アプリケーション(WAR ファイル)を準備する
  3. Jetty が埋め込まれた WAR ファイルを実行してアプリケーションをデプロイする

埋め込み Jetty サーバーを作成する

アプリケーションの WAR ファイルを埋め込み Jetty サーバーとバンドルする手順は次のとおりです。

  1. Main クラスを作成して、WAR ファイルを実行するように Jetty サーバーを初期化して構成します。Main クラスでは、デフォルトが 8080 のサーバーポートが設定されます。PORT 環境変数で指定されたポートを使用するようにソースコードを変更することもできます。Main クラスでは、WAR ファイルを提供するよう WebAppContext ハンドラが構成されます。

    import org.eclipse.jetty.server.Server;
    import org.eclipse.jetty.webapp.Configuration.ClassList;
    import org.eclipse.jetty.webapp.WebAppContext;
    
    /** Simple Jetty Main that can execute a WAR file when passed as an argument. */
    public class Main {
    
      public static void main(String[] args) throws Exception {
        if (args.length != 1) {
          System.err.println("Usage: need a relative path to the war file to execute");
          System.exit(1);
        }
        System.setProperty("org.eclipse.jetty.util.log.class", "org.eclipse.jetty.util.log.StrErrLog");
        System.setProperty("org.eclipse.jetty.LEVEL", "INFO");
    
        // Create a basic Jetty server object that will listen on port defined by
        // the PORT environment variable when present, otherwise on 8080.
        int port = Integer.parseInt(System.getenv().getOrDefault("PORT", "8080"));
        Server server = new Server(port);
    
        // The WebAppContext is the interface to provide configuration for a web
        // application. In this example, the context path is being set to "/" so
        // it is suitable for serving root context requests.
        WebAppContext webapp = new WebAppContext();
        webapp.setContextPath("/");
        webapp.setWar(args[0]);
        ClassList classlist = ClassList.setServerDefault(server);
    
        // Enable Annotation Scanning.
        classlist.addBefore(
            "org.eclipse.jetty.webapp.JettyWebXmlConfiguration",
            "org.eclipse.jetty.annotations.AnnotationConfiguration");
    
        // Set the the WebAppContext as the ContextHandler for the server.
        server.setHandler(webapp);
    
        // Start the server! By using the server.join() the server thread will
        // join with the current thread. See
        // "http://docs.oracle.com/javase/1.5.0/docs/api/java/lang/Thread.html#join()"
        // for more details.
        server.start();
        server.join();
      }
    }
  2. Maven プロジェクト ファイル pom.xml を作成し、次の構成を追加します。

    1. maven.compiler.source プロパティと maven.compiler.target プロパティを、サポートされている Java ランタイムに設定します。

      <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>21</maven.compiler.source>
        <maven.compiler.target>21</maven.compiler.target>
        <jetty.version>9.4.57.v20241219</jetty.version>
      </properties>
    2. Jetty の依存関係を追加します。

      <dependencies>
        <!-- Embedded Jetty dependencies -->
        <dependency>
          <groupId>org.eclipse.jetty</groupId>
          <artifactId>jetty-server</artifactId>
          <version>${jetty.version}</version>
        </dependency>
        <dependency>
          <groupId>org.eclipse.jetty</groupId>
          <artifactId>jetty-webapp</artifactId>
          <version>${jetty.version}</version>
          <type>jar</type>
        </dependency>
        <dependency>
          <groupId>org.eclipse.jetty</groupId>
          <artifactId>jetty-util</artifactId>
          <version>${jetty.version}</version>
        </dependency>
        <dependency>
          <groupId>org.eclipse.jetty</groupId>
          <artifactId>jetty-annotations</artifactId>
          <version>${jetty.version}</version>
        </dependency>
        <!-- extra explicit dependency needed because there is a JSP in the sample-->
        <dependency>
          <groupId>org.eclipse.jetty</groupId>
          <artifactId>apache-jsp</artifactId>
          <version>${jetty.version}</version>
        </dependency>
      </dependencies>
    3. maven-assembly-plugin プロパティをパッケージの依存関係に構成します。

      
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-assembly-plugin</artifactId>
        <version>3.0.0</version>
        <configuration>
          <finalName>jetty</finalName>
          <descriptorRefs>
            <descriptorRef>jar-with-dependencies</descriptorRef>
          </descriptorRefs>
          <archive>
            <manifest>
              <mainClass>com.example.appengine.jetty.Main</mainClass>
            </manifest>
          </archive>
        </configuration>
        <executions>
          <execution>
            <id>make-assembly</id>
            <phase>package</phase>
            <goals>
              <goal>single</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
      

    プロジェクト ディレクトリの構造は次のようになります。

    ├─src
    │  └─main
    │      └─java
    │          └─jetty
    │              └─Main.java
    └─pom.xml
    
  3. Jetty ランナー プロジェクト ディレクトリで mvn install コマンドを実行します。これにより、ターゲット ディレクトリに jetty-jar-with-dependencies.jar が生成されます。

  4. Java 8 ウェブ アプリケーション(WAR ファイル)を準備するの手順で WAR ファイルを作成します。

Jetty が埋め込まれた WAR ファイルを実行してアプリケーションをデプロイする

このセクションでは、アプリケーションを実行可能な JAR ファイルにパッケージ化する手順について説明します。アプリケーションをパッケージ化してデプロイする手順は次のとおりです。

  1. 生成された Jetty ランナー JAR jetty-jar-with-dependencies.jar とアプリケーションの WAR ファイル HelloWorldApp-1.0.war を同じディレクトリに配置します。

  2. サポートされている Java ランタイムを使用してアプリケーションを実行します。

        java -jar jetty-jar-with-dependencies.jar HelloWorldApp-1.0.war
    
    1. ウェブブラウザで http://localhost:8080/ に移動します。アプリケーションのスタートページが表示されます。
  3. entrypoint 要素を app.yaml ファイルに作成して jetty-jar-with-dependencies オブジェクトを呼び出し、WAR ファイルを引数として渡します。WAR ファイルで指定するバージョンは、pom.xml ファイルと同じバージョンである必要があります。

    runtime: java
    env: flex
    runtime_config:
      operating_system: ubuntu22
      runtime_version: 21
    entrypoint: "java -jar jetty-jar-with-dependencies.jar sample.war"
    handlers:
      - url: /.*
        script: this field is required, but ignored
    
    manual_scaling:
      instances: 1
  4. gcloud app deploy コマンドを使用してアプリケーションをデプロイします。