Docker で Spring Boot

このガイドでは、Spring Boot アプリケーションを実行するための Docker (英語) イメージを構築する手順を説明します。まずは基本的な Dockerfile から始めて、いくつかの調整を行います。次に、docker の代わりにビルドプラグイン (Maven および Gradle 用) を使用するいくつかのオプションを示します。これは「入門」ガイドであるため、範囲はいくつかの基本的なニーズに限定されています。本番環境で使用するコンテナーイメージを構築する場合、考慮すべき点が多く、短いガイドですべてを網羅することは不可能です。

Docker のトピックガイドもあり、これはここで紹介した選択肢よりも幅広い選択肢を、より詳細にカバーしています。

構築するもの

Docker (英語) は、ユーザーがコンテナーイメージを公開したり、他のユーザーが公開したコンテナーイメージを使用できるようにする、「ソーシャル」な側面を持つ Linux コンテナー管理ツールキットです。Docker イメージは、コンテナー化されたプロセスを実行するためのレシピです。このガイドでは、シンプルな Spring boot アプリケーション用に 1 つを構築します。

必要なもの

Linux マシンを使用していない場合は、仮想化サーバーが必要です。VirtualBox をインストールすると、Mac の boot2docker などの他のツールでシームレスに管理できます。VirtualBox のダウンロードサイト (英語) にアクセスして、マシンのバージョンを選択します。ダウンロードしてインストールします。実際に実行する必要はありません。

また、64 ビットマシンでのみ実行される Docker (英語) も必要です。マシンに Docker を設定する方法の詳細については、https://docs.docker.com/installation/#installation (英語) を参照してください。先に進む前に、シェルから docker コマンドを実行できることを確認してください。boot2docker を使用する場合は、まずそれを実行する必要があります。

Spring Initializr から開始

IDE を使用する場合はプロジェクト作成ウィザードを使用します。IDE を使用せずにコマンドラインなどで開発する場合は、この事前に初期化されたプロジェクトからプロジェクトを ZIP ファイルとしてダウンロードできます。このプロジェクトは、このチュートリアルの例に合うように構成されています。

プロジェクトを手動で初期化するには:

  1. IDE のメニューまたはブラウザーから Spring Initializr を開きます。アプリケーションに必要なすべての依存関係を取り込み、ほとんどのセットアップを行います。

  2. Gradle または Maven のいずれかと、使用する言語を選択します。このガイドは、Java を選択したことを前提としています。

  3. 依存関係をクリックして、Spring Web を選択します。

  4. 生成をクリックします。

  5. 結果の ZIP ファイルをダウンロードします。これは、選択して構成された Web アプリケーションのアーカイブです。

EclipseIntelliJ のような IDE は新規プロジェクト作成ウィザードから Spring Initializr の機能が使用できるため、手動での ZIP ファイルのダウンロードやインポートは不要です。
プロジェクトを Github からフォークして、IDE または他のエディターで開くこともできます。

Spring Boot アプリケーションを設定する

これで、簡単なアプリケーションを作成できます。

src/main/java/hello/Application.java

package hello;

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

@SpringBootApplication
@RestController
public class Application {

  @RequestMapping("/")
  public String home() {
    return "Hello Docker World";
  }

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

}

このクラスは @SpringBootApplication および @RestController としてフラグが付けられており、Spring MVC が Web リクエストを処理するために使用できる状態であることを意味します。@RequestMapping は / を home() メソッドにマップし、Hello World レスポンスを送信します。main() メソッドは、Spring Boot の SpringApplication.run() メソッドを使用してアプリケーションを起動します。

これで、Docker コンテナーなしで (つまり、ホスト OS で) アプリケーションを実行できます。

Gradle を使用する場合は、次のコマンドを実行します。

./gradlew build && java -jar build/libs/gs-spring-boot-docker-0.1.0.jar

Maven を使用する場合は、次のコマンドを実行します。

./mvnw package && java -jar target/gs-spring-boot-docker-0.1.0.jar

次に、localhost:8080 に移動して、"Hello Docker World" メッセージを確認します。

コンテナー化

Docker には、イメージの「レイヤー」を指定するために使用するシンプルな "Dockerfile" (英語) ファイル形式があります。Spring Boot プロジェクトで次の Dockerfile を作成します。

例 1: Dockerfile
FROM openjdk:8-jdk-alpine
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} app.jar
ENTRYPOINT ["java","-jar","/app.jar"]

Gradle を使用する場合は、次のコマンドで実行できます。

docker build --build-arg JAR_FILE=build/libs/\*.jar -t springio/gs-spring-boot-docker .

Maven を使用する場合は、次のコマンドで実行できます。

docker build -t springio/gs-spring-boot-docker .

このコマンドはイメージを構築し、springio/gs-spring-boot-docker としてタグ付けします。

この Dockerfile は非常にシンプルですが、Java と JAR ファイルだけで、Spring Boot アプリを無駄なく実行できます。ビルドにより、アプリケーションを実行するための Spring ユーザーと Spring グループが作成されます。次に、プロジェクト JAR ファイルが app.jar としてコンテナーにコピーされ (COPY コマンドによって)、ENTRYPOINT で実行されます。Java プロセスをラップするシェルがないように、Dockerfile ENTRYPOINT の配列形式が使用されます。Docker のトピックガイドでは、このトピックについてさらに詳しく説明します。

Tomcat の起動時間 [Apache] (英語) を減らすために、エントロピーのソースとして /dev/urandom を指すシステムプロパティを追加していました。JDK 8 以降 (英語) ではこれは必要ありません。

ユーザー権限でアプリケーションを実行すると、いくつかのリスクを軽減できます (例: StackExchange のスレッド (英語) を参照)。Dockerfile の重要な改善点は、アプリケーションを非ルートユーザーとして実行することです。

例 2: Dockerfile
FROM openjdk:8-jdk-alpine
RUN addgroup -S spring && adduser -S spring -G spring
USER spring:spring
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} app.jar
ENTRYPOINT ["java","-jar","/app.jar"]

アプリケーションをビルドして実行すると、アプリケーションの起動ログにユーザー名が表示されます。

docker build -t springio/gs-spring-boot-docker .
docker run -p 8080:8080 springio/gs-spring-boot-docker

最初の INFO ログエントリの started by に注意してください。

 :: Spring Boot ::        (v2.2.1.RELEASE)

2020-04-23 07:29:41.729  INFO 1 --- [           main] hello.Application                        : Starting Application on b94c86e91cf9 with PID 1 (/app started by spring in /)
...

また、Spring Boot ファット JAR ファイルでは依存関係とアプリケーションリソースが明確に分離されており、この事実を利用してパフォーマンスを向上させることができます。鍵となるのは、コンテナーファイルシステムにレイヤーを作成することです。レイヤーはビルド時と実行時 (ほとんどの実行時) の両方でキャッシュされるため、最も頻繁に変更されるリソース (通常はアプリケーション自体のクラスリソースと静的リソース) は、変更が遅いリソースの後にレイヤー化する必要があります。そのため、Dockerfile のわずかに異なる実装を使用します。

例 3: Dockerfile
FROM openjdk:8-jdk-alpine
RUN addgroup -S spring && adduser -S spring -G spring
USER spring:spring
ARG DEPENDENCY=target/dependency
COPY ${DEPENDENCY}/BOOT-INF/lib /app/lib
COPY ${DEPENDENCY}/META-INF /app/META-INF
COPY ${DEPENDENCY}/BOOT-INF/classes /app
ENTRYPOINT ["java","-cp","app:app/lib/*","hello.Application"]

この Dockerfile には、fat JAR を解凍したディレクトリを指す DEPENDENCY パラメーターがあります。Gradle で DEPENDENCY パラメーターを使用するには、次のコマンドを実行します。

mkdir -p build/dependency && (cd build/dependency; jar -xf ../libs/*.jar)

Maven で DEPENDENCY パラメーターを使用するには、次のコマンドを実行します。

mkdir -p target/dependency && (cd target/dependency; jar -xf ../*.jar)

正しく実行されていれば、依存関係 JAR を含む BOOT-INF/lib ディレクトリと、アプリケーションクラスを含む BOOT-INF/classes ディレクトリがすでに含まれています。アプリケーション独自のメインクラスである hello.Application を使用していることに注意してください。(これは、ファット JAR ランチャーによって提供される間接参照を使用するよりも高速です。)

JAR ファイルを展開すると、実行時にクラスパスの順序が変わる [GitHub] (英語) 可能性があります。適切に動作し、適切に記述されたアプリケーションでは、この点は課題になりませんが、依存関係が適切に管理されていない場合は、動作が変わることがあります。
boot2docker を使用する場合は、Docker コマンドラインまたはビルドツールで何かを行う前に、まず boot2docker を実行する必要があります (仮想マシン内で作業を処理するデーモンプロセスが実行されます)。

Gradle ビルドでは、Docker コマンドラインに明示的なビルド引数を追加する必要があります。

docker build --build-arg DEPENDENCY=build/dependency -t springio/gs-spring-boot-docker .

Maven でイメージをビルドするには、よりシンプルな Docker コマンドラインを使用できます。

docker build -t springio/gs-spring-boot-docker .
Gradle のみを使用する場合は、Dockerfile を変更して、DEPENDENCY のデフォルト値を解凍されたアーカイブの場所と一致させることができます。

Docker コマンドラインでビルドする代わりに、ビルドプラグインを使用することもできます。Spring Boot は、独自のビルドプラグインを使用して、Maven または Gradle からコンテナーをビルドすることをサポートしています。Google には、Maven および Gradle プラグインを備えた Jib [GitHub] (英語) というオープンソースツールもあります。おそらく、このアプローチの最も興味深い点は、Dockerfile が必要ないことです。docker build から取得するのと同じ標準コンテナー形式を使用してイメージをビルドできます。また、docker がインストールされていない環境でも機能します (ビルドサーバーでは珍しいことではありません)。

デフォルトでは、デフォルトの buildpacks によって生成されたイメージは、アプリケーションをルートとして実行しません。デフォルト設定を変更する方法については、Gradle または Maven の構成ガイドを確認してください。

Gradle で Docker イメージを構築する

1 つのコマンドで、Gradle を含むタグ付き docker イメージを構築できます。

./gradlew bootBuildImage --imageName=springio/gs-spring-boot-docker

Maven で Docker イメージを構築する

すぐに開始するには、pom.xml を変更せずに Spring Boot イメージジェネレーターを実行できます (Dockerfile がまだある場合は無視されることに注意してください)。

./mvnw spring-boot:build-image -Dspring-boot.build-image.imageName=springio/gs-spring-boot-docker

Docker レジストリにプッシュするには、プッシュ権限が必要ですが、デフォルトではこの権限がありません。Docker を実行する前に、イメージのプレフィックスを自分の Dockerhub ID と docker login に変更して、認証されていることを確認してください。

プッシュ後

例の docker push は失敗します (Dockerhub の "springio" 組織に属している場合を除く)。ただし、自分の docker ID と一致するように構成を変更すると、成功するはずです。すると、新しいタグ付きイメージがデプロイされます。

ローカルでビルドされた docker イメージを実行するために、docker に登録したり、何かを公開したりする必要はありません。Docker (コマンドラインまたは Spring Boot から) を使用してビルドした場合は、ローカルでタグ付けされたイメージがまだあるため、次のように実行できます。

$ docker run -p 8080:8080 -t springio/gs-spring-boot-docker
Container memory limit unset. Configuring JVM for 1G container.
Calculated JVM Memory Configuration: -XX:MaxDirectMemorySize=10M -XX:MaxMetaspaceSize=86381K -XX:ReservedCodeCacheSize=240M -Xss1M -Xmx450194K (Head Room: 0%, Loaded Class Count: 12837, Thread Count: 250, Total Memory: 1073741824)
....
2015-03-31 13:25:48.035  INFO 1 --- [           main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8080 (http)
2015-03-31 13:25:48.037  INFO 1 --- [           main] hello.Application                        : Started Application in 5.613 seconds (JVM running for 7.293)
buildpack は実行時にメモリ計算機を使用して、コンテナーに適合するように JVM のサイズを決定します。

その後、アプリケーションは http://localhost:8080 で利用できるようになります (そこにアクセスすると、"Hello Docker World" と表示されます)。

boot2docker で Mac を使用する場合、通常、起動時に次のような画面が表示されます。

Docker client to the Docker daemon, please set:
    export DOCKER_CERT_PATH=/Users/gturnquist/.boot2docker/certs/boot2docker-vm
    export DOCKER_TLS_VERIFY=1
    export DOCKER_HOST=tcp://192.168.59.103:2376

アプリケーションを表示するには、localhost ではなく DOCKER_HOST の IP アドレス (この場合は VM のパブリック IP である https://192.168.59.103:8080 (英語) ) にアクセスする必要があります。

実行中は、次の例のようなコンテナーのリストが表示されます。

$ docker ps
CONTAINER ID        IMAGE                                   COMMAND                  CREATED             STATUS              PORTS                    NAMES
81c723d22865        springio/gs-spring-boot-docker:latest   "java -Djava.secur..."   34 seconds ago      Up 33 seconds       0.0.0.0:8080->8080/tcp   goofy_brown

再度シャットダウンするには、前のリストのコンテナー ID を使用して docker stop を実行します (実際の ID は異なります)。

docker stop goofy_brown
81c723d22865

必要に応じて、使用が終わったらコンテナーを削除することもできます (コンテナーはファイルシステムの /var/lib/docker のどこかに保存されます)。

docker rm goofy_brown

Spring プロファイルの使用

新しく作成した Docker イメージを Spring プロファイルで実行するのは、環境変数を Docker 実行コマンド (prod プロファイルの場合) に渡すだけです。

docker run -e "SPRING_PROFILES_ACTIVE=prod" -p 8080:8080 -t springio/gs-spring-boot-docker

dev プロファイルでも同じことができます。

docker run -e "SPRING_PROFILES_ACTIVE=dev" -p 8080:8080 -t springio/gs-spring-boot-docker

Docker コンテナーでのアプリケーションのデバッグ

アプリケーションをデバッグするには、JPDA 輸送 [Oracle] を使用できます。コンテナーを リモートサーバーのように扱います。この機能を有効にするには、コンテナーの実行中に JAVA_OPTS 変数で Java エージェント設定を渡し、エージェントのポートを localhost にマップします。Mac 用 Docker (英語) では、ブラックマジックを使用し [GitHub] (英語) ないと IP でコンテナーにアクセスできないため、制限があります。

docker run -e "JAVA_TOOL_OPTIONS=-agentlib:jdwp=transport=dt_socket,address=5005,server=y,suspend=n" -p 8080:8080 -p 5005:5005 -t springio/gs-spring-boot-docker

要約

おめでとうございます ! Spring Boot アプリケーション用の Docker コンテナーを作成しました ! デフォルトでは、Spring Boot アプリケーションはコンテナー内のポート 8080 で実行され、コマンドラインで -p を使用して、それをホスト上の同じポートにマッピングしました。

関連事項

次のガイドも役立ちます。

新しいガイドを作成したり、既存のガイドに貢献したいですか? 投稿ガイドラインを参照してください [GitHub] (英語)

すべてのガイドは、コード用の ASLv2 ライセンス、およびドキュメント用の Attribution、NoDerivatives creative commons ライセンス (英語) でリリースされています。

コードを入手する