tkak's tech blog

This is my technological memo.

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 については色々な方がスライドや動画をアップしていて、この辺り参考になりました。

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

docs.gradle.org

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になるようです。

github.com

この環境変数を適切なパスに変更することでうまくいきそうですね。ただし、ターミナルに環境変数を渡すだけではうまくセットされなくて、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 に上げました。

github.com