LoginSignup
2
4

More than 1 year has passed since last update.

jlinkでカスタムJREを作成しコンテナ・サイズを65%削減 - Spring Boot

Last updated at Posted at 2022-09-21

Spring Boot で作成したアプリケーションをコンテナ化する際、jlink コマンドを利用してカスタム JRE を作成し、軽量なベース・イメージを利用する事で、349MB から 105MB と大幅にコンテナのイメージ・サイズを削減する事ができます。今回はその方法を詳細に紹介します。

作成方法の手順

サンプルプロジェクトの作成

Spring Starter を利用してサンプルのプロジェクトを作成します。

Create Spring Boot App

サンプル RESTful エンドポイントの作成

HelloController.java ファイルを作成し下記を実装します。

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {
    
    @GetMapping("/hello")
    public String hello(){
        return "Hello World";
    }
}

ソースコードのビルド

プロジェクトを作成したのち、ソースコードをビルドします。

> mvn clean package

プロジェクトを構築すると下記のファイルが target ディレクトリ配下に作成されます。

> ls target 
classes					hello-sample-0.0.1-SNAPSHOT.jar				maven-status
generated-sources		hello-sample-0.0.1-SNAPSHOT.jar.original	surefire-reports
generated-test-sources	maven-archiver								test-classes

ここで、hello-sample-0.0.1-SNAPSHOT.jar ファイルが Spring Boot の実行可能ファイルになります。
下記のコマンドを実行し動作確認を行う事ができます。

java -jar hello-sample-0.0.1-SNAPSHOT.jar

ブラウザもしくは curl コマンドでエンドポイントに接続します。

> curl localhost:8080/hello    
Hello World

カスタム JRE とコンテナ・イメージの作成

次に、作成したプロジェクトをコンテナ上で実行できるように、コンテナ・イメージを作成します。下記の Dockerfile を作成してください。

##################################################################
# Stage 1: Create Custom Java VM for Spring Boot
##################################################################
FROM mcr.microsoft.com/openjdk/jdk:17-ubuntu AS CREATE-CUSTOM-JDK

WORKDIR /workspace/app

RUN jlink --no-header-files --no-man-pages --compress=2 --strip-debug --add-modules java.base,java.logging,java.naming,java.desktop,java.management,java.security.jgss,java.instrument,java.sql --output custom-jre

##################################################################
# Stage 2: Need Zlib for JVM on Distroless Image
##################################################################
FROM mcr.microsoft.com/cbl-mariner/base/core:2.0 AS ZLIB-INSTALLER

RUN mkdir /staging \
    && tdnf install -y --releasever=2.0 --installroot /staging zlib

##################################################################
FROM mcr.microsoft.com/cbl-mariner/distroless/minimal:2.0 AS CBLMIN

VOLUME /tmp
WORKDIR /app

ARG DEPENDENCY=/workspace/app/
COPY --from=CREATE-CUSTOM-JDK ${DEPENDENCY}/custom-jre /app/jre
COPY --from=ZLIB-INSTALLER /staging/ /

ENV LANG='ja_JP.UTF-8' LANGUAGE='ja_JP:ja' LC_ALL='ja_JP.UTF-8'
ENV TZ='Asia/Tokyo'
ENV JAVA_HOME=/app

# COPY the Spring Boot application artifact from Local
COPY ./target/hello-sample-0.0.1-SNAPSHOT.jar    app.jar

ENTRYPOINT ["/app/jre/bin/java","-jar","/app/app.jar"]
EXPOSE 8080  

Dockerfile を作成した後、下記のコマンドを実行しコンテナ・イメージを作成します。

> docker build -t tyoshio2002/hello-spring:1.0 .

作成したイメージのファイル・サイズを確認してください。105MB のイメージが作成できた事を確認できます。

> docker images                                     
REPOSITORY                  TAG    IMAGE ID       CREATED          SIZE
tyoshio2002/hello-spring    1.0    5ef97f8669d8   26 seconds ago   105MB

作成したコンテナ・イメージを実行してください。

> docker run -it -p 8080:8080 tyoshio2002/hello-spring:1.0

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v2.7.3)

2022-09-21 14:03:37.631  INFO 1 --- [           main] com.yoshio3.HelloSampleApplication       : Starting HelloSampleApplication v0.0.1-SNAPSHOT using Java 17.0.4.1 on ae593530aadc with PID 1 (/app/app.jar started by root in /app)
2022-09-21 14:03:37.634  INFO 1 --- [           main] com.yoshio3.HelloSampleApplication       : No active profile set, falling back to 1 default profile: "default"
2022-09-21 14:03:38.420  INFO 1 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
2022-09-21 14:03:38.429  INFO 1 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2022-09-21 14:03:38.429  INFO 1 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.65]
2022-09-21 14:03:38.477  INFO 1 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2022-09-21 14:03:38.478  INFO 1 --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 797 ms
2022-09-21 14:03:38.748  INFO 1 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2022-09-21 14:03:38.756  INFO 1 --- [           main] com.yoshio3.HelloSampleApplication       : Started HelloSampleApplication in 1.481 seconds (JVM running for 1.869)

ブラウザもしくは curl コマンドでエンドポイントに接続します。

> curl localhost:8080/hello    
Hello World

この Dockerfile を使用した方法により、105MB という非常に小さなコンテナ・イメージを作成できました。
Azure 上で、小さなコンテナ・イメージを利用したい場合で、かつ Java 11 以降の環境では上記のように、jlink コマンドを使用したカスタム JRE を作成し、Microsoft が正式サポートする軽量な Linux コンテナ・イメージを作成しご利用いただく事ができます。

技術の詳細:この方法をおすすめする理由

以降では、なぜ上記の方法をお勧めするか、より詳しく説明します。

OpenJDK をインストールしたコンテナ・イメージの確認

まず、OpenJDK をインストールしたコンテナ・イメージのファイル・サイズを確認します。
Microsoft が提供する MS Build OpenJDK 関連のコンテナのベース・イメージは下記より入手可能です。

これらのイメージを実際に pull して確認すると、下記を確認できます。(2022/09/21 時点)

mcr.microsoft.com/openjdk/jdk:17-ubuntu        412MB
mcr.microsoft.com/openjdk/jdk:17-mariner       364MB
mcr.microsoft.com/openjdk/jdk:17-distroless    331MB

こちらをご覧いただくと、一番下の行に記載されている、Mariner の distroless のイメージ・サイズが最も小さい事がわかります。カスタム JRE を作成しない場合は、一番下のイメージをベース・イメージとしてご利用いただくのがお勧めです。

しかし、他のプログラミング言語で利用するコンテナのイメージサイズと比較して、330MB を超えるイメージは如何でしょうか? 実際には、自分が作成したアプリケーションをデプロイするため、このサイズに対しプラス・α のイメージ・サイズになります。
なぜ、このようにイメージ・サイズが大きくなっているのかを次で確認してみたいと思います。

OpenJDK 自身のファイルサイズの確認

それでは次に、OpenJDK のファイル・サイズを確認します。
MS Build OpenJDK では下記の URL より、各プラットフォーム毎の OpenJDK を入手する事ができます。

こちらから、Linux 版の JDK 17.0.4.1 をダウンロードして確認しました。その結果、アーカイブされた状況で 191MB でした。

-rw-r--r--@  1 teradayoshio  staff    191382575  9 21 17:03 microsoft-jdk-17.0.4.1-linux-x64.tar.gz

アーカイブを展開すると、約 324MB になりました。この事から、distroless のイメージの大半が OpenJDK のファイル群である事がお分かりいただけるかと思います。

> du -sk .
324656 .

完全な OpenJDK のイメージを含んだバージョンのイメージを利用する場合、それだけで大きなサイズのコンテナ・イメージが作成される事が上記からお分かり頂けるかと思います。
もちろん、プロバイダーは src.zip を削除したり、いらないファイルを削除する事でスリム化を試みていますが、OpenJDK を完全にインストールしたイメージを利用した場合、サイズを小さくするのに限界があります。

そこで以降では、より小さなイメージを作成するための方法をご紹介します。

jlink を利用したカスタム JRE の作成

サイズをより小さくするために、Java 9 で導入された jlink コマンドを利用します。
jlink コマンドでカスタムな Java Runtime を作成する事で、自分のアプリケーションを動かすために必要最小限の、小さな Java 実行環境を手に入れる事ができます。

Dockerfile 中の Stage 1 で、ローカルでビルドを行った Java の成果物(./target/yoshio-sample-0.0.1-SNAPSHOT.jar)をコピーし、jlink コマンドで、SpringBoot アプリケーションの実行に必要なモジュールを読み込み、カスタムな JRE(custom-jre) を作成しています。
カスタム JRE は custom-jre ディレクトリ配下に生成されます。

##################################################################
# Stage 1: Create Custom Java VM for Spring Boot
##################################################################
FROM mcr.microsoft.com/openjdk/jdk:17-ubuntu AS CREATE-CUSTOM-JDK

WORKDIR /workspace/app

RUN jlink --no-header-files --no-man-pages --compress=2 --strip-debug --add-modules java.base,java.logging,java.naming,java.desktop,java.management,java.security.jgss,java.instrument,java.sql --output custom-jre

ご注意:
jlink コマンドを含む OpenJDK が必要なため、Stage 1 では mcr.microsoft.com/openjdk/jdk:17-ubuntu のイメージを利用しています。実行環境は Stage 1 で作成したカスタムな JRE をコピーして利用します。また、カスタム JRE は一度作成したら、極力再利用をしてください。Container はレイヤー構造になっており、変更のないレイヤーは再利用されます。変更のあった部分だけを更新する事で、コンテナー・イメージの Upload や Pull の容量を減らす事ができ、起動時間の短縮に繋がります。

ご注意:
仮にアプリの中で SSL 通信が必要な場合は、jdk.crypto.ec をモジュールに追加してください。

最終成果物のコンテナ・イメージの作成

Microsoft のコンテナ環境で推奨サポートされるコンテナ・イメージ

カスタム JRE を作成したので最後に、サービスとして提供するコンテナ・イメージを作成します。
最終的な実行環境のベース・コンテナー・イメージとして利用するのは、マイクロソフトが正式にサポートする、CBL-Mariner というイメージを利用します。

CBL-Mariner Linux は、クラウド環境に必要なパッケージだけを含んだ軽量でセキュアな OS で、アプリケーションの要件に合わせてカスタマイズも可能です。
CBL-Mariner は、Microsoft クラウド サービスおよび関連製品で使用するために推奨されている Linux ディストリビューションで、Microsoft Build OpenJDK を含むコンテナ・イメージでもベース・イメージとして採用されています。

現在 CBL-Mariner は 2.0 がリリースされており "Announcing CBL-Mariner 2.0"こちらのバージョンを利用する事が推奨されています。

ご注意
古い Mariner 1.0 は、Mariner 2.0 の正式リリース後、約6か月間は維持されますが、Mariner 2.0 の使用を推奨します。

コンテナ・イメージは下記から Pull して利用できます。

  • Base container

    • mcr.microsoft.com/cbl-mariner/base/core:2.0
  • Distroless containers

    • mcr.microsoft.com/cbl-mariner/distroless/base:2.0
    • mcr.microsoft.com/cbl-mariner/distroless/debug:2.0
    • mcr.microsoft.com/cbl-mariner/distroless/minimal:2.0

実際にそれぞれの・イメージを入手してイメージ・サイズを比較した結果が下記です。

mcr.microsoft.com/cbl-mariner/base/core:2.0            66.1MB
mcr.microsoft.com/cbl-mariner/distroless/base:2.0      31.1MB
mcr.microsoft.com/cbl-mariner/distroless/debug:2.0     32.6MB
mcr.microsoft.com/cbl-mariner/distroless/minimal 2.0   203kB

これらのイメージの中から、本番環境で利用するコンテナ・イメージを選択します。

コンテナ・イメージの作成

最後に、最終成果物のコンテナ・イメージを作成します。Stage 3が最終的なサービス提供用のコンテナ・イメージです。しかし Stage 3 の前に、Stage 2 を行わないと zlib 関連のファイルが存在しないためエラーが発生します。そこでカスタム JRE の実行ができません。そこで、Stage 2zlib を入手し、Stage 3 のイメージにコピーしています。 これにより不要なファイルをインストールせずに、必要なライブラリだけを Stage 3 のイメージにコピーして作成する事ができます。

##################################################################
# Stage 2: Need Zlib for JVM on Distroless Image
##################################################################
FROM mcr.microsoft.com/cbl-mariner/base/core:2.0 AS ZLIB-INSTALLER

RUN mkdir /staging \
    && tdnf install -y --releasever=2.0 --installroot /staging zlib

##################################################################
FROM mcr.microsoft.com/cbl-mariner/distroless/minimal:2.0 AS CBLMIN

VOLUME /tmp
WORKDIR /app

ARG DEPENDENCY=/workspace/app/
COPY --from=CREATE-CUSTOM-JDK ${DEPENDENCY}/custom-jre /app/jre
COPY --from=ZLIB-INSTALLER /staging/ /

ENV LANG='ja_JP.UTF-8' LANGUAGE='ja_JP:ja' LC_ALL='ja_JP.UTF-8'
ENV TZ='Asia/Tokyo'
ENV JAVA_HOME=/app

# COPY the Spring Boot application artifact from Local
COPY ./target/hello-sample-0.0.1-SNAPSHOT.jar    app.jar

ENTRYPOINT ["/app/jre/bin/java","-jar","/app/app.jar"]
EXPOSE 8080 

最終的に、Stage 1で作成した、カスタム JRE、Stage 2 の zlib をコピーし、ロケールやタイムゾーンを設定し最後にビルドしたアプリケーションの jar ファイル(app.jar)をコピーして起動しています。

こちらの方法は、MS Build OpenJDK の提供イメージを参考にして作成した方法です。
カスタム JRE を利用したより軽量なサイズのコンテナ・イメージを作成する方法として、開発チームにも確認をしサポートされる内容ですので、どうぞ参考にしてご活用ください。

2
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
4