Spring Boot 3アプリのJARをOpen Libertyにデプロイして動かす

Open Liberty 23.0.0.9でSpring Boot 3系のアプリケーションの稼働をサポートする `springBoot-3.0` フィーチャーがリリースされました。
これまでもSpring BootアプリをWARファイルにパッケージングしてデプロイはサポートされていました。`springBoot-3.0` フィーチャー使用することで、JARファイルでの稼働もサポートされます。

この記事ではSpring Boot 3で実装したWebアプリケーションのJARをOpen Libertyにデプロイして動作させてみます。

https://openliberty.io/blog/2023/09/19/23.0.0.9.html

## 本記事で使用するコンポーネント

本記事では以下のコンポーネント・バージョンで動作確認を行っています。
各コンポーネントの別バージョンでは動作しない可能性がありますので、本番投入する際には事前に手元で動作確認することをお勧めします。

|コンポーネント名|バージョン|
|:--|:--|
|JDK|OpenJDK 17.0.8 (IBM Semeru Runtime Open Edition)|
|Open Liberty|23.0.0.9|
|Spring Boot|3.1.4|
|Podman|v4.7.0|

```shell
$ java -version
openjdk version "17.0.8" 2023-07-18
IBM Semeru Runtime Open Edition 17.0.8.0 (build 17.0.8+7)
Eclipse OpenJ9 VM 17.0.8.0 (build openj9-0.40.0, JRE 17 Mac OS X amd64-64-Bit Compressed References 20230718_503 (JIT enabled, AOT enabled)
OpenJ9   - d12d10c9e
OMR      - e80bff83b
JCL      - 77b0f754805 based on jdk-17.0.8+7)
```

## アプリケーション準備

この記事ではWebアプリケーションを事前に用意した想定で実施します。Spring Initializerなどで雛形を作成して実装してください。
Open Libertyで動作させるため、依存関係にWebアプリケーションを動作させる依存関係として `spring-boot-starter-web` を最低限含めます。サンプルの `pom.xml` を以下に示します。

なお、Open Libertyにデプロイする場合であってもアプリケーション側は特に固有の設定は不要です。

```xml
<?xml version="1.0" encoding="UTF-8"?>
<project>
    <modelVersion>4.0.0</modelVersion>

    <!-- Spring Boot 3系を使用 -->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.1.4</version>
        <relativePath/>
    </parent>

    <!-- JARファイルは {artifactId}-{version}.jar という名前で出力される -->
    <groupId>com.example</groupId>
    <artifactId>liberty-sb-web</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>liberty-sb-web</name>

    <properties>
        <java.version>17</java.version>
    </properties>

    <!-- 依存ライブラリ -->
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <!-- Mavenビルドで使用するプラグイン -->
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

    <!-- 他の設定値は記載省略 -->
</project>
```


## Open Libertyの設定

重要なポイントはここからです。Open Libertyの設定ファイル `src/main/liberty/config/server.xml` にSpring BootのJARファイルを動かす設定を追加します。

### フィーチャーの追加

Open Libertyのフィーチャーとして2つ宣言します。

* `springBoot-3.0` ... Spring BootアプリのJARファイルを起動する機能を提供
* `servlet-6.0` ... Spring Boot Starter Webが依存するServlet APIを提供

```xml
<?xml version="1.0" encoding="UTF-8"?>
<server description="Spring Boot WebApp on Liberty">
    <!-- フィーチャー -->
    <featureManager>
        <feature>springBoot-3.0</feature>
        <feature>servlet-6.0</feature>
    </featureManager>

    <!-- 他の設定値は記載省略 -->
</server>
```

### Spring Boot関連設定

(1) デプロイしたいSpring BootアプリケーションのJARファイルの場所を `springBootApplication` 要素で指定します。後続の手順でJARファイルをOpen Libertyに最適化させたJARファイルに変換するため、変換後のファイル名を指定することに注意です。
サンプルではMavenで生成されるJARファイル `liberty-sb-web-0.0.1-SNAPSHOT.jar` ではなく、最適化後の `thin-liberty-sb-web-0.0.1-SNAPSHOT.jar` を指定しています。

(2) 続いてWebアプリケーションのHTTPで公開する設定を追加します。Open LibertyでSpring Bootアプリを公開する場合のポート番号 9090 を `httpEndpoint` 要素に指定します。
もしOpen Libertyデフォルトの9080ポートを使用する場合はJARファイルの配置場所がこの記事と異なるのでドキュメントを参照してください。
  

```xml
<?xml version="1.0" encoding="UTF-8"?>
<server description="Spring Boot WebApp on Liberty">
    <!-- (1) Spring Boot 3 application -->
    <springBootApplication location="thin-liberty-sb-web-0.0.1-SNAPSHOT.jar" name="liberty-sb-web" />

    <!-- (2) HTTP endpoint -->
    <httpEndpoint id="defaultHttpEndpoint" host="*" httpPort="9090" />

    <!-- 他の設定値は記載省略 -->
</server>
```

## Open Libertyへのデプロイ

最後にデプロイのステップです。Open Libertyの稼働に最適化したJARファイルを再ビルドしたのちにデプロイします。最適化処理にはOpen Liberty内蔵の `springBootUtility` を使用します。

アプリケーションJARファイルのビルド、最適化、Open Libertyへのデプロイを一気に実行するためDockerコンテナ化して実行します。以下のサンプル `Dockerfile` ではマルチステージビルドで中間成果物をコピーしながらデプロイ済みのアプリケーションコンテナイメージを作成しています。

```dockerfile
# Build Spring Boot 3 JAR
FROM docker.io/library/maven:3.9-ibm-semeru-17-focal AS builder
WORKDIR /usr/local/builder
COPY . .
RUN mvn clean package -DskipTests -B -ntp

# Optimize Spring Boot app to Liberty
FROM icr.io/appcafe/open-liberty:23.0.0.9-full-java17-openj9-ubi AS staging
COPY --chown=1001:0 --from=builder /usr/local/builder/target/liberty-sb-web-0.0.1-SNAPSHOT.jar /staging/fat-liberty-sb-web-0.0.1-SNAPSHOT.jar
RUN springBootUtility thin \
  --sourceAppPath=/staging/fat-liberty-sb-web-0.0.1-SNAPSHOT.jar \
  --targetThinAppPath=/staging/thin-liberty-sb-web-0.0.1-SNAPSHOT.jar \
  --targetLibCachePath=/staging/lib.index.cache

# Deploy optimized Spring Boot app to Liberty
FROM icr.io/appcafe/open-liberty:23.0.0.9-kernel-slim-java17-openj9-ubi
COPY --chown=1001:0 --from=builder /usr/local/builder/src/main/liberty/config/server.xml /config/server.xml
RUN features.sh
COPY --chown=1001:0 --from=staging /staging/lib.index.cache /lib.index.cache
COPY --chown=1001:0 --from=staging /staging/thin-liberty-sb-web-0.0.1-SNAPSHOT.jar /config/apps/
RUN configure.sh
```

コンテナイメージをPodmanでビルドします。コンテナランタイムにDockerを使う場合は `podman` を `docker` に差し替えればOKです。

```shell
$ podman build -t liberty-sb-web:v1 .
```

## 稼働確認

ビルドしたコンテナイメージからアプリケーションを起動します。Spring BootとOpen Liberty両方のログが混在していますが、`Started`のログが出力されていれば無事に起動できています。

```shell
$ podman run --rm --name liberty -p 9090:9090 liberty-sb-web:v1

Launching defaultServer (Open Liberty 23.0.0.9/wlp-1.0.81.cl230920230904-1158) on Eclipse OpenJ9 VM, version 17.0.8+7 (en_US)
[AUDIT   ] CWWKE0001I: The server defaultServer has been launched.

...

2023-10-01T12:44:59.204Z  INFO 1 --- [ecutor-thread-2] com.example.LibertySbWebApplication      : Starting LibertySbWebApplication using Java 17.0.8 with PID 1 (/opt/ol/wlp/usr/servers/defaultServer/apps/thin-liberty-sb-web-0.0.1-SNAPSHOT.jar started by default in /opt/ol/wlp/output/defaultServer)
2023-10-01T12:44:59.225Z  INFO 1 --- [ecutor-thread-2] com.example.LibertySbWebApplication      : No active profile set, falling back to 1 default profile: "default"
[AUDIT   ] CWWKT0016I: Web application available (default_host): http://0193c36e412b:9090/
2023-10-01T12:45:00.701Z  INFO 1 --- [ecutor-thread-5] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 1373 ms
2023-10-01T12:45:01.394Z  INFO 1 --- [ecutor-thread-2] com.example.LibertySbWebApplication      : Started LibertySbWebApplication in 2.561 seconds (process running for 4.059)
[AUDIT   ] CWWKZ0001I: Application liberty-sb-web started in 3.028 seconds.
[AUDIT   ] CWWKF0012I: The server installed the following features: [servlet-6.0, springBoot-3.0].
[AUDIT   ] CWWKF0011I: The defaultServer server is ready to run a smarter planet. The defaultServer server started in 4.063 seconds.
```

## 参考ドキュメント

* [Spring Bootアプリケーションのデプロイ方法](https://openliberty.io/docs/latest/deploy-spring-boot.html)
* [springBoot-3.0フィーチャー](https://openliberty.io/docs/latest/reference/feature/springBoot-3.0.html)

---

以上。