Paketo Buildpackで作成したJavaコンテナイメージでトラブルシューティングを行うメモ

mvn spring-boot:build-image/gradle bootBuildImageあるいはPaketo Buildpackで作成したJavaのコンテナイメージはデフォルトではJREが使われ、JDKは含まれていません。そのため、jstackjcmdなどの診断用のツールがコンテナには含まれません。 また、最近のSpring BootではBuilderとしてPaketo Noble Java Tiny Builderを使用します。 このBuilderのStackはubuntu-noble-run-tinyであり、scratchに必要最小限のファイルを追加したイメージです。 すなわち、このイメージにはbashshなどのシェルも含まれていません。 これはイメージの軽量化および、マルチアーキテクチャのサポートのためには必要なことですが、トラブルシューティングの際に不便になることがあります。

このBuilderを使いつつ、トラブルシューティングもできるようにしたいですよね。まずは、jcmdでスタックトレースやヒープダンプを取れるようにしたいです。 そこで便利なのがPaketo Buildpack for Jattachです。

jattachは実行中のJavaプロセスに動的に接続して操作を実行するための軽量なコマンドラインツールです。 jmapjstackjcmdjinfoの機能が一つのバイナリにまとまっています。 Paketo Buildpack for Jattachはこのjattachをコンテナイメージに追加するBuildpackです。

Paketo Buildpack for Jattachはデフォルトでは有効になっていませんが、ビルド時の環境変数BP_JATTACH_ENABLEDtrueに設定することで有効化できます。 Mavenの場合は次のような設定でjattachがイメージに含まれるようになります。

      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
        <configuration>
          <image>
            <env>
              <BP_JATTACH_ENABLED>true</BP_JATTACH_ENABLED>
            </env>
          </image>
        </configuration>
        <!-- ... -->
      </plugin>

これでコンテナ内でjattachを使ってスタックトレースやヒープダンプを取れるようになります。jattachのパスは/layers/paketo-buildpacks_jattach/jattach/bin/jattachです。

jattachの使い方は次の通りです。

NAMESPACE=...
POD_NAME=...
kubectl exec -n $NAMESPACE $POD_NAME -ti -- /layers/paketo-buildpacks_jattach/jattach/bin/jattach help  

次のような出力が得られます。

jattach 2.2 built on Jan 10 2024

Usage: jattach <pid> <cmd> [args ...]

Commands:
    load  threaddump   dumpheap  setflag    properties
    jcmd  inspectheap  datadump  printflag  agentProperties
command terminated with exit code 1

以下にKubernetesのPod内でヒープダンプを取る例を示します。JavaプロセスのPIDは1です。

NAMESPACE=...
POD_NAME=...
kubectl exec -n $NAMESPACE $POD_NAME -ti -- /layers/paketo-buildpacks_jattach/jattach/bin/jattach 1 dumpheap /tmp/heapdump.hprof

次のようなログが出力されるでしょう。

Connected to remote JVM
JVM response code = 0
Dumping heap to /tmp/heapdump.hprof ...
Heap dump file created [69843865 bytes in 0.294 secs]

これで/tmp/heapdump.hprofにヒープダンプが保存されます。

ではこのヒープダンプファイルを次のコマンドで、ローカルにコピーしましょう。

kubectl cp -n $NAMESPACE $POD_NAME:/tmp/heapdump.hprof ./

しかし、次のようなエラーが出てコピーできません。tarコマンドがPod内に存在しないためです。

time="2025-12-18T09:58:08Z" level=error msg="exec failed: unable to start container process: exec: \"tar\": executable file not found in $PATH"
command terminated with exit code 255

では代わりにcatコマンドでヒープダンプを標準出力に出し、ローカルで受け取る方法を試みましょう。

kubectl exec -n $NAMESPACE $POD_NAME -- cat /tmp/heapdump.hprof > ./heapdump.hprof

しかし、またもや次のようなエラーが出てコピーできません。catコマンドもPod内に存在しないためです。

time="2025-12-18T10:00:09Z" level=error msg="exec failed: unable to start container process: exec: \"cat\": executable file not found in $PATH"
command terminated with exit code 255

このように、Paketo Noble Java Tiny Builderを使って作成したコンテナイメージにはシェルも含まれていないため、kubectl cpkubectl execでファイルをコピーすることができません。

では、どうすればよいでしょうか?実はJREにはあまり知られていないコマンドとしてjwebserverがあります。jwebserverは小さなHTTPサーバーで、特定のディレクトリを公開することができます。

次のコマンドで、Pod内でjwebserverを起動し、/tmpディレクトリをポート8000で公開します。

kubectl exec -n $NAMESPACE $POD_NAME -ti -- /layers/paketo-buildpacks_bellsoft-liberica/jre/bin/jwebserver -b 0.0.0.0 -p 8000 -d /tmp 

別ターミナルでローカルの8000ポートをPodの8000ポートに転送します。

NAMESPACE=...
POD_NAME=....
kubectl port-forward -n $NAMESPACE $POD_NAME 8000:8000

さらに別ターミナルで、wgetコマンドでヒープダンプファイルをダウンロードします。

wget http://localhost:8000/heapdump.hprof

これで無事ヒープダンプファイルをローカルに保存できました。


Paketo Buildpackで作成したJavaコンテナイメージはデフォルトでは診断ツールが含まれていませんが、BP_JATTACH_ENABLED=true環境変数を設定することでjattachコマンドを追加できます。 また、Paketo Noble Java Tiny Builderを使用したイメージにはシェルが含まれていないため、通常のkubectl cpコマンドは使用できませんが、JREに含まれるjwebserverを活用することでファイルのダウンロードが可能になります。

この方法により、軽量なコンテナイメージのメリットを享受しつつ、必要な時にはヒープダンプやスタックトレースなどの診断情報を取得できるようになります。