DUICUO

Docker での Spring Boot アプリケーションの最適化: シングルレイヤーイメージアプローチ

[[319193]]

この記事では、Docker のいくつかの主要な概念と構造コンポーネントを使用して Spring Boot アプリケーションを最適化する方法を紹介します。

Dockerは強力で使いやすいツールです。開発者はDockerを使うことで、作成したソフトウェアのポータブルイメージを作成できます。これらのイメージは繰り返しデプロイできます。Dockerは多くの価値を簡単に得ることができますが、最大限に活用するには、いくつかの概念を理解する必要があります。Dockerイメージの構築方法は、継続的インテグレーションと継続的デリバリーにおいて重要な役割を果たします。この記事では、以下の点に焦点を当てます。

  • 反復的な開発とデプロイメント中に、Spring Boot アプリケーション用の Docker イメージを構築するためのより効率的な方法を採用するにはどうすればよいでしょうか?
  • Spring Boot アプリケーション用の Docker イメージを構築する標準的なアプローチにはいくつかの欠点があるため、ここではより優れた方法を紹介します。

Dockerの主要概念

Dockerには、イメージ、レイヤー、Dockerfile、そしてDockerキャッシュという4つの主要な概念があります。簡単に言うと、DockerfileはDockerイメージの構築方法を記述したものです。イメージは複数のレイヤーで構成されています。Dockerfileはベースイメージから始まり、他のレイヤーを追加していきます。イメージに新しいコンテンツが追加されると、新しいレイヤーが作成されます。構築された各レイヤーはキャッシュされ、以降のビルドで再利用できるようになります。Dockerはビルド時に、既存のレイヤーをキャッシュから取得して再利用できます。これにより、各ビルドに必要な時間と容量が削減されます。変更された内容や以前にビルドされていない内容は、必要に応じてビルドされます。

Docker の更新頻度

ミラー層の内容は重要

イメージレイヤーの重要性。Docker キャッシュ内の既存のレイヤーは、その内容が変更されていない場合にのみ使用できます。Docker ビルド中に変更されるレイヤーが多いほど、Docker がイメージを再構築するために必要な作業量が増えます。イメージレイヤーの順序も重要です。あるレイヤーのすべての親レイヤーが変更されていない場合、そのレイヤーは再利用できます。したがって、頻繁に変更されるレイヤーは最上位に配置し、それらの変更が子レイヤーに与える影響を少なくするのが最善です。イメージレイヤーの順序と内容は非常に重要です。アプリケーションを Docker イメージにパッケージ化する場合、最も簡単な方法は、アプリケーション全体を単一のイメージレイヤーに配置することです。ただし、アプリケーションに多くの静的ライブラリ依存関係が含まれている場合、わずかなコード変更でもイメージレイヤー全体を再構築する必要があります。これにより、ビルド時間と Docker キャッシュの容量が大幅に消費されます。

イメージレイヤーはデプロイメントに影響します

Dockerイメージをデプロイする際には、イメージレイヤーも重要です。デプロイ前に、DockerイメージはリモートDockerリポジトリにプッシュされます。このリポジトリはデプロイされたすべてのイメージのソースであり、多くの場合、同じイメージの複数のバージョンが含まれています。Dockerは非常に効率的で、各レイヤーを一度だけ保存します。しかし、大規模で頻繁にデプロイされ、常に再構築されるレイヤーを持つイメージの場合、これは現実的ではありません。大規模でレイヤー化されたイメージは、たとえ内部の変更がわずかであっても、別のリポジトリに保存し、ネットワーク経由でプッシュする必要があります。変更されないコンテンツを移動して保存する必要があるため、デプロイ時間が長くなります。

Docker での Spring Boot アプリケーション

uber-jar方式を採用したSpring Bootアプリケーションは、それ自体が自己完結型のデプロイメントユニットです。このモデルは、アプリケーションが必要なものをすべて提供するため、仮想マシンへのデプロイメントやビルドパッケージに適しています。しかし、これはDockerデプロイメントにとって欠点となります。Dockerは既に依存関係をパッケージ化する手段を提供しているからです。Spring BootのJARファイルをDockerイメージに丸ごと入れるのは一般的ですが、そうするとDockerイメージのアプリケーション層に不変のコンテンツが過剰に存在してしまいます。

Java SpringBoot シングル層

Springコミュニティでは、Spring Bootアプリケーション、特にDockerを使用したアプリケーションの実行におけるデプロイメントサイズと時間の削減について、継続的な議論が行われています。私の見解では、最終的にはシンプルさと効率性のトレードオフになると考えています。Spring Bootアプリケーション用のDockerイメージを構築する最も一般的なアプローチは、私が「シングルレイヤー」アプローチと呼んでいるものです。厳密に言えば、Dockerfileは実際には複数のレイヤーを作成するため、これは完全に正確ではありませんが、議論には十分です。

単層法

シングルレベルのアプローチを見てみましょう。シングルレベルのアプローチは高速でシンプル、理解しやすく、使いやすいです。DockerのSpring Bootガイドでは、Dockerイメージを構築するためのシングルレベルのDockerfileの概要が説明されています。

  1. openjdk:8-jdk-alpineから
  2. ボリューム /tmp
  3. 引数 JAR_FILE
  4. ${JAR_FILE} app.jar をコピーする
  5. ENTRYPOINT [ "java" , "-Djava.security.egd=file:/dev/./urandom" , "-jar" , "/app.jar" ]

最終的な結果として、Spring Boot アプリケーションが期待通りに動作する Docker イメージが作成されます。ただし、これはアプリケーション JAR 全体に基づいているため、階層的な効率性の問題があります。アプリケーションソースが変更されると、Spring Boot JAR 全体が再構築されます。次に Docker イメージをビルドする際には、変更されていない依存関係を含むアプリケーションレイヤー全体が再構築されます。具体的な例として、Spring Pet Clinic を見てみましょう。

単層法に関するさらなる研究

シングルレイヤー アプローチでは、Open Boot JDK ベース イメージの上に Spring Boot JAR を Docker レイヤーとして使用して、Docker イメージを構築します。

  1. $ Dockerイメージ
  2. リポジトリタグイメージID作成サイズ 
  3. springio/spring-petclinic 最新 94b0366d5ba2 16秒前 140MB

生成されたDockerイメージは140MBです。`docker history`コマンドを使用してレイヤーを確認できます。Spring BootアプリケーションのJARがイメージにコピーされ、そのサイズは38.3MBであることがわかります。

  1. $ docker history springio/spring-petclinic
  2. 画像作成                                      サイズコメント
  3. 94b0366d5ba2 52秒前 /bin/sh -c #(nop) ENTRYPOINT [ "java" "-Djav… 0B
  4. 213dff56a4bd 53秒前 /bin/sh -c #(nop) コピーファイル:d3551559c2aa35af… 38.3MB
  5. bc453a32748e 6分前 /bin/sh -c #(nop) ARG JAR_FILE 0B
  6. 7fe0bb0d8026 6分前 /bin/sh -c #(nop) ボリューム [/tmp] 0B
  7. cc2179b8f042 8日前 /bin/sh -c set -x && apk add   --no-cache o… 97.4MB  
  8. <不足> 8日前 /bin/sh -c #(nop) ENV JAVA_ALPINE_VERSION=8… 0B
  9. <不足> 8日前 /bin/sh -c #(nop) ENV JAVA_VERSION=8u151 0B
  10. <不足> 8日前 /bin/sh -c #(nop) ENV PATH=/usr/ local /sbin:… 0B
  11. <不足> 8日前 /bin/sh -c #(nop) ENV JAVA_HOME=/usr/lib/jv… 0B
  12. <欠落> 8日前 /bin/sh -c { echo '#!/bin/sh' ; echo ' set … 87B
  13. <欠落> 8日前 /bin/sh -c #(nop) ENV LANG=C.UTF-8 0B
  14. <欠落> 5か月前 /bin/sh -c #(nop) CMD [ "/bin/sh" ] 0B
  15. <不足> 5か月前 /bin/sh -c #(nop) ADD file:093f0723fa46f6cdb… 4.15MB

次回 Docker イメージをビルドする際には、JAR ファイルが再パッケージ化されるため、38 MB のレイヤー全体が再作成されます。この例では、アプリケーションのサイズは比較的小さくなっています (spring-boot-starter-web と spring-actuator などの他の依存関係のみに基づいているため)。実際の開発では、Spring Boot ライブラリだけでなく他のサードパーティ製ライブラリも含まれるため、これらのサイズは通常はるかに大きくなります。私の経験では、実際の Spring Boot アプリケーションのサイズは 50 MB から 250 MB (それ以上の場合もあります) の範囲になります。アプリケーションをよく見ると、アプリケーション JAR のうちアプリケーションコードは 372 KB のみです。残りの 38 MB は依存関係ライブラリです。つまり、レイヤーの 0.1% のみが実際に変更され、残りの 99.9% は変更されていません。

ミラーレイヤーのライフサイクル

これは、イメージ レイヤーの基本的な考慮事項、つまりコンテンツのライフサイクルに基づいています。イメージ レイヤーのコンテンツは同じライフサイクルを持つ必要があります。Spring Boot アプリケーションのコンテンツには、頻繁に変更されない依存ライブラリと頻繁に変更されるアプリケーション クラスという 2 つの異なるライフサイクルがあります。アプリケーション コードの変更によりレイヤーが再構築されるたびに、変更されないバイナリも含まれます。アプリケーション コードが絶えず変更および再デプロイされる迅速なアプリケーション開発環境では、この追加コストが非常に高額になる可能性があります。ペット クリニックで反復処理を行っているアプリケーション チームを想像してください。チームは 1 日に 10 回アプリケーションを変更および再デプロイします。これらの 10 個の新しいレイヤーのコストは、1 日あたり 383 MB です。実際のサイズが大きい場合、1 日あたり最大 2.5 GB 以上に達する可能性があります。これにより、最終的にビルド時間、デプロイ時間、および Docker リポジトリ領域が大幅に浪費されます。迅速な反復開発と配信によって、単純な単層アプローチを継続するか、より効率的な代替手段を採用するかが決まります。

Dockerを導入して2層構造を導入する

シンプルさと効率性を天秤にかけると、「2層」アプローチが最適な選択だと私は考えています。(層を増やすことも可能ですが、多すぎると弊害が生じ、Dockerのベストプラクティスに違反する可能性があります。)2層アプローチでは、Spring Bootアプリケーションの依存関係がアプリケーションコードの下の層に配置されるようにDockerイメージを構築します。これにより、各層はコンテンツに対して異なるライフサイクルに従います。変更頻度の低い依存関係を別の層にプッシュし、最上位にはアプリケーションクラスのみを配置することで、反復的なリビルドと再デプロイメントが大幅に高速化されます。

Java Spring Boot 2層

2層アプローチは、反復的な開発を加速し、デプロイメント時間を最小限に抑えます。実際の効率はアプリケーションによって異なりますが、平均してアプリケーションのデプロイメントサイズを90%削減し、それに応じてデプロイメントサイクルを短縮します。

このシリーズの次の記事「Spring Boot アプリケーション用の 2 層 Docker イメージの作成」では、Open Liberty プロジェクトの新しいツールを使用して Spring Boot アプリケーション用の 2 層 Docker イメージを構築する方法について説明します。

翻訳者: 王燕飛 オリジナルリンク: https://dzone.com/articles/optimizing-spring-boot-application-for-docker