LoginSignup
2
1

Docker 環境で Spring Boot Webサービスを起動する:GraalVM ネイティブイメージ

Last updated at Posted at 2023-03-17

Docker 環境で Spring Boot Webサービスを起動する:GraalVM ネイティブイメージ

こんにちは、@studio_meowtoon です。今回は、WSL Ubuntu の Docker 環境で、GraalVM ネイティブイメージ Spring Boot Web アプリケーションをコンテナとして起動する方法を紹介します。

spring-boot_graalvm_on_docker.png

目的

Windows 11 の Linux でクラウド開発します。

こちらから記事の一覧がご覧いただけます。

実現すること

ローカル環境の WSL Ubuntu の Docker 環境で、Dockerfile からビルドした GraalVM ネイティブイメージ Spring Boot Web サービスのカスタムコンテナイメージを起動します。

ネイティブイメージ形式のアプリをコンテナとして起動

実行環境

要素 概要
terminal ターミナル
Ubuntu OS
Docker コンテナ実行環境

Web サービス コンテナ

要素 概要
app-hello-spring-boot-native カスタムコンテナ
app ネイティブイメージ アプリケーション
tomcat Web サーバー

技術トピック

Java におけるネイティブイメージビルドとは?

こちらを展開してご覧いただけます。
キーワード 内容
ネイティブイメージビルド Java コードをネイティブマシンコードにコンパイルすることです。通常、Java コードは Java バイトコードと呼ばれる中間言語にコンパイルされ、Java 仮想マシン (JVM) で実行されます。しかし、ネイティブイメージビルドは、Java コードを JVM を介さずに直接実行可能なネイティブマシンコードに変換することで、より高速な実行速度より低いメモリ使用量を実現することができます。

ネイティブイメージビルドは、以下のようなニーズから求められています。

キーワード 内容
パフォーマンスの向上 Java は一般的に高水準のプログラミング言語であり、JVM によって実行されるため、実行速度が遅いとされることがあります。ネイティブイメージビルドにより、高速な実行速度を実現することができます。
メモリの最適化 JVM による Java コードの実行には、多くのメモリが必要となることがあります。ネイティブイメージビルドにより、より少ないメモリ使用量でプログラムを実行できるようになります。
ネイティブプログラムの統合 Java は、C言語や C++ などの他のプログラミング言語で書かれたネイティブプログラムと統合することができます。しかし、統合するためには、ネイティブコードが必要になります。ネイティブイメージビルドにより、これらのネイティブプログラムと Java コードをシームレスに統合することができます。

Dockerfile とは?

こちらを展開してご覧いただけます。

Dockerfile

Dockerfile は、Docker コンテナを構築するためのテキストファイルです。Docker コンテナはアプリケーションやサービスを実行するための環境を含む軽量でポータブルな仮想化ユニットです。

キーワード 内容
スクリプト形式 Dockerfile はシンプルなスクリプト形式で記述されるため、コンテナのビルドプロセスを自動化することが容易です。
レイヤー構造 Docker イメージは Dockerfile の各命令が実行される際にレイヤーとして生成され、再利用やキャッシュが可能な構造となっています。
バージョン管理 Dockerfile はテキストベースであるため、コードと同様にバージョン管理システムで管理しやすいです。
ポータビリティ Dockerfile により、アプリケーションとその依存関係が1つのコンテナイメージにパッケージ化されるため、異なる環境間での移植性が高まります。
自動化と効率化 Dockerfile を使用することで、アプリケーションのビルドや環境構築を自動化できます。これにより、手動での作業時間やヒューマンエラーが減り、開発・デプロイプロセスが効率的になります。
再現性 Dockerfile はビルド手順を完全に定義するため、異なる環境で同じアプリケーションを再現できます。これにより、開発、テスト、本番環境間での一貫性が確保されます。
環境の分離 Docker コンテナはホストシステムから分離されるため、アプリケーションの依存関係やライブラリの衝突を回避し、より安全な環境で実行できます。
拡張性 Dockerfile を使用することで、カスタムイメージを作成することができます。このため、特定のニーズに合わせてカスタマイズされたコンテナイメージを容易に作成できます。

開発環境

  • Windows 11 Home 22H2 を使用しています。

WSL の Ubuntu を操作していきますので macOS の方も参考にして頂けます。

WSL (Microsoft Store アプリ版) ※ こちらの関連記事からインストール方法をご確認いただけます

> wsl --version
WSL バージョン: 1.0.3.0
カーネル バージョン: 5.15.79.1
WSLg バージョン: 1.0.47

Ubuntu ※ こちらの関連記事からインストール方法をご確認いただけます

$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 22.04.1 LTS
Release:        22.04

Java JDK ※ こちらの関連記事からインストール方法をご確認いただけます

$ java -version
openjdk version "17.0.6" 2023-01-17
OpenJDK Runtime Environment GraalVM CE 22.3.1 (build 17.0.6+10-jvmci-22.3-b13)
OpenJDK 64-Bit Server VM GraalVM CE 22.3.1 (build 17.0.6+10-jvmci-22.3-b13, mixed mode, sharing)

Maven ※ こちらの関連記事からインストール方法をご確認いただけます

$ mvn -version
Apache Maven 3.6.3
Maven home: /usr/share/maven
Java version: 11.0.18, vendor: Ubuntu, runtime: /usr/lib/jvm/java-11-openjdk-amd64

Docker ※ こちらの関連記事からインストール方法をご確認いただけます

$ docker --version
Docker version 23.0.1, build a5ee5b1

この記事では基本的に Ubuntu のターミナルで操作を行います。Vim を使用してコピペする方法を初めて学ぶ人のために、以下の記事で手順を紹介しています。ぜひ挑戦してみてください。

作成する Web アプリケーションの仕様

No エンドポイント HTTPメソッド MIME タイプ
1 /api/data GET application/json

/api/data というエンドポイントに対して HTTP GET リクエストを送信すると、JSON データがレスポンスされるシンプルな Web サービスを実装します。
{"message":"Hello World!"}

Hello World を表示する手順

GraalVM の環境設定

こちらの関連記事で手順がご確認いただけます。

JDK のバージョン確認

Java のバージョンを切り替えます。

$ sudo update-alternatives --config java
$ sudo update-alternatives --config javac

GraalVM が選択されているか確認してください。

$ java -version
openjdk version "17.0.6" 2023-01-17
OpenJDK Runtime Environment GraalVM CE 22.3.1 (build 17.0.6+10-jvmci-22.3-b13)
OpenJDK 64-Bit Server VM GraalVM CE 22.3.1 (build 17.0.6+10-jvmci-22.3-b13, mixed mode, sharing)

Spring Boot Web サービスの作成

こちらの関連記事で手順がご確認いただけます。

プロジェクトフォルダに移動

プロジェクトフォルダに移動します。
※ ~/tmp/hello-spring-boot をプロジェクトフォルダとします。

$ cd ~/tmp/hello-spring-boot
構成を単純にする為に、全ての要素をアプリケーションクラス記述した例

コントローラークラスを削除して、アプリケーションクラスを修正します。

$ rm src/main/java/com/example/springboot/controller/HelloController.java
$ vim src/main/java/com/example/springboot/Application.java

ファイルの内容

Application.java
package com.example.springboot;

import java.util.Map;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@SpringBootApplication
@RequestMapping("/api")
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

    @GetMapping("/data")
    public Map<String, String> getData() {
        Map<String, String> map = Map.of("message", "Hello World!");
        return map;
    }
}

pom.xml の修正

pom.xml ファイルを作成します。

$ vim pom.xml
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.0.4</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>hello-spring-boot</artifactId>
    <version>1.0</version>
    <name>hello-spring-boot</name>

    <properties>
        <java.version>17</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>

    <build>
        <finalName>app</finalName>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
            <plugin>
                <groupId>org.graalvm.buildtools</groupId>
                <artifactId>native-maven-plugin</artifactId>
                <configuration>
                    <imageName>app</imageName>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

ネイティブイメージをビルド

ネイティブイメージをビルドします。
※ target/app ネイティブイメージが作成されます。

$ mvn -Pnative native:compile

ここまでの手順で、ローカル環境の Ubuntu にアプリのネイティブイメージをビルドすることができました。

ネイティブイメージのコンテナイメージをビルド

Dockerfile を編集します。

$ vim Dockerfile

ファイルの内容

Dockerfile
# because the native image app was built by Ubuntu 22.04.
FROM ubuntu:22.04

# create a work dir.
WORKDIR /app

# copy a native image app.
COPY target/app app

# open port 8080 for a native image app.
EXPOSE 8080

# startup a native image app.
ENTRYPOINT ["./app"]

Docker デーモンを起動します。

$ sudo service docker start
 * Starting Docker: docker    [ OK ]

Docker 環境をお持ちでない場合は、以下の関連記事から Docker Engine のインストール手順をご確認いただけます。

コンテナイメージをビルドします。

$ docker build \
    --no-cache \
    --tag app-hello-spring-boot-native:latest .

コンテナイメージを確認します。

$ docker images | grep app-hello-spring-boot-native
app-hello-spring-boot-native   latest     f0794df4d28c   23 minutes ago   149MB

ここまでの手順で、ローカル環境の Docker にアプリのネイティブイメージカスタムコンテナイメージをビルドすることができました。

コンテナを起動

ローカルでコンテナを起動します。
※ コンテナを停止するときは ctrl + C を押します。

$ docker run --rm \
    --publish 8080:8080 \
    --name app-local \
    app-hello-spring-boot-native:latest

従来の JVM のコンテナと比べ起動時間がとても速くなっています。

2023-03-14T11:09:55.330Z  INFO 1 --- [main] com.example.springboot.Application : Started Application in 0.073 seconds (process running for 0.08)
詳細な出力を表示します。
$ docker run --rm --name app-local -p 8080:8080 app-hello-spring-boot-native:latest

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

2023-03-14T11:09:55.275Z  INFO 1 --- [           main] com.example.springboot.Application       : Starting AOT-processed Application using Java 17.0.6 with PID 1 (/workspace/com.example.springboot.Application started by cnb in /workspace)
2023-03-14T11:09:55.275Z  INFO 1 --- [           main] com.example.springboot.Application       : No active profile set, falling back to 1 default profile: "default"
2023-03-14T11:09:55.290Z  INFO 1 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
2023-03-14T11:09:55.291Z  INFO 1 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2023-03-14T11:09:55.291Z  INFO 1 --- [           main] o.apache.catalina.core.StandardEngine    : Starting Servlet engine: [Apache Tomcat/10.1.5]
2023-03-14T11:09:55.298Z  INFO 1 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2023-03-14T11:09:55.298Z  INFO 1 --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 22 ms
2023-03-14T11:09:55.329Z  INFO 1 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2023-03-14T11:09:55.330Z  INFO 1 --- [           main] com.example.springboot.Application       : Started Application in 0.073 seconds (process running for 0.08)

ここまでの手順で、ローカル環境の Docker でアプリのネイティブイメージカスタムコンテナを起動することができました。

コンテナの動作確認

別ターミナルから curl コマンドで確認します。

$ curl -v http://localhost:8080/api/data -w '\n'

出力

*   Trying 127.0.0.1:8080...
* Connected to localhost (127.0.0.1) port 8080 (#0)
> GET /api/data HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.81.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200
< Content-Type: application/json
< Transfer-Encoding: chunked
< Date: Fri, 04 Aug 2023 01:52:30 GMT
<
* Connection #0 to host localhost left intact
{"message":"Hello World!"}

ここまでの手順で、ターミナルに {"message":"Hello World!"} と表示され、JSON データを取得することが出来ました。

コンテナに接続

別ターミナルからコンテナに接続します。

$ docker exec -it app-local /bin/bash

コンテナに接続後にディレクトリを確認します。
※ コンテナから出るときは ctrl + D を押します。

# pwd
/app
# ls -lah
total 68M
drwxr-xr-x 1 root root 4.0K Mar 17 14:53 .
drwxr-xr-x 1 root root 4.0K Mar 17 14:53 ..
-rwxr-xr-x 1 root root  68M Mar 17 14:29 app

top コマンドで状況を確認します。

top - 14:58:43 up 10:17,  0 users,  load average: 0.00, 0.00, 0.22
Tasks:   3 total,   1 running,   2 sleeping,   0 stopped,   0 zombie
%Cpu(s):  0.4 us,  0.2 sy,  0.0 ni, 99.3 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
MiB Mem :   7763.1 total,   4489.9 free,   1395.1 used,   1878.1 buff/cache
MiB Swap:   2048.0 total,   2046.5 free,      1.5 used.   6110.2 avail Mem

  PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND
    1 root      20   0 1478396 108644  48388 S   0.0   1.4   0:00.21 app
   35 root      20   0    4624   3748   3236 S   0.0   0.0   0:00.02 bash
   43 root      20   0    7312   3424   2860 R   0.0   0.0   0:00.02 top

Spring Boot として動作していますが、java コマンドが起動していないことが確認できます。つまり JVM を必要としません

コンテナの情報を表示してみます。

# cat /etc/*-release
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=22.04
DISTRIB_CODENAME=jammy
DISTRIB_DESCRIPTION="Ubuntu 22.04.2 LTS"
PRETTY_NAME="Ubuntu 22.04.2 LTS"
NAME="Ubuntu"
VERSION_ID="22.04"
VERSION="22.04.2 LTS (Jammy Jellyfish)"
VERSION_CODENAME=jammy
ID=ubuntu
ID_LIKE=debian
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
UBUNTU_CODENAME=jammy

このコンテナは Ubuntu をベースに作成されています。つまり、Ubuntu と同じように扱うことができます。

まとめ

WSL Ubuntu の Docker 環境で、Dockerfile からビルドした GraalVM ネイティブイメージ Spring Boot Web アプリのカスタムコンテナを起動することができました。

Dockerfile の理解は重要です。自動ビルドツールもありますが、手動で書く必要があるケースもあります。Ubuntu を使うと Linux の知識も身に付きます。最初は難しく感じるかもしれませんが、徐々に進めていけば自信を持って書けるようになります。

どうでしたか? WSL Ubuntu で、GraalVM ネイティブイメージ Spring Boot Web アプリケーションを、Docker 環境でコンテナとして手軽に起動することができます。ぜひお試しください。今後も Java の開発環境などを紹介していきますので、ぜひお楽しみにしてください。

推奨コンテンツ

関連記事

通常の JAR ファイル形式のビルドと比較してみましょう!

2
1
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
1