はじめに
業務で長く GlassFish を使い、Payara への移行とコンテナ化も経験しました。しかし、運用や開発の負担が期待したほど軽くならず、「アプリケーションサーバに WAR を載せる」構成そのものを見直す必要を感じました。
ただし、この経験から「Payara の Docker イメージは重い」「Spring Boot の JAR は Docker と相性がよい」と単純化するのは正確ではありません。Docker で配布・再利用される単位は WAR や JAR というファイル形式ではなく、Dockerfile の各命令が作るレイヤーです。Payara でもベースイメージを再利用でき、Spring Boot でも fat JAR を一枚のレイヤーにコピーすれば、変更のたびに JAR 全体が新しいレイヤーになります。
この記事で整理したい論点は、WAR と JAR の優劣ではありません。外部アプリケーションサーバへアーカイブをデプロイする方式と、アプリケーションが実行ランタイムを内包して自己完結する方式では、設定、起動、デプロイ、障害対応の境界がどのように変わるのか、という点です。
先に訂正したいこと:コンテナイメージの差分は WAR か JAR かでは決まらない
Docker イメージは複数のレイヤーで構成されます。変更されたレイヤーと、その後に続くレイヤーは再ビルド対象になりますが、変わらないベースレイヤーは再利用できます。Docker の公式ドキュメント でも、各 Dockerfile 命令がレイヤーとなり、あるレイヤーの変更が後続レイヤーのキャッシュを無効にすることが説明されています。
Payara は公式の Docker イメージを提供しており、WAR を配置するカスタムイメージも作れます。Payara の公式ドキュメント にある方式は、次のようなものです。
FROM payara/server-full
COPY app.war $DEPLOY_DIR/app.war
この構成では、Payara 本体は payara/server-full のベースレイヤーです。アプリケーションを更新しても変わるのは主に COPY で追加した WAR のレイヤーであり、レジストリ側に同じベースレイヤーがあれば、それを改めて転送する必要はありません。したがって、「外部アプリケーションサーバを使うと、更新のたびに巨大なイメージ全体を push する」という理解は誤りです。
一方、Spring Boot の fat JAR をそのままコピーする Dockerfile もあります。
FROM eclipse-temurin:21-jre
COPY app.jar /app/app.jar
ENTRYPOINT ["java", "-jar", "/app/app.jar"]
この場合、アプリケーションコードを少し変更しただけでも app.jar 全体が新しいレイヤーになります。fat JAR にはアプリケーションコードだけでなく依存ライブラリや Spring Boot のローダーも含まれうるため、レイヤー設計をしなければ差分を小さくできるとは限りません。
Spring Boot はこの課題に対応するため、依存ライブラリ、ローダー、スナップショット依存、アプリケーションコードを分離する layered JAR をサポートしています。Spring Boot の公式ドキュメント は、変わりにくい依存関係と変わりやすいアプリケーションコードを別レイヤーに置くことで、キャッシュを活用できると説明しています。さらに、このレイヤー化は JAR だけでなく WAR でも利用できます。
比較すべきなのは、ファイル形式ではなくデプロイモデル
WAR と JAR はパッケージ形式です。しかし、実際に開発・運用の負荷へ影響するのは、実行時に誰がランタイムと設定を所有し、どの単位で起動・更新・監視するかです。ここでは、外部アプリケーションサーバ型と自己完結型という二つのデプロイモデルとして整理します。
| 観点 | 外部アプリケーションサーバ型 | 自己完結型アプリケーション |
|---|---|---|
| 実行時の構成 | サーバランタイムを起動し、その上へアプリケーションアーカイブをデプロイします。 | アプリケーションのプロセスが HTTP サーバなどの実行機能を内包します。 |
| 主な設定の境界 | サーバのドメイン設定、起動時コマンド、環境変数、アプリ側設定を分けて管理します。 | アプリケーションの設定と起動引数を中心に管理しやすい構成です。 |
| デプロイ単位 | サーバとアーカイブの組み合わせです。複数アプリケーションを同じサーバへ載せる設計も選べます。 | 原則としてアプリケーションごとにイメージとプロセスを持ちます。 |
| 更新時の論点 | アーカイブの更新に加え、デプロイ処理、サーバ設定との整合、再デプロイの挙動を確認します。 | アプリケーションイメージを入れ替え、プロセスを切り替える流れを設計しやすい構成です。 |
| イメージ配布量 | ベースイメージと WAR を別レイヤーにすれば、変更したレイヤーを中心に配布できます。 | fat JAR を一枚でコピーすると JAR 全体が更新対象です。layered JAR で分割できます。 |
この違いは「Payara は Kubernetes で使えない」「Spring Boot なら自動的にクラウドネイティブになる」という意味ではありません。Payara の公式イメージは、ドメインをフォアグラウンドで起動して JVM をコンテナの主プロセスとして扱い、アプリケーションをデプロイ用ディレクトリから配置できます。Payara の公式ドキュメント が示すように、Payara をコンテナとして運用すること自体は十分に可能です。
Payara を使うときに残る運用上の境界
Payara をコンテナ化しても、アプリケーションサーバという運用モデルは残ります。ここでいう負担はイメージの転送量ではなく、サーバ設定、起動シーケンス、デプロイ処理、アプリケーション設定の関係を継続して理解・管理することです。
たとえば Payara では、アプリケーションを配置するディレクトリのほか、環境変数、事前・事後の管理コマンド、初期化スクリプトでドメインを設定できます。これは柔軟性でもありますが、設定がアプリケーションコード、コンテナ定義、ドメイン設定、起動スクリプトへ分散しやすいことも意味します。設定をどこに置くか、何をイメージに含めるか、どの変更で再デプロイが必要かをチームで明確にしなければなりません。
複数のアプリケーションを一つのアプリケーションサーバへ載せ、共通のリソースや管理機能を使う場合には、この分離は合理的です。一方で、アプリケーションをサービス単位で独立してリリースし、設定もスケールも個別に持たせたい場合は、共有するサーバの存在が調整コストになることがあります。
開発体験で見るべきポイント
「WAR は遅く、JAR は速い」と決めつける前に、実際の待ち時間を分解して測定する必要があります。開発時の反映時間には、ビルド、アーカイブ作成、コンテナビルド、コンテナ起動、サーバの初期化、アプリケーションのデプロイ、フレームワークの初期化が含まれます。どこが支配的かは、アプリケーションの規模、利用する Jakarta EE 機能、データベース接続、開発支援機能、Dockerfile のレイヤー設計によって変わります。
| 確認したい項目 | 外部アプリケーションサーバ型で確認すること | 自己完結型で確認すること |
|---|---|---|
| コード変更の反映 | WAR 作成から再デプロイ完了までの時間と、失敗時に残る状態を確認します。 | アプリケーション再起動または開発支援機能による反映時間を確認します。 |
| 設定変更の反映 | アプリ設定、ドメイン設定、起動コマンドのどれを変えるかを区別します。 | 設定ファイル、環境変数、起動引数のどれを変えるかを区別します。 |
| 起動時間 | サーバ起動とアプリケーション配備を分けて計測します。 | JVM 起動、組み込みサーバ起動、アプリ初期化を分けて計測します。 |
| 障害時の切り分け | サーバログ、デプロイ結果、アプリログ、管理設定を関連付けます。 | プロセスログ、アプリ設定、依存サービスとの接続を関連付けます。 |
私が GlassFish から Payara へ移行した際に「軽くならない」と感じたのは、主にこの運用上の境界でした。コンテナ化によって配布と起動の形は整えられても、アプリケーションサーバの設定・起動・デプロイを管理する必要は残ります。自己完結型アプリケーションと比べると、その関係を把握する対象が増えやすい点が、開発時の負担につながりました。
どちらを選ぶべきか
外部アプリケーションサーバ型が不適切なのではありません。Jakarta EE の機能を統合的に利用したい場合、複数アプリケーションでリソースや運用機能を共有したい場合、既存の基盤・監視・セキュリティ運用がアプリケーションサーバを前提としている場合には、有力な選択肢です。
一方、サービスごとに独立してビルド、設定、リリース、スケール、ロールバックを行いたい場合は、自己完結型の構成が理解しやすくなります。その場合も、Spring Boot の JAR をそのまま一枚の Docker レイヤーに置くのではなく、依存関係とアプリケーションコードの変更頻度を意識してレイヤー化を検討します。
設計判断では、次の順番で考えると混同を避けられます。
- まず、アプリケーションをどの単位で起動・更新・スケールさせたいかを決めます。
- 次に、サーバ設定とアプリケーション設定をどこに置き、誰が変更責任を持つかを決めます。
- 最後に、そのデプロイモデルに合うパッケージ形式と Dockerfile のレイヤー構成を選びます。
まとめ
GlassFish から Payara へ移行して分かったのは、Payara が Docker や Kubernetes と相性が悪いということではありません。Payara でも公式イメージをベースレイヤーとして再利用でき、WAR 更新時の差分を抑えたコンテナイメージを作れます。反対に、Spring Boot の fat JAR をそのままコピーすれば、依存ライブラリを含む JAR 全体が変更レイヤーになります。
課題になりやすいのは、WAR という拡張子そのものではなく、外部アプリケーションサーバへアプリケーションをデプロイする運用モデルです。この方式では、サーバ設定、起動、デプロイ、アプリケーション設定の境界を管理し続けます。その柔軟性が必要な場面もありますが、サービス単位の独立性を重視する環境では、自己完結型アプリケーションのほうが構成を単純にしやすい場合があります。
この記事の結論は、「WAR を使うべきではない」ではありません。イメージの軽さを WAR と JAR の対立として判断せず、どのデプロイモデルがチームの開発・運用の単位に合うかを基準に選ぶべき、ということです。