Spring Boot2.3でCloud Native Buildpacksを利用したDocker Imageを作成する
概要
Spring Boot2.3から導入されたCloud Native Buildpackを使ったDocker Image作成の作成について掘り下げていきます。
Github Repository
Spring Boot 2.3でのDocker Image作成機能
Spring Boot 2.3ではDocker Daemonが起動していれば1コマンドでCloud Native Buildpackを利用したOCI標準なDocker Imageを作成することができるようになりました。 これによりDockerfileを書かずに誰でも簡単にDocker Imageを作成することができます。
# build dockage image $ mvn spring-boot:build-image # execute application $ docker run -it -p 8080:8080 build-image:0.0.1-SNAPSHOT ### 8080ポートでアプリケーションが立ち上がる
とはいえ、このプラグインがやっているのはDockerfileを開発者の代わりに作ってくれている、ということではありません。Cloud Native Buildpackを利用したOCI標準なDocker Imageを作成してくれています。
そして、spring-boot-maven-pluginではデフォルトでPaketo BuildpacksというCloud Foundry発のOSSを利用しています。
Cloud Native Buildpacks
Cloud Native BuildpacksはCNCFのSandboxプロジェクトであり、HerokuやPivotalで利用されていたBuildpackの技術を統合させたものになります。 これにより、今までは特定のPaaSプラットフォームでしか利用できなかった技術がDocker Image同等のものとして色んなクラウド上で扱えるようになりました。
Cloud Native Buildpackの構成
Cloud Native BuildpackのImageはBuildpack, Lifecycle, Stackによって作られたBuilder Imageにて、実際に動かすアプリケーションをbuildすることによって作られます。
各コンポーネントの役割は以下のとおりです。
Buildpack
アプリケーションを動かすためのビルド、あるいはそのための環境構築などを担うコンポーネントです。 Buildpackの実行はdetect/buildの2つのフェーズから構成されます。Buildpackは選ばれたら必ず実行されるというわけではなく、detectフェーズでこのbuildpackを実行すべきかどうか判断し、detectの条件を全て満たしたらbuild, そうじゃなかったらこのBuildpackはスキップ、といった形で処理が走ります。
detectフェーズではこのBuildpackをbuildするために必要なファイルがあるかどうかなどをチェックします。NPM buildpackであればpackage.jsonを探す、といった具合です。
buildフェーズでは文字通りソースコードのビルドなどを行います。たいていのBuilder Imageは複数のBuildpackを組み合わせて実行されるため、1つのBuildpackで全てを実行するわけではありません。中には依存関係を解決するためのBuildpackなどもあります。
Lifecycle
Buildpackより一段上の階層に立って、Buildpackの実行管理や最終的に作成するDocker Imageの構築を担います。こちらもフェーズが分かれており、detection/analysis/build/exportという4つの手順が存在します。
detectionではbuildフェーズで利用するbuildpackの選定を行います。analysisではメタデータやレイヤーキャッシュを展開するといった処理を行います。 buildでは実際にソースコードから実行可能なartifactへのビルド処理を行い、最後のexportでDocker Imageへの書き出しを行います。
ちょっと気になったのはdetectionフェーズ。Buildpackにもdetectフェーズが存在するので、個々のBuildpackのdetectに任せてもいいように見えます。Buildpack同士での競合などを防ぐなどの役割があるのでしょうか。
Stack
Buildpack, LifecycleのベースとなるOS Imageを表します。
build imageとrun imageの2種類が用意されている通り、ビルド時と実行時でStackの違うImageが利用されています。Ubuntuベースのイメージなどが多いようです。
Paketo Buildpacks
Paketo BuildpacksはCloud Native Buildpacksの規格に沿ったGo製のOSSです。 各種言語をサポートしており、 Cloud Foundryプロジェクトによって作成されています。
実は、今回紹介している spring-boot:build-imageでは、デフォルトのBuilder ImageにPaketo Buildpacksが使われています。(github)
mvn spring-boot:build-imageは何をしているか
さて、改めてmvn spring-boot:build-image
実行時のログを追ってみます。(興味があるのはbuild-imageの部分なので、そこまでのログは少し省略しています)
<@buildImage>-<⎇ master>-> mvn spring-boot:build-image [INFO] Scanning for projects... [INFO] [INFO] ----------------------< dev.yoghurt:build-image >----------------------- [INFO] Building build-image 0.0.1-SNAPSHOT [INFO] --------------------------------[ jar ]--------------------------------- [INFO] [INFO] >>> spring-boot-maven-plugin:2.3.3.RELEASE:build-image (default-cli) > package @ build-image >>> [INFO] ... [INFO] [INFO] --- maven-surefire-plugin:2.22.2:test (default-test) @ build-image --- ... ... [INFO] --- spring-boot-maven-plugin:2.3.3.RELEASE:build-image (default-cli) @ build-image --- [INFO] Building image 'docker.io/library/build-image:0.0.1-SNAPSHOT' [INFO] [INFO] > Pulling builder image 'gcr.io/paketo-buildpacks/builder:base-platform-api-0.3' 0% [INFO] > Pulling builder image 'gcr.io/paketo-buildpacks/builder:base-platform-api-0.3' 100% [INFO] > Pulled builder image 'gcr.io/paketo-buildpacks/builder@sha256:3284c03370a31854fee91c71c037081406ce2d69b5b7e3926a6a9e134f7e0d2f' [INFO] > Pulling run image 'docker.io/paketobuildpacks/run:base-cnb' 0% [INFO] > Pulling run image 'docker.io/paketobuildpacks/run:base-cnb' 24% [INFO] > Pulling run image 'docker.io/paketobuildpacks/run:base-cnb' 100% [INFO] > Pulled run image 'paketobuildpacks/run@sha256:86edad85f315d115ca1784c4a72abbde0b12650c9b993be95fd4a7bcc8900f70' [INFO] > Executing lifecycle version v0.9.1 [INFO] > Using build cache volume 'pack-cache-d1d004e77dd4.build' [INFO] [INFO] > Running creator [INFO] [creator] ===> DETECTING [INFO] [creator] 5 of 17 buildpacks participating [INFO] [creator] paketo-buildpacks/bellsoft-liberica 3.2.0 [INFO] [creator] paketo-buildpacks/executable-jar 3.1.0 [INFO] [creator] paketo-buildpacks/apache-tomcat 2.2.0 [INFO] [creator] paketo-buildpacks/dist-zip 2.2.0 [INFO] [creator] paketo-buildpacks/spring-boot 3.2.0 [INFO] [creator] ===> ANALYZING [INFO] [creator] Restoring metadata for "paketo-buildpacks/bellsoft-liberica:helper" from app image [INFO] [creator] Restoring metadata for "paketo-buildpacks/bellsoft-liberica:java-security-properties" from app image [INFO] [creator] Restoring metadata for "paketo-buildpacks/bellsoft-liberica:jre" from app image [INFO] [creator] Restoring metadata for "paketo-buildpacks/bellsoft-liberica:jvmkill" from app image [INFO] [creator] Restoring metadata for "paketo-buildpacks/executable-jar:class-path" from app image [INFO] [creator] Restoring metadata for "paketo-buildpacks/spring-boot:helper" from app image [INFO] [creator] Restoring metadata for "paketo-buildpacks/spring-boot:spring-cloud-bindings" from app image [INFO] [creator] Restoring metadata for "paketo-buildpacks/spring-boot:web-application-type" from app image [INFO] [creator] ===> RESTORING [INFO] [creator] ===> BUILDING [INFO] [creator] [INFO] [creator] Paketo BellSoft Liberica Buildpack 3.2.0 [INFO] [creator] https://github.com/paketo-buildpacks/bellsoft-liberica [INFO] [creator] Build Configuration: [INFO] [creator] $BP_JVM_VERSION 11.* the Java version [INFO] [creator] Launch Configuration: [INFO] [creator] $BPL_JVM_HEAD_ROOM 0 the headroom in memory calculation [INFO] [creator] $BPL_JVM_LOADED_CLASS_COUNT 35% of classes the number of loaded classes in memory calculation [INFO] [creator] $BPL_JVM_THREAD_COUNT 250 the number of threads in memory calculation [INFO] [creator] $JAVA_TOOL_OPTIONS the JVM launch flags [INFO] [creator] BellSoft Liberica JRE 11.0.8: Reusing cached layer [INFO] [creator] Launch Helper: Reusing cached layer [INFO] [creator] JVMKill Agent 1.16.0: Reusing cached layer [INFO] [creator] Java Security Properties: Reusing cached layer [INFO] [creator] [INFO] [creator] Paketo Executable JAR Buildpack 3.1.0 [INFO] [creator] https://github.com/paketo-buildpacks/executable-jar [INFO] [creator] Process types: [INFO] [creator] executable-jar: java org.springframework.boot.loader.JarLauncher [INFO] [creator] task: java org.springframework.boot.loader.JarLauncher [INFO] [creator] web: java org.springframework.boot.loader.JarLauncher [INFO] [creator] [INFO] [creator] Paketo Spring Boot Buildpack 3.2.0 [INFO] [creator] https://github.com/paketo-buildpacks/spring-boot [INFO] [creator] Launch Helper: Reusing cached layer [INFO] [creator] Web Application Type: Reusing cached layer [INFO] [creator] Spring Cloud Bindings 1.6.0: Reusing cached layer [INFO] [creator] Image labels: [INFO] [creator] org.opencontainers.image.title [INFO] [creator] org.opencontainers.image.version [INFO] [creator] org.springframework.boot.spring-configuration-metadata.json [INFO] [creator] org.springframework.boot.version [INFO] [creator] ===> EXPORTING [INFO] [creator] Reusing layer 'paketo-buildpacks/bellsoft-liberica:helper' [INFO] [creator] Reusing layer 'paketo-buildpacks/bellsoft-liberica:java-security-properties' [INFO] [creator] Reusing layer 'paketo-buildpacks/bellsoft-liberica:jre' [INFO] [creator] Reusing layer 'paketo-buildpacks/bellsoft-liberica:jvmkill' [INFO] [creator] Reusing layer 'paketo-buildpacks/executable-jar:class-path' [INFO] [creator] Reusing layer 'paketo-buildpacks/spring-boot:helper' [INFO] [creator] Reusing layer 'paketo-buildpacks/spring-boot:spring-cloud-bindings' [INFO] [creator] Reusing layer 'paketo-buildpacks/spring-boot:web-application-type' [INFO] [creator] Adding 1/1 app layer(s) [INFO] [creator] Reusing layer 'launcher' [INFO] [creator] Adding layer 'config' [INFO] [creator] Adding label 'io.buildpacks.lifecycle.metadata' [INFO] [creator] Adding label 'io.buildpacks.build.metadata' [INFO] [creator] Adding label 'io.buildpacks.project.metadata' [INFO] [creator] Adding label 'org.opencontainers.image.title' [INFO] [creator] Adding label 'org.opencontainers.image.version' [INFO] [creator] Adding label 'org.springframework.boot.spring-configuration-metadata.json' [INFO] [creator] Adding label 'org.springframework.boot.version' [INFO] [creator] *** Images (45ec6ed55538): [INFO] [creator] docker.io/library/build-image:0.0.1-SNAPSHOT [INFO] [INFO] Successfully built image 'docker.io/library/build-image:0.0.1-SNAPSHOT' [INFO] [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------
さきほども記載したとおり、Paketo BuildpacksのBuilderが使われていることがわかります。
また、Executing lifecycle以降を見ると、 ===> DETECTING
....===> BUILDING
とLifecycleの各フェーズが順に実行されていることがわかります。
DETECTINGフェーズでは、5つのBuildpackがヒットしていることがわかります。
[INFO] [creator] 5 of 17 buildpacks participating [INFO] [creator] paketo-buildpacks/bellsoft-liberica 3.2.0 [INFO] [creator] paketo-buildpacks/executable-jar 3.1.0 [INFO] [creator] paketo-buildpacks/apache-tomcat 2.2.0 [INFO] [creator] paketo-buildpacks/dist-zip 2.2.0 [INFO] [creator] paketo-buildpacks/spring-boot 3.2.0
また、そのうち3つのBuildpackが、(おそらく)Buildpackのdetectで成功しBUILDINGフェーズで実行されています。
[INFO] [creator] ===> BUILDING [INFO] [creator] [INFO] [creator] Paketo BellSoft Liberica Buildpack 3.2.0 ... [INFO] [creator] Paketo Executable JAR Buildpack 3.1.0 ... [INFO] [creator] Paketo Spring Boot Buildpack 3.2.0
それぞれのBuildpackが何をしているか、各BuildpackのGithubリポジトリから確認していきます。
Paketo BellSoft Liberica Buildpack
Paketo BellSoft Liberica Buildpackは名前の通り Liberica JDKのBuildpackになります。このBuildpackは他のBuildpackがjreまたはjdkを利用するときに実行されます。
JDKが必要な場面(主にbuilder image)では以下が提供されます。
build
,cache
とマークされているlayerへのJDK追加- build layerへの
JAVA_HOME
設定 - build layerへの
JDK_HOME
設定
また、JREが必要な場面(主にrun image)では以下が提供されます。
- JREの追加
JAVA_HOME
の追加-XX:ActiveProcessorCount
(プロセッサーカウントの指定)設定$MALLOC_ARENA_MAX
設定- local DNSが有効な場合にJVM DNSキャッシュを無効化
metadata.build
がtrueの場合にレイヤーにbuild
,cache
マークを付けるmetadata.launch
がtrueの場合にlaunch
マークを付けるlaunch
が付いたlayerにjvmkill
を追加launch
が付いたlayerにMemory Calculatorを追加
JDK, JREのインストールやクラウド環境向けの環境変数の設定、jvmkillやmemory calculatorなどの追加が行われています。
jvmkillやmemory calculatorは元々Cloud Foundryのプロダクトでしたが、途中でPaketo側に取り込まれたようです。実際のコードは paketo-buildpacks/libjvmにあります。
Paketo Executable JAR Buildpack
Paketo Executable JAR Buildpackは<アプリケーションルート>/META-INF/MANIFEST.MF
が Main-class
を含むときに実行されるBuildpackです。このBuildpackでは以下が実行されます。
- JREのインストールをリクエスト
- <アプリケーションルート>をクラスパスに追加
<アプリケーションルート>/META-INF/MANIFEST.MF
がクラスパスにある場合にentries
(おそらくMain class?)をクラスパスに追加executable-jar
,task
,web
のProcess Typeの指定
最後の記述について、実際に作成したイメージに対して pack inspect-image
を実行し中身を確認するとProcess Tyeが3つ指定されているのがわかります。
Process TypeはDockerのEntryPointのような位置づけのもののようです。
Processes: TYPE SHELL COMMAND ARGS web (default) bash java org.springframework.boot.loader.JarLauncher executable-jar bash java org.springframework.boot.loader.JarLauncher task bash java org.springframework.boot.loader.JarLauncher
Paketo Spring Boot Buildpack
Paketo Spring Boot Buildpackは、<アプリケーションルート>/META-INF/MANIFEST.MF
にSpring-Boot-Versionの指定がある場合に実行されます。以下が実行されます。
org.springframework.boot.version
,org.springframework.boot.spring-configuration-metadata.json
,org.opencontainers.image.title
,org.opencontainers.image.version
をImageラベルに追加- Mavenの依存関係を取得
- Spring Cloud Bindingsの追加
<アプリケーションルート>/META-INF/dataflow-configuration-metadata.propertie
が存在する場合に、org.springframework.cloud.dataflow.spring-configuration-metadata.json
をImageラベルに追加。<アプリケーションルート>/META-INF/MANIFEST.MF
にSpring-Boot-Layers-Indexが存在する場合に定義されたアプリケーションレイヤーを追加。- Reactiveアプリケーションの場合に
$BPL_JVM_THREAD_COUNT
50を設定
Spring Boot周りの設定が盛りだくさんです。リアクティブ向けの設定も行ってくれるのはありがたいですね。
Docker ImageのExport
Lifecycleの最後のフェーズでは、paketo-buildpacksの各種レイヤーがDocker Imageに追加され、labelの設定などが行われています。 このように、コマンド1つで様々な設定を盛り込んだDocker Imageが作成されているのがわかりました。
===> EXPORTING Reusing layer 'paketo-buildpacks/bellsoft-liberica:helper' Reusing layer 'paketo-buildpacks/bellsoft-liberica:java-security-properties' Reusing layer 'paketo-buildpacks/bellsoft-liberica:jre' Reusing layer 'paketo-buildpacks/bellsoft-liberica:jvmkill' Reusing layer 'paketo-buildpacks/executable-jar:class-path' Reusing layer 'paketo-buildpacks/spring-boot:helper' Reusing layer 'paketo-buildpacks/spring-boot:spring-cloud-bindings' Reusing layer 'paketo-buildpacks/spring-boot:web-application-type' Adding 1/1 app layer(s) Reusing layer 'launcher' Adding layer 'config' Adding label 'io.buildpacks.lifecycle.metadata' Adding label 'io.buildpacks.build.metadata' Adding label 'io.buildpacks.project.metadata' Adding label 'org.opencontainers.image.title' Adding label 'org.opencontainers.image.version' Adding label 'org.springframework.boot.spring-configuration-metadata.json' Adding label 'org.springframework.boot.version' *** Images (45ec6ed55538): docker.io/library/build-image:0.0.1-SNAPSHOT
まとめ
mvn spring-boot:build-image
コマンドで作成されるImageはPaketo Buildpacksを利用したOCI標準のImageとなっており、様々な設定が自動で行われているのを調べることができました。
個人的に嬉しいのはやはりjvmkillやMemory Calculatorで、これによりOutOfMemory時のJavaプロセスKillやメモリ設定などをやってくれるので、アプリケーション開発者はいっそうソースコードだけに集中することができるようになります。カスタマイズをしたければ自分でbuilder imageを作成して設定を追加することもできますが、このままでも十分プロダクション環境で利用できそうです。
今回初めてCloud Native Buldpacksについて色々調べてみましたが、一つ一つの設定細かく理解仕切ることができなかったりわからない概念がまだまだ残っていたりします。Spring Bootをクラウド上で簡単に動かせるという方向性は大歓迎ですし、実際に動いている基盤は理解していきたいので引き続き色々調べてみようと思います。