pack (Cloud Native Buildpacks) で Gradle マルチプロジェクトをビルドする
最近 Cloud Native Buildpacks を触って遊んでみたのでTipsを残しておきます。約4年ぶりくらいのブログ更新です😅
Cloud Native Buildpacksとは?
Dockerfileを書かなくても、アプリケーションのソースコードからどんな言語のランタイムかをよしなに判断してコンテナイメージを作成してくれるものです。開発者はDockerfileを頑張って書いたりメンテナンスする作業から解放され、運用者は各アプリケーションチームのDockerfileをいちいちチェックしなくても本番運用に耐えられるコンテナイメージを管理・提供することができます。ざっくり言うとKubernetesなどをベースとしたコンテナプラットフォーム環境で、HerokuやCloud FoundryなどのPaaSに近いことが可能になります。Cloud Native Buildpacks は、CNCFのsandboxプロジェクトになっています。 buildpacks.io
また、Cloud Native Buildpacks については色々な方がスライドや動画をアップしていて、この辺り参考になりました。
- https://speakerdeck.com/jacopen/cloud-native-buildpackde-mendounakontenaimezizuo-cheng-wo-zi-dong-hua-siyou
- https://speakerdeck.com/pyama86/cn-buildpacksgazuo-ruwei-lai
- https://docs.google.com/presentation/d/1rPM00F3ptJ3m9UHNrdTx2ZPrBoecb4Wca0GujfViBP0/edit?usp=sharing
- https://youtu.be/EVHHyiypiY0
Gradle マルチプロジェクトとは?
Gradleは複数のプロジェクトをまとめて管理することができます。それをGradle multi project buildと呼んでいたりします。共通系の処理をまとめたり、各サービスからそれを呼び出したりすることができます。ディレクトリレイアウトは例えばこんな感じになります。
. ├── api │ └── src │ ├── main │ │ └── java │ │ └── org │ │ └── gradle │ │ └── sample │ │ ├── api │ │ │ └── Person.java │ │ └── apiImpl │ │ └── PersonImpl.java │ └── test │ └── java │ └── org │ └── gradle │ └── PersonTest.java ├── build.gradle ├── services │ └── personService │ └── src │ ├── main │ │ └── java │ │ └── org │ │ └── gradle │ │ └── sample │ │ └── services │ │ └── PersonService.java │ └── test │ └── java │ └── org │ └── gradle │ └── sample │ └── services │ └── PersonServiceTest.java ├── settings.gradle └── shared └── src └── main └── java └── org └── gradle └── sample └── shared └── Helper.java
pack コマンド
packコマンドは、Cloud Native BuildpacksのCLIツールになります。
$ pack build foo-app --builder cnbs/sample-builder:bionic
こんな感じでpack buildサブコマンドでコンテナイメージを作成することができます。
しかし、Gradle マルチプロジェクトでpack buildをそのまま使うとエラーになります。
$ pack build greeter --builder=gcr.io/paketo-buildpacks/builder:base base: Pulling from paketo-buildpacks/builder Digest: sha256:3284c03370a31854fee91c71c037081406ce2d69b5b7e3926a6a9e134f7e0d2f Status: Image is up to date for gcr.io/paketo-buildpacks/builder:base base-cnb: Pulling from paketo-buildpacks/run f08d8e2a3ba1: Already exists 3baa9cb2483b: Already exists 94e5ff4c0b15: Already exists 1860925334f9: Already exists e45cbcfb1314: Already exists 01090515ce55: Already exists 8a22ab72e32b: Pull complete Digest: sha256:86edad85f315d115ca1784c4a72abbde0b12650c9b993be95fd4a7bcc8900f70 Status: Downloaded newer image for gcr.io/paketo-buildpacks/run:base-cnb 0.9.1: Pulling from buildpacksio/lifecycle Digest: sha256:53bf0e18a734e0c4071aa39b950ed8841f82936e53fb2a0df56c6aa07f9c5023 Status: Image is up to date for buildpacksio/lifecycle:0.9.1 ===> DETECTING [detector] 6 of 17 buildpacks participating [detector] paketo-buildpacks/bellsoft-liberica 3.2.0 [detector] paketo-buildpacks/gradle 3.1.0 [detector] paketo-buildpacks/executable-jar 3.1.0 [detector] paketo-buildpacks/apache-tomcat 2.2.0 [detector] paketo-buildpacks/dist-zip 2.2.0 [detector] paketo-buildpacks/spring-boot 3.2.0 ===> ANALYZING [analyzer] Restoring metadata for "paketo-buildpacks/bellsoft-liberica:jvmkill" from app image [analyzer] Restoring metadata for "paketo-buildpacks/bellsoft-liberica:helper" from app image [analyzer] Restoring metadata for "paketo-buildpacks/bellsoft-liberica:java-security-properties" from app image [analyzer] Restoring metadata for "paketo-buildpacks/bellsoft-liberica:jre" from app image [analyzer] Restoring metadata for "paketo-buildpacks/bellsoft-liberica:jdk" from cache [analyzer] Restoring metadata for "paketo-buildpacks/gradle:application" from cache [analyzer] Restoring metadata for "paketo-buildpacks/gradle:cache" from cache ===> RESTORING [restorer] Restoring data for "paketo-buildpacks/bellsoft-liberica:jdk" from cache [restorer] Restoring data for "paketo-buildpacks/gradle:application" from cache [restorer] Restoring data for "paketo-buildpacks/gradle:cache" from cache ===> BUILDING [builder] [builder] Paketo BellSoft Liberica Buildpack 3.2.0 [builder] https://github.com/paketo-buildpacks/bellsoft-liberica [builder] Build Configuration: [builder] $BP_JVM_VERSION 11.* the Java version [builder] Launch Configuration: [builder] $BPL_JVM_HEAD_ROOM 0 the headroom in memory calculation [builder] $BPL_JVM_LOADED_CLASS_COUNT 35% of classes the number of loaded classes in memory calculation [builder] $BPL_JVM_THREAD_COUNT 250 the number of threads in memory calculation [builder] $JAVA_TOOL_OPTIONS the JVM launch flags [builder] BellSoft Liberica JDK 11.0.8: Reusing cached layer [builder] BellSoft Liberica JRE 11.0.8: Reusing cached layer [builder] Launch Helper: Reusing cached layer [builder] JVMKill Agent 1.16.0: Reusing cached layer [builder] Java Security Properties: Reusing cached layer [builder] [builder] Paketo Gradle Buildpack 3.1.0 [builder] https://github.com/paketo-buildpacks/gradle [builder] Build Configuration: [builder] $BP_GRADLE_BUILD_ARGUMENTS --no-daemon -x test build the arguments to pass to Maven [builder] $BP_GRADLE_BUILT_ARTIFACT build/libs/*.[jw]ar the built application artifact explicitly. Supersedes $BP_GRADLE_BUILT_MODULE [builder] $BP_GRADLE_BUILT_MODULE the module to find application artifact in [builder] Creating cache directory /home/cnb/.gradle [builder] Compiled Application: Contributing to layer [builder] Executing gradlew --no-daemon -x test build [builder] To honour the JVM settings for this build a new JVM will be forked. Please consider using the daemon: https://docs.gradle.org/6.5.1/userguide/gradle_daemon.html. [builder] Daemon will be stopped at the end of the build stopping after processing [builder] > Task :greeting-library:compileJava NO-SOURCE [builder] > Task :greeting-library:compileGroovy [builder] > Task :greeting-library:processResources NO-SOURCE [builder] > Task :greeting-library:classes [builder] > Task :greeting-library:jar [builder] > Task :greeter:compileJava [builder] > Task :greeter:compileGroovy NO-SOURCE [builder] > Task :greeter:processResources NO-SOURCE [builder] > Task :greeter:classes [builder] > Task :greeter:jar [builder] > Task :greeter:startScripts [builder] > Task :greeter:distTar [builder] > Task :greeter:distZip [builder] > Task :greeter:assemble [builder] > Task :greeter:check [builder] > Task :greeter:build [builder] > Task :greeting-library:assemble [builder] > Task :greeting-library:check [builder] > Task :greeting-library:build [builder] [builder] Deprecated Gradle features were used in this build, making it incompatible with Gradle 7.0. [builder] Use '--warning-mode all' to show the individual deprecation warnings. [builder] See https://docs.gradle.org/6.5.1/userguide/command_line_interface.html#sec:command_line_warnings [builder] [builder] BUILD SUCCESSFUL in 48s [builder] 7 actionable tasks: 7 executed [builder] unable to invoke layer creator [builder] unable to contribute application layer [builder] unable to resolve artifact [builder] unable to find single built artifact in build/libs/*.[jw]ar, candidates: [] [builder] ERROR: failed to build: exit status 1 ERROR: failed to build: executing lifecycle. This may be the result of using an untrusted builder: failed with status code: 145
エラーを見ると、ビルドされたjarが見つからないのが原因のようです。packコマンド内で
https://github.com/paketo-buildpacks/gradle が使われているようなのでGitHubを見に行くと、BP_GRADLE_BUILT_ARTIFACT
で、artifactのパスがセットされていて、デフォルトだとbuild/libs/*.[jw]arになるようです。
この環境変数を適切なパスに変更することでうまくいきそうですね。ただし、ターミナルに環境変数を渡すだけではうまくセットされなくて、pack buildコマンドの--envオプションを使う必要がありました。
$ pack build greeter --env "BP_GRADLE_BUILT_ARTIFACT=greeter/build/libs/*.[jw]ar" \ --builder=gcr.io/paketo-buildpacks/builder:base # ... [exporter] *** Images (af8ccc778f29): [exporter] index.docker.io/library/greeter:latest [exporter] Reusing cache layer 'paketo-buildpacks/bellsoft-liberica:jdk' [exporter] Adding cache layer 'paketo-buildpacks/gradle:application' [exporter] Adding cache layer 'paketo-buildpacks/gradle:cache' Successfully built image greeter
無事、ビルドに成功しました!✌️イメージもちゃんとできてますね。
$ docker images | grep greeter greeter latest af8ccc778f29 40 years ago 235MB
今回試した内容は GitHub に上げました。