Skip to main content

JLinkを使ってSpring Boot Javaアプリケーション向けの小さなDockerイメージを作成する

feature kubernetes polp

2023年8月24日

0 分で読めます

コンテナは、ソフトウェアの開発とデプロイに柔軟性と俊敏性をもたらします。一方で、悪意のある攻撃者に悪用される可能性のある新たな攻撃対象領域も生み出します。コンテナが侵害されると、攻撃者はほかのコンテナやホストシステムにまでアクセスできる可能性があります。含まれるアーティファクトが少ない小さなイメージは、攻撃対象領域を縮小するうえで大きな助けになります。

この記事では、JLinkを活用してDockerイメージのサイズを最適化し、アプリケーションのセキュリティとパフォーマンスを高める方法を詳しく解説します。JLinkの使い方やDockerとの統合方法を紹介し、Spring Bootアプリケーションや一般的なJavaアプリケーションを効率よくデプロイする方法を説明します。

Javaは、世界中のエンタープライズアプリケーション開発で最も広く使われているプログラミング言語のひとつです。しかし、JavaアプリケーションをDockerコンテナにデプロイする際、Dockerイメージのサイズが課題になることがよくあります。この問題を解決する方法のひとつが、JDK 9で導入されたツール、JLinkを使うことです。

JLink(Java Linker)は、モジュールとその依存関係をまとめて最適化し、カスタムランタイムイメージを作成するコマンドラインツールです。つまり、アプリケーションの実行に必要なモジュールだけを含む、最小限のJavaランタイム環境を作成します。

$ jlink --module-path $JAVA_HOME/jmods:mlib --add-modules my.module --output myRunTime

上記のコマンドでは、my.moduleがモジュール、myRuntimeがJLinkによって作成されるカスタムランタイムイメージです。

Javaアプリケーション向けのDockerイメージを作成する際、イメージのサイズはしばしば課題になります。特に、依存関係が多いSpring Bootアプリケーションでは顕著です。Dockerイメージが大きいと、起動時間の増加、ストレージコストの上昇、デプロイプロセスの遅延につながる可能性があります。

以前のJavaでは、Java Development Kit(JDK)にJava Runtime Environment(JRE)が含まれていました。作成したJavaアーティファクトを実行するには、JREだけで十分でした。そのため、以前はDockerイメージでJREを使用するか、コンテナ用にJREのベースイメージを選ぶのが一般的でした。新しいバージョンのJavaには、必ずしもJREが含まれているとは限りませんが、ベンダーによっては引き続きJREと対応するベースイメージを提供している場合があります。それらを使うことも、アプリケーションに合わせたより具体的なJavaランタイムを使うこともできます。

JLinkを使うと、必要なモジュールだけを含む最小限のJavaランタイムを作成できます。これにより、Dockerイメージのサイズを大幅に削減できます。たとえば、標準的なJavaランタイム環境は200 MBを超えることがありますが、JLinkを使えば50 MB未満に抑えられます。

Spring Bootは、すべての依存関係を含むfat JARを作成します。また、多くのSpring Bootアプリケーションにはモジュール宣言がありません。これは必ずしも問題ではありませんが、アプリケーションに必要なモジュールと、そのすべての依存関係を特定する必要があります。

Jdepsでモジュールを特定する


Jdepsは、パッケージレベルまたはクラスレベルの依存関係を表示するJavaツールです。Java 8で導入されたこのツールを使うと、アプリケーションの依存関係を把握できます。その情報を使って、JLinkでカスタムランタイムイメージを作成できます。

すべての依存関係がひとつのディレクトリにあることを確認したら、jdepsを使って依存関係の概要を表示できます。

jdeps -cp 'mydeps/lib/*' -recursive --multi-release 17 -s target/MyJar.jar

同様に、jdepsを使ってSpring Bootアプリケーションとその依存関係における、すべてのモジュール依存関係を再帰的に表示できます。

jdeps --ignore-missing-deps -q  --recursive  --multi-release 17  --print-module-deps  --class-path 'mydeps/lib/*'  target/MyJar.jar

jdepsの出力を使うと、このSpring Bootアプリケーションに必要なモジュールだけを含むJavaランタイムをJLinkで作成できます。

Spring Bootの依存関係をフォルダに出力する

前述のとおり、Spring Bootはすべての依存関係を含むfat JARを作成します。ただし、依存関係はJAR内で特定の形式にパッケージ化されるため、jdepsから簡単にアクセスできません。jdepsで依存関係を取得するには、簡単な方法が2つあります。

  • Spring Bootが作成したfat JARファイルを展開します。

    • この方法は、ビルドアーティファクトがすでに作成されていて、アプリケーションを再ビルドできない、または再ビルドする予定がない場合に便利です。依存関係は/BOOT/libs/に展開されます。

  • ビルドツールのプラグインを使って、依存関係を特定のフォルダにコピーします。

    • たとえばMavenでは、maven-dependency-pluginを使って実現できます。次の例では、Mavenのpackageフェーズ完了後に、依存関係が/target/dependencyフォルダにコピーされます。

<project>
    <!-- ... other configurations ... -->

    <build>
        <plugins>
            <!-- Add the maven-dependency-plugin -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-dependency-plugin</artifactId>
                <version>3.1.2</version>
                <executions>
                    <execution>
                        <id>copy-dependencies</id>
                        <phase>package</phase>
                        <goals>
                            <goal>copy-dependencies</goal>
                        </goals>
                        <configuration>
                            <!-- Configure the output directory for the dependencies -->
                            <outputDirectory>${project.build.directory}/dependency</outputDirectory>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

    <!-- ... other configurations ... -->
</project>

カスタムJavaランタイムを使ってDockerイメージをビルドする

それでは、jdepsとJLinkを組み合わせてカスタムJavaランタイムをビルドしましょう。このランタイムを使えば、Spring Bootアプリケーション専用の最適な最小限のDockerイメージを作成できます。

FROM maven:3-eclipse-temurin-17 as build
RUN mkdir /usr/src/project
COPY . /usr/src/project
WORKDIR /usr/src/project
RUN mvn package -DskipTests
RUN jar xf target/JavaCoffeeShop.jar
RUN jdeps --ignore-missing-deps -q  \
    --recursive  \
    --multi-release 17  \
    --print-module-deps  \
    --class-path 'BOOT-INF/lib/*'  \
    target/JavaCoffeeShop.jar > deps.info
RUN jlink \
    --add-modules $(cat deps.info) \
    --strip-debug \
    --compress 2 \
    --no-header-files \
    --no-man-pages \
    --output /myjre
FROM debian:bookworm-slim
ENV JAVA_HOME /user/java/jdk17
ENV PATH $JAVA_HOME/bin:$PATH
COPY --from=build /myjre $JAVA_HOME
RUN mkdir /project
COPY --from=build /usr/src/project/target/JavaCoffeeShop.jar /project/
WORKDIR /project
ENTRYPOINT java -jar JavaCoffeeShop.jar

上記の例では、マルチステージDockerビルドを使用しています。最初のビルドステージは、Mavenを含むeclipse-temurin JDK 17イメージをベースにしています。このステージでは、次の処理を行います。

  • Javaアーティファクトを作成する。Mavenを使って、アプリケーション全体を含む実行可能なfat JARファイルを作成します。

  • 依存関係を取得するためにJARファイルを展開する。前述のmaven-dependency-pluginを使わない場合にのみ必要な手順です。プラグインを追加している場合は、この手順を省略できます。

  • jdepsを使って必要なモジュールを取得する。依存関係にあるすべてのJARファイルと最終アーティファクトが含まれるファイルを指定し、そのリストをdeps.infoに保存します。

  • JLinkを実行してカスタムJavaランタイムを作成する。deps.infoを入力として使い、/myjreに保存します。JLinkには必要なモジュールだけを追加し、デバッグ情報、マニュアルページ、ヘッダーファイルは削除します。

2つ目の最終ステージでは、debian:stable-slimイメージをベースに本番用イメージをビルドします。

  • 環境変数を設定する。JAVA_HOMEにmyjreのコピー先のパスを設定し、JAVA_HOMEをPATHに追加します。

  • JLinkで作成したJavaランタイムをコピーする。最初のステージを参照し、カスタムJavaランタイムをJAVA_HOMEとして定義した場所にコピーします。

  • 作成したJavaアーティファクトをコピーする。作成された実行可能なSpring Boot fat JARを、プロジェクト専用のディレクトリにコピーします。

  • エントリーポイントを設定する

Spring Boot Javaアプリケーション向けのDockerイメージを作成するうえで、JLinkにはいくつかのメリットがあります。

1. イメージサイズの縮小:前述のとおり、JLinkを使うとDockerイメージのサイズを削減でき、デプロイの高速化とストレージコストの削減につながります。

2. 起動時間の短縮:Dockerイメージが小さければ、アプリケーションをより速く起動できます。迅速なスケールが必要なアプリケーションにとって、これは重要です。

3. セキュリティ:必要なモジュールだけを含めることで、アプリケーションの攻撃対象領域を縮小できます。モジュールが少なければ、潜在的なセキュリティ脆弱性も減らせます。

セキュリティといえば、アプリケーションの安全性を確保するうえでのSnykの役割も重要です。Snykは、ソースコード、オープンソースパッケージ、コンテナイメージ、クラウド設定の脆弱性をスキャンできる、開発者向けのセキュリティツールです。Snyk ContainerとSnyk Open Sourceを使うと、Dockerイメージに含まれるものも含め、アプリケーションとその依存関係にあるセキュリティ問題を検出して修正できます。

$ snyk container test your-repo/your-image:tag

上記のコマンドでは、your-repo/your-image:tagがDockerイメージを表します。Snykはイメージをスキャンし、検出された脆弱性と修正方法の提案を報告します。

Javaアプリケーション向けに小さく、より安全なDockerイメージを作成する

この記事の例は、JLinkを使ってJavaプロジェクト向けのよりコンパクトなDockerイメージを作成する方法を紹介するものです。ここで示す例は、安全なDockerイメージに関するすべてのベストプラクティスを満たしているわけではありません。詳しく知りたい方は、「DockerでJavaコンテナを構築するための10のベストプラクティス」の記事をご覧ください。

JLinkは、Spring Boot Javaアプリケーション向けに小さく、より安全なDockerイメージを作成できる強力なツールです。Snykのようなセキュリティツールと組み合わせれば、アプリケーションのパフォーマンスと安全性を確保できます。今すぐSnykに登録して、アプリケーションのセキュリティ対策を始めましょう。

開発者ファーストのコンテナセキュリティ

Snykは、コンテナイメージとKubernetesワークロードの脆弱性を検出し、自動的に修正します。